diff --git a/LICENSE b/LICENSE index 0210352ae2ade0dd7b4c841cb6e8ba08b4780038..18795a48d6b12fcdc1aa7bac9a9cb99f83815267 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright (c) 2023 Huawei Device Co., Ltd. All rights reserved. + Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.en.md b/README.en.md index f61d5631673dc12d340073a6014996e686a2d346..d16009501035c73f2073eddd9a892213fe149819 100644 --- a/README.en.md +++ b/README.en.md @@ -4,7 +4,8 @@ With Contacts as an example, this codelab will show you how to create a distributed key-value (KV) store and how to add, delete, modify, query, and synchronize data. -![](screenshots/device/contact.en.gif) +## Preview +![](screenshots/devices/contact.en.gif) ## Concepts @@ -31,6 +32,6 @@ This codelab uses the distributed capability. It requires you to add the data ex ## Constraints 1. The sample is only supported on Huawei phones with standard systems. -2. HarmonyOS: HarmonyOS 5.0.5 Release or later. -3. DevEco Studio: DevEco Studio 5.0.5 Release or later. -4. HarmonyOS SDK: HarmonyOS 5.0.5 Release SDK or later. +2. HarmonyOS: HarmonyOS 5.1.1 Release or later. +3. DevEco Studio: DevEco Studio 5.1.1 Release or later. +4. HarmonyOS SDK: HarmonyOS 5.1.1 Release SDK or later. diff --git a/README.md b/README.md index 733a0446f4d0cdba462ec7fb410da4bd04eb004b..d4c73ad90d7891420e42bbbe14d4f6f86aef4b48 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,12 @@ # 基于分布式键值数据库实现通讯录功能 -## 简介 +## 项目简介 本篇Codelab以通讯录为例,介绍分布式键值数据库的创建、数据的增加/删除/修改/查询/同步等操作方法。 -![](screenshots/device/contact.gif) +## 效果预览 -## 相关概念 - -- 应用数据持久化概述:应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。 -- 键值型数据库:键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。 -- @ohos.data.distributedKVStore (分布式键值数据库):分布式键值数据库为应用程序提供不同设备间数据库的分布式协同能力。通过调用分布式键值数据库各个接口,应用程序可将数据保存到分布式键值数据库中,并可对分布式键值数据库中的数据进行增加、删除、修改、查询等操作。 - -## 相关权限 - -本篇Codelab用到分布式的能力,需要在配置文件module.json5里添加不同设备间的数据交换权限:ohos.permission.DISTRIBUTED_DATASYNC。 + ## 使用说明 @@ -28,9 +20,48 @@ 8. 联系人批量删除页面,选中需要删除的联系人,下方点击“删除”按钮,删除选中的联系人。 9. 联系人编辑页面,编辑好联系人信息后,点击右上角的“保存”按钮,修改联系人信息。 +## 工程目录 +``` +├──entry/src/main/ets +│ ├──common +│ │ └──CommonConstants.ets // 常量集合 +│ ├──components +│ │ ├──ContactBottomBar.ets // 通讯录删除页面底部tab组件 +│ │ ├──ContactDeleteDialog.ets // 通讯录删除弹窗组件 +│ │ ├──ContactDetailItem.ets // 通讯录详情页列表项组件 +│ │ ├──ContactDeviceDialog.ets // 通讯录设备弹窗组件 +│ │ └──ContactListItem.ets // 通讯录列表页列表项组件 +│ ├──entryability +│ │ └──EntryAbility.ets // 入口文件 +│ ├──pages +│ │ ├──ContactAddAndEditPage.ets // 通讯录添加和编辑页面 +│ │ ├──ContactDeletePage.ets // 通讯录删除页面 +│ │ ├──ContactDetailPage.ets // 通讯录详情页面 +│ │ └──ContactHomePage.ets // 通讯录首页 +│ ├──utils +│ │ ├──ContactDeviceManager.ets // 通讯录设备管理类 +│ │ └──KvManager.ets // 键值型数据库管理类 +│ └──viewmodel +│ └──ContactViewModel.ets // 通讯录model +└──entry/src/main/resources // 资源文件 +``` +## 相关概念 + +- 应用数据持久化概述:应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。 +- 键值型数据库:键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。 +- @ohos.data.distributedKVStore (分布式键值数据库) + :分布式键值数据库为应用程序提供不同设备间数据库的分布式协同能力。通过调用分布式键值数据库各个接口,应用程序可将数据保存到分布式键值数据库中,并可对分布式键值数据库中的数据进行增加、删除、修改、查询等操作。 + +## 相关权限 + +本篇Codelab用到分布式的能力,需要在配置文件module.json5里添加不同设备间的数据交换权限:ohos.permission.DISTRIBUTED_DATASYNC。 + ## 约束与限制 1. 本示例仅支持标准系统上运行,支持设备:华为手机。 -2. HarmonyOS系统:HarmonyOS 5.0.5 Release及以上。 -3. DevEco Studio版本:DevEco Studio 5.0.5 Release及以上。 -4. HarmonyOS SDK版本:HarmonyOS 5.0.5 Release SDK及以上。 +2. HarmonyOS系统:HarmonyOS 5.1.1 Release及以上。 +3. DevEco Studio版本:DevEco Studio 5.1.1 Release及以上。 +4. HarmonyOS SDK版本:HarmonyOS 5.1.1 Release SDK及以上。 +5. 双端设备需要登录同一华为账号,建议打开查找设备功能。 +6. 双端设备需要打开Wi-Fi和蓝牙开关。 +7. 双端设备都需要有该应用。 diff --git a/build-profile.json5 b/build-profile.json5 index 28db7ad2ea4faaba762bec70bd26b2a21148dc47..f5245ca5012ad9f053d615fab1e2651d35b11ee9 100644 --- a/build-profile.json5 +++ b/build-profile.json5 @@ -1,12 +1,14 @@ { "app": { - "signingConfigs": [], + "signingConfigs": [ + + ], "products": [ { "name": "default", "signingConfig": "default", - "compatibleSdkVersion": "5.0.5(17)", - "targetSdkVersion": "5.0.5(17)", + "compatibleSdkVersion": "5.1.1(19)", + "targetSdkVersion": "5.1.1(19)", "runtimeOS": "HarmonyOS" } ], diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eaa4bb5eb40a6bccd365319fa01a5da9907b3efa --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,33 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error", + "@typescript-eslint/semi": "error" + } +} \ No newline at end of file diff --git a/entry/src/main/ets/common/constants/CommonConstants.ets b/entry/src/main/ets/common/CommonConstants.ets similarity index 65% rename from entry/src/main/ets/common/constants/CommonConstants.ets rename to entry/src/main/ets/common/CommonConstants.ets index 3de6440f320958eb37966eef7befab2812760adf..6156924db4a15b163e08106f793384d62f034288 100644 --- a/entry/src/main/ets/common/constants/CommonConstants.ets +++ b/entry/src/main/ets/common/CommonConstants.ets @@ -1,18 +1,20 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +import { relationalStore } from '@kit.ArkData'; + /** * Common constants for all features. */ @@ -21,335 +23,332 @@ export default class CommonConstants { * Search font weight. */ static readonly SEARCH_FONT_WEIGHT: number = 10; - /** * The font family of search component. */ static readonly SEARCH_FONT_FAMILY: string = 'serif'; - /** * The layout weight of the list title. */ static readonly TITLE_LAYOUT_WEIGHT: number = 1; - /** * The max lines of the list title. */ static readonly TITLE_MAX_LINES: number = 1; - /** * Set component layout weight. */ static readonly WEIGHT: number = 1; - /** * The contacts detail page url. */ - static readonly PAGE_DETAIL_URL: string = 'pages/DetailPage'; - + static readonly PAGE_DETAIL_URL: string = 'pages/ContactDetailPage'; /** * The contacts editing page url. */ - static readonly ADD_EDIT_URL: string = 'pages/EditPage'; - + static readonly ADD_EDIT_URL: string = 'pages/ContactAddAndEditPage'; /** * The contacts list page url. */ - static readonly LIST_PAGE_URL: string = 'pages/ListPage'; - + static readonly LIST_PAGE_URL: string = 'pages/ContactHomePage'; /** * The contacts delete page url. */ - static readonly DELETE_PAGE_URL: string = 'pages/DeletePage'; - + static readonly DELETE_PAGE_URL: string = 'pages/ContactDeletePage'; /** * The key to transfer parameters when replace pages. */ static readonly KEY_PARAM_DATA: string = 'data'; - /** * The maximum component width. */ static readonly PERCENTAGE_MAX: string = '100%'; - /** * Eighty percent assembly width. */ static readonly PERCENTAGE_HEIGHT_MAX: string = '80%'; - /** * Delete page title width. */ static readonly PAGE_TITLE_WIDTH: string = '90%'; - /** * Edit the width of the input box on the page. */ static readonly EDIT_INPUT_WIDTH: string = '80%'; - /** * The contacts details page components width. */ static readonly DETAIL_ITEM_WIDTH: string = '70%'; - /** * Height of the address book list. */ static readonly CONTACTS_LIST_HEIGHT: string = '90%'; - /** * The width of the bottom component. */ static readonly BOTTOM_COMPONENT_WIDTH: string = '50%'; - /** * The contacts details page top height. */ static readonly DETAIL_TOP_HEIGHT: string = '30%'; - /** * The percentage of list component. */ static readonly LIST_WIDTH_PERCENT: string = '93.3%'; - /** * The contacts list divider left margin. */ static readonly CONTACTS_LIST_DIVIDER: string = '16%'; - /** * The contacts list divider left margin. */ static readonly CONTACTS_EMPTY_TOP: string = '17.8%'; - /** * Contact database key. */ static readonly CONTACTS_DATABASE_KEY: string = 'contactsKey'; - /** * The contact name. */ - static readonly CONTACTS_DETAIL_NAME: Resource = $r('app.string.edit_item_name'); - + static readonly CONTACTS_DETAIL_NAME: string = 'edit_item_name'; /** * The contact address. */ - static readonly CONTACTS_DETAIL_ADDRESS: Resource = $r('app.string.edit_item_address'); - + static readonly CONTACTS_DETAIL_ADDRESS: string = 'edit_item_address'; /** * The contact telephony. */ - static readonly CONTACTS_DETAIL_TEL: Resource = $r('app.string.edit_item_phone'); - + static readonly CONTACTS_DETAIL_TEL: string = 'edit_item_phone'; /** * The contact email. */ - static readonly CONTACTS_DETAIL_EMAIL: Resource = $r('app.string.edit_item_email'); - + static readonly CONTACTS_DETAIL_EMAIL: string = 'edit_item_email'; /** * The contact remarks. */ - static readonly CONTACTS_DETAIL_REMARKS: Resource = $r('app.string.edit_item_note'); - + static readonly CONTACTS_DETAIL_REMARKS: string = 'edit_item_note'; /** * The message prompt component duration. */ static readonly PROMPT_DURATION: number = 2000; - /** * Address book database unique identifier. */ static readonly DB_STORE_ID: string = 'DatabaseStoreId'; - /** * Ability name. */ static readonly ABILITY_NAME: string = 'EntryAbility'; - /** * Detail page id. */ static readonly DETAIL_PAGE_ID: string = 'detailId'; - /** * Delete page id. */ static readonly DELETE_PAGE_ID: string = 'deleteId'; - /** * Phone components id. */ static readonly PHONE_COMPONENTS_ID: number = 1; - /** * Email components id. */ static readonly Email_COMPONENTS_ID: number = 2; - /** * Note components id. */ static readonly Note_COMPONENTS_ID: number = 3; - /** * Device list group. */ static readonly DEVICE_GROUP: string = 'deviceGroup'; - /** * Even column. */ static readonly EVEN_COLUMN: number = 2; - /** * Indicates whether to edit the page. */ static readonly IS_EDIT: string = 'isEdit'; - /** * Key of details page information. */ static readonly DETAIL_INFO_KEY: string = 'key'; - /** * Monitors distributed database changes. */ static readonly DATA_CHANGE: string = 'dataChange'; - /** * Start ability callback. */ static readonly EXIT: string = 'exit'; - /** * Start ability callback id. */ static readonly SUBSCRIBE_ID: number = 100; - /** * The mode of start device discovery info. */ static readonly INFO_MODE: number = 0xAA; - /** * The frequency of start device discovery info. */ static readonly INFO_FREQ: number = 2; - /** * Discover device list. */ static readonly DISCOVER_DEVICE_LIST: string = 'discoverDeviceList'; - /** * Zero. */ static readonly ZERO: string = '0'; - /** * One. */ static readonly ONE: number = 1; - /** * Trusted device list. */ static readonly TRUSTED_DEVICE_LIST: string = 'trustedDeviceList'; - /** * Subscribe ID range. */ static readonly SUBSCRIBE_ID_RANGE: number = 65536; - /** * Auth type. */ static readonly AUTH_TYPE: number = 1; - /** * Subscribe mode. */ static readonly SUBSCRIBE_MODE: number = 0xAA; - /** * Subscribe medium. */ static readonly SUBSCRIBE_MEDIUM: number = 0; - /** * Subscribe freq. */ static readonly SUBSCRIBE_FREQ: number = 2; - /** * Subscribe capability. */ static readonly SUBSCRIBE_CAPABILITY: number = 0; - /** * Font weight. */ static readonly FONT_WEIGHT_500: number = 500; - /** * Invalid Index. */ static readonly INVALID_INDEX: number = -1; - - /** * One hundred percent. */ static readonly FULL_PERCENT: string = '100%'; - /** * Number one. */ static readonly NUMBER_ONE: number = 1; - /** * Device name width. */ static readonly DEVICE_NAME_WIDTH: string = '78%'; - /** * Select icon width. */ static readonly SELECT_ICON_WIDTH: string = '8%'; - /** * Localhost name. */ static readonly LOCALHOST_NAME: string = 'Local Host'; - /** * Entry ability. */ static readonly ENTRY_ABILITY: string = 'EntryAbility'; - /** * Ability name. */ static readonly BUNDLE_NAME: string = 'com.example.distributedcontacts'; - /** * App name. */ static readonly APP_NAME: string = 'Distributed Contacts'; - /** * Contacts name max length. */ static readonly CONTACTS_NAME_MAX_LENGTH: number = 10; - /** * Contacts tel max length. */ static readonly CONTACTS_TEL_MAX_LENGTH: number = 11; - /** * Contacts detail input box max length. */ static readonly CONTACTS_DETAIL_MAX: number = 20; + /** + * Distributed mail image storage location. + */ + static readonly MAIL_DISTRIBUTED_PATH: string = '/data/storage/el2/distributedfiles/'; + /** + * File name for storing cross-end photos. + */ + static readonly PICTURE_NAME: string = 'pictureName'; + /** + * Supports transfer. + */ + static readonly CAN_CONTINUATION: string = 'true'; + /** + * Forwarding is not supported. + */ + static readonly NO_CONTINUATION: string = 'false'; + /** + * With picture streaming. + */ + static readonly CAN_PHOTO: string = 'true'; + /** + * Transfer without picture. + */ + static readonly NO_PHOTO: string = 'false'; + /** + * Image stream size. + */ + static readonly IMAGE_STREAM_SIZE: number = 10240000; + /** + * Bottom toolbar picture spacing. + */ + static readonly BOTTOM_IMAGE_SPACE: number = 24; + static readonly FILE_BUFFER_SIZE: number = 4096; + static readonly APPENDIX_LIST_SPACE: number = 5; + static readonly APPENDIX_LIST_START_MARGIN: number = 15; + static readonly APPENDIX_LIST_END_MARGIN: number = 15; + /** + * Rdb database config. + */ + static readonly STORE_CONFIG: relationalStore.StoreConfig = { + name: 'database.db', + securityLevel: relationalStore.SecurityLevel.S1, + }; + /** + * Component size. + */ + static readonly DIALOG_HEIGHT = '55%'; + static readonly TABS_HEIGHT = '45%'; + static readonly MINIMUM_SIZE = 0; + static readonly FULL_SIZE = 1; + static readonly PROMPT_BOTTOM = '70vp'; + /** + * Component location. + */ + static readonly EDIT_POSITION_X = '80%'; + static readonly EDIT_POSITION_Y = '90%'; + static readonly DELETE_POSITION_X = '50%'; + static readonly DELETE_POSITION_Y = '90%'; + /** + * Log tag. + */ + static readonly RDB_TAG = '[Debug.Rdb]'; + static readonly TABLE_TAG = '[Debug.AccountTable]'; + static readonly INDEX_TAG = '[Debug.Index]'; } \ No newline at end of file diff --git a/entry/src/main/ets/common/constants/DeviceConstants.ets b/entry/src/main/ets/common/constants/DeviceConstants.ets deleted file mode 100644 index ae639978d0bf316362cab07d1cade648b71da3f0..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/common/constants/DeviceConstants.ets +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Device constants. - */ -export class DeviceConstants { - /** - * Local device. - */ - static readonly LOCAL_DEVICE: string = 'Local Device'; - - /** - * Device type. - */ - static readonly DEVICE_TYPE_NUMBER: number = 0; - - /** - * Range number. - */ - static readonly RANGE_NUMBER: number = 1000; -} diff --git a/entry/src/main/ets/common/util/CheckEmptyUtils.ets b/entry/src/main/ets/common/util/CheckEmptyUtils.ets deleted file mode 100644 index 598ab8aaa1cb60ebdaa9850081befd3be4410ba1..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/common/util/CheckEmptyUtils.ets +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { distributedKVStore } from '@kit.ArkData'; -import { ListItemData } from '../../viewmodel/ListItemData'; - -/** - * A utility class for checking whether data is empty. - */ -class CheckEmptyUtils { - /** - * Check obj is empty. - * - * @param {object} obj - * @return {boolean} true(empty) - */ - isEmptyObj(obj: object) { - return (typeof obj === 'undefined'); - } - - /** - * Check str is empty. - * - * @param {string} str - * @return {boolean} true(empty) - */ - isEmptyStr(str: string) { - return str.trim().length === 0; - } - - /** - * Check array is empty. - * - * @param {Array}arr - * @return {boolean} true(empty) - */ - isEmptyArr(arr: Array | distributedKVStore.Entry[]) { - return arr.length === 0; - } -} - -export default new CheckEmptyUtils(); \ No newline at end of file diff --git a/entry/src/main/ets/common/util/GlobalContext.ets b/entry/src/main/ets/common/util/GlobalContext.ets deleted file mode 100644 index ddcb46250914017cd225f6fcd21ec45b0c07ceb1..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/common/util/GlobalContext.ets +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License,Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export class GlobalContext { - private static instance: GlobalContext; - private _objects = new Map(); - - private constructor() { - } - - public static getContext(): GlobalContext { - if (!GlobalContext.instance) { - GlobalContext.instance = new GlobalContext(); - } - return GlobalContext.instance; - } - - getObject(value: string): Object | undefined { - return this._objects.get(value); - } - - setObject(key: string, objectClass: Object): void { - this._objects.set(key, objectClass); - } -} \ No newline at end of file diff --git a/entry/src/main/ets/common/util/Logger.ets b/entry/src/main/ets/common/util/Logger.ets deleted file mode 100644 index 06e4c060a4ce0252e782d36d7e10c2409a14c16f..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/common/util/Logger.ets +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { hilog } from '@kit.PerformanceAnalysisKit'; - -class Logger { - private domain: number; - private prefix: string; - private format: string = '%{public}s, %{public}s'; - - /** - * constructor. - * - * @param Prefix Identifies the log tag. - * @param domain Domain Indicates the service domain, which is a hexadecimal integer ranging from 0x0 to 0xFFFFF. - */ - constructor(prefix: string = 'MyApp', domain: number = 0xFF00) { - this.prefix = prefix; - this.domain = domain; - } - - debug(...args: string[]): void { - hilog.debug(this.domain, this.prefix, this.format, args); - } - - info(...args: string[]): void { - hilog.info(this.domain, this.prefix, this.format, args); - } - - warn(...args: string[]): void { - hilog.warn(this.domain, this.prefix, this.format, args); - } - - error(...args: string[]): void { - hilog.error(this.domain, this.prefix, this.format, args); - } -} - -export default new Logger('DistributedContacts', 0xFF00); \ No newline at end of file diff --git a/entry/src/main/ets/components/ContactBottomBar.ets b/entry/src/main/ets/components/ContactBottomBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..5ccea1f5ceae118927ec8e535350623363364751 --- /dev/null +++ b/entry/src/main/ets/components/ContactBottomBar.ets @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CommonConstants from '../common/CommonConstants'; + +@Extend(Text) +function setTextStyle() { + .width('100%') + .textAlign(TextAlign.Center) + .fontColor('rgba(0, 0, 0, 0.9)') + .fontSize(12); +} + +@Extend(SymbolGlyph) +function setImageStyle() { + .fontSize(20) + .fontWeight(400) + .fontColor(['rgba(0, 0, 0, 0.9)']); +} + +@Component +export default struct ContactBottomBar { + private isAll: boolean = false; + public pageId: string = ''; + public leftClickEvent: (isAll?: boolean) => void = () => { + }; + public rightClickEvent: () => void = () => { + }; + + build() { + Row() { + Column() { + Row() { + SymbolGlyph($r('sys.symbol.checkmark_square_on_square')) + .setImageStyle() + } + + Text($r('app.string.bottom_text_all_selected')) + .setTextStyle() + .margin({ + top: 4 + }) + } + .width('50%') + .onClick(() => { + if (this.pageId === CommonConstants.DELETE_PAGE_ID) { + this.isAll = !this.isAll; + } + this.leftClickEvent(this.isAll); + }) + + Column() { + Row() { + SymbolGlyph($r('sys.symbol.trash')) + .setImageStyle() + } + + Text($r('app.string.bottom_text_deletion')) + .setTextStyle() + .margin({ + top: 4 + }) + } + .width('50%') + .onClick(() => this.rightClickEvent()) + } + .width('100%') + .height(48) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/DeleteDialog.ets b/entry/src/main/ets/components/ContactDeleteDialog.ets similarity index 34% rename from entry/src/main/ets/view/DeleteDialog.ets rename to entry/src/main/ets/components/ContactDeleteDialog.ets index bdffcf81c1a8fd46dcdb889492b00b2291a71ad7..837d1bf95763e06d2b44181dcd7bc6847e9d0163 100644 --- a/entry/src/main/ets/view/DeleteDialog.ets +++ b/entry/src/main/ets/components/ContactDeleteDialog.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,60 +13,56 @@ * limitations under the License. */ -import CommonConstants from '../common/constants/CommonConstants'; - @CustomDialog -export struct DeleteDialog { - private controller: CustomDialogController; - private cancel: () => void = () => { +export struct ContactDeleteDialog { + controller: CustomDialogController; + cancel: () => void = () => { }; - private confirm: () => void = () => { + confirm: () => void = () => { }; - private promptMessage: Resource = $r('app.string.batch_delete_text'); + promptMessage: Resource = $r('app.string.batch_delete_text'); build() { Column() { Text(this.promptMessage) - .fontSize($r('app.float.popup_font_size')) - .margin({ - top: $r('app.float.delete_text_margin_top'), - bottom: $r('app.float.delete_text_margin_bottom') - }) - Row() { - Button($r('app.string.delete_cancel_text')) + .textAlign(TextAlign.Center) + .fontSize('16vp') + .fontColor('rgba(0, 0, 0, 0.9)') + .lineHeight('21vp') + + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { + Button($r('app.string.select_device_cancel'), + { buttonStyle: ButtonStyleMode.EMPHASIZED, role: ButtonRole.NORMAL }) + .flexGrow(1) + .backgroundColor(Color.Transparent) + .fontColor('#0A59F7') + .fontSize('16vp') + .fontWeight(500) .onClick(() => { - this.controller.close(); this.cancel(); }) - .backgroundColor(Color.White) - .fontColor($r('app.color.cancel_font_color')) Divider() - .width($r('app.float.divider_height')) - .height(CommonConstants.BOTTOM_COMPONENT_WIDTH) - .backgroundColor($r('app.color.list_divider')) - .margin({ - right: $r('app.float.delete_divider_margin'), - left: $r('app.float.delete_divider_margin') - }) - Button($r('app.string.bottom_text_deletion')) + .vertical(true) + .strokeWidth('0.5vp') + .height(24) + .color('rgba(0, 0, 0, 0.05)') + .margin({ left: 4, right: 4 }) + Button($r('app.string.confirm'), { buttonStyle: ButtonStyleMode.EMPHASIZED, role: ButtonRole.NORMAL }) + .flexGrow(1) + .backgroundColor(Color.Transparent) + .fontColor('#0A59F7') + .fontSize('16vp') + .fontWeight(500) .onClick(() => { - this.controller.close(); this.confirm(); }) - .backgroundColor(Color.White) - .fontColor(Color.Red) } - .width(CommonConstants.PERCENTAGE_MAX) - .height($r('app.float.delete_height')) - .margin({ bottom: $r('app.float.delete_dialog_margin') }) - .justifyContent(FlexAlign.Center) + .height('40vp') + .margin({ top: '8vp' }) } - .justifyContent(FlexAlign.Center) - .height($r('app.float.delete_dialog_height')) + .width('328vp') + .padding('24vp') + .borderRadius('32vp') .backgroundColor(Color.White) - .border({ - color: Color.White, - radius: ($r('app.float.device_list_border_radius')) - }) } } \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/DeletePageViewModel.ets b/entry/src/main/ets/components/ContactDetailItem.ets similarity index 42% rename from entry/src/main/ets/viewmodel/DeletePageViewModel.ets rename to entry/src/main/ets/components/ContactDetailItem.ets index cf5ada713d3cb840cd6b18e598f936ab1460ce8c..3c9d45256fd2d0ed4d72eba40c6ce135f4f1ea06 100644 --- a/entry/src/main/ets/viewmodel/DeletePageViewModel.ets +++ b/entry/src/main/ets/components/ContactDetailItem.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,29 +13,33 @@ * limitations under the License. */ -import { promptAction } from '@kit.ArkUI'; -import CommonConstants from '../common/constants/CommonConstants'; +@Component +export default struct ContactDetailItem { + @Prop topContent: string; + @Prop bottomContent: Resource; -const uiContext: UIContext | undefined = AppStorage.get('uiContext'); - -export class DeletePageViewModel { - /** - * No contact to be deleted is selected. - */ - unCheckedContact(): void { - uiContext!.getPromptAction().showToast({ - message: $r('app.string.prompt_delete'), - duration: CommonConstants.PROMPT_DURATION - }); - } - - /** - * Button for canceling the deletion dialog box. - */ - cancelDialog(): void { - uiContext!.getPromptAction().showToast({ - message: $r('app.string.delete_cancel_text'), - duration: CommonConstants.PROMPT_DURATION - }); + build() { + Column() { + Text(this.topContent) + .height(22) + .fontSize(16) + .fontWeight(500) + .lineHeight(21) + .fontColor('rgba(0, 0, 0, 0.9)') + .margin({ bottom: 2 }) + Text(this.bottomContent) + .fontSize(14) + .fontWeight(400) + .lineHeight(19) + .fontColor('rgba(0, 0, 0, 0.6)') + } + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + .width('100%') + .height(64) + .margin({ bottom: 16 }) + .padding({ left: 12, right: 12 }) + .borderRadius(12) + .backgroundColor(Color.White) } } \ No newline at end of file diff --git a/entry/src/main/ets/view/DeviceDialog.ets b/entry/src/main/ets/components/ContactDeviceDialog.ets similarity index 72% rename from entry/src/main/ets/view/DeviceDialog.ets rename to entry/src/main/ets/components/ContactDeviceDialog.ets index 57df67b56b23745613f718732805e442f2ce2d90..2797efaeedfb8a3bd015f031ac30ebaaedd7be73 100644 --- a/entry/src/main/ets/view/DeviceDialog.ets +++ b/entry/src/main/ets/components/ContactDeviceDialog.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,31 +14,27 @@ */ import { distributedDeviceManager } from '@kit.DistributedServiceKit'; -import { promptAction } from '@kit.ArkUI'; -import CommonConstants from '../common/constants/CommonConstants'; +import CommonConstants from '../common/CommonConstants'; @Extend(Text) function ButtonTextStyle() { - .fontColor($r('app.color.button_text_color')) + .fontColor('#0A59F7') .fontSize($r('app.float.button_font_size')) - .fontWeight(CommonConstants.FONT_WEIGHT_500) + .fontWeight(CommonConstants.FONT_WEIGHT_500); } @CustomDialog -export default struct DeviceListDialogComponent { +export default struct ContactDeviceDialog { controller?: CustomDialogController; - @StorageLink('deviceList') deviceList: Array = AppStorage.get('deviceList')!; - private selectedIndex: number | undefined = 0; - private onSelectedIndexChange: (selectedIndex: number | undefined) => void = () => { - }; - @State deviceDialogWidth: number = 0; + @Link deviceList: Array; + @Link selectedDeviceIndex: number; + onSelectedIndexChange: (index: number) => void = () => {}; build() { Column() { Row() { Text($r('app.string.select_device')) .fontSize($r('app.float.dialog_title_font_size')) - .width(CommonConstants.FULL_PERCENT) .textAlign(TextAlign.Start) .fontColor(Color.Black) .fontWeight(FontWeight.Bold) @@ -49,9 +45,10 @@ export default struct DeviceListDialogComponent { top: $r('app.float.dialog_title_padding_top') }) .height($r('app.float.dialog_title_height')) + .justifyContent(FlexAlign.Center) List() { - ForEach(this.deviceList, (item: distributedDeviceManager.DeviceBasicInfo, index: number | undefined) => { + ForEach(this.deviceList, (item: distributedDeviceManager.DeviceBasicInfo, index: number) => { ListItem() { Column() { Row() { @@ -74,24 +71,20 @@ export default struct DeviceListDialogComponent { } .justifyContent(FlexAlign.Start) - if (index === this.selectedIndex) { - Image($r('app.media.ic_single_select')) - .width(CommonConstants.SELECT_ICON_WIDTH) - .objectFit(ImageFit.Contain) - } else { - Image($r('app.media.ic_single_unselect')) - .width(CommonConstants.SELECT_ICON_WIDTH) - .objectFit(ImageFit.Contain) - .opacity($r('app.float.icon_uncheck_opacity')) - } + Radio({ + value: item.deviceId, group: 'RadioGroup', + indicatorType: RadioIndicatorType.DOT + }) + .width(20) + .height(20) + .checked(index === this.selectedDeviceIndex) } .height($r('app.float.device_info_height')) .onClick(() => { - if (this.selectedIndex !== undefined && index === this.selectedIndex) { + if (this.selectedDeviceIndex === index) { return; - } else if (this.selectedIndex !== undefined) { - this.selectedIndex = index; - this.onSelectedIndexChange(this.selectedIndex); + } else { + this.selectedDeviceIndex = index; } }) .padding({ @@ -150,18 +143,9 @@ export default struct DeviceListDialogComponent { .justifyContent(FlexAlign.Center) .height($r('app.float.button_line_height')) .onClick(() => { - if (CommonConstants.INVALID_INDEX === this.selectedIndex) { - this.getUIContext().getPromptAction().showToast({ - message: $r('app.string.please_select_device') - }); - } else { - if (this.controller !== undefined) { - this.onSelectedIndexChange(this.selectedIndex); - } - } + this.onSelectedIndexChange(this.selectedDeviceIndex); }) } - .backgroundColor(Color.White) .border({ color: Color.White, radius: $r('app.float.button_line_radius') diff --git a/entry/src/main/ets/view/ListItemComponent.ets b/entry/src/main/ets/components/ContactListItem.ets similarity index 55% rename from entry/src/main/ets/view/ListItemComponent.ets rename to entry/src/main/ets/components/ContactListItem.ets index 5b9c9ea27c0b5373ec27c64c3cc0db1aae292e29..0cdac46e8c450265fca645610a26b510dbdf61de 100644 --- a/entry/src/main/ets/view/ListItemComponent.ets +++ b/entry/src/main/ets/components/ContactListItem.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,38 +13,36 @@ * limitations under the License. */ -import CommonConstants from '../common/constants/CommonConstants'; -import { ListItemData } from '../viewmodel/ListItemData'; +import CommonConstants from '../common/CommonConstants'; +import { ListItemData } from '../viewmodel/ContactViewModel'; @Component -export struct ListItemComponent { +export struct ContactListItem { public itemInfo: ListItemData = { id: 0, - photo: $r('app.media.ic_men'), name: '', checked: false }; public isCanCheck = false; - public onCheck: () => void = () => { - }; + public onCheck: () => void = () => {}; build() { Column() { Column() { Row() { - Image(this.itemInfo.photo) - .objectFit(ImageFit.Contain) - .width($r('app.float.image_width')) - .height($r('app.float.image_height')) - .margin({ - left: $r('app.float.list_image_left'), - right: $r('app.float.list_image_right') - }) + Row() { + SymbolGlyph($r('sys.symbol.person_crop_circle_fill_1')) + .fontSize(48) + .fontColor(['#D1D1D6']) + .fontWeight(400) + } + .margin({ right: 19 }) + Column() { Text(this.itemInfo.name.toString()) - .fontSize($r('app.float.list_item_title')) - .fontColor($r('app.color.list_item_title')) - .fontWeight(FontWeight.Bolder) + .fontSize(16) + .fontColor('rgba(0, 0, 0, 0.9)') + .fontWeight(700) .maxLines(CommonConstants.TITLE_MAX_LINES) .textOverflow({ overflow: TextOverflow.Ellipsis @@ -56,7 +54,7 @@ export struct ListItemComponent { if (this.isCanCheck) { Checkbox() .select(this.itemInfo.checked) - .selectedColor($r('app.color.selected_font_color')) + .selectedColor('#0A59F7') .onChange(() => { this.onCheck(); }) @@ -64,15 +62,9 @@ export struct ListItemComponent { .height($r('app.float.checkbox_size')) } } - .height($r('app.float.list_item_height')) - - if (this.isCanCheck) { - Divider() - .height($r('app.float.divider_height')) - .backgroundColor($r('app.color.list_divider')) - .margin({ left: CommonConstants.CONTACTS_LIST_DIVIDER }) - } + .height(48) } } + .margin({ top: 16 }) } } \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index bcf8337e206775676fd380d966a8569b01fac1e5..8419756b37d60cfd761d6fd1e91f4412f93921b8 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,50 +18,40 @@ import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; import { abilityAccessCtrl } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; -import Logger from '../common/util/Logger'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { GlobalContext } from '../common/util/GlobalContext'; +import { KvManager } from '../utils/KvManager'; const TAG: string = 'EntryAbility'; +let uiContext: UIContext | undefined = undefined; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.permissions(); - AppStorage.setOrCreate('UIAbilityContext', this.context); - GlobalContext.getContext().setObject('FirstInTo', true); - GlobalContext.getContext().setObject('entryContext', this.context); - GlobalContext.getContext().setObject('contactsDataBase', new ContactsDataBase()); - Logger.info(TAG, `Ability onCreate`); + AppStorage.setOrCreate('kvManager', new KvManager(this.context)); } onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.permissions(); - AppStorage.setOrCreate('UIAbilityContext', this.context); - GlobalContext.getContext().setObject('FirstInTo', true); - GlobalContext.getContext().setObject('entryContext', this.context); - GlobalContext.getContext().setObject('contactsDataBase', new ContactsDataBase()); - Logger.info(TAG, `Ability onNewWant`); + AppStorage.setOrCreate('kvManager', new KvManager(this.context)); + hilog.info(0x0000, 'hilog', `Ability onNewWant`); } onDestroy(): void | Promise { - Logger.info(TAG, 'onDestroy execute'); - let contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - contactsDataBase.removeDataChangeListener(); - contactsDataBase.closeKVStore(); + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + let kvManager = AppStorage.get('kvManager') as KvManager; + kvManager.removeDataChangeListener(); + kvManager.closeKVStore(); } onWindowStageCreate(windowStage: window.WindowStage): void { // Main window is created, set main page for this ability. hilog.isLoggable(0x0000, TAG, hilog.LogLevel.INFO); - Logger.info(TAG, `Ability onWindowStageCreate`); - windowStage.loadContent('pages/ListPage', (err: BusinessError, data) => { + windowStage.loadContent('pages/ContactHomePage', (err: BusinessError, data) => { if (err.code) { - hilog.isLoggable(0x0000, TAG, hilog.LogLevel.ERROR); - Logger.error(TAG, `Failed to load the content. Cause: ${JSON.stringify(err)}`); + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); return; } - hilog.isLoggable(0x0000, TAG, hilog.LogLevel.INFO); - Logger.info(TAG, `Succeeded in loading the content. Data: ${JSON.stringify(data)}`); + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + uiContext = windowStage.getMainWindowSync().getUIContext(); AppStorage.setOrCreate('uiContext', windowStage.getMainWindowSync().getUIContext()); }); } @@ -73,10 +63,10 @@ export default class EntryAbility extends UIAbility { let atManager = abilityAccessCtrl.createAtManager(); try { atManager.requestPermissionsFromUser(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']).then((data) => { - Logger.info(TAG, `Data permissions:${data.permissions}`); + hilog.info(0x0000, 'hilog', `Data permissions:${data.permissions}`); }); } catch (err) { - Logger.info(TAG, `Catch err: ${err}`); + hilog.info(0x0000, 'hilog', `Catch err: ${err}`); } } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/ContactAddAndEditPage.ets b/entry/src/main/ets/pages/ContactAddAndEditPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..55791b141792056c5a030d9dbb4f2836625d5846 --- /dev/null +++ b/entry/src/main/ets/pages/ContactAddAndEditPage.ets @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { common } from '@kit.AbilityKit'; +import { KeyboardAvoidMode } from '@kit.ArkUI'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import CommonConstants from '../common/CommonConstants'; +import { ContactData } from '../viewmodel/ContactViewModel'; +import { KvManager } from '../utils/KvManager'; + +@Entry +@Component +struct ContactAddAndEditPage { + @State isSelectAll: boolean = false; + @State name: string = ''; + @State address: string = ''; + @State telephony: string = ''; + @State email: string = ''; + @State remarks: string = ''; + private isEdit: boolean = false; + private context = this.getUIContext().getHostContext() as common.UIAbilityContext; + kvManager = AppStorage.get('kvManager') as KvManager; + + aboutToAppear() { + this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE); + let params = this.getUIContext().getRouter().getParams() as Record; + this.isEdit = params.isEdit as boolean; + this.initializeData(); + } + + /** + * Contact information change. + * @param key Key of the text information. + * @param value Changed Data. + */ + contactInfoChange(key: string, value: string) { + try { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + let contactKey = context.resourceManager.getStringByNameSync(key); + switch (contactKey) { + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_NAME): + this.name = value; + break; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_ADDRESS): + this.address = value; + break; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_TEL): + this.telephony = value; + break; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_EMAIL): + this.email = value; + break; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_REMARKS): + this.remarks = value; + break; + default: + break; + } + } catch (error) { + hilog.error(0x0000, 'ContactAddAndEditPage', `Failed to copy.Code:${error.code},message: ${error.message}`); + } + } + + /** + * Initialize edit page data. + */ + initializeData(): void { + // Check whether the page is an editing page. + if (this.getUIContext().getRouter().getParams() && this.isEdit) { + let params = this.getUIContext().getRouter().getParams() as Record; + this.name = params.name as string; + this.address = params.address as string; + this.telephony = params.telephony as string; + this.email = params.email as string; + this.remarks = params.remarks as string; + } + } + + build() { + Column() { + this.NavigationTitle(); + Scroll() { + Column() { + Image($r('app.media.ic_head_portrait')) + .width(56) + .height(56) + .objectFit(ImageFit.Contain) + .margin({ bottom: 16, top: 12 }) + if (!this.isEdit) { + Text($r('app.string.edit_save_to_text')) + .fontSize(16) + .fontWeight(700) + .margin({ bottom: 20 }) + } + this.Item('edit_item_name', $r('sys.symbol.person'), this.name, true); + this.Item('edit_item_address', $r('app.media.ic_address'), this.address); + this.Item('edit_item_phone', $r('app.media.ic_phone'), this.telephony); + this.Item('edit_item_email', $r('sys.symbol.envelope'), this.email, true); + this.Item('edit_item_note', $r('app.media.ic_note'), this.remarks); + } + .margin({ top: 16, bottom: 64 }) + .layoutWeight(CommonConstants.WEIGHT) + } + .scrollBar(BarState.Off) + .align(Alignment.Top) + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') + .padding({ left: 16, right: 16 }) + .backgroundColor($r('app.color.detail_page_background')) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + @Builder + NavigationTitle() { + Flex({ alignItems: ItemAlign.Center }) { + Row() { + SymbolGlyph($r('sys.symbol.xmark')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.getUIContext().getRouter().back(); + }) + + Text(this.isEdit ? $r('app.string.edit_title') : $r('app.string.add_contacts_text')) + .fontSize(26) + .fontWeight(700) + .lineHeight(27) + .fontColor('rgba(0, 0, 0, 0.9)') + .flexGrow(1) + Row() { + SymbolGlyph($r('sys.symbol.checkmark')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + if (this.name !== '') { + const contactData: ContactData = + new ContactData(this.name, this.address, this.telephony, this.email, this.remarks); + let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + contactData.name; + // Save the contact information. + this.kvManager.addAndSave(contactsKey, JSON.stringify(contactData)); + this.getUIContext().getRouter().replaceUrl({ + url: CommonConstants.PAGE_DETAIL_URL, + params: { key: contactsKey } + }); + } else { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.contact_name'), + duration: CommonConstants.PROMPT_DURATION + }); + } + }) + } + .height(56) + .backgroundColor($r('app.color.detail_page_background')) + .expandSafeArea([SafeAreaType.KEYBOARD]) + .zIndex(3) + } + + @Builder + Item(key: string, icon: Resource, content: string, isSys: boolean = false) { + Row() { + if (isSys) { + SymbolGlyph(icon) + .fontSize(24) + .fontColor([$r('app.color.font_color')]) + .margin({ right: $r('app.float.item_icon_margin') }) + } else { + Image(icon) + .objectFit(ImageFit.Contain) + .height($r('app.float.item_icon_size')) + .width($r('app.float.item_icon_size')) + .margin({ right: $r('app.float.item_icon_margin') }) + } + Text(this.getStringValue(key)) + .fontSize($r('app.float.item_font_size')) + .fontColor($r('app.color.font_color')) + .fontWeight(FontWeight.Regular) + TextInput({ text: content }) + .type(this.getInputType(key)) + .maxLength(this.getMaxLength(key)) + .width(CommonConstants.EDIT_INPUT_WIDTH) + .margin({ right: $r('app.float.edit_input_margin') }) + .backgroundColor(Color.White) + .fontSize($r('app.float.item_font_size')) + .fontWeight(FontWeight.Regular) + .enabled(this.getStringValue(key) === this.getStringValue(CommonConstants.CONTACTS_DETAIL_NAME) && this.isEdit ? + false : true) + .onChange((value) => { + this.contactInfoChange(key, value); + }) + } + .padding({ + top: 16, + bottom: 16, + left: 12, + right: 12 + }) + .margin({ bottom: 12 }) + .justifyContent(FlexAlign.Start) + .backgroundColor(Color.White) + .borderRadius(24) + .width('100%') + } + + getStringValue(resName: string): string { + try { + return this.context.resourceManager.getStringByNameSync(resName); + } catch (error) { + hilog.error(0x0000, 'ContactAddAndEditPage', `have error .Code:${error.code},message: ${error.message}`); + return error.msg; + } + } + + getInputType(key: string): InputType { + try { + switch (this.getStringValue(key)) { + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_TEL): + return InputType.PhoneNumber; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_EMAIL): + return InputType.Email; + default: + return InputType.Normal; + } + } catch (error) { + hilog.error(0x0000, 'ContactAddAndEditPage', `have error .Code:${error.code},message: ${error.message}`); + return error.code; + } + } + + getMaxLength(key: string): number { + try { + switch (this.getStringValue(key)) { + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_NAME): + return CommonConstants.CONTACTS_NAME_MAX_LENGTH; + case this.getStringValue(CommonConstants.CONTACTS_DETAIL_TEL): + return CommonConstants.CONTACTS_TEL_MAX_LENGTH; + default: + return CommonConstants.CONTACTS_DETAIL_MAX; + } + } catch (error) { + hilog.error(0x0000, 'ContactAddAndEditPage', `have error .Code:${error.code},message: ${error.message}`); + return error.code; + } + } +} diff --git a/entry/src/main/ets/pages/ContactDeletePage.ets b/entry/src/main/ets/pages/ContactDeletePage.ets new file mode 100644 index 0000000000000000000000000000000000000000..ac990b8e732190dda67979ed2054bb055293e4d0 --- /dev/null +++ b/entry/src/main/ets/pages/ContactDeletePage.ets @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { distributedKVStore } from '@kit.ArkData'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import CommonConstants from '../common/CommonConstants'; +import ContactBottomBar from '../components/ContactBottomBar'; +import { KvManager } from '../utils/KvManager'; +import { ContactListItem } from '../components/ContactListItem'; +import { ListItemData } from '../viewmodel/ContactViewModel'; +import { ContactDeleteDialog } from '../components/ContactDeleteDialog'; + +const TAG: string = 'DeletePage'; + +@Entry +@Component +struct ContactDeletePage { + @State isSelectAll: boolean = false; + @State checkList: Array = []; + @State count: number = 0; + kvManager = AppStorage.get('kvManager') as KvManager; + dialogController: CustomDialogController = new CustomDialogController({ + builder: ContactDeleteDialog({ + cancel: () => { + this.onCancel(); + }, + confirm: () => { + this.onConfirm(); + }, + promptMessage: $r('app.string.batch_delete_text'), + }), + autoCancel: true, + alignment: DialogAlignment.Center, + customStyle: true + }); + + // Close the pop-up window + onCancel() { + try { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.delete_cancel_text'), + duration: CommonConstants.PROMPT_DURATION + }); + } catch (error) { + hilog.error(0x0000, 'ContactDeletePage', `delete_cancel_text error: ${error.code} msg:${error.message}`); + } + this.dialogController.close(); + } + + // Confirm the cancellation of transcoding + onConfirm() { + this.batchDeleteButton(); + this.dialogController.close(); + } + + aboutToAppear() { + this.initializeData(); + } + + /** + * Initialize edit page data. + */ + initializeData(): void { + this.kvManager.getEntries( + CommonConstants.CONTACTS_DATABASE_KEY, + (err: BusinessError, entries: distributedKVStore.Entry[]) => { + hilog.info(0x0000, 'hilog', TAG, `initializeData entries: ${JSON.stringify(entries)}`); + if (err) { + hilog.error(0x0000, 'hilog', `Fail to get Entries, error message is ${JSON.stringify(err)}`); + return; + } + let listItems: Array = []; + entries.forEach((item, index) => { + let itemInfo: ListItemData = new ListItemData(); + itemInfo.name = JSON.parse(item.value.value as string).name; + itemInfo.id = index; + listItems.push(itemInfo); + }); + this.checkList = listItems; + }); + } + + /** + * Deleting selected contacts in batches. + */ + batchDeleteButton(): void { + let keys: string[] = []; + this.checkList.forEach((item: ListItemData) => { + if (item.checked) { + let contactsKey: string = CommonConstants.CONTACTS_DATABASE_KEY + item.name; + keys.push(contactsKey); + } + }); + hilog.info(0x0000, 'hilog', TAG, `batchDeleteButton keys: ${JSON.stringify(keys)}`); + // Batch delete. + this.kvManager.deleteBatch(keys, (err: BusinessError) => { + if (err) { + hilog.error(0x0000, 'hilog', TAG, `Fail to delete Batch, code message is ${JSON.stringify(err)}`); + return; + } + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.prompt_message_deleted'), + duration: CommonConstants.PROMPT_DURATION + }); + this.getUIContext().getRouter().pushUrl({ + url: CommonConstants.LIST_PAGE_URL + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'hilog', TAG, `pushUrl failed, code message is ${JSON.stringify(err)}`); + }); + }); + } + + build() { + Column() { + Flex({ direction: FlexDirection.Column }) { + this.NavigationTitle(); + + Column() { + List() { + ForEach(this.checkList, (item: ListItemData) => { + ListItem() { + ContactListItem({ + itemInfo: item, + isCanCheck: true, + onCheck: () => { + item.checked = !item.checked; + item.checked ? this.count++ : this.count--; + } + }) + } + }, (item: ListItemData) => JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .width('100%') + .height(CommonConstants.PERCENTAGE_MAX) + } + .flexGrow(1) + + ContactBottomBar({ + leftClickEvent: (isAll) => { + this.checkList = this.checkList.map((item: ListItemData) => { + item.checked = isAll ? true : false; + return item; + }); + let result = this.checkList.filter((item) => { + item.checked; + }); + this.count = this.checkList.length === 0 ? 0 : result.length; + }, + rightClickEvent: () => { + if (this.count === 0) { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.prompt_delete'), + duration: CommonConstants.PROMPT_DURATION, + }); + } else { + this.dialogController.open(); + } + }, + pageId: CommonConstants.DELETE_PAGE_ID, + }) + } + } + .width('100%') + .height('100%') + .padding({ left: 16, right: 16 }) + .backgroundColor(Color.White) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + @Builder + NavigationTitle() { + Flex({ alignItems: ItemAlign.Center }) { + Row() { + SymbolGlyph($r('sys.symbol.xmark')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.getUIContext().getRouter().back(); + }) + + Row() { + Text($r('app.string.edit_text')) + .fontSize(26) + .fontWeight(700) + .lineHeight(27) + .fontColor('rgba(0, 0, 0, 0.9)') + } + .flexGrow(1) + + Row() { + SymbolGlyph($r('sys.symbol.checkmark')) + .fontColor(['rgba(0,0,0,0.9)']) + .fontSize(24) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .onClick(() => { + this.getUIContext().getRouter().back(); + }) + } + .height(56) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ContactDetailPage.ets b/entry/src/main/ets/pages/ContactDetailPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..eebde10cce079fc497f34cd1645cdec738cafbe2 --- /dev/null +++ b/entry/src/main/ets/pages/ContactDetailPage.ets @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import ContactDetailItem from '../components/ContactDetailItem'; +import { ContactData } from '../viewmodel/ContactViewModel'; +import { KvManager } from '../utils/KvManager'; +import { ContactDeleteDialog } from '../components/ContactDeleteDialog'; +import CommonConstants from '../common/CommonConstants'; + +const TAG: string = 'DetailPage'; + +@Entry +@Component +struct ContractDetailPage { + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @State name: string = ''; + @State telephony: string = ''; + @State email: string = ''; + @State remarks: string = ''; + @State address: string = ''; + kvManager = AppStorage.get('kvManager') as KvManager; + dialogController: CustomDialogController = new CustomDialogController({ + builder: ContactDeleteDialog({ + cancel: () => { + this.onCancel(); + }, + confirm: () => { + this.onConfirm(); + }, + promptMessage: $r('app.string.tip_delete'), + }), + autoCancel: true, + alignment: DialogAlignment.Center, + customStyle: true + }); + + // Close the pop-up window + onCancel() { + try { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.delete_cancel_text'), + duration: CommonConstants.PROMPT_DURATION + }); + this.dialogController.close(); + } catch (error) { + hilog.error(0x0000, 'ContactHomePage', `delete_cancel_text err: ${error.code} msg:${error.message}`); + } + } + + // Confirm the cancellation of transcoding + onConfirm() { + let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + this.name; + this.kvManager.deleteOnce(contactsKey, () => { + try { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.prompt_message_deleted'), + duration: CommonConstants.PROMPT_DURATION + }); + } catch (error) { + hilog.error(0x0000, 'ContactDetailPage', `prompt_message_deleted err: ${error.code} msg:${error.message}`); + } + this.getUIContext().getRouter().pushUrl({ + url: CommonConstants.LIST_PAGE_URL + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'ContactDetailPage', `LIST_PAGE_URL err: ${err.code} msg:${err.message}`); + }); + }); + this.dialogController.close(); + } + + aboutToAppear() { + this.initializeData(); + } + + /** + * Initialize detail page data. + */ + initializeData(): void { + let params = this.getUIContext().getRouter().getParams() as Record; + // Get contact details. + this.kvManager.getDetails(params.key as string, (err: BusinessError, data) => { + if (err) { + hilog.error(0x0000, 'hilog', TAG, `DetailPage.ets Fail to get, error message is ${JSON.stringify(err)}`); + return; + } + let contactsData: ContactData = JSON.parse(data as string); + this.name = contactsData.name as string; + this.address = contactsData.address as string; + this.telephony = contactsData.telephony as string; + this.email = contactsData.email as string; + this.remarks = contactsData.remarks as string; + }); + } + + onBackPress() { + this.uiContext?.getRouter().clear(); + try { + this.uiContext?.getRouter().replaceUrl( + { url: "pages/ContactHomePage" } + ).catch((error: BusinessError) => { + hilog.error(0x0000, 'ContactDetailPage', `have error .Code:${error.code},message: ${error.message}`); + }); + return true; + } catch (error) { + hilog.error(0x0000, 'ContactDetailPage', `have error .Code:${error.code},message: ${error.message}`); + return true; + } + } + + build() { + Column() { + this.NavigationTitle(); + this.RankList(); + } + .width('100%') + .height('100%') + .padding({ left: 16, right: 16 }) + .backgroundColor($r('app.color.detail_page_background')) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + @Builder + NavigationTitle() { + Flex({ alignItems: ItemAlign.Center }) { + Row() { + SymbolGlyph($r('sys.symbol.chevron_backward')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.getUIContext().getRouter().back(); + }) + + Blank() + .flexGrow(1) + Row() { + SymbolGlyph($r('sys.symbol.square_and_pencil')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: 'pages/ContactAddAndEditPage', + params: { + isEdit: true, + name: this.name, + address: this.address, + telephony: this.telephony, + email: this.email, + remarks: this.remarks + } + }).catch((error: BusinessError) => { + hilog.error(0x0000, 'ContactDetailPage', `have error .Code:${error.code},message: ${error.message}`); + }); + }) + + Row() { + SymbolGlyph($r('sys.symbol.trash')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.dialogController.open(); + }) + } + .height(56) + } + + @Builder + RankList() { + Column() { + Row() { + SymbolGlyph($r('sys.symbol.person_crop_circle_fill_1')) + .fontSize(122) + .fontColor(['#D1D1D6']) + } + .margin({ top: 32 }) + + Text(this.name) + .fontColor('rgba(0, 0, 0, 0.6)') + .fontSize(38) + .fontWeight(700) + .margin({ top: 32 }) + + Row() { + Row() { + SymbolGlyph($r('sys.symbol.phone_fill')) + .fontSize(26) + .fontColor([Color.White]) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(56) + .height(56) + .backgroundColor('#C4C4C4') + .borderRadius('50%') + + Row() { + SymbolGlyph($r('sys.symbol.message_fill')) + .fontSize(26) + .fontColor([Color.White]) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(56) + .height(56) + .backgroundColor('#C4C4C4') + .borderRadius('50%') + } + .width('100%') + .margin({ top: 28 }) + .padding({ left: 84, right: 84 }) + .justifyContent(FlexAlign.SpaceBetween) + + Column() { + ContactDetailItem({ + topContent: this.telephony, + bottomContent: $r('app.string.edit_item_phone') + }) + ContactDetailItem({ + topContent: this.email, + bottomContent: $r('app.string.edit_item_email') + }) + ContactDetailItem({ + topContent: this.remarks, + bottomContent: $r('app.string.edit_item_note') + }) + } + .margin({ top: 28 }) + .width('100%') + } + .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ContactHomePage.ets b/entry/src/main/ets/pages/ContactHomePage.ets new file mode 100644 index 0000000000000000000000000000000000000000..a6defd708ab8b564f0f3f803550eae2d266ac7e8 --- /dev/null +++ b/entry/src/main/ets/pages/ContactHomePage.ets @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { common } from '@kit.AbilityKit'; +import { distributedDeviceManager } from '@kit.DistributedServiceKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { distributedKVStore } from '@kit.ArkData'; +import CommonConstants from '../common/CommonConstants'; +import { ListItemData } from '../viewmodel/ContactViewModel'; +import { ContactListItem } from '../components/ContactListItem'; +import { ContactDeviceManager } from '../utils/ContactDeviceManager'; +import ContactDeviceDialog from '../components/ContactDeviceDialog'; +import { KvManager } from '../utils/KvManager'; + +@Entry +@Component +struct ContactHomePage { + @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + @State contactList: Array = []; + @State initContactList: Array = []; + @State isShowPopup: boolean = false; + @State deviceList: Array = []; + @State selectedDeviceIndex: number = 0; + @State searchValue: string = ''; + private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; + private contactDeviceManager: ContactDeviceManager = new ContactDeviceManager(this.context); + kvManager = AppStorage.get('kvManager') as KvManager; + dialogController: CustomDialogController = new CustomDialogController({ + builder: ContactDeviceDialog({ + deviceList: $deviceList, + selectedDeviceIndex: $selectedDeviceIndex, + onSelectedIndexChange: (index: number): Promise => this.onSelectedIndexChange(index) + }), + cancel: () => { + this.onClose(); + }, + autoCancel: true + }); + + aboutToAppear(): void { + this.kvManager.subscriptionKvStore(this.getAllData.bind(this)); + } + + onPageShow(): void { + this.getAllData(); + } + + onBackPress() { + this.context.terminateSelf().catch((err: BusinessError) => { + hilog.error(0x0000, 'ContactHomePage', `onBackPress err: ${err.code} msg:${err.message}`); + }); + return true; + } + + getAllData() { + this.kvManager.getEntries(CommonConstants.CONTACTS_DATABASE_KEY, ( + err: BusinessError, + entries: distributedKVStore.Entry[]) => { + hilog.info(0x0000, 'hilog', `getAllData entries: ${JSON.stringify(entries)}`); + if (err) { + hilog.error(0x0000, 'hilog', `Fail to get Entries, code message is ${JSON.stringify(err)}`); + return; + } + let listItems: Array = []; + entries.forEach((item, index) => { + let itemInfo: ListItemData = new ListItemData(); + itemInfo.name = JSON.parse(item.value.value as string).name; + itemInfo.id = index; + listItems.push(itemInfo); + }); + this.contactList = this.initContactList = listItems; + }); + } + + async onSelectedIndexChange(index: number) { + this.selectedDeviceIndex = index; + if (index === 0) { + this.onClose(); + } else { + this.selectDevice(); + } + }; + + onClose(): void { + this.deviceList = []; + this.dialogController.close(); + this.contactDeviceManager.unregisterDeviceListCallback(); + } + + selectDevice(): void { + if (this.contactDeviceManager.discoverList.length <= 0) { + this.contactDeviceManager.startAbility(this.deviceList[this.selectedDeviceIndex].networkId); + } else { + this.contactDeviceManager.authenticateDevice(this.deviceList[this.selectedDeviceIndex], () => { + for (let i = 0; i < this.contactDeviceManager.deviceList!.length; i++) { + const result = this.contactDeviceManager.deviceList![i].deviceName === + this.deviceList[this.selectedDeviceIndex].deviceName; + if (result) { + this.contactDeviceManager.startAbility(this.contactDeviceManager.deviceList![i].networkId); + } + } + }); + } + this.onClose(); + } + + showDeviceDialog(): void { + // Register a listening callback. + // A dialog box is displayed when a device is discovered or an authenticated device is found. + this.contactDeviceManager.registerDeviceListCallback(() => { + this.deviceList = []; + this.deviceList.push({ + deviceId: '0', + deviceName: CommonConstants.LOCALHOST_NAME, + deviceType: '0', + networkId: '' + }); + let deviceTempList = this.contactDeviceManager.discoverList.length > 0 ? + this.contactDeviceManager.discoverList : this.contactDeviceManager.deviceList; + if (deviceTempList.length) { + deviceTempList.forEach((item) => { + this.deviceList.push({ + deviceId: item.deviceId, + deviceName: item.deviceName, + deviceType: item.deviceType, + networkId: item.networkId + }); + }); + } + }); + this.dialogController.open(); + } + + build() { + Column() { + this.NavigationTitle(); + this.ContactList(); + } + .width('100%') + .height('100%') + .padding({ left: 16, right: 16 }) + .backgroundColor(Color.White) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + @Builder + NavigationTitle() { + Flex({ alignItems: ItemAlign.Center }) { + Text($r('app.string.page_title')) + .fontSize(20) + .fontWeight(700) + .lineHeight(27) + .fontColor('rgba(0, 0, 0, 0.9)') + .flexGrow(1) + + Row() { + SymbolGlyph($r('sys.symbol.plus')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: 'pages/ContactAddAndEditPage', + params: { + isEdit: false + } + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'ContactHomePage', `ContactAddAndEditPage err: ${err.code} msg:${err.message}`); + }); + }) + + Row() { + SymbolGlyph($r('sys.symbol.dot_grid_2x2')) + .fontSize(24) + .fontWeight(400) + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .width(40) + .height(40) + .backgroundColor('rgba(0, 0, 0, 0.05)') + .borderRadius(40) + .margin({ right: 8 }) + .bindPopup(this.isShowPopup, { + builder: this.popupBuilder, + placement: Placement.BottomRight, + popupColor: Color.White, + enableArrow: false + }) + .onClick(() => { + this.isShowPopup = !this.isShowPopup; + }) + } + .height(56) + } + + @Builder + ContactList() { + if (!this.contactList.length) { + this.emptyView(); + } else { + this.dataView(); + } + } + + @Builder + emptyView() { + Column() { + Image($r('app.media.ic_empty')) + .width($r('app.float.contact_picture_size')) + .height($r('app.float.contact_picture_size')) + Text($r('app.string.contacts_empty')) + .fontSize($r('app.float.contacts_empty_size')) + .fontColor($r('app.color.empty_font_color')) + } + .flexGrow(1) + .width('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + + @Builder + dataView() { + Column() { + Search({ value: $$this.searchValue, placeholder: Object($r('app.string.search_contact')) }) + .width('100%') + .height(40) + .borderRadius(24) + .placeholderColor('rgba(0, 0, 0, 0.6)') + .placeholderFont({ + size: 16, + weight: 400, + }) + .textFont({ size: 16 }) + .padding({ + top: 9, + right: 12, + bottom: 9, + left: 12 + }) + .onChange((searchValue: string) => { + if (searchValue === '') { + this.contactList = this.initContactList; + } + }) + .onSubmit((searchValue: string) => { + const filterArr = this.contactList.filter((item) => item.name === searchValue); + if (filterArr.length > 0) { + this.contactList = filterArr; + } else { + try { + this.uiContext!.getPromptAction().showToast({ + message: $r('app.string.no_matching_data'), + duration: CommonConstants.PROMPT_DURATION, + }); + } catch (error) { + hilog.error(0x0000, 'ContactHomePage', `have error .Code:${error.code},message: ${error.message}`); + } + } + }) + Column() { + List() { + ForEach(this.contactList, (item: ListItemData) => { + ListItem() { + ContactListItem({ itemInfo: item }) + } + .onClick(() => { + const contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + item.name; + this.getUIContext().getRouter().pushUrl({ + url: 'pages/ContactDetailPage', + params: { + key: contactsKey, + }, + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'ContactHomePage', `ContactAddAndEditPage err: ${err.code} msg:${err.message}`); + }); + }) + }, (item: ListItemData) => JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .width('100%') + .margin({ bottom: $r('app.float.list_area_height') }) + } + .width('100%') + } + .width('100%') + .margin({ top: 8 }) + } + + @Builder + popupBuilder() { + Column() { + Text($r('app.string.delete_batch')) + .fontSize($r('app.float.popup_font_size')) + .padding({ + top: $r('app.float.delete_padding_vertical'), + bottom: $r('app.float.delete_padding_vertical'), + left: $r('app.float.delete_padding_left') + }) + .width('100%') + .onClick(() => { + this.getUIContext().getRouter().pushUrl({ + url: 'pages/ContactDeletePage' + }).catch((err: BusinessError) => { + hilog.error(0x0000, 'ContactHomePage', `ContactAddAndEditPage err: ${err.code} msg:${err.message}`); + }); + }) + Divider() + .height($r('app.float.divider_height')) + .backgroundColor($r('app.color.list_divider')) + .margin({ + left: $r('app.float.delete_padding_left'), + right: $r('app.float.delete_padding_left') + }) + Text($r('app.string.connection_device')) + .width('100%') + .fontSize($r('app.float.popup_font_size')) + .padding({ + top: $r('app.float.delete_padding_vertical'), + bottom: $r('app.float.delete_padding_vertical'), + left: $r('app.float.delete_padding_left') + }) + .onClick(() => { + this.isShowPopup = !this.isShowPopup; + this.showDeviceDialog(); + }) + } + .width($r('app.float.popup_width')) + .height($r('app.float.popup_height')) + .alignItems(HorizontalAlign.Start) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/DeletePage.ets b/entry/src/main/ets/pages/DeletePage.ets deleted file mode 100644 index e6c32ff2dde87927f36ccffced21197cd922960e..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/pages/DeletePage.ets +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { distributedKVStore } from '@kit.ArkData'; -import { promptAction } from '@kit.ArkUI'; -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import CheckEmptyUtils from '../common/util/CheckEmptyUtils'; -import BottomBarComponent from '../view/BottomBarComponent'; -import PageViewModel from '../viewmodel/PageViewModel'; -import Logger from '../common/util/Logger'; -import { DeletePageViewModel } from '../viewmodel/DeletePageViewModel'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { ListItemComponent } from '../view/ListItemComponent'; -import { GlobalContext } from '../common/util/GlobalContext'; -import { ListItemData } from '../viewmodel/ListItemData'; -import { DeleteDialog } from '../view/DeleteDialog'; - -const TAG: string = 'DeletePage'; - -@Entry -@Component -struct DeletePage { - @State isSelectAll: boolean = false; - @State checkList: Array = []; - @State count: number = 0; - private deletePageViewModel: DeletePageViewModel = new DeletePageViewModel(); - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - dialogController: CustomDialogController = new CustomDialogController({ - builder: DeleteDialog({ - cancel: () => { - this.deletePageViewModel.cancelDialog(); - }, - confirm: () => { - this.batchDeleteButton(); - }, - promptMessage: $r('app.string.batch_delete_text'), - }), - autoCancel: true, - alignment: DialogAlignment.Bottom, - offset: { - dx: $r('app.float.dialog_dx'), - dy: $r('app.float.dialog_dy') - }, - customStyle: false - }); - - aboutToAppear() { - this.initializeData(); - } - - /** - * Initialize edit page data. - */ - initializeData(): void { - this.contactsDataBase.getEntries( - CommonConstants.CONTACTS_DATABASE_KEY, - (err: BusinessError, entries: distributedKVStore.Entry[]) => { - Logger.info(TAG, `initializeData entries: ${JSON.stringify(entries)}`); - if (err) { - Logger.error(`Fail to get Entries, error message is ${JSON.stringify(err)}`); - return; - } - this.checkList = PageViewModel.getData(entries); - }); - } - - /** - * Deleting selected contacts in batches. - */ - batchDeleteButton(): void { - let keys: string[] = []; - this.checkList.forEach((item: ListItemData) => { - if (item.checked) { - let contactsKey: string = CommonConstants.CONTACTS_DATABASE_KEY + item.name; - keys.push(contactsKey); - } - }); - Logger.info(TAG, `batchDeleteButton keys: ${JSON.stringify(keys)}`); - // Batch delete. - this.contactsDataBase.deleteBatch(keys, (err: BusinessError) => { - if (err) { - Logger.error(TAG, `Fail to delete Batch, code message is ${JSON.stringify(err)}`); - return; - } - this.getUIContext().getPromptAction().showToast({ - message: $r('app.string.prompt_message_deleted'), - duration: CommonConstants.PROMPT_DURATION - }); - this.getUIContext().getRouter().pushUrl({ - url: CommonConstants.LIST_PAGE_URL - }).catch((err: BusinessError) => { - Logger.error(TAG, `pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - }); - } - - build() { - Column() { - this.NavigationTitle() - Column() { - List() { - ForEach(this.checkList, (item: ListItemData) => { - ListItem() { - ListItemComponent({ - itemInfo: item, - isCanCheck: true, - onCheck: () => { - item.checked = !item.checked; - item.checked ? this.count++ : this.count--; - } - }) - } - }, (item: ListItemData) => JSON.stringify(item)) - } - .scrollBar(BarState.Off) - .width(CommonConstants.LIST_WIDTH_PERCENT) - .height(CommonConstants.PERCENTAGE_MAX) - } - .layoutWeight(CommonConstants.WEIGHT) - - BottomBarComponent({ - leftClickEvent: (isAll) => { - if (!CheckEmptyUtils.isEmptyObj(PageViewModel)) { - this.checkList = isAll ? PageViewModel.getSelectAll(this.checkList) - : PageViewModel.getUnSelectAll(this.checkList); - this.count = PageViewModel.getSelectCount(this.checkList); - } - }, - rightClickEvent: () => { - this.count === 0 ? this.deletePageViewModel.unCheckedContact() : this.dialogController.open(); - }, - pageId: CommonConstants.DELETE_PAGE_ID, - leftIcon: $r('app.media.ic_public_select'), - leftSubtitle: $r('app.string.bottom_text_all_selected'), - rightIcon: $r('app.media.ic_delete'), - rightSubtitle: $r('app.string.bottom_text_deletion') - }) - } - .width(CommonConstants.PERCENTAGE_MAX) - .height(CommonConstants.PERCENTAGE_MAX) - .backgroundColor($r('app.color.detail_page_background')) - } - - @Builder - NavigationTitle() { - Row() { - Image($r('app.media.ic_close')) - .height($r('app.float.icon_close_size')) - .width($r('app.float.icon_close_size')) - .margin({ right: $r('app.float.icon_close_margin_right') }) - .onClick(() => { - this.getUIContext().getRouter().back(); - }) - Text($r('app.string.contacts_select')) - .fontColor($r('app.color.navigation_title')) - .fontSize($r('app.float.detail_navigation_title')) - Text(`${this.count}`) - .fontColor($r('app.color.navigation_title')) - .fontSize($r('app.float.detail_navigation_title')) - Text($r('app.string.selected_text')) - .fontColor($r('app.color.navigation_title')) - .fontSize($r('app.float.detail_navigation_title')) - } - .width(CommonConstants.PAGE_TITLE_WIDTH) - .height($r('app.float.delete_title_height')) - .justifyContent(FlexAlign.Start) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/pages/DetailPage.ets b/entry/src/main/ets/pages/DetailPage.ets deleted file mode 100644 index ac25964ec7cb2be0639de54153063cf1a66483ee..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/pages/DetailPage.ets +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import DetailItemComponent from '../view/DetailItemComponent'; -import BottomBarComponent from '../view/BottomBarComponent'; -import ContactData from '../viewmodel/ContactData'; -import Logger from '../common/util/Logger'; -import { DetailPageViewModel } from '../viewmodel/DetailPageViewModel'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { GlobalContext } from '../common/util/GlobalContext'; -import { DeleteDialog } from '../view/DeleteDialog'; - -const TAG: string = 'DetailPage'; - -@Entry -@Component -struct ContactsDetail { - @State name: string = ''; - @State telephony: string = ''; - @State email: string = ''; - @State remarks: string = ''; - @State address: string = ''; - private detailPageViewModel: DetailPageViewModel = new DetailPageViewModel(); - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - dialogController: CustomDialogController = new CustomDialogController({ - builder: DeleteDialog({ - cancel: () => { - this.detailPageViewModel.cancelDialog(); - }, - confirm: () => { - this.detailPageViewModel.deleteContactButton(this.name); - }, - promptMessage: $r('app.string.delete_dialog_text') - }), - autoCancel: true, - alignment: DialogAlignment.Bottom, - offset: { - dx: $r('app.float.dialog_dx'), - dy: $r('app.float.dialog_dy') - }, - customStyle: false - }); - - aboutToAppear() { - this.initializeData(); - } - - /** - * Initialize detail page data. - */ - initializeData(): void { - let params = this.getUIContext().getRouter().getParams() as Record; - // Get contact details. - this.contactsDataBase.get(params.key as string, (err: BusinessError, data) => { - if (err) { - Logger.error(TAG, `DetailPage.ets Fail to get, error message is ${JSON.stringify(err)}`); - return; - } - let contactsData: ContactData = JSON.parse(data as string); - this.name = contactsData.name as string; - this.address = contactsData.address as string; - this.telephony = contactsData.telephony as string; - this.email = contactsData.email as string; - this.remarks = contactsData.remarks as string; - }); - } - - build() { - Column() { - Row() { - this.NavigationTitle() - } - .width(CommonConstants.PERCENTAGE_MAX) - .height(CommonConstants.DETAIL_TOP_HEIGHT) - .justifyContent(FlexAlign.Start) - .alignItems(VerticalAlign.Top) - - this.RankList() - BottomBarComponent({ - leftClickEvent: () => { - this.detailPageViewModel.redirectEditPage( - this.name, - this.address, - this.telephony, - this.email, - this.remarks - ); - }, - rightClickEvent: () => { - this.dialogController.open(); - }, - pageId: CommonConstants.DETAIL_PAGE_ID, - leftIcon: $r('app.media.ic_edit'), - leftSubtitle: $r('app.string.edit_text'), - rightIcon: $r('app.media.ic_delete'), - rightSubtitle: $r('app.string.delete_contact') - }) - } - .justifyContent(FlexAlign.End) - .backgroundColor($r('app.color.details_bg')) - .height(CommonConstants.PERCENTAGE_MAX) - } - - @Builder - NavigationTitle() { - Column() { - Row() { - Image($r('app.media.ic_back')) - .width($r('app.float.edit_icon_size')) - .height($r('app.float.edit_icon_size')) - .onClick(() => { - this.detailPageViewModel.redirectListPage(); - }) - } - .layoutWeight(CommonConstants.WEIGHT) - } - .height($r('app.float.detail_navigation_height')) - .margin({ left: $r('app.float.image_spacing_margin') }) - } - - @Builder - RankList() { - Column() { - Image($r('app.media.ic_contacts_white')) - .width($r('app.float.details_contacts_icon_size')) - .height($r('app.float.details_contacts_icon_size')) - .objectFit(ImageFit.Contain) - .margin({ top: $r('app.float.details_contacts_icon_margin') }) - Text(this.name) - .fontColor($r('app.color.contacts_icon_text_color')) - .fontSize($r('app.float.contacts_icon_font_size')) - .fontWeight(FontWeight.Medium) - .margin({ top: $r('app.float.contacts_icon_font_margin') }) - Column() { - DetailItemComponent({ - componentId: CommonConstants.PHONE_COMPONENTS_ID, - topContent: this.telephony, - bottomContent: this.address, - leftIcon: $r('app.media.ic_phone_icon'), - rightIcon: $r('app.media.ic_message') - }) - DetailItemComponent({ - componentId: CommonConstants.Email_COMPONENTS_ID, - topContent: this.email, - bottomContent: this.address, - bottomSubtitle: $r('app.string.item_personal_email'), - rightIcon: $r('app.media.ic_right_arrow') - }) - DetailItemComponent({ - componentId: CommonConstants.Note_COMPONENTS_ID, - bottomContent: this.address, - topContent: this.remarks, - bottomSubtitle: $r('app.string.item_remark') - }) - } - .margin({ top: $r('app.float.contacts_detail_list_margin') }) - .width(CommonConstants.PERCENTAGE_MAX) - .height(CommonConstants.PERCENTAGE_MAX) - } - .justifyContent(FlexAlign.Start) - .padding({ - left: $r('app.float.details_padding'), - right: $r('app.float.details_padding') - }) - .borderRadius({ - topLeft: $r('app.float.details_border_radius'), - topRight: $r('app.float.details_border_radius') - }) - .width(CommonConstants.PERCENTAGE_MAX) - .layoutWeight(CommonConstants.WEIGHT) - .backgroundColor(Color.White) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/pages/EditPage.ets b/entry/src/main/ets/pages/EditPage.ets deleted file mode 100644 index a3929ca1b22a46529c52fe4b249dcee959a7b1b0..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/pages/EditPage.ets +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { common } from '@kit.AbilityKit'; -import { router } from '@kit.ArkUI'; -import CommonConstants from '../common/constants/CommonConstants'; -import ContactData from '../viewmodel/ContactData'; -import { EditPageViewModel } from '../viewmodel/EditPageViewModel'; -import { GlobalContext } from '../common/util/GlobalContext'; - -@Entry -@Component -struct EditPage { - @State isSelectAll: boolean = false; - @State name: string = ''; - @State address: string = ''; - @State telephony: string = ''; - @State email: string = ''; - @State remarks: string = ''; - private isEdit: boolean = false; - private editPageViewModel: EditPageViewModel = new EditPageViewModel(); - private textInputContext = this.getUIContext().getHostContext() as common.UIAbilityContext; - - aboutToAppear() { - let params = this.getUIContext().getRouter().getParams() as Record; - this.isEdit = params.isEdit as boolean; - GlobalContext.getContext().setObject('FirstInTo', false); - this.initializeData(); - } - - /** - * Contact information change. - * - * @param key Key of the text information. - * @param value Changed Data. - */ - contactInfoChange(key: Resource, value: string) { - let context = this.getUIContext().getHostContext() as common.UIAbilityContext; - let contactKey = context.resourceManager.getStringSync(key); - switch (contactKey) { - case context.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_NAME): - this.name = value; - break; - case context.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_ADDRESS): - this.address = value; - break; - case context.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_TEL): - this.telephony = value; - break; - case context.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_EMAIL): - this.email = value; - break; - case context.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_REMARKS): - this.remarks = value; - break; - default: - break; - } - } - - /** - * Initialize edit page data. - */ - initializeData(): void { - // Check whether the page is an editing page. - if (this.getUIContext().getRouter().getParams() && this.isEdit) { - let params = this.getUIContext().getRouter().getParams() as Record; - this.name = params.name as string; - this.address = params.address as string; - this.telephony = params.telephony as string; - this.email = params.email as string; - this.remarks = params.remarks as string; - } - } - - build() { - Column() { - Row() { - this.NavigationTitle() - } - .width(CommonConstants.PERCENTAGE_MAX) - .height($r('app.float.detail_navigation_height')) - .backgroundColor($r('app.color.detail_page_background')) - - Column() { - Column() { - Image($r('app.media.ic_head_portrait')) - .width($r('app.float.head_portrait_size')) - .height($r('app.float.head_portrait_size')) - .objectFit(ImageFit.Contain) - .margin({ - bottom: $r('app.float.head_portrait_bottom'), - top: $r('app.float.head_portrait_top') - }) - if (!this.isEdit) { - Text($r('app.string.edit_save_to_text')) - .fontSize($r('app.float.edit_subtitle_size')) - .fontWeight(FontWeight.Medium) - .margin({ - bottom: $r('app.float.edit_save_margin_bottom') - }) - } - this.Item($r('app.string.edit_item_name'), $r('app.media.ic_name'), this.name) - this.Item($r('app.string.edit_item_address'), $r('app.media.ic_address'), this.address) - this.Item($r('app.string.edit_item_phone'), $r('app.media.ic_phone'), this.telephony) - this.Item($r('app.string.edit_item_email'), $r('app.media.ic_email'), this.email) - this.Item($r('app.string.edit_item_note'), $r('app.media.ic_note'), this.remarks) - } - } - .layoutWeight(CommonConstants.WEIGHT) - } - .width(CommonConstants.PERCENTAGE_MAX) - .height(CommonConstants.PERCENTAGE_MAX) - .backgroundColor($r('app.color.detail_page_background')) - } - - @Builder - NavigationTitle() { - Column() { - Row() { - Row() { - Image($r('app.media.ic_close')) - .width($r('app.float.edit_icon_size')) - .height($r('app.float.edit_icon_size')) - .margin({ right: $r('app.float.close_icon_margin') }) - .onClick(() => { - let url = this.isEdit ? CommonConstants.PAGE_DETAIL_URL : CommonConstants.LIST_PAGE_URL; - this.editPageViewModel.commonRouter(url, this.name); - }) - Text(this.isEdit ? $r('app.string.edit_title') : $r('app.string.add_contacts_text')) - .width(CommonConstants.PERCENTAGE_MAX) - .fontWeight(FontWeight.Medium) - .fontColor($r('app.color.navigation_title')) - .fontSize($r('app.float.detail_navigation_title')) - .focusable(true) - } - .layoutWeight(CommonConstants.WEIGHT) - - Row() { - Image($r('app.media.ic_save')) - .width($r('app.float.edit_icon_size')) - .height($r('app.float.edit_icon_size')) - } - .layoutWeight(CommonConstants.WEIGHT) - .justifyContent(FlexAlign.End) - .onClick(() => { - this.editPageViewModel.saveInfo( - new ContactData(this.name, this.address, this.telephony, this.email, this.remarks) - ); - }) - } - .margin({ - right: $r('app.float.image_spacing_margin'), - left: $r('app.float.image_spacing_margin') - }) - } - } - - @Builder - Item(key: Resource, icon: Resource, content: string) { - Row() { - Image(icon) - .objectFit(ImageFit.Contain) - .height($r('app.float.item_icon_size')) - .width($r('app.float.item_icon_size')) - .margin({ right: $r('app.float.item_icon_margin') }) - Text(key) - .fontSize($r('app.float.item_font_size')) - .fontColor($r('app.color.font_color')) - .fontWeight(FontWeight.Regular) - TextInput({ text: content }) - .type(this.getInputType(key)) - .maxLength(this.getMaxLength(key)) - .width(CommonConstants.EDIT_INPUT_WIDTH) - .margin({ right: $r('app.float.edit_input_margin') }) - .backgroundColor(Color.White) - .fontSize($r('app.float.item_font_size')) - .fontWeight(FontWeight.Regular) - .enabled(this.textInputContext.resourceManager.getStringSync(key) === this.getUIContext().getHostContext()!.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_NAME) - && this.isEdit ? false : true) - .onChange((value) => { - this.contactInfoChange(key, value); - }) - } - .padding({ - top: $r('app.float.edit_item_padding_top'), - bottom: $r('app.float.edit_item_padding_bottom'), - left: $r('app.float.edit_item_padding_left'), - right: $r('app.float.edit_item_padding_right') - }) - .margin({ - bottom: $r('app.float.edit_item_margin'), - top: $r('app.float.edit_item_margin') - }) - .justifyContent(FlexAlign.Start) - .backgroundColor(Color.White) - .borderRadius($r('app.float.edit_item_border_radius')) - .width(CommonConstants.PAGE_TITLE_WIDTH) - } - - getInputType(key: Resource): InputType { - switch (this.textInputContext.resourceManager.getStringSync(key)) { - case this.getUIContext().getHostContext()!.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_TEL): - return InputType.PhoneNumber; - case this.getUIContext().getHostContext()!.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_EMAIL): - return InputType.Email; - default: - return InputType.Normal; - } - } - - getMaxLength(key: Resource): number { - switch (this.textInputContext.resourceManager.getStringSync(key)) { - case this.getUIContext().getHostContext()!.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_NAME): - return CommonConstants.CONTACTS_NAME_MAX_LENGTH; - case this.getUIContext().getHostContext()!.resourceManager.getStringSync(CommonConstants.CONTACTS_DETAIL_TEL): - return CommonConstants.CONTACTS_TEL_MAX_LENGTH; - default: - return CommonConstants.CONTACTS_DETAIL_MAX; - } - } -} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ListPage.ets b/entry/src/main/ets/pages/ListPage.ets deleted file mode 100644 index 37986b0f8b8f87f5fe64656ae820f5d5e9b14fe0..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/pages/ListPage.ets +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { distributedKVStore } from '@kit.ArkData'; -import { distributedDeviceManager } from '@kit.DistributedServiceKit'; -import { common } from '@kit.AbilityKit'; -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import DeviceListDialogComponent from '../view/DeviceDialog'; -import PageViewModel from '../viewmodel/PageViewModel'; -import Logger from '../common/util/Logger'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { RemoteDeviceModel } from '../viewmodel/RemoteDeviceModel'; -import { ListPageViewModel } from '../viewmodel/ListPageViewModel'; -import { ListAreaComponent } from '../view/ListAreaComponent'; -import { GlobalContext } from '../common/util/GlobalContext'; -import { ListItemData } from '../viewmodel/ListItemData'; - -const TAG: string = 'ListPage'; - -@Entry -@Component -struct ListPage { - @StorageLink('deviceList') deviceList: Array = []; - @StorageLink('contactData') contactData: Array = []; - @StorageLink('isContactsEmpty') isContactsEmpty: boolean = true; - @StorageLink('contactsNumber') contactsNumber: number = 0; - @State selectedIndex: number | undefined = 0; - @State customPopup: boolean = false; - private listPageViewModel: ListPageViewModel = new ListPageViewModel(); - private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel(); - private subscriptionKvStore: () => void = (): void => { - // When the remote data changes, the page data is updated. - this.getAllData(); - }; - private dialogController: CustomDialogController | null = null; - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - onSelectedIndexChange = async (index: number | undefined) => { - Logger.info(TAG, `selectedIndexChange`); - this.selectedIndex = index; - if (this.selectedIndex === 0) { - Logger.info(TAG, `stop ability`); - this.listPageViewModel.startAbilityCallBack(CommonConstants.EXIT); - this.deviceList = []; - if (this.dialogController !== null) { - this.dialogController.close(); - } - return; - } - this.selectDevice(); - }; - - selectDevice(): void { - if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || - this.remoteDeviceModel.discoverList.length <= 0)) { - Logger.info(TAG, `continue unauthed device: ${JSON.stringify(this.deviceList)}`); - this.listPageViewModel.startAbility(this.deviceList[this.selectedIndex].networkId); - this.clearSelectState(); - return; - } - Logger.info(TAG, `start ability, needAuth:`); - if (this.selectedIndex !== undefined) { - this.remoteDeviceModel.authenticateDevice(this.deviceList[this.selectedIndex], () => { - Logger.info(TAG, `auth and online finished`); - if (this.remoteDeviceModel !== null - && this.remoteDeviceModel.deviceList !== null - && this.selectedIndex !== undefined) { - for (let i = 0; i < this.remoteDeviceModel.deviceList!.length; i++) { - if (this.remoteDeviceModel.deviceList![i].deviceName === this.deviceList[this.selectedIndex].deviceName) { - this.listPageViewModel.startAbility(this.remoteDeviceModel.deviceList![i].networkId); - } - } - } - }) - } - this.clearSelectState(); - } - - clearSelectState(): void { - this.deviceList = []; - if (this.dialogController !== null) { - this.dialogController.close(); - } - Logger.info(TAG, `cancelDialog`); - if (this.remoteDeviceModel === undefined) { - return; - } - this.remoteDeviceModel.unregisterDeviceListCallback(); - } - - onPageShow() { - // Access the listPage page to obtain the list data. - this.getAllData(); - this.getUIContext().getRouter().clear(); - } - - aboutToAppear() { - GlobalContext.getContext().setObject('FirstInTo', true); - setTimeout(() => { - this.contactsDataBase.subscriptionKvStore(this.subscriptionKvStore); - }, 50); - } - - /** - * This interface is used to obtain all data on the contact home page. - */ - getAllData(): void { - setTimeout(() => { - this.contactsDataBase.getEntries(CommonConstants.CONTACTS_DATABASE_KEY, ( - err: BusinessError, - entries: distributedKVStore.Entry[]) => { - Logger.info(TAG, `getAllData entries: ${JSON.stringify(entries)}`); - if (err) { - Logger.error(`Fail to get Entries, code message is ${JSON.stringify(err)}`); - return; - } - this.contactsNumber = entries.length; - this.isContactsEmpty = this.contactsNumber > 0 ? false : true; - this.contactData = PageViewModel.getData(entries); - } - ); - }, 50); - } - - /** - * In the dialog box that is displayed, select a device from the list and verify the device for the first time. - * After the verification is successful, start the remote device application for the second time. - */ - showDialogInfo(): void { - this.deviceList = []; - // Register a listening callback. - // A dialog box is displayed when a device is discovered or an authenticated device is found. - this.remoteDeviceModel.registerDeviceListCallback(() => { - this.deviceList = []; - Logger.info(TAG, `registerDeviceListCallback, callback entered`); - let context: common.UIAbilityContext | undefined = AppStorage.get('UIAbilityContext'); - if (context !== undefined) { - this.deviceList.push({ - deviceId: '0', - deviceName: CommonConstants.LOCALHOST_NAME, - deviceType: '0', - networkId: '' - }) - } - let deviceTempList = this.remoteDeviceModel.discoverList.length > 0 ? - this.remoteDeviceModel.discoverList : this.remoteDeviceModel.deviceList; - if (deviceTempList !== null) { - for (let i = 0; i < deviceTempList!.length; i++) { - this.deviceList.push({ - deviceId: deviceTempList![i].deviceId, - deviceName: deviceTempList![i].deviceName, - deviceType: deviceTempList![i].deviceType, - networkId: deviceTempList![i].networkId - }); - AppStorage.set('deviceList', this.deviceList); - } - } - }) - if (this.dialogController === null) { - this.dialogController = new CustomDialogController({ - builder: DeviceListDialogComponent({ - selectedIndex: this.selectedIndex, - onSelectedIndexChange: this.onSelectedIndexChange - }), - cancel: () => { - this.clearSelectState(); - }, - autoCancel: true - }) - } - if (this.dialogController !== null) { - this.dialogController.open(); - } - } - - build() { - Column() { - this.NavigationMenus() - this.ListHeader() - this.List() - } - .padding({ - left: $r('app.float.search_padding_horizontal'), - right: $r('app.float.search_padding_horizontal') - }) - .width(CommonConstants.PERCENTAGE_MAX) - .backgroundColor($r('app.color.theme_background')) - } - - @Builder - NavigationMenus() { - Row() { - Image($r('app.media.ic_add')) - .width($r('app.float.empty_view_size')) - .height($r('app.float.empty_view_size')) - .margin({ right: $r('app.float.image_spacing_margin') }) - .onClick(() => { - this.listPageViewModel.redirectAddPage(); - }) - Image($r('app.media.ic_more')) - .width($r('app.float.empty_view_size')) - .height($r('app.float.empty_view_size')) - .margin({ right: $r('app.float.image_spacing_margin') }) - .bindPopup(this.customPopup, { - builder: this.popupBuilder, - placement: Placement.Bottom, - popupColor: Color.White, - enableArrow: false - }) - .onClick(() => { - this.customPopup = !this.customPopup; - }) - } - .height($r('app.float.detail_navigation_height')) - .width(CommonConstants.PERCENTAGE_MAX) - .justifyContent(FlexAlign.End) - } - - @Builder - ListHeader() { - Column() { - Text($r('app.string.page_title')) - .textAlign(TextAlign.Start) - .width(CommonConstants.PERCENTAGE_MAX) - .fontColor($r('app.color.navigation_title')) - .fontSize($r('app.float.title_size')) - .focusable(true) - if (!this.isContactsEmpty) { - Row() { - Text(this.contactsNumber.toString()) - .margin({ top: $r('app.float.contacts_num_margin_top') }) - .fontSize($r('app.float.contacts_num_size')) - .fontColor($r('app.color.contacts_num')) - Text($r('app.string.contacts_num_template')) - .margin({ top: $r('app.float.contacts_num_margin_top') }) - .fontSize($r('app.float.contacts_num_size')) - .fontColor($r('app.color.contacts_num')) - } - .width(CommonConstants.PERCENTAGE_MAX) - } - } - .margin({ - top: $r('app.float.navigation_margin_top'), - left: $r('app.float.navigation_margin_left'), - right: $r('app.float.navigation_margin_right'), - bottom: $r('app.float.navigation_margin_bottom') - }) - } - - @Builder - List() { - if (this.isContactsEmpty) { - // There is no data in the database. - this.buildEmptyView(); - } else { - // The database has data. - this.buildDataView(); - } - } - - @Builder - buildEmptyView() { - Column() { - Image($r('app.media.ic_empty')) - .width($r('app.float.contact_picture_size')) - .height($r('app.float.contact_picture_size')) - .margin({ bottom: $r('app.float.empty_view_margin') }) - Text($r('app.string.contacts_empty')) - .fontSize($r('app.float.contacts_empty_size')) - .fontColor($r('app.color.empty_font_color')) - } - .height(CommonConstants.PERCENTAGE_MAX) - .width(CommonConstants.PERCENTAGE_MAX) - .justifyContent(FlexAlign.Start) - .padding({ - top: CommonConstants.CONTACTS_EMPTY_TOP - }) - } - - @Builder - buildDataView() { - Column() { - Search({ - placeholder: Object($r('app.string.search_contact')) - }) - .width(CommonConstants.PERCENTAGE_MAX) - .height($r('app.float.search_height')) - .border({ radius: $r('app.float.search_radius') }) - .placeholderColor($r('app.color.empty_font_color')) - .placeholderFont({ - size: $r('app.float.search_text_size'), - weight: CommonConstants.SEARCH_FONT_WEIGHT, - family: CommonConstants.SEARCH_FONT_FAMILY, - style: FontStyle.Normal - }) - .textFont({ size: $r('app.float.search_text_size') }) - .margin({ bottom: $r('app.float.search_margin_bottom') }) - .padding({ - left: $r('app.float.search_padding'), - right: $r('app.float.search_padding') - }) - ListAreaComponent({ contactData: $contactData }) - .height(CommonConstants.CONTACTS_LIST_HEIGHT) - } - .backgroundColor($r('app.color.theme_background')) - .width(CommonConstants.PERCENTAGE_MAX) - } - - @Builder - popupBuilder() { - Column() { - Text($r('app.string.delete_batch')) - .fontSize($r('app.float.popup_font_size')) - .padding({ - top: $r('app.float.delete_padding_vertical'), - bottom: $r('app.float.delete_padding_vertical'), - left: $r('app.float.delete_padding_left') - }) - .onClick(() => { - this.listPageViewModel.redirectDeletePage(); - }) - Divider() - .height($r('app.float.divider_height')) - .backgroundColor($r('app.color.list_divider')) - .margin({ - left: $r('app.float.delete_padding_left'), - right: $r('app.float.delete_padding_left') - }) - Text($r('app.string.connection_device')) - .fontSize($r('app.float.popup_font_size')) - .padding({ - top: $r('app.float.delete_padding_vertical'), - bottom: $r('app.float.delete_padding_vertical'), - left: $r('app.float.delete_padding_left') - }) - .onClick(() => { - this.customPopup = !this.customPopup; - Logger.info('Index', 'onContinueAbilityClick execute'); - this.showDialogInfo(); - }) - } - .width($r('app.float.popup_width')) - .height($r('app.float.popup_height')) - .alignItems(HorizontalAlign.Start) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/RemoteDeviceModel.ets b/entry/src/main/ets/utils/ContactDeviceManager.ets similarity index 31% rename from entry/src/main/ets/viewmodel/RemoteDeviceModel.ets rename to entry/src/main/ets/utils/ContactDeviceManager.ets index 2fb560d83bb99b4eaae63b43ab572e9e3d31fb10..52571403cda70152554420d715ef0bb98975307d 100644 --- a/entry/src/main/ets/viewmodel/RemoteDeviceModel.ets +++ b/entry/src/main/ets/utils/ContactDeviceManager.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,103 +15,128 @@ import { distributedDeviceManager } from '@kit.DistributedServiceKit'; import { Callback } from '@kit.BasicServicesKit'; -import Logger from '../common/util/Logger'; -import CommonConstants from '../common/constants/CommonConstants'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { JSON } from '@kit.ArkTS'; +import { common, Want } from '@kit.AbilityKit'; +import CommonConstants from '../common/CommonConstants'; -const TAG: string = 'RemoteDeviceModel'; +const TAG: string = 'ContactDeviceManager'; -export class RemoteDeviceModel { - public deviceList: Array | null = []; - public discoverList: Array = []; - private callback: () => void = () => { +// Address book device management +export class ContactDeviceManager { + private context: common.UIAbilityContext; + private deviceManager: distributedDeviceManager.DeviceManager; + callback: () => void = () => { }; - private authCallback: () => void = () => { + deviceList: Array = []; + discoverList: Array = []; + authCallback: () => void = () => { }; - private deviceManager: distributedDeviceManager.DeviceManager | undefined = undefined; + + constructor(context: common.UIAbilityContext) { + this.context = context; + this.deviceManager = distributedDeviceManager.createDeviceManager(CommonConstants.BUNDLE_NAME); + } + + /** + * The registered device status callback returns the device status and information. + */ private deviceStateChange: (data: DeviceManagerData) => void = (data: DeviceManagerData): void => { - if (data === null) { - return; - } - Logger.info(TAG, `deviceStateChange data= ${JSON.stringify(data)}`) - switch (data.action) { - // Physical devices go online. - case distributedDeviceManager.DeviceStateChange.AVAILABLE: - this.changeState(data.device, distributedDeviceManager.DeviceStateChange.AVAILABLE); - break; - // Device availability status. - case distributedDeviceManager.DeviceStateChange.UNKNOWN: - this.changeStateOnline(data.device); - break; - // Physical offline of the device. - case distributedDeviceManager.DeviceStateChange.UNAVAILABLE: - this.changeStateOffline(data.device); - break; - default: - break; + if (data) { + switch (data.action) { + // Physical devices go online. + case distributedDeviceManager.DeviceStateChange.AVAILABLE: + this.changeState(data.device); + break; + // Device availability status. + case distributedDeviceManager.DeviceStateChange.UNKNOWN: + this.changeStateOnline(data.device); + break; + // Physical offline of the device. + case distributedDeviceManager.DeviceStateChange.UNAVAILABLE: + this.changeStateOffline(data.device); + break; + default: + break; + } } }; + /** + * Discovering callback methods for devices + */ private discoverSuccess: (data: DeviceBasicInfo) => void = (data: DeviceBasicInfo): void => { - if (data === null) { - return; + if (data) { + this.deviceFound(data.device); } - Logger.info(TAG, `discoverSuccess data=${JSON.stringify(data)}`); - this.deviceFound(data.device); - }; - private discoverFailure: (data: DiscoverFailureData) => void = (data: DiscoverFailureData): void => { - Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`); - }; - private serviceDie: () => void = () => { - Logger.error(TAG, 'serviceDie'); }; + /** + * Register device list callback function + * @param callback + */ registerDeviceListCallback(callback: Callback) { - if (typeof (this.deviceManager) !== 'undefined') { - this.registerDeviceListCallbackImplement(callback); - return; - } try { - // Create a device management instance. - let dmInstance = distributedDeviceManager.createDeviceManager(CommonConstants.BUNDLE_NAME); - this.deviceManager = dmInstance; - this.registerDeviceListCallbackImplement(callback); + this.callback = callback; + if (!this.deviceManager) { + hilog.error(0x0000, 'hilog', TAG, 'deviceManager has not initialized'); + this.callback(); + } else { + this.deviceList = this.deviceManager.getAvailableDeviceListSync(); + this.callback(); + // Register device status callback. + this.deviceManager.on('deviceStateChange', this.deviceStateChange); + // This interface is used to call back the listener when the device is successfully registered and discovered. + this.deviceManager.on('discoverSuccess', this.discoverSuccess); + this.startDeviceDiscovery(); + } } catch (error) { - Logger.error(TAG, `createDeviceManager throw, error message is ${JSON.stringify(error)}`); + hilog.error(0x0000, 'ContactDeviceManager', `have error .Code:${error.code},message: ${error.message}`); } } - changeStateOnline(device: distributedDeviceManager.DeviceBasicInfo) { - if (this.deviceList !== null) { - this.deviceList![this.deviceList!.length] = device; - } - Logger.info(TAG, `online, device list= ${JSON.stringify(this.deviceList)}`); - this.callback(); - if (this.authCallback !== null) { - this.authCallback(); - this.authCallback = () => { + /** + * Cancel registration of device list + */ + unregisterDeviceListCallback() { + if (this.deviceManager) { + try { + this.deviceList = []; + this.discoverList = []; + this.deviceManager.stopDiscovering(); + // Device status cancellation callback. + this.deviceManager.off('deviceStateChange', this.deviceStateChange); + // Callback for successful deregistration of device discovery. + this.deviceManager.off('discoverSuccess', this.discoverSuccess); + } catch (error) { + hilog.error(0x0000, 'ContactDeviceManager', `have error .Code:${error.code},message: ${error.message}`); } } + + } + + changeStateOnline(device: distributedDeviceManager.DeviceBasicInfo) { + this.deviceList[this.deviceList.length] = device; + hilog.info(0x0000, 'hilog', TAG, `online, device list= ${JSON.stringify(this.deviceList)}`); + this.callback(); + this.authCallback(); + this.authCallback = () => { + }; } changeStateOffline(device: distributedDeviceManager.DeviceBasicInfo) { - if (this.deviceList !== null && this.deviceList!.length > 0) { - let list: Array = []; - for (let j = 0; j < this.deviceList!.length; j++) { - if (this.deviceList![j].deviceId !== device.deviceId) { - list[j] = device; - } + let list: Array = []; + for (let j = 0; j < this.deviceList!.length; j++) { + if (this.deviceList![j].deviceId !== device.deviceId) { + list[j] = device; } - this.deviceList = list; } - Logger.info(TAG, `offline, updated device list=${JSON.stringify(device)}`); + this.deviceList = list; + hilog.info(0x0000, 'hilog', TAG, `offline, updated device list=${JSON.stringify(device)}`); this.callback(); } - changeState(device: distributedDeviceManager.DeviceBasicInfo, state: number) { - if (this.deviceList !== null && this.deviceList!.length <= 0) { - this.callback(); - return; - } - if (this.deviceList !== null && state === distributedDeviceManager.DeviceStateChange.AVAILABLE) { + changeState(device: distributedDeviceManager.DeviceBasicInfo) { + try { let list: Array = new Array(); for (let i = 0; i < this.deviceList!.length; i++) { if (this.deviceList![i].deviceId !== device.deviceId) { @@ -119,69 +144,21 @@ export class RemoteDeviceModel { } } this.deviceList = list; - Logger.info(TAG, `ready, device list= ${JSON.stringify(device)}`); - this.callback(); - } else { - if (this.deviceList !== null) { - for (let j = 0; j < this.deviceList!.length; j++) { - if (this.deviceList![j].deviceId === device.deviceId) { - this.deviceList![j] = device; - break; - } - } - Logger.info(TAG, `offline, device list= ${JSON.stringify(this.deviceList)}`); - this.callback(); - } - } - } - - registerDeviceListCallbackImplement(callback: Callback) { - this.callback = callback; - if (this.deviceManager === undefined) { - Logger.error(TAG, 'deviceManager has not initialized'); - this.callback(); - return; - } - try { - // This interface is used to obtain the list of all trusted devices. - let list = this.deviceManager !== undefined ? this.deviceManager.getAvailableDeviceListSync() : null; - Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`); - if (typeof (list) !== 'undefined' && JSON.stringify(list) !== '[]') { - this.deviceList = list!; - } - Logger.info(TAG, `getTrustedDeviceListSync end, deviceList=${JSON.stringify(list)}`); } catch (error) { - Logger.error(TAG, `getTrustedDeviceListSync, error message is ${JSON.stringify(error)}`); + hilog.error(0x0000, 'ContactDeviceManager', `have error .Code:${error.code},message: ${error.message}`); } this.callback(); - try { - if (this.deviceManager !== undefined) { - // Register device status callback. - this.deviceManager.on('deviceStateChange', this.deviceStateChange); - } - if (this.deviceManager !== undefined) { - // This interface is used to call back the listener when the device is successfully registered and discovered. - this.deviceManager.on('discoverSuccess', this.discoverSuccess); - // Callback listener for device discovery failure. - this.deviceManager.on('discoverFailure', this.discoverFailure); - // Registers the dead listener of the device management service. - this.deviceManager.on('serviceDie', this.serviceDie); - } - } catch (error) { - Logger.error(TAG, `on throw, error message is ${JSON.stringify(error)}`); - } - this.startDeviceDiscovery(); } deviceFound(data: distributedDeviceManager.DeviceBasicInfo) { for (let i = 0; i < this.discoverList.length; i++) { if (this.discoverList[i].deviceId === data.deviceId) { - Logger.info(TAG, 'device founded ignored'); + hilog.info(0x0000, 'hilog', TAG, 'device founded ignored'); return; } } this.discoverList.push(data); - Logger.info(TAG, `deviceFound self.discoverList= ${this.discoverList}`); + hilog.info(0x0000, 'hilog', TAG, `deviceFound self.discoverList= ${this.discoverList}`); this.callback(); } @@ -196,79 +173,47 @@ export class RemoteDeviceModel { 'availableStatus': 0 }; try { - if (this.deviceManager !== undefined) { - // Discover peripheral devices. The discovery status lasts for two minutes. - // If the discovery status exceeds two minutes, the discovery stops. A maximum of 99 nodes can be discovered. - this.deviceManager.startDiscovering(discoverParam, filterOptions); - } + // Discover peripheral devices. The discovery status lasts for two minutes. + // If the discovery status exceeds two minutes, the discovery stops. A maximum of 99 nodes can be discovered. + this.deviceManager.startDiscovering(discoverParam, filterOptions); } catch (error) { - Logger.error(TAG, `startDeviceDiscovery throw, error message is ${JSON.stringify(error)}`); + hilog.error(0x0000, 'ContactDeviceManager', `have error .Code:${error.code},message: ${error.message}`); } } - unregisterDeviceListCallback() { - if (this.deviceManager === undefined) { - return; - } - if (this.deviceManager !== undefined) { - try { - // Stop detecting peripheral devices. - this.deviceManager.stopDiscovering(); - } catch (error) { - Logger.error(TAG, `stopDeviceDiscovery throw, error message is ${JSON.stringify(error)}`); - } - try { - // Device status cancellation callback. - this.deviceManager.off('deviceStateChange'); - // Callback for successful deregistration of device discovery. - this.deviceManager.off('discoverSuccess'); - // Callback when device discovery fails to be deregistered. - this.deviceManager.off('discoverFailure'); - // This interface is used to unregister the dead listener of the device management service. - this.deviceManager.off('serviceDie'); - } catch (error) { - Logger.error(TAG, `off throw, error message is ${JSON.stringify(error)}`); - } + startAbility(deviceId: string | undefined) { + hilog.info(0x0000, 'hilog', TAG, `startAbility deviceId: ${deviceId}`); + let want: Want = { + bundleName: CommonConstants.BUNDLE_NAME, + abilityName: CommonConstants.ENTRY_ABILITY, + deviceId: deviceId + }; + try { + this.context.startAbility(want).then((data) => { + hilog.info(0x0000, 'hilog', TAG, `start ability finished: ${data}`); + }); + } catch (error) { + hilog.error(0x0000, 'ContactDeviceManager', `have error .Code:${error.code},message: ${error.message}`); } - this.deviceList = []; - this.discoverList = []; } authenticateDevice(device: distributedDeviceManager.DeviceBasicInfo, callBack: Callback) { - Logger.info(TAG, `authenticateDevice ${JSON.stringify(device)}`); + hilog.info(0x0000, 'hilog', TAG, `authenticateDevice ${JSON.stringify(device)}`); for (let i = 0; i < this.discoverList.length; i++) { - if (this.discoverList[i].deviceId !== device.deviceId) { - continue; - } - try { - if (this.deviceManager !== undefined) { - // Authenticate the device. - this.deviceManager.bindTarget(device.deviceId, { - bindType: 1, - targetPkgName: CommonConstants.BUNDLE_NAME, - appName: CommonConstants.APP_NAME - }, (err, data) => { - if (err) { - Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`); - this.authCallback = () => { - } - return; - } - Logger.info(TAG, `authenticateDevice succeed: ${JSON.stringify(data)}`); - this.authCallback = callBack; - }); - } - } catch (error) { - Logger.error(TAG, `authenticateDevice throw, error message is ${JSON.stringify(error)}`); + if (this.discoverList[i].deviceId === device.deviceId) { + this.deviceManager.bindTarget(device.deviceId, { + bindType: 1, + targetPkgName: CommonConstants.BUNDLE_NAME, + appName: CommonConstants.APP_NAME + }, (err, data) => { + this.authCallback = err ? () => { + } : callBack; + }); } } } } -class DiscoverFailureData { - reason: number = 0; -} - class DeviceBasicInfo { device: distributedDeviceManager.DeviceBasicInfo = { deviceId: "", diff --git a/entry/src/main/ets/common/database/ContactsDataBase.ets b/entry/src/main/ets/utils/KvManager.ets similarity index 63% rename from entry/src/main/ets/common/database/ContactsDataBase.ets rename to entry/src/main/ets/utils/KvManager.ets index 72348b44ea7cebfd7630f2ceaf1c73c10a8c79ae..e93fa9a1f7fdf0fb36ed0de8c8b65d2e4bd3ff68 100644 --- a/entry/src/main/ets/common/database/ContactsDataBase.ets +++ b/entry/src/main/ets/utils/KvManager.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,32 +16,35 @@ import { distributedKVStore } from '@kit.ArkData'; import { common } from '@kit.AbilityKit'; import { AsyncCallback, BusinessError, Callback } from '@kit.BasicServicesKit'; -import { distributedDeviceManager } from '@kit.DistributedServiceKit' -import CommonConstants from '../constants/CommonConstants'; -import Logger from '../util/Logger'; -import { GlobalContext } from '../../common/util/GlobalContext'; +import { distributedDeviceManager } from '@kit.DistributedServiceKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import CommonConstants from '../common/CommonConstants'; -const TAG: string = 'ContactsDataBase'; +const TAG: string = 'KvManager'; -export class ContactsDataBase { +// Key value database management class +export class KvManager { private kvManager: distributedKVStore.KVManager | undefined = undefined; private kvStore: distributedKVStore.SingleKVStore | undefined = undefined; - private context: common.UIAbilityContext; + private context: common.UIAbilityContext | undefined = undefined; - constructor() { - this.context = GlobalContext.getContext().getObject('entryContext') as common.UIAbilityContext; + constructor(context: common.UIAbilityContext) { + this.context = context; this.createManager(); } + /** + * Create a KVManager object instance for managing database objects + */ createManager(): void { - if ((typeof (this.kvStore) !== 'undefined')) { + if (typeof this.kvStore !== 'undefined') { return; } try { // Creates a KVManager object instance. this.kvManager = distributedKVStore.createKVManager({ bundleName: CommonConstants.BUNDLE_NAME, - context: this.context + context: this.context, }); let options: distributedKVStore.Options = { createIfMissing: true, // Indicates whether to create a database when the database file does not exist. @@ -49,162 +52,150 @@ export class ContactsDataBase { backup: false, // Indicates whether to back up database files. autoSync: false, // Indicates whether to automatically synchronize database files. kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, // Set the type of database to be created. - securityLevel: distributedKVStore.SecurityLevel.S1// Setting the database security level. + securityLevel: distributedKVStore.SecurityLevel.S1, // Setting the database security level. }; // distributed key-value database. - this.kvManager.getKVStore( - CommonConstants.DB_STORE_ID, - options, + this.kvManager.getKVStore(CommonConstants.DB_STORE_ID, options, (err: BusinessError, store: distributedKVStore.SingleKVStore | undefined) => { if (err) { - Logger.error(TAG, `Failed to get KVStore: code message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `Failed to get KVStore: code message is ${JSON.stringify(err)}`); return; } this.kvStore = store; - }); + }); } catch (err) { - Logger.error(TAG, `An unexpected error occurred, code message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `An unexpected error occurred, code message is ${JSON.stringify(err)}`); } } /** - * This command is used to shut down the specified KVStore database by package name. + * Close the specified distributed key value database using the value of storeId */ closeKVStore(): void { try { - this.kvManager?.closeKVStore(this.context.abilityInfo.bundleName, CommonConstants.DB_STORE_ID, + this.kvManager?.closeKVStore(CommonConstants.BUNDLE_NAME, CommonConstants.DB_STORE_ID, (err: BusinessError) => { if (err !== undefined) { - console.error(TAG, `Failed to close KVStore, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `Failed to close KVStore, error message is ${JSON.stringify(err)}`); return; } - Logger.info(TAG, 'Succeeded in closing KVStore'); + hilog.info(0x0000, 'hilog', TAG, 'Succeeded in closing KVStore'); }); } catch (err) { - Logger.error(TAG, `CloseKVStore an unexpected error occurred, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, + `CloseKVStore an unexpected error occurred, error message is ${JSON.stringify(err)}`); } } - /** - * Deletes a specified KVStore database by package name. - */ - deleteKVStore(): void { - try { - this.kvManager?.deleteKVStore(this.context.abilityInfo.bundleName, CommonConstants.DB_STORE_ID); - this.syncRemote(); - } catch (err) { - Logger.error(TAG, `DeleteKVStore an unexpected error occurred, error message is ${JSON.stringify(err)}`); - } - } /** - * Delete key-value pairs from the KVStore database in batches. - * - * @param keys Indicates the key-value pairs to be deleted in batches. + * Subscribe to distributed data changes. * @param callback Callback function. */ - deleteBatch(keys: string[], callback: AsyncCallback): void { + subscriptionKvStore(callback: Callback): void { try { - this.kvStore?.deleteBatch(keys, callback); - this.syncRemote(); + this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, callback); } catch (err) { - Logger.error(TAG, `DeleteKVStore an unexpected error occurred, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, + `DataChange an unexpected error occured, error message is ${JSON.stringify(err)}`); } } /** - * Subscribe to distributed data changes. - * - * @param callback Callback function. + * Remove the data change listener. */ - subscriptionKvStore(callback: Callback): void { + removeDataChangeListener(): void { + if (this.kvStore === null) { + return; + } try { - this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, callback); - } catch (err) { - Logger.error(TAG, `DataChange an unexpected error occured, error message is ${JSON.stringify(err)}`); + this.kvStore?.off('dataChange'); + } catch (error) { + hilog.error(0x0000, 'KvManager', `have error .Code:${error.code},message: ${error.message}`); } } /** * Adds a key-value pair of a specified type to the database. * If the key value exists, modify the data. Otherwise, add data. - * * @param The Key Key of the data to be added. * @param The value of the data to be added. */ - save(key: string, value: string): void { + addAndSave(key: string, value: string): void { try { this.kvStore?.put(key, value); this.syncRemote(); } catch (err) { - Logger.error(TAG, `Put an unexpected error occured, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `Put an unexpected error occured, error message is ${JSON.stringify(err)}`); } } /** - * Gets the value of a specified key. - * - * @param key Key of the data to be queried. + * Single deletion + * Deletes data with a specified key value from the database. + * @param key Key of the data to be deleted. * @param callback Callback function. */ - get(key: string, callback: AsyncCallback): void { + deleteOnce(key: string, callback: AsyncCallback): void { try { - this.kvStore?.get(key, callback); + this.kvStore?.delete(key, callback); + this.syncRemote(); } catch (err) { - Logger.error(TAG, `Fail to get, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `An unexpected error occurred, error message is ${JSON.stringify(err)}`); } } /** - * Deletes data with a specified key value from the database. - * - * @param key Key of the data to be deleted. + * Batch Delete + * Delete key-value pairs from the KVStore database in batches. + * @param keys Indicates the key-value pairs to be deleted in batches. * @param callback Callback function. */ - delete(key: string, callback: AsyncCallback): void { + deleteBatch(keys: string[], callback: AsyncCallback): void { try { - this.kvStore?.delete(key, callback); + this.kvStore?.deleteBatch(keys, callback); this.syncRemote(); } catch (err) { - Logger.error(TAG, `An unexpected error occurred, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, + `DeleteKVStore an unexpected error occurred, error message is ${JSON.stringify(err)}`); } } /** - * Obtains all key-value pairs that match the specified key prefix. - * - * @param key Indicates the key prefix to be matched. + * Get individual details + * Gets the value of a specified key. + * @param key Key of the data to be queried. * @param callback Callback function. */ - getEntries(key: string, callback: AsyncCallback): void { + getDetails(key: string, callback: AsyncCallback): void { try { - this.kvStore?.getEntries(key, callback); + this.kvStore?.get(key, callback); } catch (err) { - Logger.error(TAG, `An unexpected error occurred, error message is ${JSON.stringify(err)}`); + hilog.error(0x0000, 'hilog', TAG, `Fail to get, error message is ${JSON.stringify(err)}`); } } /** - * Remove the data change listener. + * Obtains all key-value pairs that match the specified key prefix. + * @param key Indicates the key prefix to be matched. + * @param callback Callback function. */ - removeDataChangeListener(): void { - if (this.kvStore === null) { - return; - } + getEntries(key: string, callback: AsyncCallback): void { try { - this.kvStore?.off('dataChange'); - } catch (error) { - Logger.error(TAG, `removeDataChangeListener off('dataChange') failed, code message is ${JSON.stringify(error)}`); + this.kvStore?.getEntries(key, callback); + } catch (err) { + hilog.error(0x0000, 'hilog', TAG, `An unexpected error occurred, error message is ${JSON.stringify(err)}`); } } + /** - * Manually synchronize data in case of automatic synchronization failure. + * Remote synchronization of data */ private syncRemote() { let devManager: distributedDeviceManager.DeviceManager; try { // create deviceManager - devManager = distributedDeviceManager.createDeviceManager(this.context.abilityInfo.name); + devManager = distributedDeviceManager.createDeviceManager(CommonConstants.BUNDLE_NAME); let deviceIds: string[] = []; if (devManager !== null) { let devices = devManager.getAvailableDeviceListSync(); @@ -216,11 +207,11 @@ export class ContactsDataBase { this.kvStore?.sync(deviceIds, distributedKVStore.SyncMode.PUSH_ONLY, 1000); } catch (err) { let error = err as BusinessError; - Logger.error(`An unexpected error occured. Code:${error.code},message:${err.message}`); + hilog.error(0x0000, 'hilog', `An unexpected error occured. Code:${error.code},message:${err.message}`); } } catch (err) { let error = err as BusinessError; - Logger.error(`createDeviceManager errCode:${error.code},message:${error.message}`); + hilog.error(0x0000, 'hilog', `createDeviceManager errCode:${error.code},message:${error.message}`); } } } \ No newline at end of file diff --git a/entry/src/main/ets/view/BottomBarComponent.ets b/entry/src/main/ets/view/BottomBarComponent.ets deleted file mode 100644 index 402bac9aa3635de4757230f3b240b6624a1b3a14..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/view/BottomBarComponent.ets +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CommonConstants from '../common/constants/CommonConstants'; - -@Extend(Text) -function setTextStyle() { - .width(CommonConstants.PERCENTAGE_MAX) - .textAlign(TextAlign.Center) - .fontColor($r('app.color.bottom_bar_font_color')) - .fontSize($r('app.float.bottom_bar_font_size')) -} - -@Extend(Image) -function setImageStyle() { - .width($r('app.float.bottom_bar_image_size')) - .height($r('app.float.bottom_bar_image_size')) - .margin($r('app.float.bottom_bar_image_margin')) -} - -@Component -export default struct BottomBarComponent { - private isAll: boolean = false; - public pageId: string = ''; - public leftClickEvent: (isAll?: boolean) => void = () => { - }; - public rightClickEvent: () => void = () => { - }; - public leftIcon: Resource = $r('app.media.ic_edit'); - public leftSubtitle: Resource = $r('app.media.ic_delete'); - public rightIcon: Resource = $r('app.media.ic_delete'); - public rightSubtitle: Resource = $r('app.string.delete_text'); - - build() { - Column() { - Divider() - .height($r('app.float.divider_height')) - .backgroundColor($r('app.color.list_divider')) - Row() { - Column() { - Image(this.leftIcon) - .setImageStyle() - Text(this.leftSubtitle) - .setTextStyle() - } - .onClick(() => { - if (this.pageId === CommonConstants.DELETE_PAGE_ID) { - this.isAll = !this.isAll; - } - this.leftClickEvent(this.isAll); - }) - .width(CommonConstants.BOTTOM_COMPONENT_WIDTH) - - Column() { - Image(this.rightIcon) - .setImageStyle() - Text(this.rightSubtitle) - .setTextStyle() - } - .width(CommonConstants.BOTTOM_COMPONENT_WIDTH) - .onClick(() => this.rightClickEvent()) - } - .width(CommonConstants.PERCENTAGE_MAX) - .height($r('app.float.bar_bottom_height')) - .backgroundColor($r('app.color.theme_background')) - .justifyContent(FlexAlign.End) - } - } -} \ No newline at end of file diff --git a/entry/src/main/ets/view/DetailItemComponent.ets b/entry/src/main/ets/view/DetailItemComponent.ets deleted file mode 100644 index 7c056d3b4f9f15f08b85ceff995742d89e098791..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/view/DetailItemComponent.ets +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CommonConstants from '../common/constants/CommonConstants'; - -@Component -export default struct DetailItemComponent { - @Link topContent: string; - @Link bottomContent: string; - public componentId: number = CommonConstants.PHONE_COMPONENTS_ID; - public bottomSubtitle: Resource = $r('app.string.item_personal_email'); - public leftIcon: Resource = $r('app.media.ic_phone_icon'); - public rightIcon: Resource = $r('app.media.ic_message'); - - build() { - Column() { - Row() { - Column() { - Text(this.getTopContent()) - .fontColor(Color.Black) - .fontSize($r('app.float.details_item_font_size')) - .fontWeight(500) - .margin({ bottom: $r('app.float.details_item_margin') }) - Text(this.getBottomContent()) - .fontColor($r('app.color.font_color')) - .fontSize($r('app.float.details_bottom_item_size')) - .fontWeight(400) - .opacity(0.6) - } - .width(CommonConstants.DETAIL_ITEM_WIDTH) - .alignItems(HorizontalAlign.Start) - - Row() { - if (this.componentId !== CommonConstants.Note_COMPONENTS_ID) { - if (this.componentId === CommonConstants.PHONE_COMPONENTS_ID) { - Image(this.leftIcon) - .width($r('app.float.details_item_size')) - .height($r('app.float.details_item_size')) - .objectFit(ImageFit.Contain) - .margin({ right: $r('app.float.details_margin_right') }) - } - Image(this.rightIcon) - .width($r('app.float.details_item_size')) - .height($r('app.float.details_item_size')) - .objectFit(ImageFit.Contain) - } - } - .layoutWeight(CommonConstants.WEIGHT) - .justifyContent(FlexAlign.End) - } - .height($r('app.float.details_item_height')) - - Divider() - .height($r('app.float.divider_height')) - .backgroundColor($r('app.color.list_divider')) - } - } - - getTopContent(): string { - if (this.componentId === CommonConstants.PHONE_COMPONENTS_ID && this.topContent.length === 11) { - let phoneNumberStr = ''; - for (let i = 0; i < this.topContent.length; i++) { - if (i === 2 || i === 6) { - phoneNumberStr += this.topContent[i] + ' '; - } else { - phoneNumberStr += this.topContent[i]; - } - } - return phoneNumberStr; - } else { - return this.topContent; - } - } - - getBottomContent(): string | Resource { - if (this.componentId === CommonConstants.PHONE_COMPONENTS_ID) { - return this.bottomContent; - } else { - return this.bottomSubtitle; - } - } -} \ No newline at end of file diff --git a/entry/src/main/ets/view/ListAreaComponent.ets b/entry/src/main/ets/view/ListAreaComponent.ets deleted file mode 100644 index 36ca96c2fcf313396f5d3164df89789537beaff2..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/view/ListAreaComponent.ets +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CommonConstants from '../common/constants/CommonConstants'; -import PageViewModel from '../viewmodel/PageViewModel'; -import { ListItemData } from '../viewmodel/ListItemData'; -import { ListItemComponent } from './ListItemComponent'; - -@Component -export struct ListAreaComponent { - @Link contactData: Array; - - build() { - Column() { - List() { - ForEach(this.contactData, (item: ListItemData) => { - ListItem() { - ListItemComponent({ itemInfo: item }) - } - .onClick(() => { - PageViewModel.redirectDetailPage(item); - }) - }, (item: ListItemData) => JSON.stringify(item)) - } - .scrollBar(BarState.Off) - .width(CommonConstants.LIST_WIDTH_PERCENT) - .height(CommonConstants.PERCENTAGE_HEIGHT_MAX) - .margin({ bottom: $r('app.float.list_area_height') }) - } - .width(CommonConstants.PERCENTAGE_MAX) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/ContactData.ets b/entry/src/main/ets/viewmodel/ContactViewModel.ets similarity index 80% rename from entry/src/main/ets/viewmodel/ContactData.ets rename to entry/src/main/ets/viewmodel/ContactViewModel.ets index f8afd733e7bb8c5ccb5c8b57d5d383cd28d1eb4c..b4e2fa4889974a29bb9fd4bfb81b772e5f57b154 100644 --- a/entry/src/main/ets/viewmodel/ContactData.ets +++ b/entry/src/main/ets/viewmodel/ContactViewModel.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,27 +16,23 @@ /** * Contact data entity. */ -export default class ContactData { +export class ContactData { /** * Contact name. */ name: string; - /** * Contact address. */ address?: string; - /** * Contact phone number. */ telephony?: string; - /** * Contact email. */ email?: string; - /** * Contact remarks. */ @@ -49,4 +45,19 @@ export default class ContactData { this.email = email; this.remarks = remarks; } -} \ No newline at end of file +} + +export class ListItemData { + /** + * Contact id. + */ + id: number = 0; + /** + * Contact name. + */ + name: string = ''; + /** + * Contact selection box. + */ + checked: boolean = false; +} diff --git a/entry/src/main/ets/viewmodel/DetailPageViewModel.ets b/entry/src/main/ets/viewmodel/DetailPageViewModel.ets deleted file mode 100644 index b9c791a97a6964ea8c51f87b998fbef6aff1a426..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/viewmodel/DetailPageViewModel.ets +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { promptAction } from '@kit.ArkUI'; -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import Logger from '../common/util/Logger'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { GlobalContext } from '../common/util/GlobalContext'; - -const uiContext: UIContext | undefined = AppStorage.get('uiContext'); - -export class DetailPageViewModel { - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - - /** - * Button for canceling the deletion dialog box. - */ - cancelDialog(): void { - uiContext?.getPromptAction().showToast({ - message: $r('app.string.delete_cancel_text'), - duration: CommonConstants.PROMPT_DURATION - }); - } - - /** - * Delete contact button. - */ - deleteContactButton(name: string): void { - let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + name; - try { - // This interface is used to delete the information about a specified contact. - this.contactsDataBase.delete(contactsKey, () => { - uiContext?.getPromptAction().showToast({ - message: $r('app.string.prompt_message_deleted'), - duration: CommonConstants.PROMPT_DURATION - }); - uiContext?.getRouter().pushUrl({ - url: CommonConstants.LIST_PAGE_URL - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - }); - } catch (err) { - Logger.error(`An unexpected error occurred, error message is ${JSON.stringify(err)}`); - } - } - - /** - * Button for redirecting to the details page. - */ - redirectEditPage(name: string, address: string, telephony: string, email: string, remarks: string): void { - let routerParameter: router.RouterOptions = { - url: CommonConstants.ADD_EDIT_URL, - params: { - isEdit: true, - name, - address, - telephony, - email, - remarks - } - } - if (GlobalContext.getContext().getObject('FirstInTo')) { - uiContext?.getRouter().pushUrl(routerParameter).catch((err: BusinessError) => { - Logger.error(`PushUrl failed, code message is ${JSON.stringify(err)}`); - }); - } else { - uiContext?.getRouter().replaceUrl(routerParameter).catch((err: BusinessError) => { - Logger.error(`ReplaceUrl failed, code message is ${JSON.stringify(err)}`); - }); - } - } - - /** - * Redirect list page. - */ - redirectListPage(): void { - uiContext?.getRouter().pushUrl({ - url: CommonConstants.LIST_PAGE_URL - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, error message is ${JSON.stringify(err)}`); - }); - } -} \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/EditPageViewModel.ets b/entry/src/main/ets/viewmodel/EditPageViewModel.ets deleted file mode 100644 index 7942e9171563cce75883cd5a16443749d497b794..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/viewmodel/EditPageViewModel.ets +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { promptAction } from '@kit.ArkUI'; -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import ContactData from './ContactData'; -import Logger from '../common/util/Logger' -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { GlobalContext } from '../common/util/GlobalContext'; - -const uiContext: UIContext | undefined = AppStorage.get('uiContext'); - -export class EditPageViewModel { - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - - /** - * Public routing method. - * - * @param url Indicates the route address. - */ - commonRouter(url: string, name: string): void { - let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + name; - uiContext!.getRouter().pushUrl({ - url: url, - params: { - key: contactsKey - } - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - } - - /** - * Save contact information. - */ - saveInfo(contactData: ContactData): void { - if (contactData.name !== '') { - let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + contactData.name; - // Save the contact information. - this.contactsDataBase.save(contactsKey, JSON.stringify(contactData)); - uiContext!.getRouter().replaceUrl({ - url: CommonConstants.PAGE_DETAIL_URL, - params: { key: contactsKey } - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - } else { - uiContext!.getPromptAction().showToast({ - message: $r('app.string.contact_name'), - duration: CommonConstants.PROMPT_DURATION - }); - } - } -} \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/ListItemData.ets b/entry/src/main/ets/viewmodel/ListItemData.ets deleted file mode 100644 index a2d14ea0612fe4b5aada0a326d4e3bbba96b9256..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/viewmodel/ListItemData.ets +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Delete list item data entity. - */ -export class ListItemData { - /** - * Contact id. - */ - id: number = 0; - - /** - * Contact photo. - */ - photo: Resource = $r('app.media.ic_men'); - - /** - * Contact name. - */ - name: string = ''; - - /** - * Contact selection box. - */ - checked: boolean = false; -} \ No newline at end of file diff --git a/entry/src/main/ets/viewmodel/ListPageViewModel.ets b/entry/src/main/ets/viewmodel/ListPageViewModel.ets deleted file mode 100644 index dac42760240611543c98efd1cee6a758be1d8a54..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/viewmodel/ListPageViewModel.ets +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { common } from '@kit.AbilityKit'; -import { Want } from '@kit.AbilityKit'; -import { router } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; -import CommonConstants from '../common/constants/CommonConstants'; -import Logger from '../common/util/Logger'; -import { ContactsDataBase } from '../common/database/ContactsDataBase'; -import { GlobalContext } from '../common/util/GlobalContext'; - -const TAG: string = 'ListPageViewModel'; -const uiContext: UIContext | undefined = AppStorage.get('uiContext'); - -export class ListPageViewModel { - startAbilityCallBack: (key: string) => void = () => { - }; - contactsDataBase = GlobalContext.getContext().getObject('contactsDataBase') as ContactsDataBase; - - async startAbility(deviceId: string | undefined) { - Logger.info(TAG, `startAbility deviceId: ${deviceId}`); - let context = uiContext!.getHostContext() as common.UIAbilityContext; - let want: Want = { - bundleName: CommonConstants.BUNDLE_NAME, - abilityName: CommonConstants.ENTRY_ABILITY, - deviceId: deviceId - } - context.startAbility(want).then((data) => { - Logger.info(TAG, `start ability finished: ${JSON.stringify(data)}`); - this.startAbilityCallBack(CommonConstants.DATA_CHANGE); - }) - } - - /** - * Button for redirecting to the add page. - */ - redirectAddPage(): void { - uiContext!.getRouter().pushUrl({ - url: CommonConstants.ADD_EDIT_URL, - params: { - isEdit: false - } - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - } - - /** - * Button for redirecting to the delete page. - */ - redirectDeletePage(): void { - uiContext!.getRouter().pushUrl({ - url: CommonConstants.DELETE_PAGE_URL - }).catch((err: BusinessError) => { - Logger.error(`pushUrl failed, code message is ${JSON.stringify(err)}`); - }); - } -} diff --git a/entry/src/main/ets/viewmodel/PageViewModel.ets b/entry/src/main/ets/viewmodel/PageViewModel.ets deleted file mode 100644 index d106a89e2a216eea454b5e825a0df350533760d4..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/viewmodel/PageViewModel.ets +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { router } from '@kit.ArkUI'; -import { distributedKVStore } from '@kit.ArkData'; -import { BusinessError } from '@kit.BasicServicesKit'; -import Logger from '../common/util/Logger'; -import CheckEmptyUtils from '../common/util/CheckEmptyUtils'; -import CommonConstants from '../common/constants/CommonConstants'; -import { ListItemData } from './ListItemData'; - -const TAG: string = 'PageViewModel'; -const uiContext: UIContext | undefined = AppStorage.get('uiContext'); - -/** - * Interface for batch delete page and contacts Home page. - */ -export class PageViewModel { - /** - * Processes data obtained from distributed data services. - * - * @param contactData contact data queried from the database. - */ - getData(contactData: distributedKVStore.Entry[]): Array { - Logger.info(TAG, `getData contactData: ${JSON.stringify(contactData)}`); - let listItems: Array = []; - if (CheckEmptyUtils.isEmptyArr(contactData)) { - Logger.info(TAG, '[PageViewModel][getData] contactData is empty.'); - return listItems; - } - for (let i = 0; i < contactData.length; i++) { - let itemInfo: ListItemData = new ListItemData(); - itemInfo.photo = (i % CommonConstants.EVEN_COLUMN === 1) ? $r('app.media.ic_men') : $r('app.media.ic_women'); - itemInfo.name = JSON.parse(contactData[i].value.value as string).name; - itemInfo.id = i; - listItems.push(itemInfo); - } - return listItems; - } - - /** - * Obtains the quantity of a batch. - * - * @param list Contact list data. - */ - getSelectCount(list: Array): number { - if (CheckEmptyUtils.isEmptyArr(list)) { - Logger.info(TAG, '[PageViewModel][getSelectCount] list is empty.'); - Logger.error(TAG, `CheckEmptyUtils.isEmptyArr(list): ${JSON.stringify(CheckEmptyUtils.isEmptyArr(list))}`); - return 0; - } - let result = list.filter((item) => { - return item.checked; - }); - Logger.error(TAG, `result.length: ${result.length}`); - return result.length; - } - - /** - * Select all data. - */ - getSelectAll(checkList: Array): Array { - checkList.forEach((item: ListItemData) => { - item.checked = true; - }); - return checkList; - } - - /** - * Do not select all data. - */ - getUnSelectAll(checkList: Array): Array { - checkList.forEach((item: ListItemData) => { - item.checked = false; - }); - return checkList; - } - - /** - * redirect detail page. - * - * @param item List Information. - */ - redirectDetailPage(item: ListItemData): void { - let contactsKey = CommonConstants.CONTACTS_DATABASE_KEY + item.name; - uiContext!.getRouter().pushUrl({ - url: CommonConstants.PAGE_DETAIL_URL, - params: { - key: contactsKey - } - }).catch((err: BusinessError) => { - Logger.error(TAG, `pushUrl failed, error message is ${JSON.stringify(err)}`); - }); - } -} - -let pageViewModel = new PageViewModel(); - -export default pageViewModel as PageViewModel; \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index e4350490fc43c1c179cad2604eb1723bdabf43fa..b867d2eac269bd48f680b276005f88fc641ce0da 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -163,6 +163,14 @@ { "name": "contact_name", "value": "Please enter the contact name." + }, + { + "name": "no_matching_data", + "value": "No matching data" + }, + { + "name": "tip_delete", + "value": "Are you sure you want to delete this contact?" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 19def701054214f2b683bb91b573ffa194719426..32557b2818cd16cf392cfb2a1d3ca23ad5179a98 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -1,8 +1,8 @@ { "src": [ - "pages/ListPage", - "pages/DeletePage", - "pages/EditPage", - "pages/DetailPage" + "pages/ContactHomePage", + "pages/ContactDetailPage", + "pages/ContactAddAndEditPage", + "pages/ContactDeletePage" ] } diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json index e4350490fc43c1c179cad2604eb1723bdabf43fa..b867d2eac269bd48f680b276005f88fc641ce0da 100644 --- a/entry/src/main/resources/en_US/element/string.json +++ b/entry/src/main/resources/en_US/element/string.json @@ -163,6 +163,14 @@ { "name": "contact_name", "value": "Please enter the contact name." + }, + { + "name": "no_matching_data", + "value": "No matching data" + }, + { + "name": "tip_delete", + "value": "Are you sure you want to delete this contact?" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index f1350db16c901d0bfd2b4c04308f7b58ac8d13e2..fedfb31661f6046b2cec157293346d357a9cafa7 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -163,6 +163,14 @@ { "name": "contact_name", "value": "请输入联系人姓名" + }, + { + "name": "no_matching_data", + "value": "无匹配数据" + }, + { + "name": "tip_delete", + "value": "确定删除该联系人?" } ] } \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 index eb31f8301a08fedbf8ca8dd0c17b84a8347d8220..006374173a4ca5cdca753c1fc58b39c1dc23c558 100644 --- a/hvigor/hvigor-config.json5 +++ b/hvigor/hvigor-config.json5 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/screenshots/device/contact.en.gif b/screenshots/devices/contact.en.gif similarity index 100% rename from screenshots/device/contact.en.gif rename to screenshots/devices/contact.en.gif diff --git a/screenshots/device/contact.gif b/screenshots/devices/contact.gif similarity index 100% rename from screenshots/device/contact.gif rename to screenshots/devices/contact.gif diff --git a/screenshots/devices/contact_sync.gif b/screenshots/devices/contact_sync.gif new file mode 100644 index 0000000000000000000000000000000000000000..3fd0ffd7182fa269d11b240ec1d9dedb59a1e790 Binary files /dev/null and b/screenshots/devices/contact_sync.gif differ