├── common ├── consumer-rules.txt ├── Index.ets ├── src │ ├── main │ │ ├── ets │ │ │ ├── constant │ │ │ │ ├── index.ets │ │ │ │ └── SizeConstant.ts │ │ │ ├── index.ets │ │ │ └── utils │ │ │ │ ├── Objects.ts │ │ │ │ ├── index.ets │ │ │ │ ├── Toast.ets │ │ │ │ ├── Errors.ets │ │ │ │ ├── Logger.ets │ │ │ │ ├── Screens.ets │ │ │ │ ├── IpAddress.ets │ │ │ │ └── Strings.ets │ │ ├── module.json5 │ │ └── resources │ │ │ ├── base │ │ │ └── element │ │ │ │ └── string.json │ │ │ ├── en_US │ │ │ └── element │ │ │ │ └── string.json │ │ │ └── zh_CN │ │ │ └── element │ │ │ └── string.json │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── .gitignore ├── oh-package.json5 ├── hvigorfile.ts ├── build-profile.json5 └── obfuscation-rules.txt ├── serve ├── consumer-rules.txt ├── Index.ets ├── src │ ├── main │ │ ├── ets │ │ │ ├── index.ets │ │ │ ├── decode │ │ │ │ ├── index.ets │ │ │ │ └── Parser.ets │ │ │ ├── utils │ │ │ │ ├── Types.ts │ │ │ │ ├── index.ts │ │ │ │ ├── Objects.ts │ │ │ │ ├── Strings.ts │ │ │ │ └── Logger.ts │ │ │ ├── handler │ │ │ │ ├── index.ets │ │ │ │ ├── HttpHandler.ets │ │ │ │ ├── GetHandler.ets │ │ │ │ ├── PostHandler.ets │ │ │ │ ├── DefaultHttpHandler.ets │ │ │ │ └── BaseHandler.ets │ │ │ └── http │ │ │ │ ├── impl │ │ │ │ ├── index.ets │ │ │ │ └── HttpContextImpl.ets │ │ │ │ ├── InetSocketAddress.ets │ │ │ │ ├── index.ets │ │ │ │ ├── HttpContext.ets │ │ │ │ ├── HttpError.ets │ │ │ │ ├── HttpServer.ets │ │ │ │ ├── Headers.ets │ │ │ │ └── HttpExchange.ets │ │ └── module.json5 │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── .gitignore ├── hvigorfile.ts ├── oh-package.json5 ├── build-profile.json5 └── obfuscation-rules.txt ├── entry ├── src │ ├── mock │ │ └── mock-config.json5 │ ├── main │ │ ├── ets │ │ │ ├── db │ │ │ │ ├── index.ets │ │ │ │ └── mapper │ │ │ │ │ ├── index.ets │ │ │ │ │ ├── LocalSendMapper.ets │ │ │ │ │ └── ReceiveRecordMapper.ets │ │ │ ├── pages │ │ │ │ ├── editor │ │ │ │ │ └── index.ets │ │ │ │ ├── main │ │ │ │ │ ├── index.ets │ │ │ │ │ ├── send │ │ │ │ │ │ ├── index.ets │ │ │ │ │ │ └── view │ │ │ │ │ │ │ ├── SelectorButton.ets │ │ │ │ │ │ │ └── Footer.ets │ │ │ │ │ ├── receive │ │ │ │ │ │ └── index.ets │ │ │ │ │ └── setting │ │ │ │ │ │ ├── index.ets │ │ │ │ │ │ └── view │ │ │ │ │ │ ├── LanguageMenu.ets │ │ │ │ │ │ ├── SettingItem.ets │ │ │ │ │ │ ├── ThemeMenu.ets │ │ │ │ │ │ ├── InputAction.ets │ │ │ │ │ │ ├── ServiceAction.ets │ │ │ │ │ │ ├── DeviceTypeAction.ets │ │ │ │ │ │ ├── DeviceTypeMenu.ets │ │ │ │ │ │ └── AliasAction.ets │ │ │ │ ├── devtools │ │ │ │ │ ├── index.ets │ │ │ │ │ └── CardColumn.ets │ │ │ │ ├── send │ │ │ │ │ ├── index.ets │ │ │ │ │ └── SendRequestParams.ets │ │ │ │ ├── question │ │ │ │ │ ├── QuestionCard.ets │ │ │ │ │ └── QuestionPage.ets │ │ │ │ ├── receive │ │ │ │ │ ├── ReceiveItem.ets │ │ │ │ │ └── ReceiveSettingPage.ets │ │ │ │ └── record │ │ │ │ │ └── FileMenu.ets │ │ │ ├── model │ │ │ │ ├── Events.ets │ │ │ │ ├── BaseResponse.ets │ │ │ │ ├── ServiceActionType.ets │ │ │ │ ├── FileTransferStatus.ets │ │ │ │ ├── ReceiveResultActionType.ets │ │ │ │ ├── PrepareUploadModel.ets │ │ │ │ ├── ReceivedFileModel.ets │ │ │ │ ├── ErrorMessage.ets │ │ │ │ ├── AutoSaveModel.ets │ │ │ │ ├── ReceiveStatus.ets │ │ │ │ ├── TransferProgressChangedAction.ets │ │ │ │ ├── PrepareTransferModel.ets │ │ │ │ ├── ReceiveRecordModel.ets │ │ │ │ ├── IMoveFile.ets │ │ │ │ ├── FileTransferInfo.ets │ │ │ │ ├── RequestResultAction.ets │ │ │ │ ├── DeviceModel.ets │ │ │ │ ├── FileReceiveQueueModel.ets │ │ │ │ ├── Types.ts │ │ │ │ ├── TransferPageParam.ets │ │ │ │ ├── index.ets │ │ │ │ ├── MulticastModel.ets │ │ │ │ └── NearbyDeviceModel.ets │ │ │ ├── constant │ │ │ │ ├── index.ets │ │ │ │ ├── ApiConstant.ets │ │ │ │ └── Keys.ets │ │ │ ├── http │ │ │ │ ├── Terminator.ets │ │ │ │ ├── index.ets │ │ │ │ ├── HttpRequestError.ets │ │ │ │ ├── HttpTerminator.ets │ │ │ │ └── HttpLogging.ets │ │ │ ├── service │ │ │ │ ├── index.ets │ │ │ │ ├── upload │ │ │ │ │ └── TransferException.ets │ │ │ │ ├── flush │ │ │ │ │ └── FlushTask.ets │ │ │ │ └── FileReceptionService.ets │ │ │ ├── locale │ │ │ │ ├── index.ets │ │ │ │ ├── LocaleType.ets │ │ │ │ └── LocaleProvider.ets │ │ │ ├── eventbus │ │ │ │ ├── index.ets │ │ │ │ ├── EventType.ets │ │ │ │ ├── EventBus.ets │ │ │ │ └── CommonEventData.ets │ │ │ ├── transaction │ │ │ │ ├── index.ets │ │ │ │ ├── SnapShotImage.ets │ │ │ │ ├── ExtraInfo.ets │ │ │ │ ├── CardUtil.ets │ │ │ │ ├── WindowUtils.ets │ │ │ │ └── ComponentAttrUtils.ets │ │ │ ├── components │ │ │ │ ├── dialog │ │ │ │ │ ├── index.ets │ │ │ │ │ └── PasteboardAuthorizeDialog.ets │ │ │ │ ├── index.ets │ │ │ │ ├── Icon.ets │ │ │ │ ├── SpinnerButton.ets │ │ │ │ ├── ObserveVisibleLayout.ets │ │ │ │ ├── ToggleButton.ets │ │ │ │ ├── DeviceIcon.ets │ │ │ │ ├── FileIcon.ets │ │ │ │ ├── SingleLineText.ets │ │ │ │ ├── NavigationTitle.ets │ │ │ │ ├── IconButton.ets │ │ │ │ └── LoopImage.ets │ │ │ ├── utils │ │ │ │ ├── mime │ │ │ │ │ └── index.ts │ │ │ │ ├── ObservedArray.ets │ │ │ │ ├── SessionProvider.ets │ │ │ │ ├── index.ets │ │ │ │ ├── Routers.ets │ │ │ │ ├── Dates.ets │ │ │ │ ├── PersistentStorageProvider.ets │ │ │ │ └── Apps.ets │ │ │ └── theme │ │ │ │ └── DefaultColors.ets │ │ ├── resources │ │ │ ├── base │ │ │ │ ├── profile │ │ │ │ │ ├── main_pages.json │ │ │ │ │ └── route_map.json │ │ │ │ ├── media │ │ │ │ │ ├── logo.png │ │ │ │ │ ├── logo_text.png │ │ │ │ │ ├── ic_start_icon.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher.json │ │ │ │ │ ├── ic_stop.svg │ │ │ │ │ ├── ic_add_filled.svg │ │ │ │ │ ├── ic_cli.svg │ │ │ │ │ ├── ic_spinner.svg │ │ │ │ │ ├── ic_more.svg │ │ │ │ │ ├── ic_mobile.svg │ │ │ │ │ ├── ic_start.svg │ │ │ │ │ ├── ic_folder_filled.svg │ │ │ │ │ ├── ic_public_more.svg │ │ │ │ │ ├── ic_detail.svg │ │ │ │ │ ├── ic_files_application_doc.svg │ │ │ │ │ ├── ic_send_filled.svg │ │ │ │ │ ├── ic_files_text_all.svg │ │ │ │ │ ├── ic_cancel_filled.svg │ │ │ │ │ ├── ic_ok_filled.svg │ │ │ │ │ ├── ic_down.svg │ │ │ │ │ ├── ic_computer.svg │ │ │ │ │ ├── ic_todo_filled.svg │ │ │ │ │ ├── ic_files_application_ppt.svg │ │ │ │ │ ├── ic_files_application_xls.svg │ │ │ │ │ ├── ic_files_video_all.svg │ │ │ │ │ ├── ic_file.svg │ │ │ │ │ ├── ic_delete_filled.svg │ │ │ │ │ ├── ic_files_text_xml.svg │ │ │ │ │ ├── ic_files_text_txt.svg │ │ │ │ │ ├── ic_text_filled.svg │ │ │ │ │ ├── ic_favor_filled.svg │ │ │ │ │ ├── ic_files_image_all.svg │ │ │ │ │ ├── ic_files_text_html.svg │ │ │ │ │ ├── ic_reset.svg │ │ │ │ │ ├── ic_delete.svg │ │ │ │ │ ├── ic_random.svg │ │ │ │ │ ├── ic_files_audio_all.svg │ │ │ │ │ ├── ic_wlan_filled.svg │ │ │ │ │ ├── ic_files_application_pdf.svg │ │ │ │ │ ├── ic_storage.svg │ │ │ │ │ ├── ic_settings_filled.svg │ │ │ │ │ ├── ic_edit.svg │ │ │ │ │ └── ic_history.svg │ │ │ │ └── element │ │ │ │ │ └── color.json │ │ │ └── dark │ │ │ │ └── element │ │ │ │ └── color.json │ │ ├── syscap.json │ │ └── module.json5 │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── .gitignore ├── libs │ └── rdbplus-1.0.8.har ├── hvigorfile.ts ├── oh-package.json5 ├── build-profile.json5 └── obfuscation-rules.txt ├── AppScope ├── resources │ └── base │ │ ├── media │ │ └── app_icon.png │ │ └── element │ │ └── string.json └── app.json5 ├── screenshot ├── Screenshot_20251116190700034.png ├── Screenshot_20251116191315021.png ├── Screenshot_20251116192245339.png └── Screenshot_20251116192831644.png ├── .gitignore ├── hvigorfile.ts ├── oh-package.json5 ├── code-linter.json5 ├── README.md ├── oh-package-lock.json5 └── hvigor └── hvigor-config.json5 /common/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /serve/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /entry/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /serve/Index.ets: -------------------------------------------------------------------------------- 1 | export * from './src/main/ets' -------------------------------------------------------------------------------- /common/Index.ets: -------------------------------------------------------------------------------- 1 | export * from './src/main/ets/' 2 | -------------------------------------------------------------------------------- /serve/src/main/ets/index.ets: -------------------------------------------------------------------------------- 1 | export * from './http' -------------------------------------------------------------------------------- /entry/src/main/ets/db/index.ets: -------------------------------------------------------------------------------- 1 | export * from './mapper' -------------------------------------------------------------------------------- /common/src/main/ets/constant/index.ets: -------------------------------------------------------------------------------- 1 | export * from './SizeConstant' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/editor/index.ets: -------------------------------------------------------------------------------- 1 | export * from './EditorPage' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/MainPage' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/send/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Send' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/receive/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Receive' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Setting' -------------------------------------------------------------------------------- /entry/src/main/ets/model/Events.ets: -------------------------------------------------------------------------------- 1 | @Sendable 2 | export class ScanEvent { 3 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/devtools/index.ets: -------------------------------------------------------------------------------- 1 | export * from './DeveloperToolsPage' -------------------------------------------------------------------------------- /common/src/main/ets/index.ets: -------------------------------------------------------------------------------- 1 | export * from './utils' 2 | export * from './constant' 3 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/BaseResponse.ets: -------------------------------------------------------------------------------- 1 | @Sendable 2 | export class BaseResponse { 3 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/src/main/ets/constant/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ApiConstant' 2 | export * from './Keys' -------------------------------------------------------------------------------- /serve/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /serve/src/main/ets/decode/index.ets: -------------------------------------------------------------------------------- 1 | export * from './BufferPool' 2 | export * from './Parser' -------------------------------------------------------------------------------- /entry/src/main/ets/http/Terminator.ets: -------------------------------------------------------------------------------- 1 | export interface Terminator { 2 | terminate(): void 3 | } -------------------------------------------------------------------------------- /entry/src/main/ets/service/index.ets: -------------------------------------------------------------------------------- 1 | export * from './WebService' 2 | export * from './WebClient' -------------------------------------------------------------------------------- /entry/src/main/ets/locale/index.ets: -------------------------------------------------------------------------------- 1 | export * from './LocaleProvider' 2 | export * from './LocaleType' -------------------------------------------------------------------------------- /serve/src/main/ets/utils/Types.ts: -------------------------------------------------------------------------------- 1 | export interface NumberStringPair { 2 | [key: number]: string 3 | } -------------------------------------------------------------------------------- /entry/src/main/ets/service/upload/TransferException.ets: -------------------------------------------------------------------------------- 1 | export class TransferException extends Error { 2 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index" 4 | ] 5 | } -------------------------------------------------------------------------------- /entry/src/main/ets/db/mapper/index.ets: -------------------------------------------------------------------------------- 1 | export * from './DeviceMapper' 2 | export * from './ReceiveRecordMapper' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/send/index.ets: -------------------------------------------------------------------------------- 1 | export * from './SendRequestPage' 2 | export * from './SendRequestParams' -------------------------------------------------------------------------------- /entry/libs/rdbplus-1.0.8.har: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/libs/rdbplus-1.0.8.har -------------------------------------------------------------------------------- /entry/src/main/ets/model/ServiceActionType.ets: -------------------------------------------------------------------------------- 1 | export enum ServiceActionType { 2 | Start, 3 | Stop, 4 | Restart 5 | } -------------------------------------------------------------------------------- /entry/src/main/ets/service/flush/FlushTask.ets: -------------------------------------------------------------------------------- 1 | export interface FlushTask { 2 | index: number 3 | buffer: ArrayBuffer, 4 | } -------------------------------------------------------------------------------- /entry/src/main/ets/eventbus/index.ets: -------------------------------------------------------------------------------- 1 | export * from './EventBus' 2 | export * from './EventType' 3 | export * from './CommonEventData' -------------------------------------------------------------------------------- /serve/src/main/ets/handler/index.ets: -------------------------------------------------------------------------------- 1 | export * from './HttpHandler' 2 | export * from './GetHandler' 3 | export * from './PostHandler' -------------------------------------------------------------------------------- /entry/src/main/ets/locale/LocaleType.ets: -------------------------------------------------------------------------------- 1 | export enum LocaleType { 2 | CHINESE_HANS_CN = 'zh-Hans-CN', 3 | ENGLISH_US = 'en-Latn-US' 4 | } -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /screenshot/Screenshot_20251116190700034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/screenshot/Screenshot_20251116190700034.png -------------------------------------------------------------------------------- /screenshot/Screenshot_20251116191315021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/screenshot/Screenshot_20251116191315021.png -------------------------------------------------------------------------------- /screenshot/Screenshot_20251116192245339.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/screenshot/Screenshot_20251116192245339.png -------------------------------------------------------------------------------- /screenshot/Screenshot_20251116192831644.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/screenshot/Screenshot_20251116192831644.png -------------------------------------------------------------------------------- /serve/src/main/ets/http/impl/index.ets: -------------------------------------------------------------------------------- 1 | export * from './HttpContextImpl' 2 | export * from './HttpExchangeImpl' 3 | export * from './HttpServerImpl' -------------------------------------------------------------------------------- /serve/src/main/ets/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Logger' 2 | export * from './Strings' 3 | export * from './Objects' 4 | export * from './Types' -------------------------------------------------------------------------------- /common/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/src/main/resources/base/media/logo.png -------------------------------------------------------------------------------- /entry/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /serve/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /common/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /serve/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "LanSend" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/index.ets: -------------------------------------------------------------------------------- 1 | export * from './CustomNavigationUtils' 2 | export * from './ExtraInfo' 3 | export * from './LongTakeAnimationProperties' -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/src/main/resources/base/media/logo_text.png -------------------------------------------------------------------------------- /common/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "common", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default" 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_start_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/src/main/resources/base/media/ic_start_icon.png -------------------------------------------------------------------------------- /entry/src/main/ets/components/dialog/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ReceiveRequestDialog' 2 | export * from './SendRequestDialog' 3 | export * from './PasteboardAuthorizeDialog' -------------------------------------------------------------------------------- /entry/src/main/ets/http/index.ets: -------------------------------------------------------------------------------- 1 | export * from './HttpRequest' 2 | export * from './HttpRequestError' 3 | export * from './Terminator' 4 | export * from './HttpTerminator' -------------------------------------------------------------------------------- /common/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/resources/en_US/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/FileTransferStatus.ets: -------------------------------------------------------------------------------- 1 | export enum FileTransferStatus { 2 | QUEUE, 3 | TRANSFERRING, 4 | ERROR, 5 | SKIPPED, 6 | COMPLETED, 7 | CANCELED, 8 | } -------------------------------------------------------------------------------- /serve/src/main/ets/handler/HttpHandler.ets: -------------------------------------------------------------------------------- 1 | import { HttpExchange } from "../http/HttpExchange"; 2 | 3 | export interface HttpHandler { 4 | handle(exchange: HttpExchange): void 5 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/InetSocketAddress.ets: -------------------------------------------------------------------------------- 1 | export interface InetSocketAddress { 2 | address: string 3 | family?: 'IPv4' | 'IPv6' 4 | port: number 5 | size?: number 6 | } -------------------------------------------------------------------------------- /common/src/main/ets/utils/Objects.ts: -------------------------------------------------------------------------------- 1 | export class Objects { 2 | static clone(target: T, source: U): T & U { 3 | return Object.assign(target, source); 4 | } 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/src/main/resources/base/media/ic_launcher_background.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhu003/localsend-harmony/HEAD/entry/src/main/resources/base/media/ic_launcher_foreground.png -------------------------------------------------------------------------------- /serve/src/main/ets/utils/Objects.ts: -------------------------------------------------------------------------------- 1 | export class Objects { 2 | static clone(target: T, source: U): T & U { 3 | return Object.assign(target, source); 4 | } 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_launcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:ic_launcher_background", 5 | "foreground" : "$media:ic_launcher_foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /local.properties 4 | /.idea 5 | **/build 6 | /.hvigor 7 | .cxx 8 | /.clangd 9 | /.clang-format 10 | /.clang-tidy 11 | **/.test 12 | /.appanalyzer 13 | /.arkui-x -------------------------------------------------------------------------------- /entry/src/main/ets/model/ReceiveResultActionType.ets: -------------------------------------------------------------------------------- 1 | //当收到文件发送请求时返回的结果类型 2 | export enum ReceiveResultActionType { 3 | ACCEPT, //准备接收 4 | REJECT, //拒绝 5 | COMPLETE, //已接收,当发送的是单个文本时 直接接收 6 | ERROR //出错 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/ets/utils/index.ets: -------------------------------------------------------------------------------- 1 | export * from './Logger' 2 | export * from './Screens' 3 | export * from './Toast' 4 | export * from './IpAddress' 5 | export * from './Strings' 6 | export * from './Objects' 7 | export * from './Errors' -------------------------------------------------------------------------------- /common/src/main/ets/utils/Toast.ets: -------------------------------------------------------------------------------- 1 | import promptAction from '@ohos.promptAction'; 2 | 3 | export namespace toast { 4 | export function show(message: string | Resource) { 5 | promptAction.showToast({ message: message }) 6 | } 7 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/PrepareUploadModel.ets: -------------------------------------------------------------------------------- 1 | import { DeviceModel } from './DeviceModel'; 2 | import { FileInfoKeyMap } from './Types'; 3 | 4 | export interface PrepareUploadModel { 5 | info: DeviceModel 6 | files: FileInfoKeyMap 7 | } -------------------------------------------------------------------------------- /common/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "Index.ets", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": {} 9 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/ReceivedFileModel.ets: -------------------------------------------------------------------------------- 1 | import { FileInfoModel } from "."; 2 | 3 | export interface ReceivedFileModel extends FileInfoModel { 4 | sender: string, //发送人 5 | sendAt: number, //发送时间 6 | filePath?: string | undefined 7 | } -------------------------------------------------------------------------------- /common/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "common_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "default" 7 | ], 8 | "deliveryWithInstall": true, 9 | "installationFree": false 10 | } 11 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/ErrorMessage.ets: -------------------------------------------------------------------------------- 1 | export class ErrorMessage { 2 | message?: string 3 | code?: number | undefined 4 | 5 | constructor(message?: string, code?: number | undefined) { 6 | this.message = message 7 | this.code = code 8 | } 9 | } -------------------------------------------------------------------------------- /entry/src/main/ets/utils/mime/index.ts: -------------------------------------------------------------------------------- 1 | import otherTypes from './types/other'; 2 | import standardTypes from './types/standard'; 3 | import Mime from './Mime'; 4 | 5 | export { Mime }; 6 | 7 | export const mime = new Mime(standardTypes, otherTypes)._freeze(); -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "phone" 7 | ], 8 | "deliveryWithInstall": true, 9 | "installationFree": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/http/HttpRequestError.ets: -------------------------------------------------------------------------------- 1 | export class HttpRequestError extends Error { 2 | status: number 3 | 4 | constructor(status: number, message: string = '') { 5 | super() 6 | super.message = message 7 | this.status = status 8 | } 9 | } -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { appTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/src/main/ets/utils/ObservedArray.ets: -------------------------------------------------------------------------------- 1 | @Observed 2 | export class ObservedArray extends Array { 3 | constructor(args?: T[]) { 4 | if (args instanceof Array) { 5 | super(...args); 6 | } else { 7 | super(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /common/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /serve/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/AutoSaveModel.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 自动保存类型 3 | */ 4 | export enum AutoSaveModel { 5 | /** 6 | * 关闭自动保存 7 | */ 8 | CLOSE, 9 | /** 10 | * 来自收藏夹类设备的发送请求自动保存 11 | */ 12 | FAVORITES, 13 | /** 14 | * 来自网络内所有发送请求自动保存 15 | */ 16 | ALL 17 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.1", 3 | "description": "Please describe the basic information.", 4 | "dependencies": { 5 | }, 6 | "devDependencies": { 7 | "@ohos/hypium": "1.0.19", 8 | "@ohos/hamock": "1.0.0" 9 | }, 10 | "dynamicDependencies": {} 11 | } -------------------------------------------------------------------------------- /serve/src/main/ets/handler/GetHandler.ets: -------------------------------------------------------------------------------- 1 | import { BaseHandler } from './BaseHandler'; 2 | import { HttpExchange } from '../http/HttpExchange'; 3 | 4 | export class GetHandler extends BaseHandler { 5 | constructor(call: (exchange: HttpExchange) => void) { 6 | super('GET', call) 7 | } 8 | } -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.jerry.localsend", 4 | "vendor": "Jerry·H", 5 | "versionCode": 1000001, 6 | "versionName": "1.0.1", 7 | "icon": "$media:app_icon", 8 | "minAPIVersion": 12, 9 | "label": "$string:app_name" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /serve/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "Index.ets", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@ohos/node-polyfill": "1.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /serve/src/main/ets/handler/PostHandler.ets: -------------------------------------------------------------------------------- 1 | import { BaseHandler } from './BaseHandler'; 2 | import { HttpExchange } from '../http/HttpExchange'; 3 | 4 | export class PostHandler extends BaseHandler { 5 | constructor(handler: (exchange: HttpExchange) => void) { 6 | super('POST', handler) 7 | } 8 | } -------------------------------------------------------------------------------- /serve/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "serve_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1" 9 | ], 10 | "deliveryWithInstall": true, 11 | "installationFree": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /serve/src/main/ets/handler/DefaultHttpHandler.ets: -------------------------------------------------------------------------------- 1 | import { HttpExchange } from "../http/HttpExchange"; 2 | import { HttpHandler } from "./HttpHandler"; 3 | 4 | export class DefaultHttpHandler implements HttpHandler { 5 | handle(exchange: HttpExchange): void { 6 | exchange.end('HelloWorld') 7 | } 8 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/ReceiveStatus.ets: -------------------------------------------------------------------------------- 1 | import { FileInfoModel } from "." 2 | 3 | export class ReceiveStatus { 4 | progress: number = 0 5 | total: number = 0 6 | files: FileInfoModel[] = [] 7 | } 8 | 9 | export interface FileStatus extends FileInfoModel { 10 | progress: number 11 | total: number 12 | } -------------------------------------------------------------------------------- /serve/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "serve", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1" 9 | ], 10 | "requestPermissions": [ 11 | { 12 | "name": "ohos.permission.INTERNET" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /entry/src/main/syscap.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "general": [ 4 | "phone" 5 | ] 6 | }, 7 | "development": { 8 | "addedSysCaps": [ 9 | "SystemCapability.FileManagement.UserFileService.FolderSelection", 10 | "SystemCapability.FileManagement.AppFileService.FolderAuthorization" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ContentType'; 2 | export * from './HttpError'; 3 | export * from './StatusCode'; 4 | export * from './HttpExchange'; 5 | export * from './HttpServer'; 6 | export * from './HttpContext'; 7 | export * from './ServerResponse'; 8 | export * from './InetSocketAddress'; 9 | export * from './Headers'; -------------------------------------------------------------------------------- /entry/src/main/ets/utils/SessionProvider.ets: -------------------------------------------------------------------------------- 1 | import { util } from '@kit.ArkTS'; 2 | 3 | export class SessionProvider { 4 | static generateSessionId(): string { 5 | return util.generateRandomUUID() 6 | } 7 | 8 | static generateToken(session: string, fileId: string): string { 9 | return util.generateRandomUUID() 10 | } 11 | } -------------------------------------------------------------------------------- /entry/src/main/ets/constant/ApiConstant.ets: -------------------------------------------------------------------------------- 1 | export class ApiConstant { 2 | static readonly REGISTER: string = '/api/localsend/v2/register' 3 | static readonly PREPARE_UPLOAD: string = '/api/localsend/v2/prepare-upload' 4 | static readonly UPLOAD: string = '/api/localsend/v2/upload' 5 | static readonly CANCEL: string = '/api/localsend/v2/cancel' 6 | } -------------------------------------------------------------------------------- /entry/src/main/ets/utils/index.ets: -------------------------------------------------------------------------------- 1 | export * from './DeviceProvider' 2 | export * from './SessionProvider' 3 | export * from './FileProvider' 4 | export * from './Routers' 5 | export * from './ObservedArray' 6 | export * from './Dates' 7 | export * from './PersistentStorageProvider' 8 | export * from './DialogProvider' 9 | export * from './mime' 10 | export * from './Apps' -------------------------------------------------------------------------------- /entry/src/main/ets/model/TransferProgressChangedAction.ets: -------------------------------------------------------------------------------- 1 | import { FileTransferStatus } from "." 2 | 3 | /** 4 | * 文件传输进度更新数据 5 | */ 6 | export interface TransferProgressChangedAction { 7 | fileId: string 8 | status: FileTransferStatus 9 | progress: number 10 | total: number 11 | speed: number //单位: 字节/s 12 | filePath?: string //接收文件时保存的路径 13 | error?: object 14 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/HttpContext.ets: -------------------------------------------------------------------------------- 1 | import { HttpHandler } from "../handler"; 2 | import { HttpServer } from "./HttpServer"; 3 | 4 | export interface HttpContext { 5 | 6 | setHandler(handler: HttpHandler): void 7 | 8 | getHandler(): HttpHandler | undefined; 9 | 10 | getPath(): string 11 | 12 | getServer(): HttpServer 13 | 14 | getAttributes(): Map 15 | } -------------------------------------------------------------------------------- /entry/src/main/ets/db/mapper/LocalSendMapper.ets: -------------------------------------------------------------------------------- 1 | import { BaseMapper, MapperParam } from "rdbplus"; 2 | import { relationalStore } from "@kit.ArkData"; 3 | 4 | export class LocalSendMapper extends BaseMapper { 5 | constructor(params: MapperParam, getRow: (res: relationalStore.ResultSet) => T) { 6 | super(params, getRow, { name: 'localsend.db', securityLevel: relationalStore.SecurityLevel.S1 }) 7 | } 8 | } -------------------------------------------------------------------------------- /entry/src/main/ets/http/HttpTerminator.ets: -------------------------------------------------------------------------------- 1 | import { Terminator } from "./Terminator"; 2 | import { http } from "@kit.NetworkKit"; 3 | 4 | export class HttpTerminator implements Terminator { 5 | http: http.HttpRequest | null = null 6 | 7 | constructor(http: http.HttpRequest) { 8 | this.http = http 9 | } 10 | 11 | terminate(): void { 12 | this.http?.destroy() 13 | this.http = null 14 | } 15 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/PrepareTransferModel.ets: -------------------------------------------------------------------------------- 1 | import { StringPair } from "." 2 | 3 | /** 4 | * 调用prepare-upload接口返回的结果 5 | */ 6 | export class PrepareTransferModel { 7 | sessionId: string 8 | /** 9 | * Map 10 | */ 11 | files: StringPair 12 | 13 | constructor(sessionId: string, files: StringPair) { 14 | this.sessionId = sessionId 15 | this.files = files 16 | } 17 | } -------------------------------------------------------------------------------- /code-linter.json5: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "**/*.ets" 4 | ], 5 | "ignore": [ 6 | "**/src/ohosTest/**/*", 7 | "**/src/test/**/*", 8 | "**/src/mock/**/*", 9 | "**/node_modules/**/*", 10 | "**/oh_modules/**/*", 11 | "**/build/**/*", 12 | "**/.preview/**/*" 13 | ], 14 | "ruleSet": [ 15 | "plugin:@performance/recommended", 16 | "plugin:@typescript-eslint/recommended" 17 | ], 18 | "rules": { 19 | } 20 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/index.ets: -------------------------------------------------------------------------------- 1 | export * from './IconButton' 2 | export * from './Icon' 3 | export * from './LoopImage' 4 | export * from './ObserveVisibleLayout' 5 | export * from './NavigationTitle' 6 | export * from './DeviceIcon' 7 | export * from './dialog' 8 | export * from './FileIcon' 9 | export * from './DeviceItem' 10 | export * from './SingleLineText' 11 | export * from './SpinnerButton' 12 | export * from './ToggleButton' 13 | -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "entry", 3 | "version": "1.0.1", 4 | "description": "Please describe the basic information.", 5 | "main": "", 6 | "author": "", 7 | "license": "", 8 | "dependencies": { 9 | "common": "file:../common", 10 | "serve": "file:../serve", 11 | "@pura/harmony-dialog": "1.0.6", 12 | "@pura/harmony-utils": "1.2.4", 13 | "rdbplus": "file:../entry/libs/rdbplus-1.0.8.har" 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/ReceiveRecordModel.ets: -------------------------------------------------------------------------------- 1 | export class ReceiveRecordModel { 2 | id: number = NaN 3 | fileId: string = '' 4 | fileName: string = '' 5 | fileType: string = '' 6 | size: number = 0 7 | filePath: string = '' 8 | sender: string = '' 9 | sendAt: number = 0 10 | 11 | isSandbox(): boolean { 12 | if (this.filePath && this.filePath.startsWith('/data/storage')) { 13 | return true 14 | } 15 | return false 16 | } 17 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/HttpError.ets: -------------------------------------------------------------------------------- 1 | import { StatusCode } from './StatusCode' 2 | 3 | export class HttpError extends Error { 4 | status: number 5 | 6 | constructor(status: number, message: string) { 7 | super() 8 | super.message = message 9 | this.status = status 10 | } 11 | 12 | static error(code: number, msg?: string): HttpError { 13 | const message: string = msg || StatusCode[code] || 'Request Error' 14 | return new HttpError(code, message) 15 | } 16 | } -------------------------------------------------------------------------------- /entry/src/main/ets/utils/Routers.ets: -------------------------------------------------------------------------------- 1 | export class Routers { 2 | private constructor() { 3 | } 4 | 5 | static INDEX = 'INDEX' 6 | static SEND_REQUEST_PAGE = 'SendRequestPage' 7 | static TRANSFER_PAGE = 'TransferPage' 8 | static HISTORY_PAGE = 'HistoryPage' 9 | static RECEIVE_SETTING_PAGE = 'ReceiveSettingPage' 10 | static EDITOR_PAGE = 'EditorPage' 11 | static QUESTION_PAGE = 'QuestionPage' 12 | static ABOUT_PAGE = 'AboutPage' 13 | static DEVELOPER_TOOLS = 'DeveloperToolsPage' 14 | } -------------------------------------------------------------------------------- /common/src/main/ets/utils/Errors.ets: -------------------------------------------------------------------------------- 1 | import { JSON } from "@kit.ArkTS" 2 | 3 | export class Errors { 4 | static getErrorMessage(e: object): string { 5 | if (e instanceof Error) { 6 | return `${JSON.stringify(e)} ${e.message}\n${(e.stack) ? e.stack : ''}` 7 | } else if (e) { 8 | return JSON.stringify(e) 9 | } 10 | return '' 11 | } 12 | 13 | static getError(e: object): Error { 14 | if (e instanceof Error) { 15 | return e 16 | } 17 | return Error(e?.toString()) 18 | } 19 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/send/SendRequestParams.ets: -------------------------------------------------------------------------------- 1 | import { FileInfoModel, NearbyDeviceModel } from '../../model' 2 | import { ListItemExtraInfo } from '../../transaction' 3 | 4 | export class SendRequestParams extends ListItemExtraInfo { 5 | files: FileInfoModel[] 6 | target: NearbyDeviceModel 7 | 8 | constructor(files: FileInfoModel[], target: NearbyDeviceModel, id?: string, doDefaultTransition?: VoidCallback) { 9 | super(id, doDefaultTransition) 10 | this.files = files 11 | this.target = target 12 | } 13 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/IMoveFile.ets: -------------------------------------------------------------------------------- 1 | export interface IMoveFile { 2 | getFilepath(): string 3 | 4 | setNewFilepath(newFilepath: string): void 5 | } 6 | 7 | // export class SimpleMoveFile implements IMoveFile { 8 | // filepath: string = '' 9 | // 10 | // constructor(filepath: string) { 11 | // this.filepath = filepath 12 | // } 13 | // 14 | // getFilepath(): string { 15 | // return this.filepath 16 | // } 17 | // 18 | // setNewFilepath(newFilepath: string): void { 19 | // this.filepath = newFilepath 20 | // } 21 | // } -------------------------------------------------------------------------------- /entry/src/main/ets/model/FileTransferInfo.ets: -------------------------------------------------------------------------------- 1 | import { FileTransferStatus, IMoveFile } from "."; 2 | 3 | @Observed 4 | export class FileTransferInfo implements IMoveFile { 5 | fileId: string = '' 6 | status: FileTransferStatus = FileTransferStatus.QUEUE 7 | progress: number = 0 8 | speed: number = 0 9 | filePath?: string 10 | error?: object | undefined | null 11 | 12 | getFilepath(): string { 13 | return this.filePath || '' 14 | } 15 | 16 | setNewFilepath(newFilepath: string): void { 17 | this.filePath = newFilepath 18 | } 19 | } -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": false, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | } 16 | } 17 | } 18 | }, 19 | ], 20 | "targets": [ 21 | { 22 | "name": "default" 23 | }, 24 | { 25 | "name": "ohosTest", 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /entry/src/main/ets/theme/DefaultColors.ets: -------------------------------------------------------------------------------- 1 | import { Colors, CustomColors } from "@kit.ArkUI"; 2 | 3 | export class DefaultColors implements CustomColors { 4 | brand?: ResourceColor | undefined = $r('app.color.primary') 5 | fontPrimary?: ResourceColor | undefined = $r('app.color.on_background') 6 | backgroundPrimary?: ResourceColor | undefined = $r('app.color.background') 7 | } 8 | 9 | export class DefaultThemes implements CustomTheme { 10 | colors?: Partial | undefined 11 | 12 | constructor() { 13 | this.colors = new DefaultColors() 14 | } 15 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/LanguageMenu.ets: -------------------------------------------------------------------------------- 1 | import { LocaleProvider, LocaleType } from '../../../../locale' 2 | 3 | @Builder 4 | export function LanguageMenu() { 5 | Menu() { 6 | MenuItem({ content: $r('app.string.flow_system') }).onClick(() => LocaleProvider.setAutoLocale()) 7 | MenuItem({ content: '简体中文' }).onClick(() => LocaleProvider.setSpecifiedLocal(LocaleType.CHINESE_HANS_CN)) 8 | MenuItem({ content: 'English' }).onClick(() => LocaleProvider.setSpecifiedLocal(LocaleType.ENGLISH_US)) 9 | } 10 | .fontColor($r('app.color.on_container')) 11 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/SettingItem.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | 3 | @Component 4 | export struct SettingItem { 5 | @Prop text: ResourceStr = '' 6 | 7 | @Builder 8 | doNothingBuilder() { 9 | } 10 | 11 | @BuilderParam action: VoidCallback = this.doNothingBuilder 12 | 13 | build() { 14 | Row() { 15 | Text(this.text) 16 | .fontSize(SizeConstant.TEXT_XL) 17 | .fontColor($r('app.color.on_background')) 18 | .layoutWeight(1) 19 | this.action() 20 | } 21 | .padding(SizeConstant.SPACE_M) 22 | } 23 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/RequestResultAction.ets: -------------------------------------------------------------------------------- 1 | import { ReceiveResultActionType } from './' 2 | 3 | /** 4 | * emit event data 5 | */ 6 | export interface ReceiveResultAction { 7 | action: ReceiveResultActionType 8 | /** 9 | * HTTP code Message 10 | * 204 Finished (No file transfer needed) 11 | * 400 Invalid body 12 | * 401 PIN required / Invalid PIN 13 | * 403 Rejected 14 | * 409 Blocked by another session 15 | * 429 Too many requests 16 | * 500 Unknown error by receiver 17 | */ 18 | code: number | undefined 19 | message: string | undefined //action=Error时的错误信息 20 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/DeviceModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseResponse } from "./BaseResponse"; 2 | 3 | @Sendable 4 | export class DeviceModel extends BaseResponse { 5 | hostname: string = '' 6 | alias: string = '' 7 | version: string = "" // protocol version (major.minor) 8 | deviceModel: string = "" // nullable 9 | deviceType: string = "" // mobile | desktop | web | headless | server, nullable 10 | fingerprint: string = '' 11 | port: number = 53317 12 | protocol: string = "http" // http | https 13 | download?: boolean // if download API (section 5.2, 5.3) is active (optional, default: false) 14 | announce?: boolean 15 | } -------------------------------------------------------------------------------- /common/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": false, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | }, 16 | "consumerFiles": [ 17 | "./consumer-rules.txt" 18 | ] 19 | } 20 | }, 21 | }, 22 | ], 23 | "targets": [ 24 | { 25 | "name": "default" 26 | }, 27 | { 28 | "name": "ohosTest" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /serve/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": false, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | }, 16 | "consumerFiles": [ 17 | "./consumer-rules.txt" 18 | ] 19 | } 20 | }, 21 | }, 22 | ], 23 | "targets": [ 24 | { 25 | "name": "default" 26 | }, 27 | { 28 | "name": "ohosTest" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /entry/src/main/ets/components/Icon.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | 3 | @Component 4 | export struct Icon { 5 | @Prop src: Resource 6 | @Prop iconColor?: ResourceColor | null = $r('app.color.icon') 7 | @Prop spacing: Length = 0 8 | @Prop dimension: Length = SizeConstant.ICON_XL 9 | 10 | build() { 11 | if (this.iconColor) { 12 | Image(this.src) 13 | .padding(this.spacing) 14 | .size({ width: this.dimension, height: this.dimension }) 15 | .fillColor(this.iconColor) 16 | } else { 17 | Image(this.src) 18 | .padding(this.spacing) 19 | .size({ width: this.dimension, height: this.dimension }) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/HttpServer.ets: -------------------------------------------------------------------------------- 1 | import { HttpExchange, InetSocketAddress } from '.'; 2 | import { HttpServerImpl } from './impl'; 3 | 4 | export interface HttpServer { 5 | bind(addr: InetSocketAddress): HttpServer 6 | 7 | start(): Promise 8 | 9 | stop(): Promise 10 | 11 | restart(): Promise 12 | 13 | post(path: string, callback: (exchange: HttpExchange) => void): HttpServer 14 | 15 | get(path: string, callback: (exchange: HttpExchange) => void): HttpServer 16 | 17 | removeContext(path: string): void 18 | 19 | getAddress(): InetSocketAddress 20 | } 21 | 22 | export function createHttpServer(): HttpServer { 23 | return new HttpServerImpl() 24 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/SpinnerButton.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common'; 2 | import { IconButton } from '.'; 3 | 4 | @Component 5 | export struct SpinnerButton { 6 | @Prop text: ResourceStr 7 | 8 | build() { 9 | Button({ type: ButtonType.Normal, stateEffect: true }) { 10 | IconButton({ text: this.text, icon: $r('app.media.ic_spinner'), arrangement: FlexDirection.RowReverse }) 11 | } 12 | .borderRadius(SizeConstant.RADIUS_M) 13 | .backgroundColor($r('app.color.background')) 14 | .padding({ 15 | left: SizeConstant.SPACE_XL, 16 | top: SizeConstant.SPACE_S, 17 | right: SizeConstant.SPACE_L, 18 | bottom: SizeConstant.SPACE_S 19 | }) 20 | } 21 | } -------------------------------------------------------------------------------- /entry/src/main/ets/service/FileReceptionService.ets: -------------------------------------------------------------------------------- 1 | import { PrepareTransferModel, PrepareUploadModel, StringPair } from "../model"; 2 | import { SessionProvider } from "../utils"; 3 | 4 | export class FileReceptionService { 5 | /** 6 | * 返回允许接收的文件 7 | * @param upload 8 | * @returns 9 | */ 10 | static getPrepareReceptionData(upload: PrepareUploadModel): PrepareTransferModel { 11 | const session = SessionProvider.generateSessionId() 12 | const ids = Object.keys(upload.files) 13 | const files: StringPair = {} 14 | for (const id of ids) { 15 | files[id] = SessionProvider.generateToken(session, id) 16 | } 17 | return new PrepareTransferModel(session, files) 18 | } 19 | } -------------------------------------------------------------------------------- /entry/src/main/ets/eventbus/EventType.ets: -------------------------------------------------------------------------------- 1 | export class EventType { 2 | static SCAN_DEVICE = 1 3 | /** 4 | * 收到其他设备发送文件请求 弹窗提示用户 5 | */ 6 | static RECEIVE_SEND_REQUEST = 2 //接收到文件发送请求 7 | /** 8 | * 收到其他设备发送请求后,回复一个操作 拒绝or接受 9 | */ 10 | static RECEIVE_SEND_REQUEST_ACTION = 3 11 | /** 12 | * 已同意其他设备发送文件,准备接收 13 | */ 14 | static RECEIVING_FILE_ACTION = 4 15 | /** 16 | * 文件传输进度更新事件 17 | */ 18 | static TRANSFER_PROGRESS_CHANGED_ACTION = 5 19 | /** 20 | * 文件服务 启动/关闭: ServiceAction 21 | */ 22 | static SERVICE_ACTION = 6 23 | /** 24 | * 文件发送完成 25 | */ 26 | static UPLOAD_FILE_COMPLETED = 7 27 | /** 28 | * 文件发送取消 29 | */ 30 | static UPLOAD_FILE_CANCELED = 8 31 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/FileReceiveQueueModel.ets: -------------------------------------------------------------------------------- 1 | import { FileInfoModel, FileTransferStatus } from '.'; 2 | 3 | export class FileReceiveQueueModel { 4 | otherside: string //对方别名 5 | remoteIp: string //对方请求IP 6 | session: string 7 | file: FileInfoModel 8 | fileToken: string 9 | status: FileTransferStatus = FileTransferStatus.QUEUE 10 | progress: number = 0 //已接收大小 11 | total: number = 0 //文件总大小 12 | 13 | constructor(session: string, file: FileInfoModel, fileToken: string, otherside: string, remoteIp: string) { 14 | this.session = session 15 | this.file = file 16 | this.fileToken = fileToken 17 | this.otherside = otherside 18 | this.total = file.size 19 | this.remoteIp = remoteIp 20 | } 21 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/question/QuestionCard.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from "common" 2 | 3 | @Component 4 | export struct QuestionCard { 5 | @Prop title: ResourceStr 6 | @Prop text: ResourceStr 7 | 8 | build() { 9 | Column() { 10 | Text(this.title) 11 | .fontSize(SizeConstant.TEXT_XL) 12 | .fontColor($r('app.color.on_container')) 13 | Blank().height(SizeConstant.SPACE_L) 14 | Text(this.text) 15 | .fontSize(SizeConstant.TEXT_L) 16 | .fontColor($r('app.color.on_container')) 17 | } 18 | .alignItems(HorizontalAlign.Start) 19 | .borderRadius(SizeConstant.RADIUS_L) 20 | .backgroundColor($r('app.color.container')) 21 | .padding(SizeConstant.SPACE_L) 22 | } 23 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/ThemeMenu.ets: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '../../../../theme/ThemeProvider' 2 | import { ConfigurationConstant } from '@kit.AbilityKit' 3 | 4 | @Builder 5 | export function ThemeMenu() { 6 | Menu() { 7 | MenuItem({ content: $r('app.string.flow_system') }).onClick(() => ThemeProvider.setThemeModel(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)) 8 | MenuItem({ content: $r('app.string.light') }).onClick(() => ThemeProvider.setThemeModel(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT)) 9 | MenuItem({ content: $r('app.string.dark') }).onClick(() => ThemeProvider.setThemeModel(ConfigurationConstant.ColorMode.COLOR_MODE_DARK)) 10 | } 11 | .fontColor($r('app.color.on_container')) 12 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/send/view/SelectorButton.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | import { IconButton } from '../../../../components' 3 | 4 | @Component 5 | export struct SelectorButton { 6 | @Prop text: string | Resource 7 | @Prop icon: Resource 8 | 9 | build() { 10 | Column() { 11 | IconButton({ 12 | text: this.text, 13 | icon: this.icon, 14 | iconSize: SizeConstant.ICON_L, 15 | arrangement: FlexDirection.Column, 16 | space: SizeConstant.SPACE_M 17 | }) 18 | } 19 | .width('100%') 20 | .padding({ top: SizeConstant.SPACE_L, bottom: SizeConstant.SPACE_L }) 21 | .backgroundColor($r('app.color.container')) 22 | .borderRadius(SizeConstant.RADIUS_L) 23 | } 24 | } -------------------------------------------------------------------------------- /serve/src/main/ets/handler/BaseHandler.ets: -------------------------------------------------------------------------------- 1 | import { HttpError } from './..'; 2 | import { HttpExchange } from '../http/HttpExchange'; 3 | import { HttpHandler } from './HttpHandler'; 4 | 5 | export abstract class BaseHandler implements HttpHandler { 6 | method: string 7 | handler: (exchange: HttpExchange) => void 8 | 9 | constructor(method: string, handler: (exchange: HttpExchange) => void) { 10 | this.method = method 11 | this.handler = handler 12 | } 13 | 14 | handle(exchange: HttpExchange) { 15 | if (exchange.getRequestMethod() != this.method) { 16 | console.info(`reuqest 404: ${exchange.getRequestMethod()} ${exchange.originalUrl}`) 17 | throw HttpError.error(404) 18 | } 19 | return this.handler(exchange) 20 | } 21 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/Types.ts: -------------------------------------------------------------------------------- 1 | export interface FileInfoModel { 2 | id: string, 3 | fileName: string 4 | size: number 5 | fileType: string 6 | sha256?: string 7 | preview?: string 8 | metadata?: FileMetadata 9 | status?: number 10 | } 11 | 12 | export interface PendingSendFile extends FileInfoModel { 13 | filePath?: string | undefined //仅发送时有值 14 | } 15 | 16 | export interface FileMetadata { 17 | modified: string | undefined 18 | accessed: string | undefined 19 | } 20 | 21 | export interface FileInfoKeyMap { 22 | [key: string]: FileInfoModel; 23 | } 24 | 25 | export interface StringPair { 26 | [key: string]: string 27 | } 28 | 29 | export interface StringNumberPair { 30 | [key: string]: number 31 | } 32 | 33 | export type DeviceType = string | 'mobile' | 'desktop' | 'web' | 'headless' | 'server' -------------------------------------------------------------------------------- /entry/src/main/ets/components/ObserveVisibleLayout.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 用来监听组件当前在界面上是否可见 3 | */ 4 | @Component 5 | export struct ObserveVisibleLayout { 6 | @Builder 7 | doNothingBuilder() { 8 | } 9 | 10 | @BuilderParam content: VoidCallback = this.doNothingBuilder 11 | onVisibleChanged?: (isVisible: boolean) => void 12 | 13 | build() { 14 | Stack() { 15 | this.content() 16 | } 17 | .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => { 18 | if (isVisible && currentRatio >= 1.0) { 19 | if (this.onVisibleChanged) { 20 | this.onVisibleChanged(true) 21 | } 22 | } 23 | if (!isVisible && currentRatio <= 0.0) { 24 | if (this.onVisibleChanged) { 25 | this.onVisibleChanged(false) 26 | } 27 | } 28 | }) 29 | } 30 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/ToggleButton.ets: -------------------------------------------------------------------------------- 1 | export interface ToggleSwitchedEvent { 2 | isOn: boolean 3 | off: VoidCallback 4 | } 5 | 6 | @Component 7 | export struct ToggleButton { 8 | private __isChangedByUser: boolean = false 9 | @Prop isOn: boolean 10 | onToggleSwitched?: (event: ToggleSwitchedEvent, isFromUser: boolean) => void 11 | 12 | build() { 13 | Toggle({ type: ToggleType.Switch, isOn: $$this.isOn }) 14 | .onChange((isOn) => { 15 | if (this.onToggleSwitched) { 16 | this.onToggleSwitched({ 17 | isOn: isOn, off: () => { 18 | this.isOn = false 19 | } 20 | } as ToggleSwitchedEvent, this.__isChangedByUser) 21 | } 22 | this.__isChangedByUser = false 23 | }) 24 | .onClick(() => { 25 | this.__isChangedByUser = true 26 | }) 27 | } 28 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/send/view/Footer.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from "common" 2 | import { Routers } from "../../../../utils" 3 | 4 | @Component 5 | export struct Footer { 6 | @Consume('pages') page: NavPathStack 7 | 8 | build() { 9 | Column() { 10 | Blank().height(SizeConstant.SPACE_L) 11 | Text($r('app.string.troubleshooting')) 12 | .fontSize(SizeConstant.TEXT_L) 13 | .fontColor($r('app.color.primary')) 14 | .onClick(() => { 15 | this.page.pushDestination({ name: Routers.QUESTION_PAGE }) 16 | }) 17 | Blank().height(SizeConstant.SPACE_XXL) 18 | Text($r("app.string.same_network_tips")) 19 | .fontSize(SizeConstant.TEXT_L) 20 | .fontColor($r('app.color.on_container_secondary')) 21 | } 22 | .width('100%') 23 | .alignItems(HorizontalAlign.Center) 24 | } 25 | } -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/SnapShotImage.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Huawei Device Co., Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | import { image } from '@kit.ImageKit'; 17 | 18 | export class SnapShotImage { 19 | public static pixelMap: image.PixelMap | undefined = undefined; 20 | } -------------------------------------------------------------------------------- /entry/src/main/ets/http/HttpLogging.ets: -------------------------------------------------------------------------------- 1 | import { http } from '@kit.NetworkKit' 2 | import { logger } from 'common' 3 | import { StringPair } from '../model' 4 | import { JSON } from '@kit.ArkTS' 5 | 6 | const TAG = 'HttpLogging' 7 | 8 | export class HttpLogging { 9 | static print(url: String, options: http.HttpRequestOptions) { 10 | if (options.method) { 11 | } 12 | HttpLogging.log(`---> ${options.method} ${url}\n`) 13 | if (options.header) { 14 | for (const entries of Object.entries(options.header as StringPair)) { 15 | HttpLogging.log(`${entries[0]}: ${entries[1]}`) 16 | } 17 | } 18 | if (options.extraData) { 19 | HttpLogging.log(`${JSON.stringify(options.extraData)}`) 20 | } 21 | HttpLogging.log(`---> END`) 22 | } 23 | 24 | static log(message: string) { 25 | logger.info(TAG, message) 26 | } 27 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/TransferPageParam.ets: -------------------------------------------------------------------------------- 1 | import { DeviceModel, FileInfoModel, StringPair } from '.' 2 | 3 | export class TransferPageParam { 4 | isSent: boolean 5 | /** 6 | * 发送或接收的文件列表,为发送文件时列表中需要有filePath字段 7 | */ 8 | files: FileInfoModel[] 9 | /** 10 | * 保存文件fd 11 | */ 12 | fd?: number 13 | /** 14 | * 发送时必填参数,prepare-upload接口返回的sessionId 15 | */ 16 | sessionId?: string 17 | /** 18 | * 发送时传接收方设备信息 19 | */ 20 | target?: DeviceModel[] 21 | /** 22 | * 键值对 prepare-upload接口返回的files 23 | */ 24 | fileToken?: StringPair 25 | 26 | /** 27 | * 跳转文件传输界面的传参 28 | * @param isSent true: 发送文件 false: 接收文件 29 | * @param files 发送或接收的文件列表,为发送文件时列表中需要有filePath字段 30 | */ 31 | constructor(isSent: boolean, files: FileInfoModel[], fd?: number) { 32 | this.isSent = isSent 33 | this.files = files 34 | this.fd = fd 35 | } 36 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/index.ets: -------------------------------------------------------------------------------- 1 | export * from './MulticastModel' 2 | export * from './BaseResponse' 3 | export * from './Events' 4 | export * from './PrepareUploadModel' 5 | export * from './DeviceModel' 6 | export * from './PrepareTransferModel' 7 | export * from './ErrorMessage' 8 | export * from './FileReceiveQueueModel' 9 | export * from './ReceiveStatus' 10 | // export * from './FileInfoModel' 11 | export * from './Types' 12 | export * from './RequestResultAction' 13 | export * from './ReceiveResultActionType' 14 | export * from './AutoSaveModel' 15 | export * from './FileTransferStatus' 16 | export * from './TransferPageParam' 17 | export * from './TransferProgressChangedAction' 18 | export * from './FileTransferInfo' 19 | export * from './ReceivedFileModel' 20 | export * from './ServiceActionType' 21 | export * from './NearbyDeviceModel' 22 | export * from './ReceiveRecordModel' 23 | export * from './IMoveFile' -------------------------------------------------------------------------------- /common/src/main/ets/utils/Logger.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | 3 | export namespace logger { 4 | export function debug(tag: string = "LocalSend", format: string, ...args: (string | number)[]) { 5 | hilog.debug(0x0000, tag, format, args) 6 | } 7 | 8 | export function info(tag: string = "LocalSend", format: string, ...args: (string | number)[]) { 9 | hilog.info(0x0000, tag, format, args) 10 | } 11 | 12 | export function warn(tag: string = "LocalSend", format: string, ...args: (string | number)[]) { 13 | hilog.warn(0x0000, tag, format, args) 14 | } 15 | 16 | export function error(tag: string = "LocalSend", format: string, ...args: (string | number)[]) { 17 | hilog.error(0x0000, tag, format, args) 18 | } 19 | 20 | export function fatal(tag: string = "LocalSend", format: string, ...args: (string | number)[]) { 21 | hilog.fatal(0x0000, tag, format, args) 22 | } 23 | } -------------------------------------------------------------------------------- /entry/src/main/ets/model/MulticastModel.ets: -------------------------------------------------------------------------------- 1 | import { DeviceModel } from './DeviceModel' 2 | import { DeviceProvider } from '../utils' 3 | import { Keys } from '../constant' 4 | 5 | @Sendable 6 | export class MulticastModel extends DeviceModel { 7 | constructor(announce: boolean | undefined = undefined) { 8 | super() 9 | super.alias = AppStorage.get(Keys.ALIAS) || '' 10 | super.version = '2.1' 11 | super.port = AppStorage.get(Keys.PORT) || 53317 12 | super.announce = announce 13 | super.fingerprint = DeviceProvider.getDeviceFingerprint() 14 | super.deviceModel = AppStorage.get(Keys.DEVICE_MODE) || 'HarmonyOS' 15 | super.deviceType = AppStorage.get(Keys.DEVICE_TYPE) || 'mobile' // mobile | desktop | web | headless | server, nullable 16 | super.protocol = "http" // http | https 17 | super.download = false // if download API (section 5.2, 5.3) is active (optional, default: false) 18 | } 19 | } -------------------------------------------------------------------------------- /serve/src/main/ets/utils/Strings.ts: -------------------------------------------------------------------------------- 1 | import { util } from "@kit.ArkTS"; 2 | import { getLogger, Logger } from "./Logger"; 3 | 4 | const logger: Logger = getLogger('Strings') 5 | 6 | export function buf2String(buf: ArrayBuffer): string { 7 | let msgArray: Uint8Array = new Uint8Array(buf); 8 | let textDecoder: util.TextDecoder = util.TextDecoder.create("utf-8"); 9 | return textDecoder.decodeToString(msgArray) 10 | } 11 | 12 | const FORMAT_PLACEHOLDER = /%s/ 13 | 14 | export function formatString(format: string, ...args: string[]) { 15 | let result = format 16 | args.forEach(value => { 17 | result = result.replace(FORMAT_PLACEHOLDER, value) 18 | }) 19 | return result 20 | } 21 | 22 | export function decodeUrl(str: string): string { 23 | let decoded: string = ''; 24 | try { 25 | decoded = decodeURI(str); 26 | } catch (e) { 27 | logger.debug(`decode url error: ${e}`) 28 | } 29 | return decoded; 30 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/impl/HttpContextImpl.ets: -------------------------------------------------------------------------------- 1 | import { HttpContext } from "../HttpContext"; 2 | import { HttpHandler } from "../../handler"; 3 | import { HttpServer } from "../HttpServer"; 4 | 5 | export class HttpContextImpl implements HttpContext { 6 | private handler?: HttpHandler 7 | private attributes?: Map 8 | private path: string 9 | private server: HttpServer 10 | 11 | constructor(path: string, server: HttpServer) { 12 | this.server = server 13 | this.path = path 14 | } 15 | 16 | setHandler(handler?: HttpHandler): void { 17 | this.handler = handler 18 | } 19 | 20 | getHandler(): HttpHandler | undefined { 21 | return this.handler 22 | } 23 | 24 | getPath(): string { 25 | return this.path 26 | } 27 | 28 | getServer(): HttpServer { 29 | return this.server 30 | } 31 | 32 | getAttributes(): Map { 33 | return this.attributes || new Map() 34 | } 35 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/devtools/CardColumn.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from "common" 2 | 3 | @Component 4 | export struct CardColumn { 5 | @Prop title: ResourceStr 6 | 7 | @Builder 8 | doNothingBuilder() { 9 | } 10 | 11 | @BuilderParam content: VoidCallback = this.doNothingBuilder 12 | 13 | build() { 14 | Column() { 15 | if (this.title) { 16 | Row() { 17 | Text(this.title) 18 | .fontColor($r('app.color.on_container')) 19 | .fontSize(SizeConstant.TEXT_XL) 20 | } 21 | .width('100%') 22 | .justifyContent(FlexAlign.Start) 23 | Blank().height(SizeConstant.SPACE_M) 24 | } 25 | this.content() 26 | } 27 | .width('100%') 28 | .padding(SizeConstant.SPACE_M) 29 | .margin({ top: SizeConstant.SPACE_S, bottom: SizeConstant.SPACE_S }) 30 | .borderRadius(SizeConstant.RADIUS_M) 31 | .backgroundColor($r('app.color.container')) 32 | } 33 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/InputAction.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | 3 | @Component 4 | export struct InputAction { 5 | @Link value: string 6 | @State _value: string = this.value 7 | @Prop inputType: InputType = InputType.Normal 8 | onChanged?: VoidCallback 9 | 10 | build() { 11 | TextInput({ text: $$this._value }) 12 | .onChange(() => { 13 | if (this.value != this._value) { 14 | this.value = this._value 15 | if (this.onChanged) { 16 | this.onChanged() 17 | } 18 | } 19 | }) 20 | .type(this.inputType) 21 | .fontSize(SizeConstant.TEXT_XL) 22 | .fontColor($r('app.color.on_background')) 23 | .maxLines(1) 24 | .maxLength(50) 25 | .width(150) 26 | .height(48) 27 | .textAlign(TextAlign.Center) 28 | .backgroundColor($r('app.color.background')) 29 | .borderRadius(SizeConstant.RADIUS_M) 30 | } 31 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/DeviceIcon.ets: -------------------------------------------------------------------------------- 1 | import { Icon } from '.'; 2 | import { DeviceType } from '../model'; 3 | 4 | @Component 5 | export struct DeviceIcon { 6 | @Prop iconColor: ResourceColor = $r('app.color.icon') 7 | @Prop deviceType: DeviceType = 'web' 8 | @Prop dimension: Length = 60 9 | 10 | build() { 11 | Icon({ src: this.getIcon(this.deviceType), dimension: this.dimension, iconColor: this.iconColor }) 12 | } 13 | 14 | //mobile | desktop | web | headless | server 15 | getIcon(deviceType: string): Resource { 16 | switch (deviceType) { 17 | case 'mobile': 18 | return $r('app.media.ic_mobile') 19 | case 'desktop': 20 | return $r('app.media.ic_computer') 21 | case 'server': 22 | return $r('app.media.ic_storage') 23 | case 'headless': 24 | return $r('app.media.ic_cli') 25 | case 'web': 26 | return $r('app.media.ic_web') 27 | default: 28 | return $r('app.media.ic_computer') 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/FileIcon.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common'; 2 | import { Icon } from '.'; 3 | import { FileProvider } from '../utils'; 4 | import { fileUri } from '@kit.CoreFileKit'; 5 | 6 | @Component 7 | export struct FileIcon { 8 | @Prop fileName: string = '' 9 | @Prop fileType: string = '' 10 | @Prop bgColor: ResourceColor = $r('app.color.card') 11 | @Prop filePath: string|undefined 12 | 13 | build() { 14 | Stack() { 15 | if (this.filePath && FileProvider.isImage(this.fileType)) { 16 | Image(fileUri.getUriFromPath(this.filePath)) 17 | .width(48) 18 | .height(48) 19 | .borderRadius(SizeConstant.RADIUS_L) 20 | }else { 21 | Icon({ 22 | src: FileProvider.getFileIconRes(this.fileName, this.fileType), 23 | iconColor: null 24 | }) 25 | } 26 | } 27 | .width(48) 28 | .height(48) 29 | .borderRadius(SizeConstant.RADIUS_L) 30 | .backgroundColor(this.bgColor) 31 | } 32 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/SingleLineText.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from "common" 2 | import { TextModifier } from "@kit.ArkUI" 3 | 4 | @Component 5 | export struct SingleLineText { 6 | @Prop text: ResourceStr = '' 7 | @Prop textSize: Length = SizeConstant.TEXT_XL 8 | @Prop textColor: ResourceColor = $r('app.color.on_container') 9 | @Prop overflow: TextOverflow = TextOverflow.Ellipsis 10 | @Prop ellipsisMode: EllipsisMode = EllipsisMode.CENTER 11 | @Prop textLineHeight: number | string | Resource 12 | @Prop modifier: TextModifier 13 | 14 | build() { 15 | Text(this.text) 16 | .fontSize(this.textSize) 17 | .fontColor(this.textColor) 18 | .maxLines(1) 19 | .textOverflow({ 20 | overflow: this.overflow 21 | }) 22 | .attributeModifier(this.modifier) 23 | .ellipsisMode(this.ellipsisMode) 24 | .wordBreak(WordBreak.BREAK_ALL)// 设置断行规则WordBreak.BREAK_ALL后实现字母为单位进行截断 25 | .textAlign(TextAlign.Start) 26 | .constraintSize({ maxWidth: '100%' }) 27 | } 28 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_add_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_add_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/model/NearbyDeviceModel.ets: -------------------------------------------------------------------------------- 1 | import { DeviceModel } from '.'; 2 | 3 | @Sendable 4 | export class NearbyDeviceModel extends DeviceModel { 5 | id: number = NaN 6 | key: string = '' 7 | isFavorited: number = 0 8 | 9 | static create(other: DeviceModel): NearbyDeviceModel { 10 | const device: NearbyDeviceModel = new NearbyDeviceModel() 11 | device.hostname = other.hostname 12 | device.alias = other.alias 13 | device.version = other.version // protocol version (major.minor) 14 | device.deviceModel = other.deviceModel // nullable 15 | device.deviceType = other.deviceType // nullable 16 | device.fingerprint = other.fingerprint 17 | device.port = other.port 18 | device.protocol = other.protocol // http | https 19 | device.download = other.download // if download API (section 5.2, 5.3) is active (optional, default: false) 20 | device.announce = other.announce 21 | 22 | device.key = `device_${other.hostname}` 23 | device.isFavorited = 0 24 | return device 25 | } 26 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_cli.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LocalSend for HarmonyOS NEXT 2 | 3 | **介绍** 4 | - 本项目仅用于HarmonyOS学习目的,非官方客户端,其他版本请使用[LocalSend官方提供的客户端](https://github.com/localsend/localsend) 5 | - 兼容[LocalSend Protocol v2.1协议](https://github.com/localsend/protocol) 6 | - 仅支持http传输,https加密传输暂不支持,发送文件前需关闭`发送/接收方`加密功能。 7 | 8 | **多播 (UDP)** 9 | 10 | - Port: 53317 11 | - Address: 224.0.0.167 12 | 13 | **HTTP (TCP)** 14 | 15 | - Port: 53317 16 | 17 | **使用方法** 18 | - 克隆源代码自行编译 19 | 20 | ### 截图 21 | screenshot screenshot screenshot 22 | screenshot -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_spinner 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true, 4 | "enableUnifiedLockfile": false 5 | }, 6 | "lockfileVersion": 3, 7 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 8 | "specifiers": { 9 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", 10 | "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19" 11 | }, 12 | "packages": { 13 | "@ohos/hamock@1.0.0": { 14 | "name": "", 15 | "version": "1.0.0", 16 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", 17 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", 18 | "registryType": "ohpm" 19 | }, 20 | "@ohos/hypium@1.0.19": { 21 | "name": "", 22 | "version": "1.0.19", 23 | "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==", 24 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har", 25 | "registryType": "ohpm" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /common/src/main/ets/utils/Screens.ets: -------------------------------------------------------------------------------- 1 | import window from '@ohos.window' 2 | import { logger } from '.' 3 | import { display } from '@kit.ArkUI' 4 | 5 | export class Screens { 6 | static statusBarHeight: number 7 | static navigationBarHeight: number 8 | 9 | static init(win: window.Window) { 10 | Screens.statusBarHeight = px2vp(win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height) 11 | Screens.navigationBarHeight = px2vp(win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect.height) 12 | logger.debug("Screens", `init: statusBarHeight=${Screens.statusBarHeight} navigationBarHeight=${Screens.navigationBarHeight}`) 13 | } 14 | 15 | static getStatusBarHeight(): number { 16 | return Screens.statusBarHeight 17 | } 18 | 19 | static getNavigationBarHeight(): number { 20 | return Screens.navigationBarHeight 21 | } 22 | 23 | static getScreenHeight(): number { 24 | return display.getDefaultDisplaySync().height 25 | } 26 | 27 | static getScreenWidth(): number { 28 | return display.getDefaultDisplaySync().width 29 | } 30 | } -------------------------------------------------------------------------------- /common/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /entry/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_more_list 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /serve/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope 19 | 20 | -enable-property-obfuscation 21 | -enable-toplevel-obfuscation 22 | -enable-filename-obfuscation 23 | -enable-export-obfuscation -------------------------------------------------------------------------------- /serve/src/main/ets/http/Headers.ets: -------------------------------------------------------------------------------- 1 | import { JSON } from "@kit.ArkTS" 2 | 3 | export class Headers { 4 | private headers: Map = new Map() 5 | 6 | setHeader(name: string, value: string) { 7 | this.headers.set(name.trim().toLowerCase(), value) 8 | } 9 | 10 | getHeader(name: string): string | undefined { 11 | return this.headers.get(name) 12 | } 13 | 14 | getAllHeaders(): Map { 15 | return this.headers 16 | } 17 | 18 | hasChunked(): boolean { 19 | return this.headers.get('transfer-encoding') === 'chunked' 20 | } 21 | 22 | getContentLength(): number | undefined { 23 | const length = this.headers.get('content-length') 24 | if (length) { 25 | return Number.parseInt(length) 26 | } 27 | return undefined 28 | } 29 | 30 | clear() { 31 | this.headers.clear() 32 | } 33 | 34 | toJson(): string { 35 | let obj: Record = {}; 36 | this.headers.forEach((value, key) => { 37 | if (key !== undefined && value !== undefined) { 38 | obj[key] = value; 39 | } 40 | }) 41 | return JSON.stringify(obj) 42 | } 43 | } -------------------------------------------------------------------------------- /common/src/main/ets/utils/IpAddress.ets: -------------------------------------------------------------------------------- 1 | export class IpAddress { 2 | 3 | isIpv4Address(ip: string): boolean { 4 | if (!ip) { 5 | return false 6 | } 7 | let lines: Array = ip.split(".") 8 | if (lines.length != 4) { 9 | return false 10 | } 11 | for (let pattern of lines) { 12 | try { 13 | let number = Number.parseInt(pattern.toString()) 14 | if (number >= 0 && number <= 255) { 15 | continue 16 | } else { 17 | return false 18 | } 19 | } catch (e) { 20 | return false 21 | } 22 | } 23 | return true 24 | } 25 | 26 | ip2int(ip: string): number { 27 | if (!this.isIpv4Address(ip)) { 28 | throw new Error('invalid ipv4 address') 29 | } 30 | let result = 0; 31 | let lines: Array = ip.split("."); 32 | for (let pattern of lines) { 33 | let number = Number.parseInt(pattern); 34 | result = number | (result << 8); 35 | } 36 | return result; 37 | } 38 | 39 | int2ip(intIp: number): string { 40 | return ((intIp >> 24) & 0xff) + "." + ((intIp >> 16) & 0xff) + "." + ((intIp >> 8) & 0xff) + "." + (intIp & 0xff); 41 | } 42 | } -------------------------------------------------------------------------------- /serve/src/main/ets/http/HttpExchange.ets: -------------------------------------------------------------------------------- 1 | import { Headers, HttpContext, HttpError, InetSocketAddress } from './' 2 | import { buffer } from '@kit.ArkTS' 3 | 4 | export interface HttpExchange { 5 | originalUrl: string 6 | protocol: string 7 | path: string 8 | query: Map 9 | isEnd: boolean 10 | 11 | getRequestHeaders(): Headers 12 | 13 | getRequestURI(): string 14 | 15 | getRequestMethod(): string 16 | 17 | getQueryParams(name: string): string 18 | 19 | getHttpContext(): HttpContext | undefined 20 | 21 | setResponseContentType(contentType: string): HttpExchange 22 | 23 | setKeepLive(): HttpExchange 24 | 25 | setStatusCode(statusCode: number): HttpExchange 26 | 27 | writeJson(json: object): Promise 28 | 29 | writeError(error: HttpError): Promise 30 | 31 | end(message?: string | undefined): Promise 32 | 33 | getRequestBody(): buffer.Buffer 34 | 35 | getRequestStringBody(): string 36 | 37 | getRequestJsonBody(): T 38 | 39 | getRemoteAddress(): InetSocketAddress | undefined 40 | 41 | getAttribute(name: string): object | undefined 42 | 43 | setAttribute(name: string, value: object): void 44 | 45 | reset(): void 46 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_mobile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_play 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/utils/Dates.ets: -------------------------------------------------------------------------------- 1 | export class Dates { 2 | private constructor() { 3 | } 4 | 5 | static now(): number { 6 | return new Date().getTime() 7 | } 8 | 9 | /** 10 | * 返回当前时间戳与给定的时间戳相差的时间 11 | * @param mill 12 | * @returns 13 | */ 14 | static consumed(mill: number): number { 15 | return new Date().getTime() - mill 16 | } 17 | 18 | /** 19 | * 将秒转换为时间 20 | * @param sec 21 | * @returns 22 | */ 23 | static getSecondsToTime(sec: number) { 24 | const seconds = Math.floor(sec) % 60; 25 | const minutes = Math.floor(sec / 60) % 60; 26 | const hours = Math.floor(sec / (60 * 60)) % 24; 27 | const days = Math.floor(sec / (60 * 60 * 24)); 28 | let time: string = '' 29 | if (days > 0) { 30 | time += `${days}:` 31 | } 32 | if (hours > 0) { 33 | time += `${hours.toString().padStart(2, '0')}:` 34 | } 35 | time += `${minutes.toString().padStart(2, '0')}:` 36 | time += `${seconds.toString().padStart(2, '0')}` 37 | return time 38 | } 39 | 40 | static delay(ms: number): Promise { 41 | return new Promise((resolve) => { 42 | setTimeout(() => { 43 | resolve() 44 | }, ms) 45 | }) 46 | } 47 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_folder_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_folder_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/ExtraInfo.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Huawei Device Co., Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | export class ExtraInfo { 17 | } 18 | 19 | export class NormalPageExtraInfo extends ExtraInfo { 20 | public geometryId: string; 21 | 22 | constructor(geometryId: string) { 23 | super(); 24 | this.geometryId = geometryId; 25 | } 26 | } 27 | 28 | export class ListItemExtraInfo extends ExtraInfo { 29 | id?: string 30 | doDefaultTransition?: VoidCallback 31 | 32 | constructor(id?: string, doDefaultTransition?: VoidCallback) { 33 | super() 34 | this.id = id 35 | this.doDefaultTransition = doDefaultTransition 36 | } 37 | } -------------------------------------------------------------------------------- /serve/src/main/ets/utils/Logger.ts: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | 3 | export class Logger { 4 | private tag: string 5 | private enable: boolean = false 6 | 7 | constructor(tag: string, enable: boolean) { 8 | this.tag = tag 9 | this.enable = enable 10 | } 11 | 12 | debug(format: string, ...args: (string | number)[]) { 13 | if (this.enable) { 14 | hilog.debug(0x0000, this.tag, format, args) 15 | } 16 | } 17 | 18 | info(format: string, ...args: (string | number)[]) { 19 | if (this.enable) { 20 | hilog.info(0x0000, this.tag, format, args) 21 | } 22 | } 23 | 24 | warn(format: string, ...args: (string | number)[]) { 25 | if (this.enable) { 26 | hilog.warn(0x0000, this.tag, format, args) 27 | } 28 | } 29 | 30 | error(format: string, ...args: (string | number)[]) { 31 | if (this.enable) { 32 | hilog.error(0x0000, this.tag, format, args) 33 | } 34 | } 35 | 36 | fatal(format: string, ...args: (string | number)[]) { 37 | if (this.enable) { 38 | hilog.fatal(0x0000, this.tag, format, args) 39 | } 40 | } 41 | } 42 | 43 | export function getLogger(tag: string, enable: boolean = true): Logger { 44 | return new Logger(tag, enable) 45 | } -------------------------------------------------------------------------------- /common/src/main/ets/constant/SizeConstant.ts: -------------------------------------------------------------------------------- 1 | export class SizeConstant { 2 | // 标题栏高度 3 | static readonly APP_BAR_HEIGHT = 38 4 | 5 | static readonly DRIVER = '1px' 6 | 7 | 8 | // 文本大小相关 9 | // 小号文本 10 | static readonly TEXT_S = 10 11 | // 中号 12 | static readonly TEXT_M = 12 13 | // 大号文本 14 | static readonly TEXT_L = 14 15 | // X大号文本 16 | static readonly TEXT_XL = 16 17 | // XX大号文本 18 | static readonly TEXT_XXL = 18 19 | 20 | 21 | // 图标大小相关 22 | // 小号图标 23 | static readonly ICON_S = 13 24 | // 中号图标 25 | static readonly ICON_M = 16 26 | // 大号图标 27 | static readonly ICON_L = 19 28 | // X大号图标 29 | static readonly ICON_XL = 24 30 | // XX大号图标 31 | static readonly ICON_XXL = 32 32 | 33 | // 圆角大小相关 34 | // 小号圆角 35 | static readonly RADIUS_S = 3 36 | // 中号圆角 37 | static readonly RADIUS_M = 5 38 | // 大号圆角 39 | static readonly RADIUS_L = 14 40 | // 大号圆角 41 | static readonly RADIUS_XXL = 26 42 | 43 | // 间距相关 44 | // 小号间距 45 | static readonly SPACE_EXTRA_S = 3 46 | // 小号间距 47 | static readonly SPACE_S = 5 48 | // 中号间距 49 | static readonly SPACE_M = 10 50 | // 大号间距 51 | static readonly SPACE_L = 14 52 | // X大号间距 53 | static readonly SPACE_XL = 18 54 | // XX大号间距 55 | static readonly SPACE_XXL = 26 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_public_more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_more 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_detail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_detail 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_application_doc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_doc 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_send_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_email_send_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "primary", 5 | "value": "#E1B679" 6 | }, 7 | { 8 | "name": "start_window_background", 9 | "value": "#EDEDED" 10 | }, 11 | { 12 | "name": "error", 13 | "value": "#F04533" 14 | }, 15 | { 16 | "name": "warn", 17 | "value": "#FF9B0A" 18 | }, 19 | { 20 | "name": "container", 21 | "value": "#F2F3F5" 22 | }, 23 | { 24 | "name": "on_container", 25 | "value": "#181818" 26 | }, 27 | { 28 | "name": "on_container_secondary", 29 | "value": "#B2B2B2" 30 | }, 31 | { 32 | "name": "background", 33 | "value": "#FFFFFF" 34 | }, 35 | { 36 | "name": "on_background", 37 | "value": "#171717" 38 | }, 39 | { 40 | "name": "background_secondary", 41 | "value": "#F2F2F0" 42 | }, 43 | { 44 | "name": "highlights", 45 | "value": "#576B95" 46 | }, 47 | { 48 | "name": "icon", 49 | "value": "#B2B2B2" 50 | }, 51 | { 52 | "name": "card", 53 | "value": "#F3F3FA" 54 | }, 55 | { 56 | "name": "button_background", 57 | "value": "#F3F3F3" 58 | }, 59 | { 60 | "name": "mask_background", 61 | "value": "#40000000" 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /entry/src/main/resources/dark/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#181818" 6 | }, 7 | { 8 | "name": "primary", 9 | "value": "#E1B679" 10 | }, 11 | { 12 | "name": "error", 13 | "value": "#F04533" 14 | }, 15 | { 16 | "name": "warn", 17 | "value": "#FF9B0A" 18 | }, 19 | { 20 | "name": "container", 21 | "value": "#212224" 22 | }, 23 | { 24 | "name": "on_container", 25 | "value": "#D5D5D5" 26 | }, 27 | { 28 | "name": "on_container_secondary", 29 | "value": "#6C6C6C" 30 | }, 31 | { 32 | "name": "background", 33 | "value": "#181818" 34 | }, 35 | { 36 | "name": "on_background", 37 | "value": "#D1D1D1" 38 | }, 39 | { 40 | "name": "background_secondary", 41 | "value": "#323232" 42 | }, 43 | { 44 | "name": "highlights", 45 | "value": "#576B95" 46 | }, 47 | { 48 | "name": "icon", 49 | "value": "#D5D5D5" 50 | }, 51 | { 52 | "name": "card", 53 | "value": "#3F4759" 54 | }, 55 | { 56 | "name": "button_background", 57 | "value": "#2C2D2F" 58 | }, 59 | { 60 | "name": "mask_background", 61 | "value": "#40000000" 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/ServiceAction.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | import { Icon } from '../../../../components' 3 | import { Keys } from '../../../../constant' 4 | import { EventBus, EventType, NumberEvent } from '../../../../eventbus' 5 | import { ServiceActionType } from '../../../../model' 6 | 7 | @Component 8 | export struct ServiceAction { 9 | @StorageLink(Keys.SERVICE_IS_ONLINE) isOnline: boolean = true 10 | 11 | build() { 12 | Row() { 13 | Icon({ 14 | src: this.isOnline ? $r('app.media.ic_reset') : $r('app.media.ic_start'), 15 | spacing: 2, 16 | iconColor: $r('app.color.on_container') 17 | }).onClick(() => { 18 | if (this.isOnline) { 19 | EventBus.getInstance().post(EventType.SERVICE_ACTION, NumberEvent(ServiceActionType.Restart)) 20 | } else { 21 | EventBus.getInstance().post(EventType.SERVICE_ACTION, NumberEvent(ServiceActionType.Start)) 22 | } 23 | }) 24 | Blank().width(SizeConstant.SPACE_M) 25 | Icon({ src: $r('app.media.ic_stop'), iconColor: this.isOnline ? $r('app.color.on_container') : $r('app.color.icon') }) 26 | .enabled(this.isOnline) 27 | .onClick(() => { 28 | EventBus.getInstance().post(EventType.SERVICE_ACTION, NumberEvent(ServiceActionType.Stop)) 29 | }) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_text_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_documents 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_cancel_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_cancel_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.1", 3 | "dependencies": { 4 | // "@ohos/hvigor-ohos-arkui-x-plugin": "4.2.3", 5 | }, 6 | "execution": { 7 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ 8 | // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ 9 | // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ 10 | // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ 11 | // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ 12 | }, 13 | "logging": { 14 | // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ 15 | }, 16 | "debugging": { 17 | // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ 18 | }, 19 | "nodeOptions": { 20 | // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ 21 | // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ 22 | } 23 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_ok_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_ok_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_gallery_sort_order 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/route_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "routerMap": [ 3 | { 4 | "name": "SendRequestPage", 5 | "pageSourceFile": "src/main/ets/pages/send/SendRequestPage.ets", 6 | "buildFunction": "SendRequestPageBuilder" 7 | },{ 8 | "name": "TransferPage", 9 | "pageSourceFile": "src/main/ets/pages/transfer/TransferPage.ets", 10 | "buildFunction": "TransferPageBuilder" 11 | },{ 12 | "name": "ReceiveSettingPage", 13 | "pageSourceFile": "src/main/ets/pages/receive/ReceiveSettingPage.ets", 14 | "buildFunction": "ReceiveSettingPageBuilder" 15 | },{ 16 | "name": "HistoryPage", 17 | "pageSourceFile": "src/main/ets/pages/record/ReceiveRecordPage.ets", 18 | "buildFunction": "ReceiveRecordPageBuilder" 19 | },{ 20 | "name": "EditorPage", 21 | "pageSourceFile": "src/main/ets/pages/editor/EditorPage.ets", 22 | "buildFunction": "EditorPageBuilder" 23 | },{ 24 | "name": "QuestionPage", 25 | "pageSourceFile": "src/main/ets/pages/question/QuestionPage.ets", 26 | "buildFunction": "QuestionPageBuilder" 27 | },{ 28 | "name": "AboutPage", 29 | "pageSourceFile": "src/main/ets/pages/about/AboutPage.ets", 30 | "buildFunction": "AboutPageBuilder" 31 | },{ 32 | "name": "DeveloperToolsPage", 33 | "pageSourceFile": "src/main/ets/pages/devtools/DeveloperToolsPage.ets", 34 | "buildFunction": "DeveloperToolsPageBuilder" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/CardUtil.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Huawei Device Co., Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | export class CardUtil { 17 | // For the scenario where one card mirror is attached to the other picture, the picture on the pop-up page needs to be marked with an id. Set the id in a unified way here. 18 | public static getPostPageImageId(prePageClickedCardId: string | undefined): string | undefined { 19 | if (!prePageClickedCardId) { 20 | return undefined; 21 | } 22 | return 'Post_Page_Image' + prePageClickedCardId; 23 | } 24 | 25 | public static getDeviceItemIdByIndex(index: number): string { 26 | return `device_item_${index}`; 27 | } 28 | 29 | public static isLargeSize(): boolean { 30 | let currentBreakPoint: string | undefined = AppStorage.get('currentBreakpoint'); 31 | return (currentBreakPoint === 'md' || currentBreakPoint === 'lg'); 32 | } 33 | } -------------------------------------------------------------------------------- /entry/src/main/ets/eventbus/EventBus.ets: -------------------------------------------------------------------------------- 1 | import emitter from '@ohos.events.emitter' 2 | 3 | export class EventBus { 4 | private static instance: EventBus 5 | 6 | private constructor() { 7 | 8 | } 9 | 10 | static getInstance(): EventBus { 11 | if (!EventBus.instance) { 12 | EventBus.instance = new EventBus() 13 | } 14 | return EventBus.instance 15 | } 16 | 17 | //持续订阅指定的事件,并在接收到该事件时,执行对应的回调处理函数。 18 | on(name: number, callback: (eventData: emitter.EventData) => void) { 19 | let event: emitter.InnerEvent = { 20 | eventId: name, //事件ID 21 | priority: emitter.EventPriority.IMMEDIATE //事件立即被投递 22 | } 23 | emitter.on(event, callback) 24 | } 25 | 26 | //持续订阅指定的事件,并在接收到该事件时,执行对应的回调处理函数。 27 | once(name: number, callback: (eventData: emitter.EventData) => void) { 28 | let event: emitter.InnerEvent = { 29 | eventId: name, //事件ID 30 | priority: emitter.EventPriority.IMMEDIATE //事件立即被投递 31 | } 32 | emitter.once(event, callback) 33 | } 34 | 35 | 36 | //取消针对该事件ID的订阅,传入可选参数callback,并且该callback已经通过on或者once接口订阅,则取消该订阅;否则,不做任何处理。 37 | unregister(name: number) { 38 | emitter.off(name) 39 | } 40 | 41 | //发送指定的事件 42 | post(name: number, data?: emitter.EventData) { 43 | let event: emitter.InnerEvent = { 44 | eventId: name, //事件ID 45 | priority: emitter.EventPriority.IMMEDIATE //事件立即被投递 46 | } 47 | let eventData: emitter.EventData = { 48 | data: data?.data 49 | } 50 | emitter.emit(event, eventData) 51 | } 52 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_computer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Linear/ic_device_matebook 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/utils/PersistentStorageProvider.ets: -------------------------------------------------------------------------------- 1 | import { DeviceProvider } from '.'; 2 | import { Keys } from '../constant'; 3 | import { util } from '@kit.ArkTS'; 4 | import { AutoSaveModel, DeviceType } from '../model'; 5 | import { ConfigurationConstant } from '@kit.AbilityKit'; 6 | 7 | /** 8 | * 用于初始化所有需要持久化保存的数据 9 | */ 10 | export class PersistentStorageProvider { 11 | static init(context: Context) { 12 | //主题:默认跟随系统 13 | PersistentStorage.persistProp(Keys.COLOR_MODE, ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET) 14 | //语言:是否跟随系统 15 | PersistentStorage.persistProp(Keys.LANGUAGE_IS_AUTO, true) 16 | //PIN:默认不开启PIN 17 | PersistentStorage.persistProp(Keys.USE_PIN_WHEN_RECEIVE, undefined) 18 | //保存接收记录 19 | PersistentStorage.persistProp(Keys.SAVE_RECEIVE_RECORD, true) 20 | //随机生成一个别名 21 | PersistentStorage.persistProp(Keys.ALIAS, DeviceProvider.getRandomAlias(context.resourceManager)) 22 | //设备类型 23 | PersistentStorage.persistProp(Keys.DEVICE_TYPE, 'mobile') 24 | //设备型号 25 | PersistentStorage.persistProp(Keys.DEVICE_MODE, 'HarmonyOS') 26 | //组播地址 27 | PersistentStorage.persistProp(Keys.MULTICAST_ADDRESS, '224.0.0.167') 28 | //指纹 29 | PersistentStorage.persistProp(Keys.FINGERPRINT, util.generateRandomUUID()) 30 | //自动保存模: 默认关闭 31 | PersistentStorage.persistProp(Keys.AUTO_SAVE_MODEL, AutoSaveModel.CLOSE) 32 | //Web服务监听端口 33 | PersistentStorage.persistProp(Keys.PORT, 53317) 34 | //开发者模式: 默认关闭 35 | PersistentStorage.persistProp(Keys.IS_DEV_MODE, false) 36 | } 37 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/receive/ReceiveItem.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | import { FileIcon, Icon, SingleLineText } from '../../components' 3 | import { FileInfoModel } from '../../model' 4 | import { FileProvider } from '../../utils' 5 | 6 | @Component 7 | export struct ReceiveItem { 8 | @Prop file: FileInfoModel 9 | @State isLongPress: boolean = false 10 | @State isModified: boolean = false 11 | 12 | build() { 13 | Row() { 14 | FileIcon({ fileType: this.file.fileType }) 15 | Blank().width(SizeConstant.SPACE_M) 16 | Column() { 17 | SingleLineText({ 18 | text: this.file.fileName, 19 | overflow: this.isLongPress ? TextOverflow.MARQUEE : TextOverflow.Ellipsis, 20 | textSize: SizeConstant.TEXT_XL 21 | }).width('100%') 22 | Text() { 23 | Span($r('app.string.unchanged')) 24 | Span(" - ") 25 | Span(FileProvider.formatBytes(this.file.size)) 26 | } 27 | .fontColor($r('app.color.on_container_secondary')) 28 | .fontSize(SizeConstant.TEXT_L) 29 | } 30 | .alignItems(HorizontalAlign.Start) 31 | .layoutWeight(1) 32 | 33 | Blank().width(SizeConstant.SPACE_M) 34 | Icon({ src: $r('app.media.ic_edit') }) 35 | Toggle({ type: ToggleType.Checkbox, isOn: true }) 36 | .size({ width: 20, height: 20 }) 37 | .selectedColor($r('app.color.primary')) 38 | .onChange((isOn: boolean) => { 39 | console.info('Component status:' + isOn) 40 | }) 41 | } 42 | .width('100%') 43 | } 44 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/NavigationTitle.ets: -------------------------------------------------------------------------------- 1 | import { Screens, SizeConstant } from 'common' 2 | import { LengthMetrics } from '@kit.ArkUI' 3 | 4 | @Component 5 | export struct NavigationTitle { 6 | @State title: ResourceStr | undefined = undefined 7 | @State isHideBackButton: boolean = false 8 | @State titleBackgroundColor: ResourceColor = $r('app.color.background') 9 | @State menus: Array = [] 10 | 11 | @Builder 12 | doNothingBuilder() { 13 | } 14 | 15 | @BuilderParam content: VoidCallback = this.doNothingBuilder 16 | 17 | @Builder 18 | titleBar() { 19 | Row() { 20 | if (this.title) { 21 | Text(this.title) 22 | .fontColor($r('app.color.on_background')) 23 | .fontSize(22) 24 | .fontWeight(600) 25 | .align(Alignment.Center) 26 | } 27 | } 28 | .width('100%') 29 | .height('100%') 30 | .padding({ left: SizeConstant.SPACE_L, right: SizeConstant.SPACE_L }) 31 | .backgroundColor(this.titleBackgroundColor) 32 | } 33 | 34 | build() { 35 | Navigation() { 36 | this.content() 37 | } 38 | .title(this.titleBar, { 39 | paddingStart: LengthMetrics.vp(SizeConstant.SPACE_L), 40 | paddingEnd: LengthMetrics.vp(SizeConstant.SPACE_L), 41 | }) 42 | .titleMode(NavigationTitleMode.Mini) 43 | .hideBackButton(this.isHideBackButton) 44 | .padding({ top: Screens.getStatusBarHeight() }) 45 | .backgroundColor(this.titleBackgroundColor) 46 | .mode(NavigationMode.Stack) 47 | .menus(this.menus) 48 | } 49 | } 50 | 51 | export interface IconMenu { 52 | icon: Resource 53 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_todo_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_todo_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_application_ppt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_pptx 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_application_xls.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_xls 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_video_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_vedio 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/DeviceTypeAction.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant, Strings } from "common"; 2 | import { DeviceIcon, Icon } from "../../../../components"; 3 | import { Keys } from "../../../../constant"; 4 | import { DeviceTypeMenu } from "./DeviceTypeMenu"; 5 | 6 | @Component 7 | export struct DeviceTypeAction { 8 | @StorageLink(Keys.DEVICE_TYPE) @Watch('onChanged') deviceType: string = '' 9 | @State type: string = this.deviceType 10 | onDeviceTypeChanged?: VoidCallback 11 | 12 | build() { 13 | RelativeContainer() { 14 | DeviceIcon({ deviceType: this.deviceType, dimension: SizeConstant.ICON_L, iconColor: $r('app.color.on_background') }) 15 | .alignRules({ 16 | middle: { anchor: '__container__', align: HorizontalAlign.Center }, 17 | center: { anchor: '__container__', align: VerticalAlign.Center }, 18 | }) 19 | Icon({ src: $r('app.media.ic_spinner'), iconColor: $r('app.color.on_background') }) 20 | .alignRules({ 21 | right: { anchor: '__container__', align: HorizontalAlign.End }, 22 | center: { anchor: '__container__', align: VerticalAlign.Center }, 23 | }) 24 | } 25 | .borderRadius(SizeConstant.RADIUS_M) 26 | .backgroundColor($r('app.color.background')) 27 | .width(150) 28 | .height(48) 29 | .bindMenu(DeviceTypeMenu) 30 | } 31 | 32 | onChanged() { 33 | console.info(`deviceType: ${this.deviceType} isNotEmpty: ${Strings.isNotEmpty(this.deviceType)} type: ${this.type}`) 34 | if (this.onDeviceTypeChanged && Strings.isNotEmpty(this.deviceType) && this.type != this.deviceType) { 35 | this.onDeviceTypeChanged() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_file 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_delete_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_delete_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/record/FileMenu.ets: -------------------------------------------------------------------------------- 1 | import { ReceiveRecordModel } from '../../model' 2 | import { FileInfomation } from './FileInfomation' 3 | import { DialogHelper } from '@pura/harmony-dialog' 4 | import { ClickUtil } from '@pura/harmony-utils' 5 | import { Apps } from '../../utils/Apps' 6 | import { common } from '@kit.AbilityKit' 7 | 8 | @Component 9 | export struct FileMenu { 10 | @Prop data: ReceiveRecordModel 11 | onRemoveRecordClick?: (id: number) => void 12 | 13 | @Builder 14 | FileInfomationContent() { 15 | FileInfomation({ data: this.data }) 16 | } 17 | 18 | build() { 19 | Menu() { 20 | MenuItem({ content: $r('app.string.open_file') }).onClick(() => { 21 | Apps.useOtherAppOpenFile(getContext(this) as common.UIAbilityContext, this.data.filePath) 22 | }) 23 | MenuItem({ content: $r('app.string.open_in_fm') }).onClick(() => { 24 | Apps.toAppDownloadFolder(getContext(this) as common.UIAbilityContext) 25 | }) 26 | MenuItem({ content: $r('app.string.information') }).onClick(() => this.onInformationClick()) 27 | MenuItem({ content: $r('app.string.delete_from_history') }).onClick(() => ClickUtil.throttle(() => this.onDeleteRecordClick())) 28 | } 29 | .fontColor($r('app.color.on_container')) 30 | } 31 | 32 | onInformationClick() { 33 | DialogHelper.showCustomContentDialog({ 34 | title: $r('app.string.file_information'), 35 | buttons: [{ value: $r('app.string.close'), }], 36 | contentBuilder: () => { 37 | this.FileInfomationContent() 38 | } 39 | }) 40 | } 41 | 42 | onDeleteRecordClick() { 43 | if (this.onRemoveRecordClick) { 44 | this.onRemoveRecordClick(this.data.id) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /entry/src/main/ets/eventbus/CommonEventData.ets: -------------------------------------------------------------------------------- 1 | import { emitter } from '@kit.BasicServicesKit' 2 | import { PrepareUploadModel, ReceiveResultAction, TransferProgressChangedAction } from '../model' 3 | 4 | export class ValueEventData { 5 | value: object 6 | 7 | constructor(value: object) { 8 | this.value = value 9 | } 10 | } 11 | 12 | export class NumberEventData { 13 | value: number 14 | 15 | constructor(value: number) { 16 | this.value = value 17 | } 18 | } 19 | 20 | /** 21 | * send: 22 | * EventBus.getInstance().post(EventType.RECEIVE_SEND_REQUEST_ACTION, ValueEvent(data)) 23 | * 24 | * receive: 25 | * EventBus.getInstance().once(EventType.RECEIVE_SEND_REQUEST_ACTION, (eventData: emitter.EventData) => { 26 | * if (eventData.data?.data?.value === true) {} 27 | * } 28 | * @param value 29 | * @returns 30 | */ 31 | export function ValueEvent(value: object): emitter.EventData { 32 | return { 33 | data: new ValueEventData(value) 34 | } 35 | } 36 | 37 | export function NumberEvent(value: number): emitter.EventData { 38 | return { 39 | data: new NumberEventData(value) 40 | } 41 | } 42 | 43 | export function PrepareUploadEvent(value: PrepareUploadModel): emitter.EventData { 44 | return { 45 | data: value 46 | } 47 | } 48 | 49 | /** 50 | * 收到文件发送请求后通知WebService返回对方处理结果 51 | * @param value 52 | * @returns 53 | */ 54 | export function ReceiveResultActionEvent(value: ReceiveResultAction): emitter.EventData { 55 | return { 56 | data: value 57 | } 58 | } 59 | 60 | /** 61 | * 文件传输进度更新 62 | * @param value 63 | * @returns 64 | */ 65 | export function TransferFileProgressEvent(value: TransferProgressChangedAction): emitter.EventData { 66 | return { 67 | data: value 68 | } 69 | } -------------------------------------------------------------------------------- /entry/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /serve/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /common/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_text_xml.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_xml 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /entry/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone" 9 | ], 10 | "deliveryWithInstall": true, 11 | "installationFree": false, 12 | "pages": "$profile:main_pages", 13 | "routerMap": "$profile:route_map", 14 | "abilities": [ 15 | { 16 | "name": "EntryAbility", 17 | "srcEntry": "./ets/entryability/EntryAbility.ets", 18 | "description": "$string:EntryAbility_desc", 19 | "icon": "$media:ic_launcher", 20 | "label": "$string:EntryAbility_label", 21 | "startWindowIcon": "$media:ic_start_icon", 22 | "startWindowBackground": "$color:start_window_background", 23 | "exported": true, 24 | "skills": [ 25 | { 26 | "entities": [ 27 | "entity.system.home" 28 | ], 29 | "actions": [ 30 | "action.system.home" 31 | ] 32 | } 33 | ] 34 | } 35 | ], 36 | "requestPermissions": [ 37 | { 38 | "name": "ohos.permission.GET_WIFI_INFO", 39 | }, 40 | { 41 | "name": "ohos.permission.INTERNET" 42 | } 43 | // { 44 | // "name": 'ohos.permission.FILE_ACCESS_PERSIST' 45 | // }, 46 | // { 47 | // "name": 'ohos.permission.READ_PASTEBOARD', 48 | // "reason": '$string:read_pasteboard_reason', 49 | // "usedScene": { 50 | // "abilities": [ 51 | // "EntryAbility" 52 | // ], 53 | // "when": "inuse" 54 | // } 55 | // } 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/dialog/PasteboardAuthorizeDialog.ets: -------------------------------------------------------------------------------- 1 | import { BaseDialogOptions, DialogAction } from "@pura/harmony-dialog" 2 | import { SizeConstant, toast } from "common" 3 | 4 | export interface PasteboardAuthorizeDialogOptions extends BaseDialogOptions { 5 | onAuthorized: VoidCallback 6 | } 7 | 8 | @Builder 9 | export function PasteboardAuthorizeDialog(options: PasteboardAuthorizeDialogOptions) { 10 | Column() { 11 | Blank().height(SizeConstant.SPACE_L) 12 | Text($r('app.string.clipboard')) 13 | .fontColor($r('app.color.on_container')) 14 | .fontSize(SizeConstant.TEXT_XXL) 15 | .fontWeight(500) 16 | Blank().height(SizeConstant.SPACE_L) 17 | Text($r('app.string.clipboard_permission_tips')) 18 | .fontColor($r('app.color.on_container')) 19 | .fontSize(SizeConstant.TEXT_L) 20 | Blank().height(SizeConstant.SPACE_L) 21 | PasteButton() 22 | // .backgroundColor($r('app.color.primary')) 23 | .fontColor(Color.White) 24 | .onClick((_: ClickEvent, result: PasteButtonOnClickResult) => { 25 | if (result === PasteButtonOnClickResult.SUCCESS) { 26 | options.onAuthorized() 27 | } else { 28 | toast.show($r('app.string.clipboard_permission_failed')) 29 | } 30 | }) 31 | Blank().height(SizeConstant.SPACE_L) 32 | Button($r('app.string.cancel'), { buttonStyle: ButtonStyleMode.TEXTUAL }) 33 | .width('100%') 34 | .fontColor($r('app.color.on_container_secondary')) 35 | .onClick(() => { 36 | if (options.onAction) { 37 | options.onAction(DialogAction.ONE, '', '') 38 | } 39 | }) 40 | } 41 | .width(350) 42 | .padding(SizeConstant.SPACE_L) 43 | .backgroundColor($r('app.color.container')) 44 | .borderRadius(SizeConstant.RADIUS_L) 45 | } -------------------------------------------------------------------------------- /entry/src/main/ets/constant/Keys.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * AppStorage KEY 3 | */ 4 | export class Keys { 5 | private constructor() { 6 | } 7 | 8 | static WINDOW_STAGE = 'WINDOW_STAGE' 9 | /** 10 | * 系统颜色模式 11 | */ 12 | static SYSTEM_COLOR_MODE = 'SYSTEM_COLOR_MODE' 13 | /** 14 | * 当前APP设置的颜色模式 15 | */ 16 | static COLOR_MODE = 'COLOR_MODE' 17 | /** 18 | * 当前APP设置的颜色模式 19 | */ 20 | static LANGUAGE_IS_AUTO = 'LANGUAGE_IS_AUTO' 21 | /** 22 | * 接收时开启PIN码 string 23 | */ 24 | static USE_PIN_WHEN_RECEIVE = 'USE_PIN_WHEN_RECEIVE' 25 | /** 26 | * 保存到历史记录 bool 27 | */ 28 | static SAVE_RECEIVE_RECORD = 'SAVE_RECEIVE_RECORD' 29 | /** 30 | * WebService已开启 bool 31 | */ 32 | static SERVICE_IS_ONLINE = 'SERVICE_IS_ONLINE' 33 | /** 34 | * 设备类型 35 | */ 36 | static DEVICE_TYPE = 'DEVICE_TYPE' 37 | /** 38 | * 设备型号 39 | */ 40 | static DEVICE_MODE = 'DEVICE_MODE' 41 | /** 42 | * UDP组播地址 43 | */ 44 | static MULTICAST_ADDRESS = 'MULTICAST_ADDRESS' 45 | /** 46 | * 当前是否正处于文件传输状态 47 | * value = [boolean] 48 | */ 49 | static CURRENTLY_TRANSFERRING_STATUS = 'CURRENTLY_TRANSFERRING_STATUS' 50 | /** 51 | * 当前设备别名 52 | * value = [string] 53 | */ 54 | static ALIAS = 'alias' 55 | /** 56 | * HttpService监听端口 57 | * value = [string] 58 | */ 59 | static PORT = 'PORT' 60 | /** 61 | * 当前设备指纹 62 | * value = [string] 63 | */ 64 | static FINGERPRINT = 'fingerprint' 65 | /** 66 | * 当前设置的自动保存类型 67 | * value = [AutoSaveModel] 68 | */ 69 | static AUTO_SAVE_MODEL = 'AUTO_SAVE_MODEL' 70 | /** 71 | * 当前正在接收的请求sessionId 72 | * value = [string] 73 | */ 74 | static CURRENTLY_RECEIVING_SESSION = 'CURRENTLY_RECEIVING_SESSION' 75 | /** 76 | * 开发者模式 77 | */ 78 | static IS_DEV_MODE = 'IS_DEV_MODE' 79 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_text_txt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_txt 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_text_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_text_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/utils/Apps.ets: -------------------------------------------------------------------------------- 1 | import { common, OpenLinkOptions, Want, wantConstant } from '@kit.AbilityKit'; 2 | import { deviceInfo } from '@kit.BasicServicesKit'; 3 | import { mime } from './mime'; 4 | import { fileUri } from '@kit.CoreFileKit'; 5 | 6 | export class Apps { 7 | /** 8 | * 跳转至Download文件夹 9 | * @param context 10 | * @returns 11 | */ 12 | static toAppDownloadFolder(context: common.UIAbilityContext): Promise { 13 | if (deviceInfo.sdkApiVersion < 12) { 14 | let want: Want = { 15 | bundleName: 'com.huawei.hmos.filemanager', 16 | abilityName: 'MainAbility' 17 | }; 18 | return context.startAbility(want); 19 | } else { 20 | let link: string = 'filemanager://openDirectory'; 21 | let openLinkOptions: OpenLinkOptions = { 22 | parameters: { 23 | fileUri: 'file:///storage/Users/currentUser/Download/' + context.applicationInfo.name + '/' 24 | } 25 | }; 26 | return context.openLink(link, openLinkOptions) 27 | } 28 | } 29 | 30 | static async useOtherAppOpenFile(context: common.UIAbilityContext, filepath: string): Promise { 31 | try { 32 | console.log(`打开文件 格式 -> ${mime.getType(filepath)} path: ${filepath}`) 33 | let uri = fileUri.getUriFromPath(filepath); 34 | // 构造请求数据 35 | let want: Want = { 36 | action: 'ohos.want.action.viewData', // 表示查看数据的操作,文件打开场景固定为此值 37 | uri: uri, 38 | type: mime.getType(filepath) || '*/*', // 表示待打开文件的类型 39 | // 配置被分享文件的读写权限,例如对文件打开应用进行读写授权 40 | flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION 41 | }; 42 | return await context.startAbility(want) 43 | } catch (error) { 44 | console.error(`Open file failed: ${JSON.stringify(error)}`); 45 | return Promise.reject(error) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_favor_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_favor_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/question/QuestionPage.ets: -------------------------------------------------------------------------------- 1 | import { Screens, SizeConstant } from 'common' 2 | import { NavigationTitle } from '../../components' 3 | import { Keys } from '../../constant' 4 | import { QuestionCard } from './QuestionCard' 5 | 6 | @Builder 7 | export function QuestionPageBuilder() { 8 | QuestionPage() 9 | } 10 | 11 | @Component 12 | export struct QuestionPage { 13 | build() { 14 | NavDestination() { 15 | NavigationTitle({ title: $r('app.string.troubleshooting') }) { 16 | Scroll() { 17 | Column() { 18 | Text($r('app.string.solution_tips')) 19 | .fontSize(SizeConstant.TEXT_L) 20 | .fontColor($r('app.color.on_container')) 21 | Blank().height(SizeConstant.SPACE_L) 22 | QuestionCard({ 23 | title: $r('app.string.firewall_title'), 24 | text: $r('app.string.firewall_text', AppStorage.get(Keys.PORT) || 53317) 25 | }) 26 | Blank().height(SizeConstant.SPACE_L) 27 | QuestionCard({ 28 | title: $r('app.string.discover_title'), 29 | text: $r('app.string.discover_text') 30 | }) 31 | Blank().height(SizeConstant.SPACE_L) 32 | QuestionCard({ title: $r('app.string.discover_two_way_title'), text: $r('app.string.discover_two_way_text') }) 33 | } 34 | .justifyContent(FlexAlign.Start) 35 | .padding({ 36 | left: SizeConstant.SPACE_L, 37 | top: SizeConstant.SPACE_L, 38 | right: SizeConstant.SPACE_L, 39 | bottom: SizeConstant.SPACE_L + Screens.getNavigationBarHeight(), 40 | }) 41 | .width('100%') 42 | .height('100%') 43 | } 44 | .width('100%') 45 | .height('100%') 46 | } 47 | } 48 | .hideTitleBar(true) 49 | } 50 | } -------------------------------------------------------------------------------- /common/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /serve/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_image_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_image 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/DeviceTypeMenu.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | import { DeviceIcon } from '../../../../components' 3 | import { Keys } from '../../../../constant' 4 | 5 | @Extend(Column) 6 | function itemStyles() { 7 | .alignItems(HorizontalAlign.Center) 8 | .width('100%') 9 | .padding({ top: SizeConstant.SPACE_S, bottom: SizeConstant.SPACE_S }) 10 | } 11 | 12 | @Builder 13 | export function DeviceTypeMenu() { 14 | Menu() { 15 | MenuItem() { 16 | Column() { 17 | DeviceIcon({ deviceType: 'mobile', iconColor: $r('app.color.on_container'), dimension: SizeConstant.ICON_XXL }) 18 | } 19 | .itemStyles() 20 | } 21 | .onClick(() => AppStorage.set(Keys.DEVICE_TYPE, 'mobile')) 22 | 23 | MenuItem() { 24 | Column() { 25 | DeviceIcon({ deviceType: 'desktop', iconColor: $r('app.color.on_container'), dimension: SizeConstant.ICON_XXL }) 26 | } 27 | .itemStyles() 28 | } 29 | .alignSelf(ItemAlign.Center) 30 | .onClick(() => AppStorage.set(Keys.DEVICE_TYPE, 'desktop')) 31 | 32 | MenuItem() { 33 | Column() { 34 | DeviceIcon({ deviceType: 'web', iconColor: $r('app.color.on_container'), dimension: SizeConstant.ICON_XXL }) 35 | } 36 | .itemStyles() 37 | } 38 | .onClick(() => AppStorage.set(Keys.DEVICE_TYPE, 'web')) 39 | 40 | MenuItem() { 41 | Column() { 42 | DeviceIcon({ deviceType: 'headless', iconColor: $r('app.color.on_container'), dimension: SizeConstant.ICON_XXL }) 43 | } 44 | .itemStyles() 45 | } 46 | .onClick(() => AppStorage.set(Keys.DEVICE_TYPE, 'headless')) 47 | 48 | MenuItem() { 49 | Column() { 50 | DeviceIcon({ deviceType: 'server', iconColor: $r('app.color.on_container'), dimension: SizeConstant.ICON_XXL }) 51 | } 52 | .itemStyles() 53 | } 54 | .onClick(() => AppStorage.set(Keys.DEVICE_TYPE, 'server')) 55 | } 56 | .width(150) 57 | } -------------------------------------------------------------------------------- /entry/src/main/ets/locale/LocaleProvider.ets: -------------------------------------------------------------------------------- 1 | import i18n from '@ohos.i18n'; 2 | import { Errors, logger } from 'common'; 3 | import { Keys } from '../constant'; 4 | import { deviceInfo } from '@kit.BasicServicesKit'; 5 | import { LocaleType } from './LocaleType'; 6 | 7 | export class LocaleProvider { 8 | static setSpecifiedLocal(lang: string, isFromAuto: boolean = false): boolean { 9 | const app = i18n.System.getAppPreferredLanguage() 10 | if (lang.toString() == app) { 11 | logger.info("LocaleProvider", `和当前App语言一致,不修改 ${lang}`) 12 | return true 13 | } 14 | logger.info("LocaleProvider", `设置语言 ${app} -> ${lang}`) 15 | try { 16 | i18n.System.setAppPreferredLanguage(lang) 17 | logger.info('LocaleProvider', `语言已设置: ${i18n.System.getAppPreferredLanguage()}`) 18 | if (!isFromAuto) { 19 | AppStorage.set(Keys.LANGUAGE_IS_AUTO, false) 20 | } 21 | return true 22 | } catch (e) { 23 | logger.warn('LocaleProvider', `语言设置失败: ${Errors.getErrorMessage(e)}`) 24 | return false 25 | } 26 | } 27 | 28 | static setAutoLocale(): boolean { 29 | let lang = LocaleProvider.getSystemLanguage() 30 | if (lang != LocaleType.ENGLISH_US && lang != LocaleType.CHINESE_HANS_CN) { 31 | lang = LocaleType.ENGLISH_US 32 | } 33 | if (LocaleProvider.setSpecifiedLocal(lang, true)) { 34 | AppStorage.set(Keys.LANGUAGE_IS_AUTO, true) 35 | return true 36 | } 37 | return false 38 | } 39 | 40 | static getLocalText(): Resource { 41 | if (AppStorage.get(Keys.LANGUAGE_IS_AUTO) === true) { 42 | console.log("显示跟随系统") 43 | return $r('app.string.flow_system') 44 | } else { 45 | console.log("显示指定语言") 46 | return $r('app.string.locale') 47 | } 48 | } 49 | 50 | static getSystemLanguage(): string { 51 | if (deviceInfo.sdkApiVersion < 20) { 52 | return i18n.getSystemLanguage() 53 | } else { 54 | return i18n.System.getSystemLanguage() 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/WindowUtils.ets: -------------------------------------------------------------------------------- 1 | // 窗口信息 2 | import { window } from '@kit.ArkUI'; 3 | 4 | export class WindowUtils { 5 | public static window: window.Window; 6 | public static windowWidth_px: number; 7 | public static windowHeight_px: number; 8 | public static topAvoidAreaHeight_px: number; 9 | public static navigationIndicatorHeight_px: number; 10 | 11 | static onWindowStageCreate(windowStage: window.WindowStage){ 12 | // 获取窗口宽高 13 | WindowUtils.window = windowStage.getMainWindowSync(); 14 | WindowUtils.windowWidth_px = WindowUtils.window.getWindowProperties().windowRect.width; 15 | WindowUtils.windowHeight_px = WindowUtils.window.getWindowProperties().windowRect.height; 16 | 17 | // 获取上方避让区(状态栏等)高度 18 | let avoidArea = WindowUtils.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); 19 | WindowUtils.topAvoidAreaHeight_px = avoidArea.topRect.height; 20 | 21 | // 获取导航条高度 22 | let navigationArea = WindowUtils.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); 23 | WindowUtils.navigationIndicatorHeight_px = navigationArea.bottomRect.height; 24 | 25 | // 监听窗口尺寸、状态栏高度及导航条高度的变化并更新 26 | try { 27 | WindowUtils.window.on('windowSizeChange', (data) => { 28 | WindowUtils.windowWidth_px = data.width; 29 | WindowUtils.windowHeight_px = data.height; 30 | AppStorage.setOrCreate('windowSizeChanged', Date.now()) 31 | }) 32 | 33 | WindowUtils.window.on('avoidAreaChange', (data) => { 34 | if (data.type == window.AvoidAreaType.TYPE_SYSTEM) { 35 | let topRectHeight = data.area.topRect.height; 36 | WindowUtils.topAvoidAreaHeight_px = topRectHeight; 37 | } else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { 38 | let bottomRectHeight = data.area.bottomRect.height; 39 | WindowUtils.navigationIndicatorHeight_px = bottomRectHeight; 40 | } 41 | }) 42 | } catch (exception) { 43 | console.log('register failed ' + JSON.stringify(exception)); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_text_html.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_html 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/receive/ReceiveSettingPage.ets: -------------------------------------------------------------------------------- 1 | import { Screens, SizeConstant } from 'common' 2 | import { NavigationTitle } from '../../components' 3 | import { FileInfoModel, PrepareUploadModel, ReceivedFileModel } from '../../model' 4 | import { Dates, FileProvider, mime, ObservedArray, Routers } from '../../utils' 5 | import { ReceiveItem } from './ReceiveItem' 6 | import { util } from '@kit.ArkTS' 7 | import { RandomUtil } from '@pura/harmony-utils' 8 | 9 | @Builder 10 | export function ReceiveSettingPageBuilder() { 11 | ReceiveSettingPage() 12 | } 13 | 14 | @Component 15 | export struct ReceiveSettingPage { 16 | @Consume('pages') pages: NavPathStack; 17 | @State files: ObservedArray = new ObservedArray() 18 | 19 | build() { 20 | NavDestination() { 21 | NavigationTitle({ title: $r('app.string.setting') }) { 22 | List({ space: SizeConstant.SPACE_M }) { 23 | ForEach(this.files, (item: FileInfoModel) => { 24 | ListItem() { 25 | ReceiveItem({ file: item }) 26 | } 27 | }, (item: FileInfoModel, index: number) => `${item.id}_${index}`) 28 | ListItem().height(Screens.getNavigationBarHeight()) 29 | } 30 | .padding(SizeConstant.SPACE_L) 31 | .width('100%') 32 | .height('100%') 33 | } 34 | } 35 | .hideTitleBar(true) 36 | } 37 | 38 | aboutToAppear(): void { 39 | const params: Array = this.pages.getParamByName(Routers.RECEIVE_SETTING_PAGE) as Array 40 | if (params && params.length != 0) { 41 | // this.param = params[0] 42 | } 43 | for (let i = 0; i < 40; i++) { 44 | this.files.push({ 45 | id: util.generateRandomUUID(), 46 | fileName: 'ic_add_filled.svg', 47 | size: RandomUtil.getRandomInt(10, 100000), 48 | fileType: mime.getType('ic_add_filled.svg') || '', 49 | filePath: FileProvider.getDefaultFileDir(getContext(this)), 50 | sender: '巨大的蓝莓', 51 | sendAt: Dates.now() 52 | }) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_reset 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /common/src/main/ets/utils/Strings.ets: -------------------------------------------------------------------------------- 1 | import { buffer, util } from '@kit.ArkTS'; 2 | import { Context } from '@kit.AbilityKit'; 3 | 4 | export class Strings { 5 | private constructor() { 6 | } 7 | 8 | static buf2String(buf: ArrayBuffer): string { 9 | let msgArray: Uint8Array = new Uint8Array(buf); 10 | let textDecoder: util.TextDecoder = util.TextDecoder.create("utf-8"); 11 | return textDecoder.decodeToString(msgArray) 12 | } 13 | 14 | 15 | static FORMAT_PLACEHOLDER = /%s/ 16 | 17 | static formatString(format: string, ...args: string[]) { 18 | let result = format 19 | args.forEach(value => { 20 | result = result.replace(Strings.FORMAT_PLACEHOLDER, value) 21 | }) 22 | return result 23 | } 24 | 25 | /** 26 | * 将指定字符串按指定字符数分割 27 | * @param array 用于接收返回数据数组,传空数组 28 | * @param str 用于分割的数组 29 | * @param len 分割长度 30 | * @returns 31 | */ 32 | static getSliceByBytes(array: string[], str: string, len: number): string[] { 33 | if (!str) { 34 | return [] 35 | } 36 | const size: number = buffer.byteLength(str, 'utf-8') 37 | if (size < len) { 38 | array.push(str) 39 | return array 40 | } 41 | let currentSize = 0; 42 | let lastCharLen = 0 43 | let subEnd = 0 44 | for (subEnd = 0; subEnd < str.length; subEnd++) { 45 | lastCharLen = ((str.charCodeAt(subEnd) > 255) ? 2 : 1) 46 | currentSize += lastCharLen 47 | if (currentSize >= len) { 48 | array.push(str.substring(0, subEnd + 1)) 49 | break; 50 | } 51 | } 52 | if (currentSize < size) { 53 | Strings.getSliceByBytes(array, str.slice(subEnd + parseInt((lastCharLen / 2).toFixed())), len) 54 | } 55 | return array 56 | } 57 | 58 | static getString(context: Context, resourse: Resource): string { 59 | return context.resourceManager.getStringSync(resourse) 60 | } 61 | 62 | static isEmpty(str: string | null | undefined): boolean { 63 | if (str == null || str == undefined) { 64 | return true 65 | } 66 | return str.length == 0 67 | } 68 | 69 | static isNotEmpty(str: string | null | undefined): boolean { 70 | return !Strings.isEmpty(str) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_delete 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_random.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_random 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_audio_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_audio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /entry/src/main/ets/db/mapper/ReceiveRecordMapper.ets: -------------------------------------------------------------------------------- 1 | import { Connection } from 'rdbplus'; 2 | import { LocalSendMapper } from './LocalSendMapper'; 3 | import { relationalStore } from '@kit.ArkData'; 4 | import { ReceiveRecordModel } from '../../model/ReceiveRecordModel'; 5 | import { Errors } from 'common'; 6 | 7 | const getRow = (res: relationalStore.ResultSet): ReceiveRecordModel => { 8 | const record: ReceiveRecordModel = new ReceiveRecordModel() 9 | record.id = res.getLong(res.getColumnIndex('id')) 10 | record.fileId = res.getString(res.getColumnIndex('fileId')) 11 | record.fileName = res.getString(res.getColumnIndex('fileName')) 12 | record.size = res.getLong(res.getColumnIndex('size')) 13 | record.fileType = res.getString(res.getColumnIndex('fileType')) 14 | record.sender = res.getString(res.getColumnIndex('sender')) 15 | record.sendAt = res.getLong(res.getColumnIndex('sendAt')) 16 | record.filePath = res.getString(res.getColumnIndex('filePath')) 17 | return record 18 | } 19 | const tbName: string = 't_record' 20 | 21 | export class ReceivedRecordMapper extends LocalSendMapper { 22 | 23 | constructor() { 24 | super({ tableName: tbName, primaryKey: 'id' }, getRow) 25 | } 26 | 27 | private createTable(db: Connection): Promise { 28 | const sql: string = 29 | `create table if not exists ${tbName} 30 | ( 31 | id integer primary key autoincrement, 32 | fileId varchar(100), 33 | fileName varchar(100), 34 | size integer, 35 | fileType varchar(100), 36 | sender varchar(100), 37 | sendAt integer, 38 | filePath varchar(300) 39 | );` 40 | return db.execDML(sql) 41 | } 42 | 43 | async insert(obj: ReceiveRecordModel, conn?: Connection | undefined): Promise { 44 | let _conn: Connection | null = null 45 | try { 46 | _conn = conn || await this.getConnection() 47 | await this.createTable(_conn) 48 | await super.insert(obj, _conn) 49 | } catch (e) { 50 | console.error(`insert error : ${Errors.getErrorMessage(e)}`) 51 | throw Error(e) 52 | } finally { 53 | if (_conn !== conn && _conn != null) { 54 | await _conn.close() 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_wlan_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_wlan_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /serve/src/main/ets/decode/Parser.ets: -------------------------------------------------------------------------------- 1 | import { buffer, JSON, uri } from '@kit.ArkTS'; 2 | import { HttpError } from '../../../../Index'; 3 | import { getLogger, Logger } from '../utils'; 4 | import { HttpExchangeImpl } from '../http/impl'; 5 | 6 | const HTTP_VERSION_V1 = 'HTTP/1.1' 7 | const EMPTY_DATA: object = Object 8 | 9 | const logger: Logger = getLogger('Parser') 10 | 11 | export class Parser { 12 | static parseHeader(header: string, exchange: HttpExchangeImpl) { 13 | if (exchange.isEnd) { 14 | return 15 | } 16 | // 把socket字符串按照换行符分割成多行,并取出第一行 17 | const lines: string[] = header.split(/[\r\n]+/); 18 | let line: string | undefined = lines.shift(); 19 | 20 | // 把第一行按照空格分割,得到请求行数据 {POST /index HTTP/1.1} 21 | let status: string[] = line?.split(' ') || [] 22 | if (status.length != 3) { 23 | logger.error(`--- parse http protocol error: ${line}`) 24 | throw HttpError.error(400, "Bad Request") 25 | } 26 | 27 | exchange.setRequestMethod(status[0].toUpperCase()) 28 | try { 29 | //获取URL请求路径和URL参数 30 | let original = status[1] 31 | const result: uri.URI = new uri.URI(original) 32 | const queryNames = result.getQueryNames() 33 | for (let name of queryNames) { 34 | exchange.query.set(name, result.getQueryValue(name)) 35 | } 36 | exchange.originalUrl = original 37 | exchange.path = result.path 38 | exchange.protocol = status[2] 39 | } catch (e) { 40 | logger.error(`--- parse header error: ${JSON.stringify(e)} message: ${e?.message} stack:\n ${e?.stack}`) 41 | throw HttpError.error(400, "Bad Request") 42 | } 43 | 44 | // 请求头 45 | line = lines.shift(); 46 | while (line) { 47 | const index: number = line.indexOf(':'); 48 | if (index > 0) { 49 | exchange.getRequestHeaders().setHeader(line.substring(0, index), line.substring(index + 1).trim()) 50 | } 51 | line = lines.shift() 52 | } 53 | 54 | const connection = exchange.getRequestHeaders().getHeader('connection') 55 | if (HTTP_VERSION_V1 === exchange.protocol && (connection === undefined || !(/close/ig.test(connection)))) { 56 | exchange.setKeepLive() 57 | } 58 | } 59 | 60 | static parseJson(body: buffer.Buffer): T { 61 | const data = body.toString() 62 | if (!data) { 63 | return EMPTY_DATA as T 64 | } 65 | return JSON.parse(data) as T 66 | } 67 | } -------------------------------------------------------------------------------- /entry/src/main/ets/components/IconButton.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant } from 'common' 2 | import { LengthMetrics, MeasureText, MeasureUtils } from '@kit.ArkUI' 3 | import { resourceManager } from '@kit.LocalizationKit' 4 | 5 | @Component 6 | export struct IconButton { 7 | @Prop text: string | Resource 8 | @Prop icon: PixelMap | ResourceStr | DrawableDescriptor 9 | @Prop textSize: number = SizeConstant.TEXT_L 10 | @Prop iconSize: number = SizeConstant.ICON_M 11 | @Prop arrangement: FlexDirection = FlexDirection.Row 12 | @Prop space: number = SizeConstant.SPACE_S 13 | @Prop horizontalPadding: number = 0 14 | @Prop verticalPadding: number = 0 15 | @Prop textColor: ResourceColor = $r('app.color.on_background') 16 | @Prop iconColor: ResourceColor = $r('app.color.icon') 17 | @Prop clickable: boolean = true 18 | 19 | build() { 20 | Stack() { 21 | Flex({ 22 | direction: this.arrangement, 23 | alignItems: ItemAlign.Center, 24 | justifyContent: FlexAlign.Center, 25 | space: { 26 | main: LengthMetrics.vp(this.space), 27 | cross: LengthMetrics.vp(this.space) 28 | } 29 | }) { 30 | Image(this.icon) 31 | .size({ width: this.iconSize, height: this.iconSize }) 32 | .fillColor(this.iconColor) 33 | Text(this.text) 34 | .fontSize(this.textSize) 35 | .fontColor(this.clickable ? this.textColor : $r('app.color.on_container_secondary')) 36 | } 37 | .align(Alignment.Center) 38 | } 39 | .size({ 40 | width: this.calcWidth(), 41 | height: this.calcHeight() 42 | }) 43 | } 44 | 45 | calcWidth(): number { 46 | const context = this.getUIContext() 47 | let text = '' 48 | if (typeof this.text === 'string') { 49 | text = this.text as string 50 | } else { 51 | text = context.getHostContext()?.resourceManager.getStringSync((this.text as Resource).id) || "" 52 | } 53 | const textWidth = this.getUIContext().getMeasureUtils().measureText({ 54 | textContent: text, 55 | fontSize: `${this.textSize}px`, 56 | maxLines: 1 57 | }) 58 | return textWidth + this.iconSize + this.space + (this.horizontalPadding * 2) 59 | 60 | } 61 | 62 | calcHeight(): number { 63 | const isVertical = this.arrangement == FlexDirection.Column 64 | return this.iconSize + (isVertical ? this.space : 0) + this.textSize + (this.verticalPadding * 2) 65 | } 66 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/setting/view/AliasAction.ets: -------------------------------------------------------------------------------- 1 | import { DialogHelper } from '@pura/harmony-dialog' 2 | import { SizeConstant } from 'common' 3 | import { Icon } from '../../../../components' 4 | import { Keys } from '../../../../constant' 5 | import { DeviceProvider } from '../../../../utils' 6 | 7 | @Component 8 | export struct AliasAction { 9 | @StorageLink(Keys.ALIAS) alias: string = '' 10 | @State value: string = this.alias 11 | onChanged?: VoidCallback 12 | 13 | @Builder 14 | InputContent() { 15 | Column() { 16 | Icon({ src: $r('app.media.ic_random') }) 17 | .onClick(() => { 18 | const rm = this.getUIContext().getHostContext()?.resourceManager 19 | if (rm != null) { 20 | this.value = DeviceProvider.getRandomAlias(rm) 21 | } 22 | }) 23 | Blank().height(SizeConstant.SPACE_M) 24 | TextInput({ text: $$this.value }) 25 | .fontSize(SizeConstant.TEXT_XL) 26 | .fontColor($r('app.color.on_background')) 27 | .maxLines(1) 28 | .maxLength(50) 29 | .defaultFocus(true) 30 | .textAlign(TextAlign.Center) 31 | .backgroundColor($r('app.color.container')) 32 | .borderRadius(SizeConstant.RADIUS_M) 33 | Blank().height(SizeConstant.SPACE_M) 34 | } 35 | .alignItems(HorizontalAlign.Start) 36 | } 37 | 38 | build() { 39 | Text(this.alias) 40 | .backgroundColor($r('app.color.background')) 41 | .borderRadius(SizeConstant.RADIUS_M) 42 | .width(150) 43 | .height(48) 44 | .textAlign(TextAlign.Center) 45 | .onClick(() => { 46 | DialogHelper.showCustomContentDialog({ 47 | title: $r("app.string.alias"), 48 | contentBuilder: () => { 49 | this.InputContent() 50 | }, 51 | buttons: [{ 52 | value: $r('app.string.cancel'), 53 | buttonStyle: ButtonStyleMode.TEXTUAL, 54 | fontColor: $r('app.color.on_container_secondary') 55 | }, { 56 | value: $r('app.string.positive'), 57 | buttonStyle: ButtonStyleMode.TEXTUAL, 58 | action: () => { 59 | if (this.alias != this.value) { 60 | this.alias = this.value 61 | if (this.onChanged) { 62 | this.onChanged() 63 | } 64 | } 65 | } 66 | }] 67 | }) 68 | }) 69 | } 70 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_files_application_pdf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_normal_white_grid_pdf 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_storage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_storage 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/components/LoopImage.ets: -------------------------------------------------------------------------------- 1 | import { AnimatorOptions, AnimatorResult } from '@kit.ArkUI'; 2 | import { ObserveVisibleLayout } from './ObserveVisibleLayout'; 3 | 4 | @Component 5 | export struct LoopImage { 6 | @Prop images: Array 7 | @State wh: Length = 64 8 | @State alpha: number = 0 9 | @State current: number = 0 //当前Images下标 10 | animator: AnimatorResult | null = null 11 | timer: number | undefined 12 | 13 | build() { 14 | if (this.images && this.images.length > 0) { 15 | ObserveVisibleLayout({ 16 | onVisibleChanged: (isVisible: boolean) => { 17 | if (isVisible) { 18 | this.startAnim() 19 | } else { 20 | this.stopAnim() 21 | } 22 | } 23 | }) { 24 | Image(this.images[this.current]) 25 | .size({ width: this.wh, height: this.wh }) 26 | .fillColor($r('app.color.icon')) 27 | .opacity(this.alpha) 28 | } 29 | } 30 | } 31 | 32 | aboutToDisappear(): void { 33 | this.stopAnim(true) 34 | } 35 | 36 | private startAnim() { 37 | let context: UIContext = this.getUIContext() 38 | if (context) { 39 | if (!this.animator) { 40 | let options:AnimatorOptions = { 41 | duration: 1000, 42 | easing: "smooth", 43 | delay: 0, 44 | fill: "none", 45 | direction: "normal", 46 | iterations: 1, 47 | begin: this.alpha, 48 | end: 1 49 | } 50 | this.animator = context.createAnimator(options) 51 | this.animator.onFrame = (progress: number) => { 52 | if (progress != 0) { 53 | this.alpha = progress 54 | } 55 | } 56 | this.animator.onFinish = () => { 57 | this.timer = setTimeout(() => { 58 | this.alpha = 0 59 | this.setNextImage() 60 | this.animator?.play() 61 | }, 3000) 62 | } 63 | } 64 | this.animator.play() 65 | } 66 | } 67 | 68 | private stopAnim(isDisappear: boolean = false) { 69 | 70 | if (this.animator) { 71 | if (isDisappear) { 72 | this.animator.cancel() 73 | this.animator = null 74 | this.alpha = 0 75 | } else { 76 | this.animator.pause() 77 | } 78 | } 79 | if (this.timer != undefined) { 80 | clearTimeout(this.timer) 81 | } 82 | } 83 | 84 | private setNextImage() { 85 | if (this.current < this.images.length - 1) { 86 | this.current += 1 87 | } else { 88 | this.current = 0 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_settings_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_settings_filled 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_edit 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/ets/transaction/ComponentAttrUtils.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Huawei Device Co., Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | import { componentUtils, UIContext } from '@kit.ArkUI'; 17 | import { JSON } from '@kit.ArkTS'; 18 | import { hilog } from '@kit.PerformanceAnalysisKit'; 19 | 20 | export class ComponentAttrUtils { 21 | // Gets the location information of a component based on its id. 22 | public static getRectInfoById(context: UIContext, id: string): RectInfoInPx { 23 | if (!context || !id) { 24 | throw Error('object is empty'); 25 | } 26 | let componentInfo: componentUtils.ComponentInfo = context.getComponentUtils().getRectangleById(id); 27 | 28 | hilog.info(0x0000, 'ComponentAttrUtils', 'the value is ' + JSON.stringify(componentInfo)); 29 | 30 | if (!componentInfo) { 31 | throw new Error('custom transition exception'); 32 | } 33 | 34 | let rstRect: RectInfoInPx = new RectInfoInPx(); 35 | const widthScaleGap = componentInfo.size.width * (1 - componentInfo.scale.x) / 2; 36 | const heightScaleGap = componentInfo.size.height * (1 - componentInfo.scale.y) / 2; 37 | rstRect.left = componentInfo.translate.x + componentInfo.screenOffset.x + widthScaleGap; 38 | rstRect.top = componentInfo.translate.y + componentInfo.screenOffset.y + heightScaleGap; 39 | rstRect.right = componentInfo.translate.x + componentInfo.screenOffset.x + componentInfo.size.width - widthScaleGap; 40 | rstRect.bottom = componentInfo.translate.y + componentInfo.screenOffset.y + componentInfo.size.height - heightScaleGap; 41 | rstRect.width = rstRect.right - rstRect.left; 42 | rstRect.height = rstRect.bottom - rstRect.top; 43 | return { 44 | left: rstRect.left, 45 | right: rstRect.right, 46 | top: rstRect.top, 47 | bottom: rstRect.bottom, 48 | width: rstRect.width, 49 | height: rstRect.height 50 | }; 51 | } 52 | } 53 | 54 | export class RectInfoInPx { 55 | left: number = 0; 56 | top: number = 0; 57 | right: number = 0; 58 | bottom: number = 0; 59 | width: number = 0; 60 | height: number = 0; 61 | } 62 | 63 | export class RectJson { 64 | $rect: Array = []; 65 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public/ic_public_history 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------