From f1e1f97e982ce12ee5549057f5a0765f74a6444a Mon Sep 17 00:00:00 2001 From: wangwei30043812 Date: Mon, 29 May 2023 16:14:32 +0800 Subject: [PATCH] update Signed-off-by: wangwei30043812 --- AppScope/app.json5 | 4 +- common/hvigorfile.js | 2 +- .../ets/StaticSubscriber/StaticSubscriber.ts | 7 + .../component/contact/ContactListItemView.ets | 3 +- .../contact/accountants/ShowDayTime.ets | 2 +- .../main/ets/model/ContactAbilityModel.ets | 257 ++++++++- entry/src/main/ets/model/bean/ContactInfo.ets | 4 +- .../src/main/ets/model/bean/FavoriteBean.ets | 82 +++ .../main/ets/model/bean/FavoriteDataSource.ts | 58 ++ .../main/ets/model/bean/FavoriteListBean.ets | 33 ++ .../ets/model/bean/SearchContactsBean.ets | 74 +++ .../ets/model/bean/SearchContactsSource.ts | 56 ++ .../main/ets/model/calllog/CalllogModel.ets | 29 +- .../main/ets/pages/contacts/ContactList.ets | 235 +++++--- .../contacts/accountants/Accountants.ets | 16 +- .../alphabetindex/AlphabetIndexerPage.ets | 61 ++ .../BatchSelectContactsPage.ets | 107 ++-- .../SingleSelectContactPage.ets | 131 +++-- .../pages/contacts/details/ContactDetail.ets | 63 +- .../pages/contacts/search/ContactSearch.ets | 325 +++++++++++ .../ets/pages/dialer/callRecord/AllRecord.ets | 57 +- .../ets/pages/dialog/SelectMultiNumDialog.ets | 143 +++++ .../ets/pages/favorites/editFavoriteList.ets | 540 ++++++++++++++++++ .../main/ets/pages/favorites/favoriteList.ets | 445 +++++++++++++++ entry/src/main/ets/pages/index.ets | 65 ++- .../main/ets/pages/phone/dialer/Dialer.ets | 241 +++++--- .../src/main/ets/presenter/IndexPresenter.ets | 37 +- .../main/ets/presenter/PresenterManager.ets | 8 +- .../contact/ContactListPresenter.ets | 71 ++- .../accountants/AccountantsPresenter.ets | 34 +- .../AlphabetIndexerPresenter.ets | 119 ++++ .../BatchSelectContactsPresenter.ets | 126 +++- .../contact/detail/DetailPresenter.ets | 41 +- .../ets/presenter/dialer/DialerPresenter.ets | 103 +++- .../dialer/callRecord/CallRecordPresenter.ets | 17 +- .../favorite/EditFavoriteListPresenter.ets | 196 +++++++ .../favorite/FavoriteListPresenter.ets | 372 ++++++++++++ entry/src/main/ets/util/StringFormatUtil.ets | 2 +- entry/src/main/ets/workers/DataWorkerTask.ets | 52 +- .../main/ets/workers/DataWorkerWrapper.ets | 141 ----- entry/src/main/module.json5 | 3 + .../main/resources/base/element/string.json | 37 +- .../resources/base/media/ic_no_favorites.svg | 17 + .../resources/base/media/ic_public_close.svg | 8 + .../base/media/ic_public_close_gray.svg | 27 + .../base/media/ic_public_collect.svg | 8 + .../base/media/ic_public_collected.svg | 8 + .../base/media/ic_public_drag_handle.svg | 8 + .../base/media/ic_public_more_gray.svg | 30 + .../base/media/ic_public_phone_dialog.svg | 31 + .../resources/base/media/ic_public_search.svg | 27 + .../resources/base/profile/main_pages.json | 3 +- .../main/resources/en_US/element/string.json | 34 +- .../main/resources/zh_CN/element/string.json | 36 +- .../ets/missedcall/MissedCallNotifier.ets | 58 +- .../main/ets/missedcall/MissedCallService.ets | 7 + .../src/main/ets/repo/CallLogRepository.ets | 40 +- .../src/main/ets/repo/ICallLogRepository.ets | 2 +- .../src/main/ets/contract/DataColumns.ets | 2 + .../src/main/ets/contract/DataType.ets | 1 + .../src/main/ets/contract/SearchContacts.ets | 27 + .../ets/contract/SearchContactsColumns.ets | 39 ++ .../contact/src/main/ets/entity/DataItem.ets | 5 + .../src/main/ets/entity/SearchContact.ets | 51 ++ .../src/main/ets/repo/ContactListItem.ets | 4 +- .../src/main/ets/repo/ContactRepository.ets | 268 ++++++++- .../main/ets/repo/ContactUsuallyListItem.ets | 53 ++ .../src/main/ets/repo/FavoriteListItem.ets | 49 ++ .../src/main/ets/repo/IContactRepository.ets | 4 +- .../main/ets/repo/SearchContactListItem.ets | 63 ++ .../phonenumber/src/main/ets/PhoneNumber.ets | 14 + sign/contacts.p7b | Bin 3916 -> 3954 bytes 72 files changed, 4818 insertions(+), 505 deletions(-) create mode 100644 entry/src/main/ets/model/bean/FavoriteBean.ets create mode 100644 entry/src/main/ets/model/bean/FavoriteDataSource.ts create mode 100644 entry/src/main/ets/model/bean/FavoriteListBean.ets create mode 100644 entry/src/main/ets/model/bean/SearchContactsBean.ets create mode 100644 entry/src/main/ets/model/bean/SearchContactsSource.ts create mode 100644 entry/src/main/ets/pages/contacts/alphabetindex/AlphabetIndexerPage.ets create mode 100644 entry/src/main/ets/pages/contacts/search/ContactSearch.ets create mode 100644 entry/src/main/ets/pages/dialog/SelectMultiNumDialog.ets create mode 100644 entry/src/main/ets/pages/favorites/editFavoriteList.ets create mode 100644 entry/src/main/ets/pages/favorites/favoriteList.ets create mode 100644 entry/src/main/ets/presenter/contact/alphabetindex/AlphabetIndexerPresenter.ets create mode 100644 entry/src/main/ets/presenter/favorite/EditFavoriteListPresenter.ets create mode 100644 entry/src/main/ets/presenter/favorite/FavoriteListPresenter.ets delete mode 100644 entry/src/main/ets/workers/DataWorkerWrapper.ets create mode 100644 entry/src/main/resources/base/media/ic_no_favorites.svg create mode 100644 entry/src/main/resources/base/media/ic_public_close.svg create mode 100644 entry/src/main/resources/base/media/ic_public_close_gray.svg create mode 100644 entry/src/main/resources/base/media/ic_public_collect.svg create mode 100644 entry/src/main/resources/base/media/ic_public_collected.svg create mode 100644 entry/src/main/resources/base/media/ic_public_drag_handle.svg create mode 100644 entry/src/main/resources/base/media/ic_public_more_gray.svg create mode 100644 entry/src/main/resources/base/media/ic_public_phone_dialog.svg create mode 100644 entry/src/main/resources/base/media/ic_public_search.svg create mode 100644 feature/contact/src/main/ets/contract/SearchContacts.ets create mode 100644 feature/contact/src/main/ets/contract/SearchContactsColumns.ets create mode 100644 feature/contact/src/main/ets/entity/SearchContact.ets create mode 100644 feature/contact/src/main/ets/repo/ContactUsuallyListItem.ets create mode 100644 feature/contact/src/main/ets/repo/FavoriteListItem.ets create mode 100644 feature/contact/src/main/ets/repo/SearchContactListItem.ets diff --git a/AppScope/app.json5 b/AppScope/app.json5 index 1471cb6..382f2fb 100644 --- a/AppScope/app.json5 +++ b/AppScope/app.json5 @@ -2,8 +2,8 @@ "app": { "bundleName": "com.ohos.contacts", "vendor": "example", - "versionCode": 10004024, - "versionName": "1.0.4.024", + "versionCode": 10004032, + "versionName": "1.0.4.032", "icon": "$media:app_icon", "label": "$string:app_name", "distributedNotificationEnabled": true diff --git a/common/hvigorfile.js b/common/hvigorfile.js index 7c56413..42ed4b4 100644 --- a/common/hvigorfile.js +++ b/common/hvigorfile.js @@ -1,3 +1,3 @@ // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').HarTasks +module.exports = require('@ohos/hvigor-ohos-plugin').harTasks diff --git a/entry/src/main/ets/StaticSubscriber/StaticSubscriber.ts b/entry/src/main/ets/StaticSubscriber/StaticSubscriber.ts index 1a3b3a0..785f9ed 100644 --- a/entry/src/main/ets/StaticSubscriber/StaticSubscriber.ts +++ b/entry/src/main/ets/StaticSubscriber/StaticSubscriber.ts @@ -55,9 +55,16 @@ export default class StaticSubscriber extends StaticSubscriberExtensionAbility { onReceiveEvent(event) { HiLog.i(TAG, 'onReceiveEvent, event:' + JSON.stringify(event)); + const missCallData = JSON.parse(JSON.stringify(event)); + const parameters = JSON.parse(JSON.stringify(missCallData.parameters)); + let updateMissedCallNotificationsMap: Map = new Map(); MissedCallService.getInstance().init(this.context); if ("usual.event.INCOMING_CALL_MISSED" == event.event) { MissedCallService.getInstance().updateMissedCallNotifications(); + if (parameters.countList != null) { + updateMissedCallNotificationsMap.set("missedPhoneJson", missCallData.parameters); + MissedCallService.getInstance().unreadCallNotification(updateMissedCallNotificationsMap); + } } else if ("contact.event.CANCEL_MISSED" == event.event) { if (event.parameters?.missedCallData) { if ('notification.event.dialBack' == event.parameters?.action) { diff --git a/entry/src/main/ets/component/contact/ContactListItemView.ets b/entry/src/main/ets/component/contact/ContactListItemView.ets index b3f47c4..8bed399 100644 --- a/entry/src/main/ets/component/contact/ContactListItemView.ets +++ b/entry/src/main/ets/component/contact/ContactListItemView.ets @@ -63,7 +63,8 @@ export default struct ContactListItemView { autoCancel: false, alignment: (EnvironmentProp.isTablet() ? DialogAlignment.Center : DialogAlignment.Bottom), offset: { dx: 0, dy: -16 }, - gridCount: 4 + gridCount: 4, + closeAnimation: { duration: 100 } }); onDeleteClick() { diff --git a/entry/src/main/ets/component/contact/accountants/ShowDayTime.ets b/entry/src/main/ets/component/contact/accountants/ShowDayTime.ets index cea32bb..cb2500a 100644 --- a/entry/src/main/ets/component/contact/accountants/ShowDayTime.ets +++ b/entry/src/main/ets/component/contact/accountants/ShowDayTime.ets @@ -21,7 +21,7 @@ import { Birthday } from '../../../../../../../feature/contact/src/main/ets/cont @CustomDialog export struct ShowDayTime { - private date = new Date(2000, 0, 1); + private date = new Date(1970, 0, 31); @Link mPresent: AccountantsPresenter; @Prop itemIndex: number; @Prop itemType: number; diff --git a/entry/src/main/ets/model/ContactAbilityModel.ets b/entry/src/main/ets/model/ContactAbilityModel.ets index 8c4b004..8eecf69 100644 --- a/entry/src/main/ets/model/ContactAbilityModel.ets +++ b/entry/src/main/ets/model/ContactAbilityModel.ets @@ -29,6 +29,9 @@ import { StringUtil } from '../../../../../common/src/main/ets/util/StringUtil'; import { ArrayUtil } from '../../../../../common/src/main/ets/util/ArrayUtil'; import { CallLogRepository } from '../../../../../feature/call/src/main/ets/repo/CallLogRepository'; import { PhoneNumber } from '../../../../../feature/phonenumber/src/main/ets/PhoneNumber'; +import { FavoriteBean } from './bean/FavoriteBean'; +import { CallLog } from '../../../../../feature/call/src/main/ets/entity/CallLog'; +import { SearchContactsBean } from './bean/SearchContactsBean'; const TAG = "ContactAbility: "; const PROFILE_CONTACT_DATA_URI = 'datashare:///com.ohos.contactsdataability/profile/contact_data'; @@ -595,13 +598,14 @@ export default { * * @param {string} DAHelper 数据库地址 * @param {Object} callBack 回调 + * @param {Object} addParams Contact Information */ - getAllContactWithPhoneNumbers: function (callBack, context?) { + getAllContactWithPhoneNumbers: function (callBack, addParams, context?) { HiLog.i(TAG, 'Start to query all contacts with PhoneNumbers'); if (context) { ContactRepository.getInstance().init(context); } - ContactRepository.getInstance().findByPhoneIsNotNull(contactList => { + ContactRepository.getInstance().findByPhoneIsNotNull(addParams.favorite, addParams.editContact, contactList => { if (ArrayUtil.isEmpty(contactList)) { HiLog.i(TAG, 'getAllContactWithPhoneNumbers-SelectcontactsModel queryContact resultSet is empty!'); let emptyResult: ContactVo[] = []; @@ -668,6 +672,7 @@ export default { * @param {Object} actionData Contact data */ dealResult: function (dataItem: DataItem, contactDetailInfo) { + contactDetailInfo.favorite = dataItem.getFavorite(); switch (dataItem.getContentTypeId()) { case DataItemType.NAME: contactDetailInfo.display_name = dataItem.getData(); @@ -875,4 +880,252 @@ export default { data_row.close(); HiLog.i(TAG, 'End to query contactID by phone number.'); }, + + /** + * Edit Contact Favorite + * + * @param {string} DAHelper + * @param {Object} addParams Contact Information + * @param {Object} callBack Contact ID + */ + updateFavorite: async function (DAHelper, addParams, callBack, context?) { + HiLog.i(TAG, 'updateFavorite start.'); + if (DAHelper == undefined || DAHelper == null || DAHelper.length == 0) { + DAHelper = await dataShare.createDataShareHelper(context ? context : globalThis.context, Contacts.CONTENT_URI); + } + let condition = new dataSharePredicates.DataSharePredicates(); + condition.equalTo('contact_id', addParams.id); + const va = { + 'favorite': addParams.favorite, + } + DAHelper.update( + RawContacts.CONTENT_URI, + condition, + va + ).then(data => { + if (data == -1) { + HiLog.e(TAG, 'updateFavorite data failed!'); + } + HiLog.i(TAG, 'updateFavorite data success!'); + callBack(addParams.favorite) + }).catch(err => { + HiLog.e(TAG, 'updateFavorite failed. Cause: ' + JSON.stringify(err.message)); + }); + HiLog.i(TAG, 'updateFavorite end.'); + }, + + /** + * Querying the Mobile Numbers of All Favorites + * + * @param {string} DAHelper + * @param {Object} callBack + */ + getAllFavorite: async function (actionData, callBack, context?) { + HiLog.i(TAG, 'getAllFavorite start.'); + if (context) { + ContactRepository.getInstance().init(context); + ContactRepository.getInstance().findAllFavorite(actionData, favoriteList => { + if (ArrayUtil.isEmpty(favoriteList)) { + let emptyResult: FavoriteBean[] = []; + callBack(emptyResult); + return; + } + let resultList = []; + for (let contactItem of favoriteList) { + let jsonObj: FavoriteBean = new FavoriteBean('', -1, '', '', '', '', '', true, '', false, 0, ''); + jsonObj.contactId = contactItem.id.toString(); + jsonObj.isCommonUseType = 0; + jsonObj.displayName = contactItem.displayName; + jsonObj.namePrefix = contactItem.sortFirstLetter; + jsonObj.nameSuffix = contactItem.photoFirstName; + jsonObj.position = contactItem.position; + jsonObj.portraitColor = MorandiColor.Color[Math.abs(parseInt(jsonObj.contactId)) % 6]; + jsonObj.show = contactItem.show; + jsonObj.isUsuallyShow = false; + jsonObj.favorite = 1; + jsonObj.setShowName(); + resultList.push(jsonObj); + } + HiLog.i(TAG, 'getAllFavorite data success!'); + callBack(resultList); + }); + } + HiLog.i(TAG, 'getAllFavorite end.'); + }, + + /** + * Query call records + * + * @param {string} DAHelper + * @param {Object} callBack call records + */ + getAllUsually: async function (actionData, callBack, context?) { + HiLog.i(TAG, 'getAllUsually start.'); + if (context) { + ContactRepository.getInstance().init(context); + ContactRepository.getInstance().findAllUsually(actionData, callLog => { + if (ArrayUtil.isEmpty(callLog)) { + let emptyResult: CallLog[] = []; + callBack(emptyResult); + return; + } + let resultList = []; + resultList = callLog; + HiLog.i(TAG, 'getAllUsually data success! '); + callBack(resultList); + }); + } + HiLog.i(TAG, 'getAllUsually end.'); + }, + + /** + * DisplayName Query Favorite + * + * @param {string} DAHelper + * @param {Object} addParams Contact Information + * @param {Object} callBack Contact Information + */ + getDisplayNamesFindUsually: async function (displayName, usuallyPhone, callBack, context?){ + HiLog.i(TAG, 'getDisplayNamesFindUsually start.'); + if (context) { + ContactRepository.getInstance().init(context); + ContactRepository.getInstance().getDisplayNameByFavorite(displayName, usuallyPhone, favoriteList => { + if (ArrayUtil.isEmpty(favoriteList)) { + let emptyResult: FavoriteBean[] = []; + callBack(emptyResult); + return; + } + let resultList = []; + for (let i = 0; i < favoriteList.length; i++) { + let jsonObj: FavoriteBean = new FavoriteBean('', -1, '', '', '', '', '', false, '', false, 0, ''); + jsonObj.contactId = favoriteList[i].id.toString(); + jsonObj.isCommonUseType = 1; + jsonObj.displayName = favoriteList[i].displayName; + jsonObj.namePrefix = favoriteList[i].sortFirstLetter; + jsonObj.nameSuffix = favoriteList[i].photoFirstName; + jsonObj.position = favoriteList[i].position; + jsonObj.portraitColor = MorandiColor.Color[Math.abs(parseInt(jsonObj.contactId)) % 6]; + jsonObj.show = favoriteList[i].show; + jsonObj.isUsuallyShow = false; + jsonObj.favorite = 0; + jsonObj.phoneNum = favoriteList[i].detailInfo; + jsonObj.setShowName(); + resultList.push(jsonObj); + } + HiLog.i(TAG, 'getDisplayNamesFindUsually sc.'); + callBack(resultList); + }) + } + HiLog.i(TAG, 'getDisplayNamesFindUsually end! '); + }, + + /** + * Move Favorite Data Sorting + * + * @param {string} DAHelper + * @param {Object} addParams Contact Information + * @param {Object} callBack favoriteOrder + */ + moveSortFavorite: async function (DAHelper, addParams, callBack, context?) { + HiLog.i(TAG, 'moveSortFavorite start.'); + if (DAHelper == undefined || DAHelper == null || DAHelper.length == 0) { + DAHelper = await dataShare.createDataShareHelper(context ? context : globalThis.context, Contacts.CONTENT_URI); + } + let condition = new dataSharePredicates.DataSharePredicates(); + condition.equalTo('contact_id', addParams.id); + const va = { + 'favorite_order': addParams.favoriteOrder > 9 ? addParams.favoriteOrder : '0' + addParams.favoriteOrder + } + DAHelper.update( + RawContacts.CONTENT_URI, + condition, + va + ).then(data => { + if (data == -1) { + HiLog.e(TAG, 'moveSortFavorite data failed!'); + callBack('') + return; + } + HiLog.i(TAG, 'moveSortFavorite data success!'); + callBack(addParams.favoriteOrder); + }).catch(err => { + HiLog.e(TAG, 'moveSortFavorite failed. Cause: ' + JSON.stringify(err.message)); + }); + HiLog.i(TAG, 'End to update moveSortFavorite.'); + }, + + /** + * Querying the Mobile Numbers of Search Contacts + * + * @param {string} DAHelper + * @param {Object} callBack + */ + getSearchContact: async function (actionData, callBack, context?) { + HiLog.i(TAG, 'getSearchContact start.'); + if (context) { + ContactRepository.getInstance().init(context); + } + ContactRepository.getInstance().searchContact(actionData, contactList => { + if (ArrayUtil.isEmpty(contactList)) { + HiLog.i(TAG, 'getSearchContact resultSet is empty!'); + let emptyResult: SearchContactsBean[] = []; + callBack(emptyResult); + return; + } + let resultList = []; + for (let contactItem of contactList) { + let jsonObj: SearchContactsBean = new SearchContactsBean('', '', '', '', '', '', '', '', '', 0,'', '', '', '', '', ''); + jsonObj.id = contactItem.id.toString(); + jsonObj.accountId = contactItem.accountId; + jsonObj.contactId = contactItem.contactId; + jsonObj.rawContactId = contactItem.rawContactId; + jsonObj.searchName = contactItem.searchName; + jsonObj.displayName = contactItem.displayName; + jsonObj.phoneticName = contactItem.phoneticName; + jsonObj.photoId = contactItem.photoId; + jsonObj.photoFileId = contactItem.photoFileId; + jsonObj.isDeleted = contactItem.isDeleted; + jsonObj.position = contactItem.position; + jsonObj.sortFirstLetter = contactItem.sortFirstLetter; + jsonObj.photoFirstName = contactItem.photoFirstName; + jsonObj.portraitColor = MorandiColor.Color[Math.abs(parseInt(jsonObj.contactId)) % 6]; + jsonObj.detailInfo = contactItem.detailInfo; + jsonObj.hasPhoneNumber = contactItem.hasPhoneNumber; + resultList.push(jsonObj); + } + callBack(resultList); + HiLog.i(TAG, 'getSearchContact end.'); + }); + }, + + getQueryT9PhoneNumbers: function (callBack, addParams, context?) { + if (context) { + ContactRepository.getInstance().init(context); + } + ContactRepository.getInstance().queryT9PhoneIsNotNull(addParams.favorite, contactList => { + if (ArrayUtil.isEmpty(contactList)) { + HiLog.i(TAG, 'getQueryT9PhoneNumbers queryContact resultSet is empty!'); + let emptyResult: ContactVo[] = []; + callBack(emptyResult); + return; + } + let resultList = []; + for (let contactItem of contactList) { + let jsonObj: ContactVo = new ContactVo("", "", "", "", "", "", true, "", ""); + jsonObj.contactId = contactItem.id.toString(); + jsonObj.emptyNameData = contactItem.displayName; + jsonObj.namePrefix = contactItem.sortFirstLetter; + jsonObj.nameSuffix = contactItem.photoFirstName; + jsonObj.company = contactItem.company; + jsonObj.position = contactItem.position; + jsonObj.portraitColor = MorandiColor.Color[Math.abs(parseInt(jsonObj.contactId)) % 6]; + jsonObj.show = false; + jsonObj.phoneNumbers = contactItem.phoneNumbers; + jsonObj.setShowName(); + resultList.push(jsonObj); + } + callBack(resultList); + HiLog.i(TAG, 'End of querying all contacts'); + }); + }, } \ No newline at end of file diff --git a/entry/src/main/ets/model/bean/ContactInfo.ets b/entry/src/main/ets/model/bean/ContactInfo.ets index 63a75ac..852812e 100644 --- a/entry/src/main/ets/model/bean/ContactInfo.ets +++ b/entry/src/main/ets/model/bean/ContactInfo.ets @@ -37,11 +37,12 @@ export class ContactInfo { relationships: AssociatedPersonBean[]; events: EventBean[]; groups: GroupBean[]; + favorite: number; constructor(id: string, display_name: string, nickname: string, phones: PhoneNumBean[], emails: EmailBean[], position: string, company: string, remarks: string, aims: AIMBean[], houses: HouseBean[], websites: string[], - relationships: AssociatedPersonBean[], events: EventBean[], groups: GroupBean[]) { + relationships: AssociatedPersonBean[], events: EventBean[], groups: GroupBean[], favorite: number) { this.id = id; this.display_name = display_name; this.nickname = nickname; @@ -56,6 +57,7 @@ export class ContactInfo { this.relationships = relationships; this.events = events; this.groups = groups; + this.favorite = favorite; } setID(id: string) { diff --git a/entry/src/main/ets/model/bean/FavoriteBean.ets b/entry/src/main/ets/model/bean/FavoriteBean.ets new file mode 100644 index 0000000..fdf8a0b --- /dev/null +++ b/entry/src/main/ets/model/bean/FavoriteBean.ets @@ -0,0 +1,82 @@ +/** + * 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 { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil'; + +/** + * Favorite List Data Structure Entity + */ +export class FavoriteBean { + contactId: string; + /** + * 0 Favorite 1 Usually + */ + isCommonUseType: number; + /** + * Contact Name + */ + displayName: string; + phoneNum: string; + nameSuffix: string; + namePrefix: string; + portraitColor: string; + /** + * Display Usually + */ + isUsuallyShow: boolean; + portraitPath: string; + isEditSelect: boolean + favorite: number; + company: string; + position: string; + show: boolean; + showName: string; + phoneNumbers: object[]; + title: string; + subTitle: string; + favoriteOrder: string; + + constructor( + contactId: string, + isCommonUseType: number, + displayName: string, + phoneNum: string, + nameSuffix: string, + namePrefix: string, + portraitColor: string, + isUsuallyShow: boolean, + portraitPath: string, + isEditSelect: boolean, + favorite: number, + favoriteOrder: string, + ) { + this.contactId = contactId; + this.isCommonUseType = isCommonUseType; + this.displayName = displayName; + this.phoneNum = phoneNum; + this.nameSuffix = nameSuffix; + this.namePrefix = namePrefix; + this.portraitColor = portraitColor; + this.isUsuallyShow = isUsuallyShow; + this.portraitPath = portraitPath; + this.isEditSelect = isEditSelect; + this.favorite = favorite; + this.favoriteOrder = favoriteOrder; + } + + public setShowName() { + this.showName = !StringUtil.isEmpty(this.displayName) ? this.displayName : (!StringUtil.isEmpty(this.company) ? this.company : (!StringUtil.isEmpty(this.position) ? this.position : "")) + } +} diff --git a/entry/src/main/ets/model/bean/FavoriteDataSource.ts b/entry/src/main/ets/model/bean/FavoriteDataSource.ts new file mode 100644 index 0000000..36e6475 --- /dev/null +++ b/entry/src/main/ets/model/bean/FavoriteDataSource.ts @@ -0,0 +1,58 @@ +/** + * 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 BasicDataSource from './BasicDataSource'; +import type { FavoriteBean } from '../bean/FavoriteBean'; +import { FavoriteListBean } from '../bean/FavoriteListBean'; +import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; +import { ArrayUtil } from '../../../../../../common/src/main/ets/util/ArrayUtil'; + +const TAG = 'FavoriteDataSource '; + +export default class FavoriteDataSource extends BasicDataSource { + private favoriteList: FavoriteBean[] = []; + + public totalCount(): number { + return this.favoriteList.length; + } + + public getFavoriteList(): FavoriteBean[] { + return this.favoriteList; + } + + public getData(index: number): FavoriteListBean { + if (ArrayUtil.isEmpty(this.favoriteList) || index >= this.favoriteList.length) { + HiLog.i(TAG, 'getData contactlist is empty'); + return null; + } else { + let favorite: FavoriteBean = this.favoriteList[index]; + let preContact: FavoriteBean = this.favoriteList[index - 1]; + let addContact: FavoriteBean = this.favoriteList[index + 1]; + let showIndex: boolean = (index === 0 || !(favorite.namePrefix === preContact.namePrefix)); + let showDivider: boolean = false; + if (index < this.favoriteList.length - 1) { + showDivider = !addContact.isUsuallyShow; + } else { + showDivider = false; + } + return new FavoriteListBean(index, showIndex, showDivider, favorite, null); + } + } + + public refresh(favoriteList: FavoriteBean[]): void { + HiLog.i(TAG, 'refresh!'); + this.favoriteList = favoriteList; + this.notifyDataReload(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/model/bean/FavoriteListBean.ets b/entry/src/main/ets/model/bean/FavoriteListBean.ets new file mode 100644 index 0000000..8955697 --- /dev/null +++ b/entry/src/main/ets/model/bean/FavoriteListBean.ets @@ -0,0 +1,33 @@ +/** + * 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 { FavoriteBean } from './FavoriteBean'; +import { SearchContactsBean } from './SearchContactsBean'; + + +export class FavoriteListBean { + index: number; + showIndex: boolean; + showDivider: boolean; + favorite: FavoriteBean; + contact: SearchContactsBean; + constructor(index: number, showIndex: boolean, showDivider: boolean, favorite: FavoriteBean, contact: SearchContactsBean) { + this.index = index; + this.showIndex = showIndex; + this.showDivider = showDivider; + this.favorite = favorite; + this.contact = contact; + } +} diff --git a/entry/src/main/ets/model/bean/SearchContactsBean.ets b/entry/src/main/ets/model/bean/SearchContactsBean.ets new file mode 100644 index 0000000..509dceb --- /dev/null +++ b/entry/src/main/ets/model/bean/SearchContactsBean.ets @@ -0,0 +1,74 @@ +/** + * 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. + */ + + +/** + * SearchContactsBean List Data Structure Entity + */ +export class SearchContactsBean { + id: string; + accountId: string; + contactId: string; + rawContactId: string; + searchName: string; + displayName: string; + phoneticName: string; + photoId: string; + photoFileId: string; + isDeleted: number; + position: string; + photoFirstName: string; + sortFirstLetter: string; + portraitColor: string; + portraitPath: string; + detailInfo: string; + hasPhoneNumber: string; + + constructor( + id: string, + accountId: string, + contactId: string, + rawContactId: string, + searchName: string, + displayName: string, + phoneticName: string, + photoId: string, + photoFileId: string, + isDeleted: number, + position: string, + photoFirstName: string, + sortFirstLetter: string, + portraitColor: string, + detailInfo: string, + hasPhoneNumber: string, + ) { + this.id = id; + this.accountId = accountId; + this.contactId = contactId; + this.rawContactId = rawContactId; + this.searchName = searchName; + this.displayName = displayName; + this.phoneticName = phoneticName; + this.photoId = photoId; + this.photoFileId = photoFileId; + this.isDeleted = isDeleted; + this.position = position; + this.photoFirstName = photoFirstName; + this.sortFirstLetter = sortFirstLetter; + this.portraitColor = portraitColor; + this.detailInfo = detailInfo; + this.detailInfo = hasPhoneNumber; + } +} diff --git a/entry/src/main/ets/model/bean/SearchContactsSource.ts b/entry/src/main/ets/model/bean/SearchContactsSource.ts new file mode 100644 index 0000000..24cf870 --- /dev/null +++ b/entry/src/main/ets/model/bean/SearchContactsSource.ts @@ -0,0 +1,56 @@ +/** + * 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 { SearchContactsBean } from '../bean/SearchContactsBean'; +import { FavoriteListBean } from '../bean/FavoriteListBean'; +import BasicDataSource from './BasicDataSource'; +import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; +import { ArrayUtil } from '../../../../../../common/src/main/ets/util/ArrayUtil'; + +const TAG = 'SearchContactsSource '; + +export default class SearchContactsSource extends BasicDataSource { + private contactList: SearchContactsBean[] = []; + public contactObj: { [key: string]: SearchContactsBean[] } = {}; + public contactIndexObj: { [key: string]: number } = {}; + + public totalCount(): number { + return this.contactList.length; + } + + public getData(index: number): FavoriteListBean { + if (ArrayUtil.isEmpty(this.contactList) || index >= this.contactList.length) { + HiLog.i(TAG, 'getData contactlist is empty'); + return null; + } else { + let contact: SearchContactsBean = this.contactList[index]; + let preContact: SearchContactsBean = this.contactList[index - 1]; + let showIndex: boolean = (index === 0 || !(contact.sortFirstLetter === preContact.sortFirstLetter)); + let showDivider: boolean = false; + if (index < this.contactList.length - 1) { + let nextContact: SearchContactsBean = this.contactList[index + 1]; + showDivider = (contact.sortFirstLetter === nextContact.sortFirstLetter); + } else { + showDivider = false; + } + return new FavoriteListBean(index, showIndex, showDivider, null, contact); + } + } + + public refresh(contactList: SearchContactsBean[]): void { + HiLog.i(TAG, ' refresh!'); + this.contactList = contactList; + this.notifyDataReload(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/model/calllog/CalllogModel.ets b/entry/src/main/ets/model/calllog/CalllogModel.ets index f3639f6..0be427f 100644 --- a/entry/src/main/ets/model/calllog/CalllogModel.ets +++ b/entry/src/main/ets/model/calllog/CalllogModel.ets @@ -29,12 +29,12 @@ export default { * @param {string} mergeRule Call Record Type * @param {Object} callBack Call log data */ - getAllCalls: async function (actionData, mergeRule, callBack, context?) { + getAllCalls: async function (param, actionData, mergeRule, callBack, context?) { if (context) { CallLogRepository.getInstance().init(context); } HiLog.i(TAG, 'getAllCalls in:' + JSON.stringify(actionData)); - CallLogRepository.getInstance().findAll(actionData, result => { + CallLogRepository.getInstance().findAll(param.favorite, actionData, result => { let resultData = { callLogList: [], missedList: [] }; @@ -106,5 +106,30 @@ export default { missed.displayName = ""; } } + }, + + getCallHistorySearch: async function (actionData, mergeRule, callBack, context?) { + if (context) { + CallLogRepository.getInstance().init(context); + } + CallLogRepository.getInstance().findSearch(actionData, result => { + HiLog.i(TAG, 'getCallHistorySearch resultSet.rowCount :' + JSON.stringify(result.rowCount)); + let resultData = { + callLogList: [], missedList: [] + }; + if (ArrayUtil.isEmpty(result)) { + HiLog.i(TAG, 'getCallHistorySearch logMessage callLog resultSet is empty!'); + callBack(resultData); + return; + } + CallLogService.getInstance().init(context); + CallLogService.getInstance().setMergeRule(mergeRule) + resultData.callLogList = CallLogService.getInstance().mergeCallLogs(result); + resultData.missedList = CallLogService.getInstance().mergeMissedCalls(result); + let numberList = this.getNumberList(resultData); + this.queryContactsName(numberList, resultData, resultData => { + callBack(resultData); + }, context) + }); } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/contacts/ContactList.ets b/entry/src/main/ets/pages/contacts/ContactList.ets index ae93c3c..e26d64b 100644 --- a/entry/src/main/ets/pages/contacts/ContactList.ets +++ b/entry/src/main/ets/pages/contacts/ContactList.ets @@ -19,8 +19,12 @@ import { HiLog, ArrayUtil } from '../../../../../../common'; import emitter from '@ohos.events.emitter'; import { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil'; import Constants from '../../../../../../common/src/main/ets/Constants'; +import { ContactSearch } from './search/ContactSearch'; +import { AlphabetIndexerPage } from './alphabetindex/AlphabetIndexerPage'; +import AlphabetIndexerPresenter from '../../presenter/contact/alphabetindex/AlphabetIndexerPresenter'; const TAG = 'ContactList '; +const EMITTER_SEARCH_ID: number = 105; let storage = LocalStorage.GetShared(); /** @@ -49,6 +53,13 @@ export default struct ContactListPage { this.refresh() }) this.refresh(); + let innerEventSearch = { + eventId: EMITTER_SEARCH_ID, + priority: emitter.EventPriority.HIGH + }; + emitter.on(innerEventSearch, (data) => { + this.mContactPresenter.isSearchPage = data.data['isSearchPage']; + }) } aboutToDisappear() { @@ -103,6 +114,12 @@ struct ContactContent { @Link private presenter: ContactListPresenter; @Link private contactListListLen: number; @LocalStorageProp('breakpoint') curBp: string = 'sm'; + private scroller: Scroller = new Scroller(); + @State alphabetSelected: number = 0; + @State isAlphabetClicked: boolean = false; + @State dragList: boolean = true; + @State alphabetIndexPresenter: AlphabetIndexerPresenter = this.presenter.alphabetIndexPresenter; + @State type: number = 0; @Builder GroupsView(imageRes: Resource, title: string | Resource, showArrow: boolean) { @@ -135,88 +152,154 @@ struct ContactContent { } build() { - Column() { - GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: {x: 12, y: 0}}) { - GridCol({span: {sm: 4, md:6, lg: 8}, offset: {sm: 0, md: 1, lg: 2}}) { - TitleGuide() - } - GridCol({span: {sm: 4, md:6, lg: 8}, offset: {sm: 0, md: 2, lg: 4}}) { - Column() { - Text($r("app.string.contact")) - .fontSize(30) - .fontWeight(FontWeight.Bold) - .fontColor($r("sys.color.ohos_id_color_text_primary")) - .margin({bottom: $r("app.float.id_card_margin_sm")} ) - .lineHeight(42) - .margin({top:8, bottom: 2}) - - Text($r("app.string.contact_num", this.contactListListLen)) - .fontSize($r("sys.float.ohos_id_text_size_body2")) - .fontWeight(FontWeight.Regular) - .fontColor($r("sys.color.ohos_id_color_text_tertiary")) - .lineHeight(19) - } - .alignItems(HorizontalAlign.Start) - .width('100%') - .height(82) - } - } - - GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: {x: 12, y: 0}}) { - GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { - List({ space: 0, initialIndex: 0 }) { - LazyForEach(this.presenter.contactListDataSource, (item, index: number) => { - ListItem() { - Stack({ alignContent: Alignment.BottomEnd }) { - Column() { - if (item.showIndex && !StringUtil.isEmpty(item.contact.namePrefix)) { - Row() { - Text(item.contact.namePrefix) - .fontColor($r("sys.color.ohos_fa_text_secondary")) - .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) - .fontWeight(FontWeight.Medium) - .textAlign(TextAlign.Start) - } - .alignItems(VerticalAlign.Bottom) - .direction(Direction.Ltr) - .padding({ left: this.curBp === 'lg' ? $r("app.float.id_card_margin_max") : 0, - bottom: $r("app.float.id_card_margin_large")}) - .height($r("app.float.id_item_height_mid")) - } - - ContactListItemView({ - item: item.contact, - index: index, - showIndex: item.showIndex, - showDivifer: item.showDivifer - }) - } - .alignItems(HorizontalAlign.Start) - - if (item.showDivifer) { - Divider() - .color($r("sys.color.ohos_id_color_list_separator")) - .margin({right: this.curBp === 'lg' ? 24 : 0, - left: this.curBp === 'lg' ? 76 : 52 - }) - } - } + Stack({ alignContent: Alignment.TopStart }) { + Stack({ alignContent: Alignment.TopEnd }) { + Column() { + Stack({ alignContent: Alignment.Top }) { + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + TitleGuide() } - }, (item) => JSON.stringify(item)) + + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 2, lg: 4 } }) { + Column() { + Text($r("app.string.contact")) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor($r("sys.color.ohos_id_color_text_primary")) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .lineHeight(42) + .margin({ top: 8, bottom: 2 }) + + Text($r("app.string.contact_num", this.contactListListLen)) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontWeight(FontWeight.Regular) + .fontColor($r("sys.color.ohos_id_color_text_tertiary")) + .lineHeight(19) + + Stack({ alignContent: Alignment.Bottom }) { + TextInput({ placeholder: $r('app.string.contact_list_search') }) + .placeholderColor(Color.Grey) + .placeholderFont({ + size: $r('sys.float.ohos_id_text_size_headline9'), + weight: FontWeight.Normal, + style: FontStyle.Normal + }) + .type(InputType.Normal) + .caretColor($r('sys.color.ohos_id_color_text_primary_activated')) + .enterKeyType(EnterKeyType.Search) + .padding({ left: $r('app.float.id_card_margin_xxxxl') }) + .height($r("app.float.id_item_height_mid")) + .enabled(false) + .border({ + color: $r("sys.color.ohos_id_color_fourth"), + radius: $r('app.float.id_card_margin_max') + }) + + Column(){ + Image($r('app.media.ic_public_search')) + .width($r('app.float.id_card_margin_xxxl')) + .height($r('app.float.id_card_margin_xxxl')) + .objectFit(ImageFit.Contain) + .margin({ left: $r('app.float.id_card_margin_large') }) + } + .width('100%') + .margin({ bottom: $r('app.float.id_corner_radius_card_mid') }) + .alignItems(HorizontalAlign.Start) + } + .onClick(() => { + this.presenter.isSearchPage = true; + this.presenter.sendEmitter(this.presenter.isSearchPage); + this.presenter.inputKeyword = ''; + }) + .margin({ top: $r('app.float.dialer_common_very_small_margin2') }) + .width('100%') + } + .justifyContent(FlexAlign.Start) + .alignItems(HorizontalAlign.Start) + .width('100%') + } + } + } + .visibility(this.presenter.isSearchPage ? Visibility.None : Visibility.Visible) + .padding({ bottom: $r('app.float.id_card_margin_large') }) + // .height(180) + + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + List({ space: 0, initialIndex: 0, scroller: this.scroller }) { + LazyForEach(this.presenter.contactListDataSource, (item, index: number) => { + ListItem() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + if (item.showIndex && !StringUtil.isEmpty(item.contact.namePrefix)) { + Row() { + Text(item.contact.namePrefix) + .fontColor($r("sys.color.ohos_fa_text_secondary")) + .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + } + .alignItems(VerticalAlign.Bottom) + .direction(Direction.Ltr) + .padding({ left: this.curBp === 'lg' ? $r("app.float.id_card_margin_max") : 0, + bottom: $r("app.float.id_card_margin_large") }) + .height($r("app.float.id_item_height_mid")) + } + + ContactListItemView({ + item: item.contact, + index: index, + showIndex: item.showIndex, + showDivifer: item.showDivifer + }) + } + .alignItems(HorizontalAlign.Start) + + if (item.showDivifer) { + Divider() + .color($r("sys.color.ohos_id_color_list_separator")) + .margin({ right: this.curBp === 'lg' ? 24 : 0, + left: this.curBp === 'lg' ? 76 : 52 + }) + } + } + } + }, (item) => JSON.stringify(item)) + } + .width('100%') + .height('100%') + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.None) + .scrollBar(BarState.Off) + .onScrollIndex((firstIndex: number, lastIndex: number) => { + if (!this.isAlphabetClicked) { + this.alphabetSelected = this.alphabetIndexPresenter.getAlphabetSelected(firstIndex); + } + }) + .onScrollStart(() => { + this.dragList = true; + }) + .onScrollStop(() => { + this.isAlphabetClicked = false; + }) + } } - .width('100%') .height('100%') - .listDirection(Axis.Vertical) - .edgeEffect(EdgeEffect.None) - .scrollBar(BarState.Off) + .flexShrink(1) } + .padding({ left: 24, right: 24 }) + .height("100%") + .width("100%") + + ContactSearch({ presenter: $presenter, type: $type }) + .visibility(this.presenter.isSearchPage ? Visibility.Visible : Visibility.None) + + AlphabetIndexerPage({scroller: this.scroller, presenter: $alphabetIndexPresenter, selected: this.alphabetSelected, + isClicked: $isAlphabetClicked, drag: $dragList}) + .margin({top: '30%', bottom: '10%'}) } - .height('100%') - .flexShrink(1) } - .padding({left:24, right:24}) - .height("100%") - .width("100%") } } diff --git a/entry/src/main/ets/pages/contacts/accountants/Accountants.ets b/entry/src/main/ets/pages/contacts/accountants/Accountants.ets index 135a2d9..259e201 100644 --- a/entry/src/main/ets/pages/contacts/accountants/Accountants.ets +++ b/entry/src/main/ets/pages/contacts/accountants/Accountants.ets @@ -76,7 +76,17 @@ struct Accountants { //Components shared by the TIP for creating or updating a contact by mistake and the TIP for deleting a contact builder: DeleteDialogEx({ cancel: () => { - router.back(); + if (0 === this.mPresenter.editContact){ + router.replaceUrl({ + url: 'pages/contacts/details/ContactDetail', + params: { + sourceHasPhone: true, + phoneNumberShow: this.mPresenter.phoneNumberShow + } + }) + } else { + router.back(); + } }, confirm: () => { this.mPresenter.saveContact(); @@ -132,6 +142,10 @@ struct Accountants { let obj: any = router.getParams(); this.mPresenter.contactId = obj.contactId; this.mPresenter.updateShow = true; + this.mPresenter.phones = obj.phones; + this.mPresenter.editContact = obj.editContact; + this.mPresenter.phoneNumberShow = obj.phoneNumberShow; + this.mPresenter.callId = obj.callId; this.mPresenter.updatesInit(); } } diff --git a/entry/src/main/ets/pages/contacts/alphabetindex/AlphabetIndexerPage.ets b/entry/src/main/ets/pages/contacts/alphabetindex/AlphabetIndexerPage.ets new file mode 100644 index 0000000..1e53f9b --- /dev/null +++ b/entry/src/main/ets/pages/contacts/alphabetindex/AlphabetIndexerPage.ets @@ -0,0 +1,61 @@ +/** + * 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 AlphabetIndexerPresenter from '../../../presenter/contact/alphabetindex/AlphabetIndexerPresenter'; + +@Component +export struct AlphabetIndexerPage { + scroller: Scroller; + @Link presenter: AlphabetIndexerPresenter; + private selectedAlphabetIndex: number = 0; + private popDataSource: string[] = []; + @Prop selected: number; + @Link isClicked: boolean; + @Link drag: boolean; + + build() { + Column() { + AlphabetIndexer({ arrayValue: this.presenter.alphabetIndexList, selected: this.selected }) + .selectedColor(0x1358e4) + .popupColor(0x0254f6) + .selectedBackgroundColor(0xe7eefe) + .popupBackground(0xf3f3f3) + .usingPopup(this.drag ? false : true) + .selectedFont({ size: 16 }) + .popupFont({ size: 24 }) + .itemSize(20) + .alignStyle(IndexerAlign.Right) + .onSelect((index: number) => { + this.drag = false; + this.isClicked = true; + this.selectedAlphabetIndex = index; + let scrollIndex = this.presenter.getListScrollIndex(this.selectedAlphabetIndex); + this.scroller.scrollToIndex(scrollIndex); + // Set the click status of the index bar, otherwise there will be issues with the list scroll + setTimeout(() => { + this.isClicked = false; + }, 200); + }) + .onRequestPopupData((index: number) => { + this.popDataSource = this.presenter.getAlphabetPopData(index).slice(); + return this.popDataSource; + }) + .onPopupSelect((index: number) => { + let scrollIndex = this.presenter.getListScrollIndex(this.selectedAlphabetIndex, this.popDataSource, index); + this.scroller.scrollToIndex(scrollIndex); + }) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/contacts/batchselectcontacts/BatchSelectContactsPage.ets b/entry/src/main/ets/pages/contacts/batchselectcontacts/BatchSelectContactsPage.ets index f7aee0e..1fbfe25 100644 --- a/entry/src/main/ets/pages/contacts/batchselectcontacts/BatchSelectContactsPage.ets +++ b/entry/src/main/ets/pages/contacts/batchselectcontacts/BatchSelectContactsPage.ets @@ -19,6 +19,9 @@ import { ArrayUtil } from '../../../../../../../common/src/main/ets/util/ArrayUt import BatchSelectRecentItemView from '../../../component/contact/batchselectcontacts/BatchSelectRecentItemView'; import BatchSelectContactItemView from '../../../component/contact/batchselectcontacts/BatchSelectContactItemView'; import BatchTabGuide from '../../../component/contact/batchselectcontacts/BatchTabGuide'; +import router from '@ohos.router'; +import AlphabetIndexerPresenter from '../../../presenter/contact/alphabetindex/AlphabetIndexerPresenter'; +import { AlphabetIndexerPage } from '../alphabetindex/AlphabetIndexerPage'; const TAG = 'BatchSelectContactsPage '; @@ -35,6 +38,8 @@ export default struct BatchSelectContactsPage { aboutToAppear() { HiLog.i(TAG, 'aboutToAppear') + let obj: any = router.getParams(); + this.mPresenter.addFavorite = obj?.addFavorite; this.mPresenter.aboutToAppear() } @@ -55,7 +60,7 @@ export default struct BatchSelectContactsPage { onBackPress() { HiLog.i(TAG, 'onBackPress') - this.mPresenter.cancel(); + this.mPresenter.backCancel(); return true; } @@ -74,7 +79,7 @@ export default struct BatchSelectContactsPage { .flexShrink(0) .margin({ left: $r("app.float.id_card_margin_max"), right: $r("app.float.id_card_margin_xxl") }) .onClick(() => { - this.mPresenter.cancel() + this.mPresenter.backCancel(); }) Text(this.mPresenter.selectCount == 0 ? $r('app.string.no_select') : $r('app.string.select_num', this.mPresenter.selectCount)) @@ -93,7 +98,8 @@ export default struct BatchSelectContactsPage { .enabled(!this.mPresenter.selectDisabled) .fillColor(this.mPresenter.selectDisabled ? $r("sys.color.ohos_id_color_tertiary") : $r("sys.color.ohos_id_color_primary")) .onClick(() => { - this.mPresenter.comfirm() + this.mPresenter.selectBatchContact(); + this.mPresenter.contactsList = []; }) if (this.curBp === 'lg') { @@ -221,6 +227,7 @@ struct RecentList { .listDirection(Axis.Vertical) .edgeEffect(EdgeEffect.Spring) } + .height('100%') .width('100%') .backgroundColor(Color.White) .padding({ top: $r("app.float.id_card_margin_mid"), bottom: $r("app.float.id_card_margin_mid") }) @@ -231,54 +238,74 @@ struct RecentList { @Component struct ContactsList { @Link presenter: BatchSelectContactsPresenter; + private scroller: Scroller = new Scroller(); + @State alphabetSelected: number = 0; + @State isAlphabetClicked: boolean = false; + @State dragList: boolean = true; + @State alphabetIndexPresenter: AlphabetIndexerPresenter = this.presenter.alphabetIndexPresenter; build() { Column() { - List({ initialIndex: this.presenter.initialIndex }) { - LazyForEach(this.presenter.contactsSource, (item, index) => { + Stack({ alignContent: Alignment.TopEnd }) { + List({ initialIndex: this.presenter.initialIndex, scroller: this.scroller }) { + LazyForEach(this.presenter.contactsSource, (item, index) => { - ListItem() { - Stack({ alignContent: Alignment.BottomEnd }) { - Column() { - if (item.showIndex) { - Flex({ direction: FlexDirection.Column, - justifyContent: FlexAlign.End, - alignItems: ItemAlign.Start }) { - Text(item.contact.namePrefix) - .fontColor($r("sys.color.ohos_fa_text_secondary")) - .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) - .fontWeight(FontWeight.Medium) - .textAlign(TextAlign.Start) + ListItem() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + if (item.showIndex) { + Flex({ direction: FlexDirection.Column, + justifyContent: FlexAlign.End, + alignItems: ItemAlign.Start }) { + Text(item.contact.namePrefix) + .fontColor($r("sys.color.ohos_fa_text_secondary")) + .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + } + .padding({ left: $r("app.float.id_card_margin_max"), bottom: $r("app.float.id_card_margin_large") }) + .width('100%') + .height($r("app.float.id_item_height_mid")) } - .padding({ left: $r("app.float.id_card_margin_max"), bottom: $r("app.float.id_card_margin_large") }) - .width('100%') - .height($r("app.float.id_item_height_mid")) + + BatchSelectContactItemView({ + item: item.contact, + index: item.index, + onContactItemClicked: (index, indexChild) => this.presenter.onContactItemClicked(index, indexChild), + showIndex: item.showIndex, + showDivifer: item.showDivifer + }) } - BatchSelectContactItemView({ - item: item.contact, - index: item.index, - onContactItemClicked: (index, indexChild) => this.presenter.onContactItemClicked(index, indexChild), - showIndex: item.showIndex, - showDivifer: item.showDivifer - }) - } - - if (item.showDivifer) { - Divider() - .color($r("sys.color.ohos_id_color_list_separator")) - .margin({ left: 76, right: $r("app.float.id_card_margin_max") }) + if (item.showDivifer) { + Divider() + .color($r("sys.color.ohos_id_color_list_separator")) + .margin({ left: 76, right: $r("app.float.id_card_margin_max") }) + } } } + }, (item) => JSON.stringify(item)) + } + .width('100%') + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.Spring) + .scrollBar(BarState.Off) + .onScrollIndex((firstIndex: number, lastIndex: number) => { + this.presenter.resetInitialIndex(firstIndex); + if (!this.isAlphabetClicked) { + this.alphabetSelected = this.alphabetIndexPresenter.getAlphabetSelected(firstIndex); } - }, (item) => JSON.stringify(item)) + }) + .onScrollStart(() => { + this.dragList = true; + }) + .onScrollStop(() => { + this.isAlphabetClicked = false; + }) + AlphabetIndexerPage({scroller: this.scroller, presenter: $alphabetIndexPresenter, selected: this.alphabetSelected, + isClicked: $isAlphabetClicked, drag: $dragList}) + .margin({ top: '10%', bottom: '10%' }) } - .width('100%') - .listDirection(Axis.Vertical) - .edgeEffect(EdgeEffect.Spring) - .onScrollIndex((firstIndex: number, lastIndex: number) => { - this.presenter.resetInitialIndex(firstIndex); - }) } .width('100%') .padding({ top: $r("app.float.id_card_margin_mid"), bottom: $r("app.float.id_card_margin_mid") }) diff --git a/entry/src/main/ets/pages/contacts/batchselectcontacts/SingleSelectContactPage.ets b/entry/src/main/ets/pages/contacts/batchselectcontacts/SingleSelectContactPage.ets index 8ddd409..7f3dbf9 100644 --- a/entry/src/main/ets/pages/contacts/batchselectcontacts/SingleSelectContactPage.ets +++ b/entry/src/main/ets/pages/contacts/batchselectcontacts/SingleSelectContactPage.ets @@ -13,10 +13,13 @@ * limitations under the License. */ +import router from '@ohos.router'; import BatchSelectContactsPresenter from '../../../presenter/contact/batchselectcontacts/BatchSelectContactsPresenter'; import { HiLog } from '../../../../../../../common/src/main/ets/util/HiLog'; import { ArrayUtil } from '../../../../../../../common/src/main/ets/util/ArrayUtil'; import BatchSelectContactItemView from '../../../component/contact/batchselectcontacts/BatchSelectContactItemView'; +import { AlphabetIndexerPage } from '../alphabetindex/AlphabetIndexerPage'; +import AlphabetIndexerPresenter from '../../../presenter/contact/alphabetindex/AlphabetIndexerPresenter'; const TAG = 'BatchSelectContactsPage '; @@ -32,6 +35,12 @@ export default struct SingleSelectContactPage { aboutToAppear() { HiLog.i(TAG, 'aboutToAppear') + let obj: any = router.getParams(); + this.mPresenter.editContact = obj?.editContact; + this.mPresenter.contactId = obj?.contactId; + this.mPresenter.callId = obj?.callId; + this.mPresenter.phones = obj?.phones; + this.mPresenter.phoneNumberShow = obj?.phoneNumberShow; this.mPresenter.aboutToAppear() } @@ -52,7 +61,7 @@ export default struct SingleSelectContactPage { onBackPress() { HiLog.i(TAG, 'onBackPress') - this.mPresenter.cancel(); + this.mPresenter.singleBackCancel(); return true; } @@ -85,7 +94,7 @@ struct TitleGuide { .flexShrink(0) .margin({ left: $r("app.float.id_card_margin_max"), right: $r("app.float.id_card_margin_xxl") }) .onClick(() => { - this.mPresenter.cancel() + this.mPresenter.singleBackCancel(); }) Text($r('app.string.select_contact')) @@ -106,60 +115,80 @@ struct TitleGuide { struct ContactsList { @Link private presenter: BatchSelectContactsPresenter; @LocalStorageProp('breakpoint') curBp: string = 'sm'; + private scroller: Scroller = new Scroller(); + @State alphabetSelected: number = 0; + @State isAlphabetClicked: boolean = false; + @State dragList: boolean = true; + @State alphabetIndexPresenter: AlphabetIndexerPresenter = this.presenter.alphabetIndexPresenter; build() { Column() { - GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: { sm: 12, md: 12, lg: 24 }, y: 0 } }) { - GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { - TitleGuide() - } - } - .height("100%") - .onBreakpointChange((breakpoint: string) => { - this.curBp = breakpoint - }) - - GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: {x: 12, y: 0}}) { - GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { - List({ initialIndex: this.presenter.initialIndex }) { - LazyForEach(this.presenter.contactsSource, (item, index) => { - ListItem() { - Stack({ alignContent: Alignment.BottomEnd }) { - Column() { - if (item.showIndex) { - Column() { - Text(item.contact.namePrefix) - .fontColor($r("sys.color.ohos_fa_text_secondary")) - .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) - .fontWeight(FontWeight.Medium) - .textAlign(TextAlign.Start) - } - .alignItems(HorizontalAlign.Start) - .padding({ left: $r("app.float.id_card_margin_max"), bottom: $r("app.float.id_card_margin_large") }) - .width('100%') - .height($r("app.float.id_item_height_mid")) - } - } - - BatchSelectContactItemView({ - single: item.single = true, - item: item.contact, - index: item.index, - onSingleContactItemClick: (num, name) => this.presenter.onSingleContactItemClick(num, name), - showIndex: item.showIndex, - }) - } - } - }, (item) => JSON.stringify(item)) + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: { sm: 12, md: 12, lg: 24 }, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + TitleGuide() } - .scrollBar(BarState.Off) - .width('100%') - .listDirection(Axis.Vertical) - .edgeEffect(EdgeEffect.Spring) - .onScrollIndex((firstIndex: number, lastIndex: number) => { - this.presenter.resetInitialIndex(firstIndex); - }) } + .height("100%") + .onBreakpointChange((breakpoint: string) => { + this.curBp = breakpoint + }) + + Stack({ alignContent: Alignment.TopEnd }) { + GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: {x: 12, y: 0}}) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + List({ initialIndex: this.presenter.initialIndex, scroller: this.scroller }) { + LazyForEach(this.presenter.contactsSource, (item, index) => { + ListItem() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + if (item.showIndex) { + Column() { + Text(item.contact.namePrefix) + .fontColor($r("sys.color.ohos_fa_text_secondary")) + .fontSize($r("sys.float.ohos_id_text_size_sub_title3")) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + } + .alignItems(HorizontalAlign.Start) + .padding({ left: $r("app.float.id_card_margin_max"), bottom: $r("app.float.id_card_margin_large") }) + .width('100%') + .height($r("app.float.id_item_height_mid")) + } + } + + BatchSelectContactItemView({ + single: item.single = true, + item: item.contact, + index: item.index, + onSingleContactItemClick: (num, name) => this.presenter.onSingleContactItemClick(num, name, item.contact), + showIndex: item.showIndex, + }) + } + } + }, (item) => JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .width('100%') + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.Spring) + .onScrollIndex((firstIndex: number, lastIndex: number) => { + this.presenter.resetInitialIndex(firstIndex); + if (!this.isAlphabetClicked) { + this.alphabetSelected = this.alphabetIndexPresenter.getAlphabetSelected(firstIndex); + } + }) + .onScrollStart(() => { + this.dragList = true; + }) + .onScrollStop(() => { + this.isAlphabetClicked = false; + }) + } + } + .height('93%') + AlphabetIndexerPage({scroller: this.scroller, presenter: $alphabetIndexPresenter, selected: this.alphabetSelected, + isClicked: $isAlphabetClicked, drag: $dragList}) + .margin({ top: '10%', bottom: '10%' }) } .height('100%') .flexShrink(1) diff --git a/entry/src/main/ets/pages/contacts/details/ContactDetail.ets b/entry/src/main/ets/pages/contacts/details/ContactDetail.ets index fe665e1..f6d8b65 100644 --- a/entry/src/main/ets/pages/contacts/details/ContactDetail.ets +++ b/entry/src/main/ets/pages/contacts/details/ContactDetail.ets @@ -35,6 +35,7 @@ let storage = LocalStorage.GetShared() struct ContactDetail { @State mPresenter: DetailPresenter = DetailPresenter.getInstance(); @LocalStorageProp('breakpoint') curBp: string = 'sm'; + @StorageLink("params") params: { [key: string]: any } = {}; @State private mMoreMenu: Array<{ value: string, action: () => void @@ -69,14 +70,17 @@ struct ContactDetail { this.isShow++; }, 100); let obj: any = router.getParams(); - if (!obj) { - HiLog.i(TAG, "params is Miss or error!") - return; + if (obj != undefined) { + this.mPresenter.sourceHasId = obj.sourceHasId; + this.mPresenter.contactId = obj.contactId; + this.mPresenter.sourceHasPhone = obj.sourceHasPhone; + this.mPresenter.phoneNumberShow = obj.phoneNumberShow; + } else if (this.params != {}) { + this.mPresenter.sourceHasId = this.params.sourceHasId; + this.mPresenter.contactId = this.params.contactId; + this.mPresenter.sourceHasPhone = this.params.sourceHasPhone; + this.mPresenter.phoneNumberShow = this.params.phoneNumberShow; } - this.mPresenter.sourceHasId = obj.sourceHasId; - this.mPresenter.contactId = obj.contactId; - this.mPresenter.sourceHasPhone = obj.sourceHasPhone; - this.mPresenter.phoneNumberShow = obj.phoneNumberShow; this.mPresenter.aboutToAppear(); this.mMoreMenu = this.mPresenter.getSettingsMenus(); let that = this; @@ -150,6 +154,28 @@ struct ContactDetail { if (this.curBp !== 'lg') { Row() { + Column() { + Image('1' === this.mPresenter.isFavorited ? $r('app.media.ic_public_collected') : $r('app.media.ic_public_collect')) + .objectFit(ImageFit.Contain) + .height($r("app.float.id_card_image_small")) + .width($r("app.float.id_card_image_small")) + .margin({ bottom: 3 }) + Text('1' === this.mPresenter.isFavorited ? $r('app.string.cancel_favorite') : $r('app.string.favorite')) + .fontSize($r('sys.float.ohos_id_text_size_caption')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.ohos_id_color_toolbar_text')) + } + .onClick(() => { + if ('0' === this.mPresenter.isFavorited) { + this.mPresenter.isFavorited = '1'; + } else { + this.mPresenter.isFavorited = '0'; + } + this.mPresenter.updateFavorite(parseInt(this.mPresenter.isFavorited)); + }) + .visibility(this.mPresenter.contactId != undefined ? Visibility.Visible : Visibility.None) + .width('33%') + Column() { Image(this.mPresenter.contactId != undefined ? $r("app.media.ic_public_edit") @@ -168,10 +194,27 @@ struct ContactDetail { .onClick(() => { this.mPresenter.updateContact(); }) - .width(this.mPresenter.contactId != undefined ? '50%' : '100%') + .width(this.mPresenter.contactId != undefined ? '33%' : '50%') Column() { - Image($r("app.media.ic_public_more")) + Image($r('app.media.ic_public_contacts_group')) + .objectFit(ImageFit.Contain) + .height($r('app.float.id_card_image_small')) + .width($r('app.float.id_card_image_small')) + .margin({ bottom: 3 }) + Text($r('app.string.save_to_existing_contacts')) + .fontSize($r('sys.float.ohos_id_text_size_caption')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.ohos_id_color_toolbar_text')) + } + .onClick(() => { + this.mPresenter.saveExistingContact() + }) + .visibility(this.mPresenter.contactId != undefined ? Visibility.None : Visibility.Visible) + .width('50%') + + Column() { + Image($r('app.media.ic_public_more')) .objectFit(ImageFit.Contain) .height($r("app.float.id_card_image_small")) .width($r("app.float.id_card_image_small")) @@ -183,7 +226,7 @@ struct ContactDetail { .fontColor($r("sys.color.ohos_id_color_toolbar_text")) } .visibility(this.mPresenter.contactId != undefined ? Visibility.Visible : Visibility.None) - .width('50%') + .width('33%') } .alignItems(VerticalAlign.Center) .height($r("app.float.id_item_height_large")) diff --git a/entry/src/main/ets/pages/contacts/search/ContactSearch.ets b/entry/src/main/ets/pages/contacts/search/ContactSearch.ets new file mode 100644 index 0000000..a5b3b1f --- /dev/null +++ b/entry/src/main/ets/pages/contacts/search/ContactSearch.ets @@ -0,0 +1,325 @@ +/** + * 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 ContactListPresenter from '../../../presenter/contact/ContactListPresenter'; +import { StringUtil } from '../../../../../../../common/'; +import emitter from '@ohos.events.emitter'; +import BatchSelectContactsPresenter from '../../../presenter/contact/batchselectcontacts/BatchSelectContactsPresenter'; +import router from '@ohos.router'; + +const TAG = 'ContactSearch '; + +@Component +export struct ContactSearch { + @State batchSelectContactsPresenter: BatchSelectContactsPresenter = BatchSelectContactsPresenter.getInstance(); + @Link presenter: ContactListPresenter; + @Link type: number; + @State contactSearchNumber: number = 0; + emitterId: number = 102; + @State placeholder: string = ''; + @State cancelIsTouch: boolean = false; + + aboutToAppear() { + let innerEvent = { + eventId: this.emitterId, + priority: emitter.EventPriority.HIGH + }; + emitter.on(innerEvent, (data) => { + this.contactSearchNumber = data.data['contactSearchList']; + }) + } + + build() { + Column() { + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + Column() { + Row() { + Image($r('app.media.ic_public_back')) + .fillColor($r('sys.color.ohos_id_color_primary')) + .objectFit(ImageFit.Contain) + .height($r('app.float.id_card_image_small')) + .width($r('app.float.id_card_image_small')) + .onClick(() => { + this.presenter.isSearchPage = false; + this.presenter.sendEmitter(this.presenter.isSearchPage); + this.presenter.inputKeyword = ''; + }) + + Stack({ alignContent: Alignment.Center }) { + TextInput({ text: this.presenter.inputKeyword, placeholder: $r('app.string.contact_list_search') }) + .placeholderColor(Color.Grey) + .placeholderFont({ + size: $r('sys.float.ohos_id_text_size_headline9'), + weight: FontWeight.Normal, + style: FontStyle.Normal + }) + .type(InputType.Normal) + .caretColor($r('sys.color.ohos_id_color_text_primary_activated')) + .enterKeyType(EnterKeyType.Search) + .margin({ left: $r('app.float.id_card_image_small'), right: $r('app.float.id_card_image_small') }) + .padding({ left: $r('app.float.id_card_margin_xxxxl') }) + .height($r("app.float.id_item_height_mid")) + .border({ + color: $r('sys.color.ohos_id_color_fourth'), + radius: $r('app.float.id_card_margin_max') + }) + .onChange((value: string) => { + this.presenter.inputKeyword = value + this.presenter.getSearchContact(this.presenter.inputKeyword); + }) + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Image($r("app.media.ic_public_search")) + .width($r('app.float.id_card_margin_xxxl')) + .height($r('app.float.id_card_margin_xxxl')) + .margin({ left: $r('app.float.id_card_margin_xxxxl') }) + .objectFit(ImageFit.Contain) + + if (this.presenter.inputKeyword != '') { + Image($r('app.media.ic_public_cancel')) + .width($r('app.float.id_card_margin_max')) + .height($r('app.float.id_card_margin_max')) + .objectFit(ImageFit.Contain) + .fillColor($r('sys.color.ohos_id_color_primary')) + .opacity(0.6) + .margin({ right: $r('app.float.id_card_margin_xxxxl') }) + .align(Alignment.End) + .onClick(() => { + this.presenter.inputKeyword = ''; + }) + } + } + .hitTestBehavior(HitTestMode.Transparent) + } + .align(Alignment.Center) + } + .padding({ top: $r('app.float.id_card_image_xs'), bottom: $r('app.float.id_card_image_xs') }) + .width('100%') + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Start) + .align(Alignment.Start) + } + .width('100%') + .backgroundColor(Color.White) + .padding({ left: $r('app.float.id_card_image_small'), right: $r('app.float.id_card_image_small') }) + } + } + .visibility(this.type === 1 || this.type === 2 ? Visibility.None : Visibility.Visible) + + Column() { + Text($r('app.string.found_contacts', this.contactSearchNumber)) + .fontColor(Color.Gray) + .margin({ top: $r('app.float.id_card_margin_xxl'), bottom: $r('app.float.id_card_margin_xxl') }) + .width('100%') + .visibility(this.contactSearchNumber > 0 ? Visibility.Visible : Visibility.None) + + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + List({ space: 0, initialIndex: 0 }) { + LazyForEach(this.presenter.searchContactsSource, (item, index: number) => { + ListItem() { + Stack({ alignContent: Alignment.BottomEnd }) { + Row() { + Row() { + if (StringUtil.isEmpty(item?.contact?.photoFirstName)) { + Image(StringUtil.isEmpty(item?.contact?.portraitPath) ? $r("app.media.ic_user_portrait") : item?.contact?.portraitPath) + .width($r("app.float.id_card_image_mid")) + .height($r("app.float.id_card_image_mid")) + .objectFit(ImageFit.Contain) + .borderRadius($r("app.float.id_card_image_mid")) + .backgroundColor(item?.contact?.portraitColor) + + } else { + Text(item?.contact?.photoFirstName.toUpperCase()) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor(Color.White) + .backgroundColor(item?.contact?.portraitColor) + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + .textAlign(TextAlign.Center) + .borderRadius($r("app.float.id_card_image_mid")) + } + } + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + + Column() { + Row() { + ForEach(item?.contact?.displayName.split(this.presenter.inputKeyword), (itemData1, idx: number) => { + Row() { + Text(itemData1.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + if (idx === 0) { + if (item?.contact?.displayName.indexOf(this.presenter.inputKeyword) !== -1) { + Text(this.presenter.inputKeyword.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontColor(Color.Blue) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + } + } else if (item?.contact?.displayName.split(this.presenter.inputKeyword) + .length - 1 !== idx) { + Text(this.presenter.inputKeyword.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + } + } + }) + } + + Row() { + if (!StringUtil.isEmpty(item?.contact?.detailInfo) && '0' !== item?.contact?.hasPhoneNumber) { + ForEach(item?.contact?.detailInfo?.split(this.presenter.inputKeyword), (itemData, idx: number) => { + Row() { + Text(itemData.toString()) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + if (idx === 0) { + if (item?.contact?.detailInfo.indexOf(this.presenter.inputKeyword) !== -1) { + Text(this.presenter.inputKeyword.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontColor(Color.Blue) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + } + } else if (item?.contact?.detailInfo.split(this.presenter.inputKeyword) + .length - 1 !== idx) { + Text(this.presenter.inputKeyword.toString()) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(2) + } + } + }) + } + } + } + .alignItems(HorizontalAlign.Start) + .padding({ + top: $r("app.float.id_card_margin_mid"), + bottom: $r("app.float.id_card_margin_mid"), + }) + .margin({ left: $r("app.float.id_card_margin_xl") }) + } + .constraintSize({ minHeight: $r("app.float.id_item_height_max") }) + .width('100%') + .height($r("app.float.id_item_height_large")) + + Divider() + .color($r("sys.color.ohos_id_color_list_separator")) + .visibility(this.contactSearchNumber > 1 && this.contactSearchNumber - item.index > 1 ? Visibility.Visible : Visibility.None) + .margin({ + left: $r("app.float.id_item_height_large"), + right: $r('app.float.id_card_image_small') + }) + } + } + .onClick(() => { + if (this.type === 0) { + router.pushUrl( + { + url: "pages/contacts/details/ContactDetail", + params: { + sourceHasId: true, + contactId: item.contact.contactId + } + } + ); + } else if (this.type === 1) { + this.batchSelectContactsPresenter.onSingleContactItemClick(0, item.contact.displayName, item.contact.contactId); + } else if (this.type === 2) { + } + }) + }, item => JSON.stringify(item)) + } + .height('90%') + .width('100%') + .listDirection(Axis.Vertical) + } + } + .height('100%') + .flexShrink(1) + } + .height("100%") + .width("100%") + .padding({ left: this.type === 0 && this.contactSearchNumber > 0 ? $r('app.float.id_card_image_small') : 0 }) + .visibility(this.contactSearchNumber > 0 && !this.presenter.isSearchBackgroundColor ? Visibility.Visible : Visibility.None) + + ContactSearchEmptyPage({ contactSearchNumber: $contactSearchNumber, presenter: $presenter, type: $type }); + } + .padding({ bottom: $r('app.float.dialer_calllog_item_height') }) + .height("100%") + .width("100%") + .backgroundColor(this.type === 1 || this.type === 2 ? $r("sys.color.ohos_id_color_sub_background") : this.type === 0 && this.contactSearchNumber > 0 ? Color.White : '#450a0a0a') + } +} + +@Component +export struct ContactSearchEmptyPage { + @Link contactSearchNumber: number; + @Link presenter: ContactListPresenter; + @Link type: number; + + build() { + Column() { + Image($r('app.media.no_contacts_illustration')) + .objectFit(ImageFit.Contain) + .width($r("app.float.id_card_image_large")) + .height($r("app.float.id_card_image_large")) + .margin({ bottom: $r("app.float.id_card_margin_large") }) + + Text($r('app.string.contact_list_search_empty')) + .width($r('app.float.id_card_image_large')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .textAlign(TextAlign.Center) + .margin({ bottom: $r('app.float.dialer_calllog_item_height') }) + + } + .padding({ + left: $r('app.float.id_card_image_small'), + right: $r('app.float.id_card_image_small'), + bottom: $r('app.float.account_listItem_text_common_width') + }) + .justifyContent(FlexAlign.Center) + .height('100%') + .width('100%') + .backgroundColor(this.type === 1 ? $r("sys.color.ohos_id_color_sub_background") : Color.White) + .visibility(this.contactSearchNumber > 0 ? (!this.presenter.isSearchBackgroundColor ? Visibility.Visible : Visibility.None) : + (this.presenter.isSearchBackgroundColor ? Visibility.None : Visibility.Visible)) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/dialer/callRecord/AllRecord.ets b/entry/src/main/ets/pages/dialer/callRecord/AllRecord.ets index 6ee9374..984f53e 100644 --- a/entry/src/main/ets/pages/dialer/callRecord/AllRecord.ets +++ b/entry/src/main/ets/pages/dialer/callRecord/AllRecord.ets @@ -24,8 +24,10 @@ import DetailPresenter from '../../../presenter/contact/detail/DetailPresenter'; import { PhoneNumber } from '../../../../../../../feature/phonenumber/src/main/ets/PhoneNumber'; import IndexPresenter from '../../../presenter/IndexPresenter'; import { HiLog } from "../../../../../../../common" +import emitter from '@ohos.events.emitter' const TAG = "AllRecord "; +const EMITTER_SAVE_ID = 103; @Component export default struct AllRecord { @@ -49,10 +51,20 @@ export default struct AllRecord { aboutToAppear() { HiLog.i(TAG, 'aboutToAppear,recordType:' + this.recordType) this.onChanged(); + let innerEventContact = { + eventId: EMITTER_SAVE_ID, + priority: emitter.EventPriority.HIGH + }; + emitter.on(innerEventContact, (data) => { + let phoneNumber: string = data.data['phoneNumber']; + let callId: string = data.data['callId']; + this.mPresenter.saveCallRecordExistingContact(phoneNumber, callId); + }) } aboutToDisappear() { HiLog.i(TAG, 'aboutToDisappear,recordType:' + this.recordType) + emitter.off(EMITTER_SAVE_ID); } build() { @@ -125,7 +137,7 @@ struct EmptyView { } enum MenuType { - sendMessage, Copy, EditBeforeCall, BlockList, DeleteCallLogs + sendMessage, Copy, EditBeforeCall, BlockList, DeleteCallLogs, SaveExistingContacts } @Component @@ -153,7 +165,8 @@ struct ContactItem { }), autoCancel: true, alignment: (EnvironmentProp.isTablet() ? DialogAlignment.Center : DialogAlignment.Bottom), - offset: { dx: 0, dy: -16 } + offset: { dx: 0, dy: -16 }, + closeAnimation: { duration: 100 } }); build() { @@ -242,7 +255,6 @@ struct ContactItem { .margin({ top: $r("app.float.id_card_margin_sm"), right: 24 }) .onClick(() => { this.mPresenter.jumpToContactDetail(this.item.phoneNumber); - DialerPresenter.getInstance().panelShow = true; AppStorage.SetOrCreate("showDialBtn", true); }) } @@ -275,7 +287,6 @@ struct ContactItem { this.mPresenter.dialing(this.item.phoneNumber); } } - DialerPresenter.getInstance().panelShow = true; AppStorage.SetOrCreate("showDialBtn", true); }) .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) @@ -297,14 +308,15 @@ struct ContactItem { .textOverflow({ overflow: TextOverflow.Ellipsis }) .height($r("app.float.id_item_height_max")) } - - this.MenuView($r("app.string.send_message"), MenuType.sendMessage) - this.MenuDivider() - this.MenuView($r("app.string.copy_phoneNumber"), MenuType.Copy) - this.MenuDivider() - this.MenuView($r("app.string.edit_beforeCall"), MenuType.EditBeforeCall) - this.MenuDivider() - this.MenuView($r("app.string.delete_call_logs"), MenuType.DeleteCallLogs) + this.MenuView($r("app.string.save_to_existing_contacts"), MenuType.SaveExistingContacts, this.item.displayName) + this.MenuDivider(MenuType.SaveExistingContacts, this.item.displayName) + this.MenuView($r("app.string.send_message"), MenuType.sendMessage, this.item.displayName) + this.MenuDivider(MenuType.sendMessage, this.item.displayName) + this.MenuView($r("app.string.copy_phoneNumber"), MenuType.Copy, this.item.displayName) + this.MenuDivider(MenuType.Copy, this.item.displayName) + this.MenuView($r("app.string.edit_beforeCall"), MenuType.EditBeforeCall, this.item.displayName) + this.MenuDivider(MenuType.EditBeforeCall, this.item.displayName) + this.MenuView($r("app.string.delete_call_logs"), MenuType.DeleteCallLogs, this.item.displayName) } .width("150vp") .alignItems(HorizontalAlign.Start) @@ -312,7 +324,7 @@ struct ContactItem { .backgroundColor($r('sys.color.ohos_id_color_primary_contrary')) } - @Builder MenuView(menuName, itemType) { + @Builder MenuView(menuName, itemType, displayName) { Row() { Text(menuName) .fontSize($r("sys.float.ohos_id_text_size_body1")) @@ -323,13 +335,14 @@ struct ContactItem { .width("100%") .height($r("app.float.id_item_height_mid")) .backgroundColor($r('sys.color.ohos_id_color_primary_contrary')) + .visibility(itemType !== MenuType.SaveExistingContacts || itemType === MenuType.SaveExistingContacts && + '' === displayName ? Visibility.Visible : Visibility.None) .onClick(() => { switch (itemType) { case MenuType.DeleteCallLogs: this.deleteDialogController.open(); break; case MenuType.EditBeforeCall: - DialerPresenter.getInstance().isClickDelete = false; DialerPresenter.getInstance().editPhoneNumber(this.item.phoneNumber); AppStorage.SetOrCreate("showDialBtn", true); break; @@ -342,16 +355,30 @@ struct ContactItem { case MenuType.Copy: this.mIndexPresenter.getCopy(this.item.phoneNumber); break; + case MenuType.SaveExistingContacts: + let innerEvent = { + eventId: EMITTER_SAVE_ID, + priority: emitter.EventPriority.HIGH + }; + emitter.emit(innerEvent, { + data: { + 'callId': this.item.id, + 'phoneNumber': this.item.phoneNumber + } + }); + break; } }) } - @Builder MenuDivider() { + @Builder MenuDivider(itemType, displayName) { Divider() .color($r('sys.color.ohos_id_color_list_separator')) .lineCap(LineCapStyle.Square) .width(0) .alignSelf(ItemAlign.Stretch) .padding({ left: $r("app.float.id_card_margin_xxl"), right: $r("app.float.id_card_margin_xxl") }) + .visibility(itemType !== MenuType.SaveExistingContacts || itemType === MenuType.SaveExistingContacts && + '' === displayName ? Visibility.Visible : Visibility.None) } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/dialog/SelectMultiNumDialog.ets b/entry/src/main/ets/pages/dialog/SelectMultiNumDialog.ets new file mode 100644 index 0000000..28485e8 --- /dev/null +++ b/entry/src/main/ets/pages/dialog/SelectMultiNumDialog.ets @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2022-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, sharedPreferencesUtils } from '../../../../../../common'; + +const TAG = "SelectMultiNumDialog"; + +@CustomDialog +export struct SelectMultiNumDialog { + @Link builder: SelectNumDialogBuilder; + private controller: CustomDialogController; + private selectDefault: Boolean = false; + + aboutToAppear() { + HiLog.i(TAG, JSON.stringify(this.builder)); + sharedPreferencesUtils.init(globalThis.getContext()) + } + + build() { + Column() { + Text(this.builder.title) + .fontSize($r("sys.float.ohos_id_text_size_dialog_tittle")) + .fontColor($r("sys.color.ohos_id_color_text_primary")) + .fontWeight(FontWeight.Bold) + .alignSelf(ItemAlign.Center) + .width("100%") + .height("48vp") + .padding({ left: "16vp", }) + List() { + ForEach(this.builder.multiNumCardItems, (item, index) => { + ListItem() { + Row() { + Image(item.img) + .height("30vp") + .width("30vp") + .margin({ right: "8vp" }) + .onError((event => { + HiLog.e(TAG, "Num:" + index + " Image onError" + JSON.stringify(event)) + })) + Column() { + Text(item.number) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor($r("sys.color.ohos_id_color_text_primary")) + .fontWeight(FontWeight.Lighter) + Text(item.numType) + .fontSize($r("sys.float.ohos_id_text_size_body2")) + .fontColor($r('sys.color.ohos_dialog_text_alert_transparent')) + .fontWeight(FontWeight.Lighter) + .margin({ top: "4vp" }) + }.alignItems(HorizontalAlign.Start) + }.width('100%') + .height("56vp") + .justifyContent(FlexAlign.Start) + .padding({ left: "16vp", }) + }.onClick(() => { + this.confirm(item, this.builder.contactId); + }) + }) + }.divider({ + strokeWidth: 0.8, + startMargin: 56, + endMargin: $r("app.float.id_card_margin_max"), + }) + + Row() { + Checkbox({ name: 'checkbox2', group: 'checkboxGroup' }) + .select(false) + .selectedColor(0x39a2db) + .onChange((value: boolean) => { + this.selectDefault = value + console.info(' msz Checkbox2 change is' + value) + }) + Column() { + Text($r("app.string.set_default_values")) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor($r('sys.color.ohos_dialog_text_alert_transparent')) + .fontWeight(FontWeight.Lighter) + }.alignItems(HorizontalAlign.Start) + }.width('100%') + .height("56vp") + .justifyContent(FlexAlign.Start) + .padding({ left: "16vp", }) + + Text($r("app.string.cancel")) + .alignSelf(ItemAlign.Center) + .textAlign(TextAlign.Center) + .fontWeight(FontWeight.Medium) + .fontColor(0x39a2db) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .width("100%") + .height("48vp") + .onClick(() => { + this.cancel() + }); + }.backgroundColor(Color.White) + } + + confirm(item, contactId) { + this.controller.close() + if (this.selectDefault) { + sharedPreferencesUtils.saveToPreferences(contactId + '', item.number); + } + if (this.builder.callback) { + this.builder.callback(item); + } + } + + cancel() { + this.controller.close() + } +} + +class MultiNumCardItems { + number: String; + numType: Resource; + img: Resource; +} + +interface Controller { + close(); + + open(); +} + +export class SelectNumDialogBuilder { + title: string | Resource; + contactId: String + multiNumCardItems: Array; + callback?: (item: MultiNumCardItems) => void; + controller?: Controller; +} diff --git a/entry/src/main/ets/pages/favorites/editFavoriteList.ets b/entry/src/main/ets/pages/favorites/editFavoriteList.ets new file mode 100644 index 0000000..9ab8934 --- /dev/null +++ b/entry/src/main/ets/pages/favorites/editFavoriteList.ets @@ -0,0 +1,540 @@ +/** + * 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 EditFavoriteListPresenter from '../../presenter/favorite/EditFavoriteListPresenter'; +import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; +import { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil'; +import router from '@ohos.router'; +import { FavoriteBean } from '../../model/bean/FavoriteBean'; + +const TAG = 'EditFavoriteList'; + +@Entry +@Component +export default struct EditFavoriteList { + @State mFavoriteListPresenter: EditFavoriteListPresenter = EditFavoriteListPresenter.getInstance(); + @State isEdit: boolean = true + @State favoriteList: FavoriteBean[] = []; + @State selectNumbers: number = 0; + @State favoriteNumbers: number = 0; + @State usuallyNumbers: number = 0; + + aboutToAppear() { + HiLog.i(TAG, 'EditFavoriteList aboutToAppear!'); + let obj: any = router.getParams(); + this.mFavoriteListPresenter.favoriteList = obj.favoriteList; + this.mFavoriteListPresenter.isEditSelect = obj.isEditSelectList; + this.selectNumbers = obj.selectNumber; + this.favoriteNumbers = obj.favoriteNumber; + this.usuallyNumbers = obj.usuallyNumber; + this.mFavoriteListPresenter.selectFavoriteBean = obj.selectFavoriteBean; + this.mFavoriteListPresenter.aboutToAppear(); + } + + aboutToDisappear() { + HiLog.i(TAG, 'EditFavoriteList aboutToDisappear!'); + this.mFavoriteListPresenter.aboutToDisappear(); + } + + onPageShow() { + HiLog.i(TAG, 'EditFavoriteList onPageShow') + this.mFavoriteListPresenter.onPageShow() + } + + onPageHide() { + HiLog.i(TAG, 'EditFavoriteList onPageHide') + this.mFavoriteListPresenter.onPageHide() + } + + build() { + Column() { + if (this.mFavoriteListPresenter.favoriteList.length > 0) { + FavoriteContent({ + presenter: $mFavoriteListPresenter, + selectNumbers: $selectNumbers, + favoriteNumber: $favoriteNumbers, + usuallyNumber: $usuallyNumbers + }) + } else { + FavoriteEmptyPage({ presenter: $mFavoriteListPresenter, isEdit: $isEdit }) + } + } + .width('100%') + .height('100%') + } +} + +@Component +struct FavoriteContent { + @Link presenter: EditFavoriteListPresenter; + isUsuallyShow: boolean = false; + @Link selectNumbers: number; + @Link favoriteNumber: number; + @Link usuallyNumber: number; + @State selectNumber: number = this.selectNumbers; + @State selectFavoriteIdList: string[] = []; + @State isEditSelectList: string[] = null != this.presenter.isEditSelect && + this.presenter.isEditSelect.length > 0 ? this.presenter.isEditSelect : []; + @State favoriteListPresenter: EditFavoriteListPresenter = this.presenter; + @State isSelectAll: boolean = false; + @State isSelectAllStatus: boolean = false; + item: any = {}; + @State text: string = ''; + @State isEditDrag: boolean = false; + @State select: number = 0; + @State currentIndex: number = 0; + @State isDragShow: boolean = false; + @State offsetY: number = 50; + @State positionYDown: number = 0; + @State positionYUp: number = 0; + + @Builder pixelMapBuilder() { + Column() { + FavoriteListItem({ + item: this.item, + isEditSelectList: $isEditSelectList, + presenter: $favoriteListPresenter, + selectNumber: $selectNumber, + isSelectAll: $isSelectAll, + usuallyNumber: $usuallyNumber, + favoriteNumber: $favoriteNumber, + isEditDrag: this.isEditDrag + }) + } + } + + build() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + Stack({ alignContent: Alignment.TopStart }) { + Row() { + Text(this.selectNumber > 0 ? $r('app.string.select_num', this.selectNumber) : $r('app.string.no_select')) + .maxFontSize(22) + .minFontSize(18) + .maxLines(1) + .fontWeight(FontWeight.Bold) + .fontColor(Color.Black) + .margin({ top: $r('app.float.id_card_margin_large'), bottom: $r('app.float.id_card_margin_large') }) + } + .margin({ left: this.offsetY < 0 ? 0 : $r('app.float.id_item_height_mid') }) + .height(this.offsetY > 0 ? 56 : 138) + .animation({ + duration: 200, + iterations: 1, + }) + + TitleGuide({ + isDragShow: $isDragShow, + isEditSelectList: $isEditSelectList, + presenter: $favoriteListPresenter, + selectNumber: $selectNumber, + offsetY: $offsetY + }) + } + + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + List({ space: 0, initialIndex: 0 }) { + LazyForEach(this.presenter.favoriteDataSource, (item, index: number) => { + ListItem() { + FavoriteListItem({ + item: item, + isEditSelectList: $isEditSelectList, + presenter: $favoriteListPresenter, + selectNumber: $selectNumber, + isSelectAll: $isSelectAll, + usuallyNumber: $usuallyNumber, + favoriteNumber: $favoriteNumber, + isEditDrag: false + }) + } + .onDragStart((event: DragEvent, extraParams: string) => { + console.log('ListItem onDragStarts, ' + extraParams) + var jsonString = JSON.parse(extraParams) + if (jsonString.selectedIndex >= this.favoriteNumber) { + console.log('List onDragStarts , return ') + return; + } + this.isEditDrag = true; + this.select = jsonString.selectedIndex; + this.item = item; + return this.pixelMapBuilder(); + }) + }, (item) => JSON.stringify(item)) + } + .editMode(true) + .width('100%') + .height('100%') + .scrollBar(BarState.Off) + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.Spring) + .onDrop((event: DragEvent, extraParams: string) => { + let jsonString = JSON.parse(extraParams); + if (jsonString.insertIndex >= this.favoriteNumber) { + return; + } + if (this.isEditDrag) { + this.isDragShow = true; + let index = this.presenter.favoriteDataSource.getFavoriteList().indexOf(this.item.favorite); + this.presenter.favoriteDataSource.getFavoriteList().splice(index, 1); + this.presenter.favoriteDataSource.getFavoriteList() + .splice(jsonString.insertIndex, 0, this.item.favorite); + this.presenter.favoriteDataSource.refresh(this.presenter.favoriteDataSource.getFavoriteList()); + this.isEditDrag = false; + } + }) + .onScroll((scrollOffset, scrollState) => { + this.offsetY = this.positionYDown - this.positionYUp; + if (this.offsetY > 0) { + animateTo({ duration: 1000 }, () => { + }); + } else { + animateTo({ duration: 1000 }, () => { + }); + } + }) + .onTouch((event) => { + switch (event.type) { + case TouchType.Down: + this.positionYDown = Math.abs(event.touches[0].y); + break; + case TouchType.Move: + this.positionYUp = Math.abs(event.touches[0].y); + case TouchType.Up: + this.positionYUp = Math.abs(event.touches[0].y); + break; + } + }) + } + } + .height('100%') + .flexShrink(1) + + Row() { + Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) { + Column() { + Image(this.selectNumber > 0 ? $r('app.media.ic_public_close') : $r('app.media.ic_public_close_gray')) + .objectFit(ImageFit.Contain) + .height($r('app.float.id_card_image_small')) + .width($r('app.float.id_card_image_small')) + .margin({ bottom: 3 }) + Text($r('app.string.favorite_remove')) + .fontColor(this.selectNumber > 0 ? $r('sys.color.ohos_id_color_toolbar_text') : Color.Gray) + .fontSize($r('sys.float.ohos_id_text_size_caption')) + .fontWeight(FontWeight.Medium) + .margin({ top: $r('app.float.id_card_margin_large') }) + } + .onClick(() => { + if (this.isEditSelectList.length > 0) { + this.presenter.deleteFavoriteInfo(this.isEditSelectList); + this.isEditSelectList = []; + this.selectNumber = this.isEditSelectList.length; + router.back(); + } + }) + .width('40%') + .height('100%') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + + Column() { + Image(this.presenter.favoriteList.length === this.selectNumber ? $r('app.media.ic_public_select_all_filled') : $r('app.media.ic_public_select_all')) + .objectFit(ImageFit.Contain) + .height($r('app.float.id_card_image_small')) + .width($r('app.float.id_card_image_small')) + .margin({ bottom: 3 }) + .fillColor($r('sys.color.ohos_id_color_primary')) + Text(this.presenter.favoriteList.length === this.selectNumber ? $r('app.string.unselect_all') : $r('app.string.select_all')) + .fontColor(this.presenter.favoriteList.length === this.selectNumber ? $r('sys.color.ohos_id_color_toolbar_text') : Color.Gray) + .fontSize($r('sys.float.ohos_id_text_size_caption')) + .fontWeight(FontWeight.Medium) + .margin({ top: $r('app.float.id_card_margin_large') }) + + } + .onClick(() => { + this.isSelectAll = this.presenter.favoriteList.length === this.selectNumber; + if (this.isSelectAll) { + this.isEditSelectList = this.presenter.cancelAllFavoriteSelectInfo(this.presenter.favoriteList, this.isEditSelectList); + } else { + this.isEditSelectList = this.presenter.addAllFavoriteSelectInfo(this.presenter.favoriteList); + } + this.selectNumber = this.isEditSelectList.length; + this.presenter.favoriteDataSource.refresh(this.presenter.favoriteList); + }) + .width('40%') + .height('100%') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + } + } + .padding({ left: 24, right: 24 }) + .backgroundColor(Color.White) + .width('100%') + .height($r('app.float.id_item_height_max')) + } + .padding({ left: 24, right: 24 }) + .height('100%') + .width('100%') + } + .height('100%') + .width('100%') + } +} + +@Component +export struct FavoriteListItem { + @Link presenter: EditFavoriteListPresenter; + @State item: any = {}; + @Link isEditSelectList: string[]; + @Link selectNumber: number; + @Link isSelectAll: boolean; + @Link usuallyNumber: number; + @Link favoriteNumber: number; + isEditDrag: boolean = false; + + build() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + if (1 === this.item.favorite.isCommonUseType) { + Text($r('app.string.common_use_num', this.usuallyNumber)) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor('#666666') + .lineHeight(19) + .textAlign(TextAlign.Center) + .margin({ top: $r('app.float.id_card_margin_xxl'), bottom: $r('app.float.id_card_margin_xxl') }) + .borderRadius($r('app.float.id_card_image_mid')) + .visibility(this.item.favorite.isUsuallyShow ? Visibility.Visible : Visibility.None) + } else if (0 === this.item.favorite.isCommonUseType && this.item.index === 0) { + Text($r('app.string.favorite_num', this.favoriteNumber)) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor('#666666') + .lineHeight(19) + .textAlign(TextAlign.Center) + .margin({ top: $r('app.float.id_card_margin_xxl'), bottom: $r('app.float.id_card_margin_xxl') }) + .borderRadius($r('app.float.id_card_image_mid')) + .visibility(this.isEditDrag ? Visibility.None : Visibility.Visible) + } + + Row() { + Flex({ + direction: FlexDirection.Row, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Stretch + }) { + Row() { + if (0 === this.item.favorite.isCommonUseType) { + Image($r('app.media.ic_public_drag_handle')) + .width($r('app.float.id_card_image_small')) + .height($r('app.float.id_card_image_small')) + .objectFit(ImageFit.Contain) + .borderRadius($r('app.float.id_card_image_mid')) + .margin({ right: $r('app.float.id_card_image_xs') }) + .width('10%') + } + Row() { + if (StringUtil.isEmpty(this.item.favorite.nameSuffix)) { + Image(StringUtil.isEmpty(this.item.favorite.portraitPath) ? $r('app.media.ic_user_portrait') : this.item.favorite.portraitPath) + .width($r("app.float.id_card_image_mid")) + .height($r("app.float.id_card_image_mid")) + .objectFit(ImageFit.Contain) + .borderRadius($r("app.float.id_card_image_mid")) + .backgroundColor(this.item.favorite.portraitColor) + } else { + Text(this.item.favorite.namePrefix.toUpperCase()) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor(Color.White) + .backgroundColor(this.item.favorite.portraitColor) + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + .textAlign(TextAlign.Center) + .borderRadius($r("app.float.id_card_image_mid")) + } + } + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + if (0 === this.item.favorite.isCommonUseType) { + Text(this.item.favorite.displayName) + .maxLines(1) + .fontSize(40) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontWeight(FontWeight.Bold) + .fontColor(Color.Black) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .margin({ left: $r('app.float.dialer_common_small_margin') }) + .textAlign(TextAlign.Start) + .height('100%') + .width('83%') + } else { + Column() { + Text(this.item.favorite.displayName) + .maxLines(1) + .fontSize(40) + .fontWeight(FontWeight.Bold) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor(Color.Black) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .margin({ bottom: 5 }) + .textAlign(TextAlign.Start) + + Text($r('app.string.phone_type_mobile_expansion', this.item.favorite.phoneNum)) + .fontColor($r('sys.color.ohos_fa_text_secondary')) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .textAlign(TextAlign.Start) + } + .width('83%') + .padding({ left: $r('app.float.id_card_margin_xxl'), right: $r('app.float.id_card_margin_large') }) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + } + } + + Column() { + Checkbox({ name: this.item.favorite.contactId }) + .width($r('app.float.id_card_image_mid')) + .height($r('app.float.id_card_image_mid')) + .selectedColor('#007DFF') + .padding($r("app.float.id_card_margin_mid")) + .select(this.item.favorite.isEditSelect ? true : false) + .onChange((value: boolean) => { + this.isSelectAll = false; + this.item.favorite.isEditSelect = value; + if (value) { + this.isEditSelectList = this.presenter.addSingleFavoriteSelectInfo(this.isEditSelectList, this.item.favorite); + } else { + this.isEditSelectList = this.presenter.cancelSingleFavoriteSelectInfo(this.isEditSelectList, this.item.favorite); + } + this.selectNumber = this.isEditSelectList.length; + this.presenter.favoriteDataSource.refresh(this.presenter.favoriteList); + }) + .width('100%') + } + .width('9%') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + } + .width('100%') + } + .alignItems(VerticalAlign.Center) + .direction(Direction.Ltr) + .justifyContent(FlexAlign.Start) + .constraintSize({ minHeight: $r('app.float.id_item_height_max') }) + .height($r('app.float.id_item_height_large')) + .width('100%') + }.alignItems(HorizontalAlign.Start) + + if (this.item.showDivider || this.item.index >= 0 && this.favoriteNumber > 1 && this.favoriteNumber - 1 + != this.item.index && this.presenter.favoriteList.length - 1 != this.item.index) { + Divider() + .color($r('sys.color.ohos_id_color_list_separator')) + .margin({ left: $r('app.float.id_item_height_large') }) + } + } + } +} + +@Component +export struct FavoriteEmptyPage { + @Link presenter: EditFavoriteListPresenter; + @Link isEdit: boolean ; + + build() { + Column() { + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 2, lg: 4 } }) { + Text($r('app.string.favorite')) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .height($r("app.float.id_item_height_large")) + .textAlign(TextAlign.Start) + .width('100%') + } + } + + Column() { + Image($r('app.media.no_contacts_illustration')) + .objectFit(ImageFit.Contain) + .width($r('app.float.id_card_image_large')) + .height($r('app.float.id_card_image_large')) + .margin({ bottom: $r('app.float.id_card_margin_large') }) + + Text($r('app.string.cancel_favorite')) + .width($r('app.float.id_card_image_large')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .textAlign(TextAlign.Center) + } + .margin({ bottom: 192 }) + } + .padding({ left: 24, right: 24 }) + .justifyContent(FlexAlign.SpaceBetween) + .height('100%') + .width('100%') + } +} + +@Component +struct TitleGuide { + @Link presenter: EditFavoriteListPresenter; + @Link isEditSelectList: string[]; + @Link isDragShow: boolean; + @Link selectNumber: number; + @Link offsetY: number; + + build() { + Row() { + Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) { + Image(this.offsetY > 0 ? $r("app.media.ic_public_back") : $r('app.media.ic_public_cancel')) + .width($r('app.float.id_card_image_large')) + .height($r('app.float.id_item_height_large')) + .padding({ + top: $r('app.float.id_card_margin_xxl'), + right: $r('app.float.dialer_keypad_button_height'), + bottom: $r('app.float.id_card_margin_xxl'), + }) + .onClick(() => { + this.presenter.cancelEditFavorite(); + }) + + Image($r('app.media.ic_public_ok')) + .width($r('app.float.id_item_height_large')) + .height($r('app.float.id_item_height_large')) + .padding({ + top: $r('app.float.id_card_margin_xxl'), + left: $r('app.float.id_card_margin_max'), + bottom: $r('app.float.id_card_margin_xxl') + }) + .visibility(this.isDragShow ? Visibility.Visible : Visibility.Hidden) + .onClick(() => { + this.presenter.moveSortFavorite(this.presenter.favoriteDataSource.getFavoriteList()); + }) + } + .width('100%') + } + .justifyContent(FlexAlign.End) + .alignItems(VerticalAlign.Center) + .height($r('app.float.id_item_height_large')) + .width('100%') + } + + onBackPress() { + this.presenter.cancelEditFavorite(); + } +} diff --git a/entry/src/main/ets/pages/favorites/favoriteList.ets b/entry/src/main/ets/pages/favorites/favoriteList.ets new file mode 100644 index 0000000..622549c --- /dev/null +++ b/entry/src/main/ets/pages/favorites/favoriteList.ets @@ -0,0 +1,445 @@ +/** + * 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 { SelectDialogBuilder } from '../../component/mutisim/SelectSimIdDialog'; +import { SelectMultiNumDialog } from '../dialog/SelectMultiNumDialog'; +import { SelectNumDialogBuilder } from '../dialog/SelectMultiNumDialog'; +import { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil'; +import { HiLog, sharedPreferencesUtils } from '../../../../../../common'; +import FavoriteListPresenter from '../../presenter/favorite/FavoriteListPresenter'; +import { PhoneNumber } from '../../../../../../feature/phonenumber/src/main/ets/PhoneNumber'; +import ContactAbilityModel from '../../model/ContactAbilityModel'; +import emitter from '@ohos.events.emitter'; +import { Phone } from '../../../../../../feature/contact/src/main/ets/contract/Phone'; +import prompt from '@ohos.prompt'; + +const TAG = 'FavoriteList'; + +@Entry +@Component +export default struct FavoriteList { + @State mFavoriteListPresenter: FavoriteListPresenter = FavoriteListPresenter.getInstance(); + @State isEdit: boolean = false; + @State favoriteListListLen: number = 0; + emitterId: number = 100; + + aboutToAppear() { + HiLog.i(TAG, 'Favorite aboutToAppear!'); + this.mFavoriteListPresenter.aboutToAppear(); + let innerEvent = { + eventId: this.emitterId, + priority: emitter.EventPriority.HIGH + }; + emitter.on(innerEvent, (data) => { + this.favoriteListListLen = data.data['favoriteListListLen']; + }) + } + + aboutToDisappear() { + HiLog.i(TAG, 'Favorite aboutToDisappear!'); + this.mFavoriteListPresenter.aboutToDisappear(); + } + + onPageShow() { + HiLog.i(TAG, 'Favorite onPageShow'); + this.mFavoriteListPresenter.onPageShow(); + } + + onPageHide() { + HiLog.i(TAG, 'Favorite onPageHide'); + this.mFavoriteListPresenter.onPageHide(); + } + + build() { + Column() { + if (this.favoriteListListLen === 0) { + FavoriteEmptyPage({ presenter: $mFavoriteListPresenter, isEdit: $isEdit }); + } else { + FavoriteContent({ presenter: $mFavoriteListPresenter }); + } + } + .width('100%') + .height('100%') + } +} + +@Component +struct FavoriteContent { + @Link presenter: FavoriteListPresenter; + @State mPresenter: FavoriteListPresenter = this.presenter; + isUsuallyShow: boolean = false; + + build() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + TitleGuide({ mPresenter: $mPresenter }); + } + + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 2, lg: 4 } }) { + Column() { + Text($r('app.string.favorite')) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .margin({ bottom: $r("app.float.id_card_margin_sm") }) + .lineHeight(42) + .margin({ top: 8, bottom: 2 }) + } + .alignItems(HorizontalAlign.Start) + .width('100%') + .height(82) + } + } + + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + List({ space: 0, initialIndex: 0 }) { + LazyForEach(this.presenter.favoriteDataSource, (item, index: number) => { + ListItem() { + FavoriteListItem({ presenter: $mPresenter, item: item, mPresenter: $presenter }); + } + }, (item) => JSON.stringify(item)) + } + .scrollBar(BarState.Off) + .editMode(true) + .width('100%') + .height('100%') + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.Spring) + } + } + .height('100%') + .flexShrink(1) + } + .padding({ left: 24, right: 24 }) + .height('100%') + .width('100%') + } + .backgroundColor(Color.White) + .height('100%') + .width('100%') + } +} + +@Component +export struct FavoriteListItem { + @Link presenter: FavoriteListPresenter; + @Link private mPresenter: { [key: string]: any }; + @State selectSimBuilder: SelectDialogBuilder = { + title: 'test title', + multiSimCardItems: [], + }; + @State item: any = {}; + @State builder: SelectNumDialogBuilder = { + title: $r('app.string.contacts_call'), + multiNumCardItems: [], + contactId: '' + }; + mSelectSlotIdDialog: CustomDialogController = new CustomDialogController({ + builder: SelectMultiNumDialog({ + builder: $builder + }), + customStyle: false, + autoCancel: true, + alignment: DialogAlignment.Bottom + }) + @State isEmergencyNum: boolean = false; + @StorageLink("haveSimCard") haveSimCard: boolean = false; + @StorageLink('haveMultiSimCard') haveMultiSimCard: boolean = false; + + build() { + Stack({ alignContent: Alignment.BottomEnd }) { + Column() { + if (1 === this.item.favorite.isCommonUseType) { + Text($r('app.string.common_use')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor('#666666') + .margin({ top: $r('app.float.id_card_margin_xxl') }) + .textAlign(TextAlign.Center) + .borderRadius($r('app.float.id_card_image_mid')) + .visibility(this.item.favorite.isUsuallyShow ? Visibility.Visible : Visibility.None) + } + Row() { + Flex({ + direction: FlexDirection.Row, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Stretch + }) { + Row() { + Row() { + if (StringUtil.isEmpty(this.item.favorite.nameSuffix)) { + Image(StringUtil.isEmpty(this.item.favorite.portraitPath) ? $r('app.media.ic_user_portrait') : this.item.favorite.portraitPath) + .width($r("app.float.id_card_image_mid")) + .height($r("app.float.id_card_image_mid")) + .objectFit(ImageFit.Contain) + .borderRadius($r("app.float.id_card_image_mid")) + .backgroundColor(this.item.favorite.portraitColor) + } else { + Text(this.item.favorite.namePrefix.toUpperCase()) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor(Color.White) + .backgroundColor(this.item.favorite.portraitColor) + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + .textAlign(TextAlign.Center) + .borderRadius($r("app.float.id_card_image_mid")) + } + } + .height($r("app.float.id_card_image_mid")) + .width($r("app.float.id_card_image_mid")) + if (0 === this.item.favorite.isCommonUseType) { + Text(this.item.favorite.displayName) + .maxLines(1) + .fontSize(40) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontWeight(FontWeight.Bold) + .fontColor(Color.Black) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .margin({ left: $r('app.float.dialer_common_small_margin') }) + .textAlign(TextAlign.Start) + .width('83%') + } else { + Column() { + Text(this.item.favorite.displayName) + .maxLines(1) + .fontSize(40) + .fontWeight(FontWeight.Bold) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor(Color.Black) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .margin({ bottom: 5 }) + .textAlign(TextAlign.Start) + + Text($r('app.string.phone_type_mobile_expansion', this.item.favorite.phoneNum)) + .fontColor($r('sys.color.ohos_fa_text_secondary')) + .fontSize($r('sys.float.ohos_id_text_size_sub_title3')) + .textAlign(TextAlign.Start) + } + .width('83%') + .padding({ left: $r('app.float.id_card_margin_xxl'), right: $r('app.float.id_card_margin_large') }) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + } + } + + Image($r('app.media.ic_public_detail')) + .height($r('app.float.id_card_margin_xxl')) + .objectFit(ImageFit.Contain) + .borderRadius($r('app.float.id_card_margin_xl')) + .onClick(() => { + this.presenter.goContactDetail(this.item.favorite.contactId); + }) + .width('7%') + } + .width('100%') + } + .alignItems(VerticalAlign.Center) + .direction(Direction.Ltr) + .justifyContent(FlexAlign.Start) + .constraintSize({ minHeight: $r('app.float.id_item_height_max') }) + .height($r('app.float.id_item_height_large')) + .width('100%') + } + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + + if (this.item.showDivider) { + Divider() + .color($r('sys.color.ohos_id_color_list_separator')) + .margin({ left: $r('app.float.id_item_height_large') }) + } + } + .onClick(() => { + ContactAbilityModel.getContactById(this.item.favorite.contactId, result => { + if (result.data.hasOwnProperty('phones')) { + if (result.data.phones.length > 1) { + new Promise(async () => { + let defaultNumber: String = await sharedPreferencesUtils.getFromPreferences( + this.item.favorite.contactId + '', '-1'); + this.builder.multiNumCardItems = [] + this.builder.contactId = result.data.id + var hasDefault = false + result.data.phones.forEach((element) => { + let formatNum = PhoneNumber.fromString(element.num).getNumber().replace(/\s+/g, '') + if (defaultNumber != '-1' && defaultNumber == formatNum) { + this.dealCallNumber(formatNum) + hasDefault = true; + return; + } + this.builder.multiNumCardItems.push({ number: element.num, img: $r("app.media.ic_public_phone_dialog"), + numType: Phone.getTypeLabelResource(parseInt(element.numType, 10)) }) + }); + this.builder.callback = (item) => { + this.dealCallNumber(item.number) + } + if (!hasDefault) + this.mSelectSlotIdDialog.open() + }); + } else if (result.data.phones.length > 0) { + this.dealCallNumber(result.data.phones[0].num) + } + } else { + prompt.showToast({ message: '联系人无可用号码!'}) + HiLog.e(TAG, ' popupSelectNumber phones null'); + } + }, globalThis.getContext()) + }) + .gesture( + LongPressGesture({ repeat: false }) + .onAction((event: GestureEvent) => { + for (let i = 0; i < this.presenter.favoriteList.length; i++) { + if (this.item.favorite.contactId === this.presenter.favoriteList[i].contactId) { + this.presenter.favoriteList[i].isEditSelect = true; + } else { + this.presenter.favoriteList[i].isEditSelect = false; + } + } + this.presenter.isEditSelectList.pop(); + this.presenter.isEditSelectList.push(this.item.favorite.contactId + ''); + this.presenter.longItemEditFavorite(this.presenter.favoriteList, this.presenter.isEditSelectList, this.item.favorite); + }) + ) + } + + dealCallNumber(phoneNum) { + if (!this.haveSimCard) { + HiLog.i(TAG, "No SIM card!"); + PhoneNumber.fromString(phoneNum).isDialEmergencyNum().then((res) => { + this.isEmergencyNum = res; + }) + if (!this.isEmergencyNum) { + HiLog.i(TAG, "Is not Emergency Phone Number!"); + return; + } else { + HiLog.i(TAG, "No SIM card, but is Emergency Phone Number"); + PhoneNumber.fromString(phoneNum).dial(); + } + } else if (this.haveMultiSimCard) { + this.selectSimBuilder.title = $r("app.string.contacts_call_number", phoneNum); + this.selectSimBuilder.callback = (value) => { + PhoneNumber.fromString(phoneNum).dial({ + accountId: value, + }); + } + this.selectSimBuilder.lastSimId = this.mPresenter.lastUsedSlotId; + let spnList = AppStorage.Get>('spnList'); + for (var index = 0; index < spnList.length; index++) { + this.selectSimBuilder.multiSimCardItems[index].name = spnList[index]; + } + this.selectSimBuilder.controller?.open(); + } else { + PhoneNumber.fromString(phoneNum).dial(); + } + } +} + +@Component +export struct FavoriteEmptyPage { + @Link presenter: FavoriteListPresenter; + @Link isEdit: boolean ; + + build() { + Column() { + GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 0 } }) { + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 1, lg: 2 } }) { + TitleGuide({ mPresenter: $presenter }); + } + + GridCol({ span: { sm: 4, md: 6, lg: 8 }, offset: { sm: 0, md: 2, lg: 4 } }) { + Text($r('app.string.favorite')) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .height($r('app.float.id_item_height_large')) + .textAlign(TextAlign.Start) + .width('100%') + } + } + + Column() { + Image($r('app.media.ic_no_favorites')) + .objectFit(ImageFit.Contain) + .width($r('app.float.id_card_image_large')) + .height($r('app.float.id_card_image_large')) + .margin({ bottom: $r('app.float.id_card_margin_large') }) + + Text($r('app.string.no_favorite')) + .width($r('app.float.id_card_image_large')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + .textAlign(TextAlign.Center) + } + .height('100%') + .width('100%') + .margin({ top: $r('app.float.account_listItem_text_common_width') }) + } + .padding({ left: 24, right: 24 }) + .justifyContent(FlexAlign.SpaceBetween) + .backgroundColor(Color.White) + .height('100%') + .width('100%') + } +} + +@Component +struct TitleGuide { + @Link mPresenter: FavoriteListPresenter; + + @Builder MenuBuilder() { + Column() { + Text($r("app.string.edit")) + .fontSize($r("app.float.id_corner_radius_card_mid")) + .textAlign(TextAlign.Start) + .fontColor($r("sys.color.ohos_id_color_text_primary")) + .lineHeight(19) + .width($r("app.float.account_Divider_width")) + .height(21) + } + .width($r('app.float.account_MenuBuilder_width')) + .justifyContent(FlexAlign.Center) + .height($r("app.float.id_item_height_mid")) + .onClick(() => { + this.mPresenter.editFavorite(); + }) + } + + build() { + Row() { + Image($r('app.media.ic_public_add')) + .width($r('app.float.id_card_image_mid')) + .height($r('app.float.id_card_image_mid')) + .padding($r('app.float.id_card_margin_large')) + .onClick(() => { + this.mPresenter.addFavorite(); + }); + + Image(this.mPresenter.favoriteList.length > 0 ? $r('app.media.ic_public_more') : $r('app.media.ic_public_more_gray')) + .width($r('app.float.id_card_image_mid')) + .height($r('app.float.id_card_image_mid')) + .objectFit(ImageFit.Contain) + .padding($r('app.float.id_card_margin_large')) + .enabled(this.mPresenter.favoriteList.length > 0 ? true : false) + .bindMenu(this.MenuBuilder) + } + .justifyContent(FlexAlign.End) + .alignItems(VerticalAlign.Center) + .height($r('app.float.id_item_height_large')) + .width('100%') + } +} diff --git a/entry/src/main/ets/pages/index.ets b/entry/src/main/ets/pages/index.ets index 77ee615..d63c150 100644 --- a/entry/src/main/ets/pages/index.ets +++ b/entry/src/main/ets/pages/index.ets @@ -14,16 +14,18 @@ */ import callTabletPage from './dialer/DialerTablet'; import contactPage from './contacts/ContactList'; +import favoritePage from './favorites/favoriteList'; import callPage from './phone/dialer/Dialer'; import IndexPresenter from '../presenter/IndexPresenter'; -import { HiLog } from '../../../../../common/src/main/ets/util/HiLog'; +import { HiLog, StringUtil } from '../../../../../common'; import { PermissionManager } from '../../../../../common/src/main/ets/permission/PermissionManager'; import DialerPresenter from '../presenter/dialer/DialerPresenter'; import call from '@ohos.telephony.call'; import ContactListPresenter from '../presenter/contact/ContactListPresenter'; import CallRecordPresenter from '../presenter/dialer/callRecord/CallRecordPresenter'; +import FavoriteListPresenter from '../presenter/favorite/FavoriteListPresenter'; import device from '@system.device'; -import router from '@ohos.router'; +import emitter from '@ohos.events.emitter'; const TAG = 'Index '; @@ -41,12 +43,14 @@ struct Index { @State bottomTabIndex: number = call.hasVoiceCapability() ? 0 : 1; @StorageLink("targetPage") @Watch("targetPageChange") targetPage: any = {}; @LocalStorageProp('breakpoint') curBp: string = 'sm'; + @State isContactSearch: boolean = false; + emitterId: number = 105; teleNumberChange() { - if (AppStorage.Has('teleNumber')) { - HiLog.i(TAG, "teleNumberChange:" + this.teleNumber); - this.mDialerPresenter.editPhoneNumber(AppStorage.Get('teleNumber')); - AppStorage.Delete('teleNumber'); + if (!StringUtil.isEmpty(this.teleNumber)) { + this.mDialerPresenter.editPhoneNumber(this.teleNumber); + AppStorage.SetOrCreate("showDialBtn", true); + this.teleNumber = ""; } } @@ -54,6 +58,7 @@ struct Index { if (this.targetPage && this.targetPage.url) { HiLog.i(TAG, "targetPageChange in" + this.targetPage); this.mIndexPresenter.goToPage(this.targetPage.url, this.targetPage.pageIndex, this.targetPage.params) + this.targetPage = {} } } @@ -66,12 +71,12 @@ struct Index { } this.bottomTabIndex = this.mainTabsIndex; this.controller.changeIndex(this.mainTabsIndex); - if (this.mainTabsIndex == 0) { - ContactListPresenter.getInstance().setPageShow(false); - CallRecordPresenter.getInstance().setPageShow(true); + CallRecordPresenter.getInstance().setPageShow(this.bottomTabIndex == 0); + ContactListPresenter.getInstance().setPageShow(this.bottomTabIndex == 1); + if (this.mainTabsIndex != 2) { + FavoriteListPresenter.getInstance().onPageHide() } else { - CallRecordPresenter.getInstance().setPageShow(false); - ContactListPresenter.getInstance().setPageShow(true); + FavoriteListPresenter.getInstance().onPageShow() } } } @@ -83,13 +88,8 @@ struct Index { onPageShow() { this.mIndexPresenter.onPageShow(); - if (this.bottomTabIndex == 0) { - ContactListPresenter.getInstance().setPageShow(false); - CallRecordPresenter.getInstance().setPageShow(true); - } else { - CallRecordPresenter.getInstance().setPageShow(false); - ContactListPresenter.getInstance().setPageShow(true); - } + CallRecordPresenter.getInstance().setPageShow(this.bottomTabIndex == 0); + ContactListPresenter.getInstance().setPageShow(this.bottomTabIndex == 1); } onPageHide() { @@ -103,10 +103,25 @@ struct Index { this.getInfo(); this.onIndexChanged(); this.teleNumberChange() + let innerEvent = { + eventId: this.emitterId, + priority: emitter.EventPriority.HIGH + }; + emitter.on(innerEvent, (data) => { + this.isContactSearch = data.data['isSearchPage']; + }) } aboutToDisappear() { this.mIndexPresenter.aboutToDisappear(); + emitter.off(this.emitterId); + } + + onBackPress() { + if (this.isContactSearch) { + ContactListPresenter.getInstance().sendEmitter(false); + return true; + } } getInfo() { @@ -134,6 +149,7 @@ struct Index { } .height('100%') .zIndex(3) + .visibility(this.isContactSearch ? Visibility.None : Visibility.Visible) } Column() { Tabs({ @@ -153,6 +169,10 @@ struct Index { TabContent() { contactPage() } + + TabContent() { + favoritePage() + } } .width('100%') .vertical(false) @@ -175,6 +195,7 @@ struct Index { .width('100%') .height($r("app.float.id_item_height_large")) .flexShrink(0) + .visibility(this.isContactSearch ? Visibility.None : Visibility.Visible) } } .backgroundColor($r("sys.color.ohos_fa_sub_background")) @@ -189,7 +210,7 @@ struct Index { @Component struct TabBars { - private tabSrc: number[] = call.hasVoiceCapability() ? [0, 1] : [1]; + private tabSrc: number[] = call.hasVoiceCapability() ? [0, 1, 2] : [1]; private controller: TabsController; @Link bottomTabIndex: number; @State mIndexPresenter: IndexPresenter = IndexPresenter.getInstance() @@ -224,9 +245,15 @@ struct TabBars { if (item == 0) { ContactListPresenter.getInstance().setPageShow(false); CallRecordPresenter.getInstance().setPageShow(true); + FavoriteListPresenter.getInstance().onPageHide(); } else if (item == 1) { CallRecordPresenter.getInstance().setPageShow(false); ContactListPresenter.getInstance().setPageShow(true); + FavoriteListPresenter.getInstance().onPageHide(); + } else if (item == 2) { + CallRecordPresenter.getInstance().setPageShow(false); + ContactListPresenter.getInstance().setPageShow(false); + FavoriteListPresenter.getInstance().onPageShow(); } } }) diff --git a/entry/src/main/ets/pages/phone/dialer/Dialer.ets b/entry/src/main/ets/pages/phone/dialer/Dialer.ets index 4da4845..f56c3a1 100644 --- a/entry/src/main/ets/pages/phone/dialer/Dialer.ets +++ b/entry/src/main/ets/pages/phone/dialer/Dialer.ets @@ -16,9 +16,8 @@ import CallRecord from '../../dialer/callRecord/CallRecord'; import { HiLog } from '../../../../../../../common/src/main/ets/util/HiLog'; import DialerPresenter from './../../../presenter/dialer/DialerPresenter'; -import EnvironmentProp from '../../../feature/EnvironmentProp'; -import IndexPresenter from '../../../presenter/IndexPresenter'; import { DialerButtonView } from "../../../component/dialer/DialerButtonView"; +import { PhoneNumber } from '../../../../../../../feature/phonenumber/src/main/ets/PhoneNumber'; const TAG = 'Dialer'; @@ -30,47 +29,59 @@ struct DialButton { build() { Column() { - Button() { - Column() { - if (`${this.button_number}` == '*') { - Image($r("app.media.symbol_phone")) - .width(24) - .height(24) - } else if (`${this.button_number}` == '#') { - Image($r("app.media.symbols_phone")) - .width(24) - .height(24) - } else { - Text(`${this.button_number}`) - .fontSize(25) - .fontColor($r('sys.color.ohos_id_color_text_primary')) - .fontWeight(FontWeight.Medium) - } - if ((this.button_char == 'ic')) { - Image($r("app.media.ic_contacts_voicemail_mini")) - .width(14) - .height(14) - } else if ((this.button_char == '+')) { - Text(`${this.button_char}`) - .fontColor($r('sys.color.ohos_id_color_text_secondary')) - .fontSize(17.5) - } else { - Text(`${this.button_char}`) - .fontColor($r('sys.color.ohos_id_color_text_secondary')) - .fontSize($r("sys.float.ohos_id_text_size_chart1")) + Column() { + Button() { + Column() { + if (`${this.button_number}` == '*') { + Image($r("app.media.symbol_phone")) + .width(24) + .height(24) + } else if (`${this.button_number}` == '#') { + Image($r("app.media.symbols_phone")) + .width(24) + .height(24) + } else { + Text(`${this.button_number}`) + .fontSize(25) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .fontWeight(FontWeight.Medium) + } + if ((this.button_char == 'ic')) { + Image($r("app.media.ic_contacts_voicemail_mini")) + .width(14) + .height(14) + } else if ((this.button_char == '+')) { + Text(`${this.button_char}`) + .fontColor($r('sys.color.ohos_id_color_text_secondary')) + .fontSize(17.5) + } else { + Text(`${this.button_char}`) + .fontColor($r('sys.color.ohos_id_color_text_secondary')) + .fontSize($r("sys.float.ohos_id_text_size_chart1")) + } } } + .backgroundColor($r('sys.color.ohos_id_color_panel_bg')) + .type(ButtonType.Circle) + .height('100%') + .width('100%') } - .backgroundColor($r('sys.color.ohos_id_color_panel_bg')) - .type(ButtonType.Circle) - .height('100%') + .justifyContent(FlexAlign.Center) .width('100%') - .onClick(() => { - this.mPresenter.ifNeedSpace(); - AppStorage.SetOrCreate("tele_number", AppStorage.Get("tele_number") + this.button_number); - this.mPresenter.all_number += this.button_number - this.mPresenter.viewNumberTextProc(); - this.mPresenter.playAudio(this.button_number); + .onTouch((event: TouchEvent) => { + switch (event.type) { + case TouchType.Down: + this.mPresenter.ifNeedSpace(); + AppStorage.SetOrCreate("tele_number", AppStorage.Get("tele_number") + this.button_number); + this.mPresenter.all_number += this.button_number; + this.mPresenter.dealSecretCode(this.mPresenter.all_number); + PhoneNumber.fromString(this.mPresenter.secretCode).dialerSpecialCode(); + this.mPresenter.viewNumberTextProc(); + this.mPresenter.playAudio(this.button_number); + break; + case TouchType.Up: + break; + } }) }.height($r("app.float.dialer_calllog_item_height")) .width('33.33%') @@ -128,7 +139,7 @@ export default struct Call { @StorageLink("haveMultiSimCard") @Watch("onSimChanged") haveMultiSimCard: boolean = false; @LocalStorageProp('breakpoint') curBp: string = 'sm'; @State panWidth: number = 0; - @StorageLink("showDialBtn") @Watch('onShowDialBtnChange') showDialBtn: boolean = false; + @StorageLink("showDialBtn") @Watch('onShowDialBtnChange') showDialBtn: boolean = true; onSimChanged() { HiLog.i(TAG, "haveMultiSimCard change:" + JSON.stringify(this.haveMultiSimCard)); @@ -137,6 +148,10 @@ export default struct Call { } onShowDialBtnChange() { + if (this.showDialBtn != this.mPresenter.btnShow) { + HiLog.i(TAG, 'onShowDialBtnChange not change'); + return; + } HiLog.i(TAG, 'onShowDialBtnChange ' + this.showDialBtn); if (this.showDialBtn) { HiLog.i(TAG, 'show DialBtn '); @@ -150,9 +165,9 @@ export default struct Call { }, () => { this.mPresenter.call_p = 0; this.mPresenter.call_y = 0; - this.mPresenter.moveY = 0 + this.mPresenter.moveY = 0; this.mPresenter.dialerButtonWidth = this.haveMultiSimCard ? 210 : 56; - this.mPresenter.dialerButtonHeight = 56 + this.mPresenter.dialerButtonHeight = 56; }) this.mPresenter.btnShow = false; } else { @@ -170,7 +185,7 @@ export default struct Call { aboutToAppear() { HiLog.i(TAG, 'aboutToAppear CallTablet '); - this.showDialBtn = true; + this.onShowDialBtnChange(); this.mPresenter.aboutToAppear(); } @@ -189,6 +204,100 @@ export default struct Call { } .width('100%') .height($r("app.float.dialer_telephone_number_height")) + Flex({ + direction: FlexDirection.Column, + alignItems: ItemAlign.Center, + justifyContent: FlexAlign.Start + }) { + List({ space: 0, initialIndex: 0 }) { + LazyForEach(this.mPresenter.mAllCallRecordListDataSource, (item, index: number) => { + ListItem() { + Flex({ + direction: FlexDirection.Row, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center + }) { + Column() { + Row() { + ForEach(item.displayName.split(this.tele_number.replace(/\s*/g,"")), (displayName, idx: number) => { + Row() { + Text(displayName.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + if(idx === 0 && item.displayName.indexOf(this.tele_number.replace(/\s*/g,"")) !== -1){ + Text(this.tele_number.toString().replace(/\s*/g,"")) + .fontColor(Color.Blue) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontWeight(FontWeight.Medium) + .constraintSize({ maxWidth: this.curBp == "sm" ? 160 : 260 }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + }else if(item.displayName.split(this.tele_number.replace(/\s*/g,"")).length - 1 !== idx) { + Text(this.tele_number.toString().replace(/\s*/g,"")) + + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontWeight(FontWeight.Medium) + .fontColor((item.callType === 3 || item.callType === 5) ? + $r('sys.color.ohos_id_color_warning') : $r('sys.color.ohos_id_color_text_primary')) + .constraintSize({ maxWidth: this.curBp == "sm" ? 160 : 260 }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + } + } + }) + } + + Row() { + ForEach(item.phoneNumber.split(this.tele_number.replace(/\s*/g,"")), (phoneNumber, idx: number) => { + Row() { + Text(phoneNumber.toString()) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + if(idx === 0 && item.phoneNumber.indexOf(this.tele_number.replace(/\s*/g,""))!== -1){ + Text(this.tele_number.toString().replace(/\s*/g,"")) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor(Color.Blue) + }else if(item.phoneNumber.split(this.tele_number.replace(/\s*/g,"")).length - 1 !== idx) { + Text(this.tele_number.toString().replace(/\s*/g,"")) + .fontSize($r("sys.float.ohos_id_text_size_body1")) + .fontColor($r('sys.color.ohos_id_color_text_tertiary')) + } + } + }) + } + }.onClick(() => { + this.mPresenter.jumpToContactDetail(item.phoneNumber); + }) + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + .justifyContent(FlexAlign.Center) + .height($r("app.float.id_item_height_max")) + Image($r('app.media.ic_public_detail')) + .height($r('app.float.id_card_margin_max')) + .width($r('app.float.id_card_margin_max')) + .objectFit(ImageFit.Contain) + .borderRadius($r('app.float.id_card_margin_xl')) + .onClick(() => { + this.mPresenter.jumpToContactDetail(item.phoneNumber); + }) + } + .margin({ + left: $r("app.float.id_card_image_small"), + right: $r("app.float.id_card_image_small") + }) + } + .height($r("app.float.id_item_height_max")) + }, item => JSON.stringify(item)) + } + .divider({ + strokeWidth: 0.5, + color: $r('sys.color.ohos_id_color_list_separator'), + startMargin: $r("app.float.id_card_margin_max"), + endMargin: $r("app.float.id_card_margin_max") + }) + } + .height('100%') + .zIndex(1) } if (this.tele_number.length === 0) { Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) { @@ -225,7 +334,6 @@ export default struct Call { if (this.mPresenter.callBtnClick) { return; } - this.mPresenter.panelShow = false; this.showDialBtn = false; }) } @@ -286,22 +394,25 @@ export default struct Call { }) //This component is temporarily shielded because there is no requirement currently. - // Button() { - // Column() { - // Image($r("app.media.ic_public_contacts_group")) - // .width($r("app.float.id_card_image_small")) - // .height($r("app.float.id_card_image_small")) - // .margin({ bottom: this.curBp === 'lg' ? 0 : '3vp' }) - // Text($r("app.string.save_to_existing_contacts")) - // .fontSize($r("sys.float.ohos_id_text_size_caption1")) - // .fontWeight(FontWeight.Medium) - // .fontColor($r("sys.color.ohos_id_color_text_primary")) - // } - // } - // .layoutWeight(1) - // .type(ButtonType.Normal) - // .height($r("app.float.id_item_height_large")) - // .backgroundColor($r('sys.color.ohos_id_color_panel_bg')) + Button() { + Column() { + Image($r("app.media.ic_public_contacts_group")) + .width($r("app.float.id_card_image_small")) + .height($r("app.float.id_card_image_small")) + .margin({ bottom: this.curBp === 'lg' ? 0 : '3vp' }) + Text($r("app.string.save_to_existing_contacts")) + .fontSize($r("sys.float.ohos_id_text_size_caption1")) + .fontWeight(FontWeight.Medium) + .fontColor($r("sys.color.ohos_id_color_text_primary")) + } + } + .layoutWeight(1) + .type(ButtonType.Normal) + .height($r("app.float.id_item_height_large")) + .backgroundColor($r('sys.color.ohos_id_color_panel_bg')) + .onClick(() => { + this.mPresenter.saveCallRecordExistingContact(); + }) Button() { Column() { @@ -325,7 +436,7 @@ export default struct Call { } .width('100%') .height($r("app.float.id_item_height_large")) - .offset({ x: 0, y: -336 }) + .offset({ x: -23, y: -336 }) .zIndex(3) .padding({ top: 4 }) } @@ -368,13 +479,15 @@ export default struct Call { .gesture( LongPressGesture({ repeat: false, fingers: 1, duration: 700 }) .onAction((event: GestureEvent) => { + AppStorage.SetOrCreate("tele_number", ''); + this.mPresenter.all_number = ''; this.mPresenter.editPhoneNumber(""); }) ) .onClick(() => { - this.mPresenter.isClickDelete = true; this.mPresenter.pressVibrate(); - this.mPresenter.all_number = this.mPresenter.all_number.substr(0, this.mPresenter.all_number.length - 1) + let number: string = AppStorage.Get("tele_number"); + this.mPresenter.all_number = number.substr(0, number.length - 1); this.mPresenter.deleteTeleNum(); this.mPresenter.deleteAddSpace(); this.mPresenter.viewNumberTextProc(); @@ -388,7 +501,7 @@ export default struct Call { } .margin({ top: this.tele_number.length >= 3 ? 55.6 : 0 }) .width('100%') - .zIndex(this.mPresenter.panelShow ? 2 : -1) + .zIndex(this.showDialBtn ? 2 : -1) } .width("100%") .backgroundColor($r('sys.color.ohos_id_color_panel_bg')) diff --git a/entry/src/main/ets/presenter/IndexPresenter.ets b/entry/src/main/ets/presenter/IndexPresenter.ets index 159c999..a240bdc 100644 --- a/entry/src/main/ets/presenter/IndexPresenter.ets +++ b/entry/src/main/ets/presenter/IndexPresenter.ets @@ -20,13 +20,13 @@ import { StringUtil } from '../../../../../common/src/main/ets/util/StringUtil'; import StringFormatUtil from '../util/StringFormatUtil'; import { missedCallManager } from '../feature/missedCall/MissedCallManager'; import CallRecordPresenter from './dialer/callRecord/CallRecordPresenter'; +import FavoriteListPresenter from './favorite/FavoriteListPresenter'; const TAG = 'IndexPresenter '; export default class IndexPresenter { private static instance: IndexPresenter; - public static getInstance(): IndexPresenter { if (!IndexPresenter.instance) { IndexPresenter.instance = new IndexPresenter(); @@ -42,27 +42,24 @@ export default class IndexPresenter { CallRecordPresenter.getInstance().requestItem(); AppStorage.SetOrCreate("sysTime", parseInt(StringFormatUtil.judgeSysTime())); } + FavoriteListPresenter.getInstance().onPageShow(); } - getCurrentUri() { - const state = router?.getState() - if (!state) { - return ''; - } - return state.path + state.name + getCurrentUrl(){ + let url = router.getState().path+router.getState().name; + HiLog.i(TAG,"getCurrentUrl:"+url) + return url; } goToPage(url: string, pageIndex?: number, params?) { - HiLog.i(TAG, "goToPage:" + url) - if (url == this.getCurrentUri() || url == globalThis.presenterManager.mainUrl) { - router.back({ - url: url, params: params - }) - return - } + HiLog.i(TAG, "goToPage: " + url); if (pageIndex != undefined && router.getState().index >= pageIndex) { - if (pageIndex == 1) { - router.clear(); + if (url == globalThis.presenterManager?.mainUrl || this.getCurrentUrl() == url) { + router.back({ + url: url, + params: params + }) + return; } router.replaceUrl({ url: url, @@ -106,7 +103,10 @@ export default class IndexPresenter { getTabSrc(tabIndex: number, index: number): Resource { let imgSrc = $r('app.media.ic_call_filled_normal'); if (index === 1) { - imgSrc = $r("app.media.ic_contacts_normal_filled"); + imgSrc = $r('app.media.ic_contacts_normal_filled'); + } + if (index === 2) { + imgSrc = $r('app.media.ic_feature_normal_filled'); } return imgSrc; } @@ -116,6 +116,9 @@ export default class IndexPresenter { if (index === 1) { text = $r('app.string.contact'); } + if (index === 2) { + text = $r('app.string.favorite'); + } return text; } diff --git a/entry/src/main/ets/presenter/PresenterManager.ets b/entry/src/main/ets/presenter/PresenterManager.ets index 1e0e584..b2643c0 100644 --- a/entry/src/main/ets/presenter/PresenterManager.ets +++ b/entry/src/main/ets/presenter/PresenterManager.ets @@ -35,7 +35,7 @@ export default class PresenterManager { } onCreate(want: Want) { - HiLog.i(TAG, "onCreate") + HiLog.i(TAG, "onCreate"); this.onRequest(want.parameters, true); this.callRecordPresenter.onCreate(this.context, this.worker); this.contactListPresenter.onCreate(this.context, this.worker); @@ -45,7 +45,8 @@ export default class PresenterManager { } onNewWant(want: Want) { - this.onRequest(want.parameters, false) + HiLog.i(TAG, "onNewWant"); + this.onRequest(want.parameters, false); } initDataCache() { @@ -153,13 +154,14 @@ export default class PresenterManager { } if (isOnCreate) { this.mainUrl = url; + AppStorage.SetOrCreate('params', params); } else { AppStorage.SetOrCreate(Constants.Storage.targetPage, { url: url, pageIndex: pageIndex, params: params }) - HiLog.i(TAG, "pageRouteHandler:" + JSON.stringify(AppStorage.Get("targetPage"))); + HiLog.i(TAG, "pageRouteHandler finish!"); } } } \ No newline at end of file diff --git a/entry/src/main/ets/presenter/contact/ContactListPresenter.ets b/entry/src/main/ets/presenter/contact/ContactListPresenter.ets index f97e558..c435dfe 100644 --- a/entry/src/main/ets/presenter/contact/ContactListPresenter.ets +++ b/entry/src/main/ets/presenter/contact/ContactListPresenter.ets @@ -19,11 +19,16 @@ import { ContactVo } from '../../model/bean/ContactVo'; import { ArrayUtil } from '../../../../../../common/src/main/ets/util/ArrayUtil'; import { CallLogRepository } from '../../../../../../feature/call/src/main/ets/repo/CallLogRepository'; import { ContactRepository } from '../../../../../../feature/contact/src/main/ets/repo/ContactRepository'; +import emitter from '@ohos.events.emitter'; import ContactListDataSource from '../../model/bean/ContactListDataSource'; import WorkerWrapper from '../../workers/base/WorkerWrapper'; +import { SearchContactsBean } from '../../model/bean/SearchContactsBean'; +import SearchContactsSource from '../../model/bean/SearchContactsSource'; +import AlphabetIndexerPresenter from './alphabetindex/AlphabetIndexerPresenter'; const TAG = 'ContactListPresenter '; const DELAY_TIME: number = 1000; +const EMITTER_SEARCH_ID: number = 105; /** * Type of the control that is clicked in the contact list. */ @@ -50,12 +55,22 @@ export default class ContactListPresenter { shareList: Resource[] = [$r("app.string.qr_code"), $r("app.string.v_card"), $r("app.string.text")]; settingsMenu: Resource[] = [$r("app.string.contact_setting_type_scancard"), $r("app.string.call_setting_type_setting")]; contactListDataSource: ContactListDataSource = new ContactListDataSource(); + searchContactsSource: SearchContactsSource = new SearchContactsSource(); isShow: boolean = false; context: Context; worker: WorkerWrapper; loading: boolean; initStarted: boolean = false; - taskId: number = -1; + contactSearchList: SearchContactsBean[] = []; + contactSearchCount: number = 0; + tempValue: string = ''; + taskId: number = -1;; + isSearchBackgroundColor: boolean = true; + isSearchPage: boolean = false; + inputKeyword: string = ''; + contactList: ContactVo[] = []; + alphabetIndexPresenter: AlphabetIndexerPresenter = AlphabetIndexerPresenter.getInstance(); + refreshState: () => void onContactChange = () => { HiLog.i(TAG, 'onContactChange refresh'); @@ -206,6 +221,8 @@ export default class ContactListPresenter { HiLog.i(TAG, `refreshPage ${page} getAllContact, length is: ` + result.length); if (Array.prototype.isPrototypeOf(result)) { this.contactListDataSource.refresh(this.refreshIndex, this.contactListPages[page -1], result); + this.contactList.splice(this.refreshIndex, this.contactListPages[page -1], ...result); + this.alphabetIndexPresenter.initContactList(this.contactList); } this.contactListPages[page -1] = result.length; this.refreshIndex += this.contactListPages[page -1]; @@ -354,4 +371,56 @@ export default class ContactListPresenter { } ); } + + getSearchContact(value: string) { + HiLog.i(TAG, 'getSearchContact start.'); + if ('' === value) { + this.isSearchBackgroundColor = true; + this.searchContactsSource.refresh(this.contactSearchList); + let innerEvent = { + eventId: 102, + priority: emitter.EventPriority.HIGH + }; + emitter.emit(innerEvent, { + data: { + 'contactSearchList': 0 + } + }); + return; + } + this.tempValue = value; + let actionData: any = {}; + actionData.value = this.tempValue; + globalThis.DataWorker.sendRequest("getSearchContact", { + actionData: actionData, + context: globalThis.context + }, (result) => { + this.isSearchBackgroundColor = false; + this.contactSearchList = result; + this.contactSearchCount = this.contactSearchList.length; + this.searchContactsSource.refresh(this.contactSearchList); + let innerEvent = { + eventId: 102, + priority: emitter.EventPriority.HIGH + }; + emitter.emit(innerEvent, { + data: { + 'contactSearchList': this.contactSearchCount + } + }); + }) + HiLog.i(TAG, 'getSearchContact end.'); + } + + sendEmitter(isSearchPage: boolean) { + let innerEvent = { + eventId: EMITTER_SEARCH_ID, + priority: emitter.EventPriority.HIGH + }; + emitter.emit(innerEvent, { + data: { + 'isSearchPage': isSearchPage + } + }); + } } \ No newline at end of file diff --git a/entry/src/main/ets/presenter/contact/accountants/AccountantsPresenter.ets b/entry/src/main/ets/presenter/contact/accountants/AccountantsPresenter.ets index bc8331d..f54d60c 100644 --- a/entry/src/main/ets/presenter/contact/accountants/AccountantsPresenter.ets +++ b/entry/src/main/ets/presenter/contact/accountants/AccountantsPresenter.ets @@ -55,13 +55,17 @@ export default class AccountantsPresenter { isShowPosition: boolean = false; showMore: boolean = false; addState: boolean = false; + phones: string = ''; + editContact: number = -1; + phoneNumberShow: string = ''; + callId: string = ''; // refresh mark changed: boolean = false; // contact detail - contactInfoBefore: ContactInfo = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], []); - contactInfoAfter: ContactInfo = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], []); + contactInfoBefore: ContactInfo = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], [], 0); + contactInfoAfter: ContactInfo = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], [], 0); MagList: object = []; private constructor() { @@ -80,8 +84,8 @@ export default class AccountantsPresenter { this.contactId = ""; this.updateShow = false; this.MagList = [1]; - this.contactInfoBefore = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], []); - this.contactInfoAfter = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], []); + this.contactInfoBefore = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], [], 0); + this.contactInfoAfter = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], [], 0); this.clickBefEvent = new Date(); this.clickAftEvent = this.clickBefEvent; this.refreshState = refreshState; @@ -172,7 +176,7 @@ export default class AccountantsPresenter { } private dealRecordDetailsData(data) { - let contactTemp = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], []); + let contactTemp = new ContactInfo("", "", "", [], [], "", "", "", [], [], [], [], [], [], 0); if (!data.hasOwnProperty('id') || data.id != this.contactId) { HiLog.e(TAG, 'Failed to query the database based on the ID.'); return; @@ -223,6 +227,26 @@ export default class AccountantsPresenter { contactTemp.setGroups(data.groups); } this.contactInfoBefore = contactTemp; + if (0 === this.editContact) { + let saveTemp: Array<{ [key: string]: any }> = this.getArray(this.phones); + for(let i = 0; i < saveTemp.length ; i ++){ + let phoneNumBean: PhoneNumBean = new PhoneNumBean('','','','',''); + phoneNumBean.id = saveTemp[i].item.id; + phoneNumBean.num = saveTemp[i].item.num; + phoneNumBean.numType = saveTemp[i].item.type; + phoneNumBean.homeArea = ''; + phoneNumBean.carriers = ''; + this.contactInfoBefore.phones.push(phoneNumBean); + } + } else if (1 === this.editContact || 2 === this.editContact) { + let phoneNumBean: PhoneNumBean = new PhoneNumBean('','','','',''); + phoneNumBean.id = this.callId; + phoneNumBean.num = this.phoneNumberShow; + phoneNumBean.numType = ''; + phoneNumBean.homeArea = ''; + phoneNumBean.carriers = ''; + this.contactInfoBefore.phones.push(phoneNumBean); + } this.getPhones = this.getArray(this.contactInfoBefore.phones); this.getEmails = this.getArray(this.contactInfoBefore.emails); this.contactInfoAfter = contactTemp; diff --git a/entry/src/main/ets/presenter/contact/alphabetindex/AlphabetIndexerPresenter.ets b/entry/src/main/ets/presenter/contact/alphabetindex/AlphabetIndexerPresenter.ets new file mode 100644 index 0000000..431269c --- /dev/null +++ b/entry/src/main/ets/presenter/contact/alphabetindex/AlphabetIndexerPresenter.ets @@ -0,0 +1,119 @@ +/** + * 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 {ContactVo, NameVo } from '../../..//model/bean/ContactVo'; +import { ArrayUtil } from '../../../../../../../common/src/main/ets/util/ArrayUtil'; +import { HiLog } from '../../../../../../../common/src/main/ets/util/HiLog'; + +const TAG = 'AlphabetIndexerPresenter'; +const CHINESE_CHAR_CODE = 255; + +export default class AlphabetIndexerPresenter { + private static sInstance: AlphabetIndexerPresenter; + alphabetIndexList: string[] = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; + private alphabetObj: {[key: string] : NameVo[]} = {}; + private alphabetIndexObj: {[key: string] : number} = {}; + private contactList: ContactVo[] = []; + + public static getInstance(): AlphabetIndexerPresenter { + if (AlphabetIndexerPresenter.sInstance == null) { + HiLog.i(TAG, 'alphabetIndexer getInstance!'); + AlphabetIndexerPresenter.sInstance = new AlphabetIndexerPresenter(); + } + return AlphabetIndexerPresenter.sInstance; + } + + initContactList(list: ContactVo[]) { + this.contactList = list.slice(); + this.getAlphabetIndexData(); + } + + getAlphabetIndexData() { + if (ArrayUtil.isEmpty(this.contactList)) { + return null; + } + // Get the position of the index in the list + // Get index data + this.contactList.forEach((item, index) => { + let preContact: ContactVo = null; + if (index > 0) { + preContact = this.contactList[index - 1]; + } + if (index == 0 || !(item.namePrefix == preContact.namePrefix)) { + this.alphabetObj[item.namePrefix] = []; + this.alphabetIndexObj[item.namePrefix] = this.contactList.indexOf(item); + } + let nameVo = new NameVo(item.emptyNameData, item.namePrefix, item.nameSuffix); + this.alphabetObj[item.namePrefix].push(nameVo); + }) + } + + getAlphabetPopData(index: number): string[] { + let popData: string[] = []; + if (this.alphabetIndexList.length <= index) { + return popData; + } + + // Get Index Popup Data + let selected = this.alphabetIndexList[index]; + let list = this.alphabetObj[selected]; + if (list && list.length !== 0) { + list.forEach(item => { + if(item.nameSuffix.charCodeAt(0) > CHINESE_CHAR_CODE) { + if (popData.length === 0) { + popData.push(item.nameSuffix); + } else { + let hasIndex = popData.indexOf(item.nameSuffix); + if (hasIndex === -1) { + popData.push(item.nameSuffix); + } + } + } + }); + } + return popData; + } + + getListScrollIndex(selectedAlphabetIndex: number, popDataSource?: string[], popIndex?: number): number { + // get list scroll index + let selected = this.alphabetIndexList[selectedAlphabetIndex]; + let alphabetIndex = this.alphabetIndexObj[selected]; + let scrollIndex: number = alphabetIndex; + if (popIndex >= 0) { + let alphabetContacts = this.alphabetObj[selected]; + if (alphabetContacts) { + let popData = popDataSource[popIndex]; + for (let index = 0; index < alphabetContacts.length; index++) { + const element = alphabetContacts[index]; + if (element.nameSuffix === popData) { + scrollIndex = scrollIndex + index; + break; + } + } + } + } + return scrollIndex; + } + + getAlphabetSelected(scrollIndex: number): number { + let selected = 0; + if (this.contactList.length > scrollIndex) { + let obj = this.contactList[scrollIndex]; + let namePrefix = obj.namePrefix; + selected = this.alphabetIndexList.indexOf(namePrefix); + } + return selected; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/presenter/contact/batchselectcontacts/BatchSelectContactsPresenter.ets b/entry/src/main/ets/presenter/contact/batchselectcontacts/BatchSelectContactsPresenter.ets index 55e8f6d..2e05e79 100644 --- a/entry/src/main/ets/presenter/contact/batchselectcontacts/BatchSelectContactsPresenter.ets +++ b/entry/src/main/ets/presenter/contact/batchselectcontacts/BatchSelectContactsPresenter.ets @@ -24,6 +24,8 @@ import router from '@ohos.router'; import BatchSelectRecentSource from '../../../model/bean/BatchSelectRecentSource'; import BatchSelectContactSource from '../../../model/bean/BatchSelectContactSource'; import CallLogSetting from "../../../../../../../feature/call/src/main/ets/CallLogSetting" +import { ContactVo } from '../../../model/bean/ContactVo'; +import AlphabetIndexerPresenter from '../alphabetindex/AlphabetIndexerPresenter'; const TAG = 'BatchSelectContactsPresenter '; @@ -62,6 +64,15 @@ export default class BatchSelectContactsPresenter { recentSource: BatchSelectRecentSource = new BatchSelectRecentSource(); contactsSource: BatchSelectContactSource = new BatchSelectContactSource(); actionData: { [key: string]: any } = {}; + alphabetIndexPresenter: AlphabetIndexerPresenter = AlphabetIndexerPresenter.getInstance(); + editContact: number = -1; + contactId: number; + /** Contact Temporary */ + callId: string = ''; + phones: string = ''; + phoneNumberShow: string = ''; + isNewNumber: boolean = false; + addFavorite: number = -1; public static getInstance(): BatchSelectContactsPresenter { if (BatchSelectContactsPresenter.sInstance == null) { @@ -84,6 +95,30 @@ export default class BatchSelectContactsPresenter { onPageHide() { } + backCancel() { + if (0 === this.addFavorite) { + router.back(); + } else { + this.cancel(); + } + } + + singleBackCancel() { + if (0 === this.editContact) { + router.replaceUrl({ + url: 'pages/contacts/details/ContactDetail', + params: { + sourceHasPhone: true, + phoneNumberShow: this.phoneNumberShow + } + }) + } else if (1 === this.editContact || 2 === this.editContact) { + router.back(); + } else { + this.cancel(); + } + } + cancel() { let parameters = { contactObjects: "" @@ -109,6 +144,31 @@ export default class BatchSelectContactsPresenter { this.initialIndex = firstIndex; } + selectBatchContact() { + if (0 === this.addFavorite) { + this.addFavoriteContacts(); + } else { + this.comfirm() + } + } + + addFavoriteContacts() { + HiLog.i(TAG, 'addFavoriteContacts start.'); + let checkedList = []; + this.contactsList.forEach((value) => { + if (value.phoneNumbers.length > 0) { + value.phoneNumbers.forEach((values) => { + if (values.checked === true) { + checkedList.push(value.contactId); + } + }) + } + }) + AppStorage.SetOrCreate>('addFavoriteContactData', checkedList); + router.back(); + HiLog.i(TAG, 'addFavoriteContacts end.'); + } + comfirm() { let checkedList = []; this.selectedNumberMap.forEach((value) => { @@ -134,6 +194,41 @@ export default class BatchSelectContactsPresenter { }); } + /** + * Edit Contact + */ + updateContact(contactId: string) { + let upDataShow = false; + if (contactId != undefined) { + upDataShow = true + } + router.replaceUrl({ + url: 'pages/contacts/accountants/Accountants', + params: { + updataShow: upDataShow, + contactId: contactId, + callId: this.callId, + phones: this.phones, + editContact: this.editContact, + phoneNumberShow: this.phoneNumberShow + }, + }); + } + + dealContactSelectId(checkedList) { + let contacts = []; + for (let item of checkedList) { + if (item.phoneNumbers) { + + } + let contact = { + contactId: item.contactId, + }; + contacts.push(contact); + } + return contacts; + } + dealContactName(checkedList) { let contacts = []; for (let item of checkedList) { @@ -314,13 +409,17 @@ export default class BatchSelectContactsPresenter { }); } - onSingleContactItemClick(num: number, name: string) { + onSingleContactItemClick(num: number, name: string, item: ContactVo) { HiLog.i(TAG, 'onSingleContactItemClick in '); this.selectedNumberMap.set(num, { name: name, number: num }); - this.comfirm(); + if (0 === this.editContact || 1 === this.editContact || 2 === this.editContact) { + this.updateContact(item.contactId); + } else { + this.comfirm(); + } } onContactItemClicked(index: number, indexChild: number) { @@ -576,11 +675,17 @@ export default class BatchSelectContactsPresenter { HiLog.i(TAG, 'initCallLog start !'); let tempMap = new Map(); let tempList: any[] = []; - + let favoriteForm: any = {} + if (0 === this.addFavorite) { + favoriteForm.favorite = 0; + } else { + favoriteForm.favorite = -1; + } globalThis.DataWorker?.sendRequest("getAllCalls", { context: globalThis.context, mergeRule: CallLogSetting.getInstance().getMergeRule(), - actionData: this.actionData + actionData: this.actionData, + favoriteForm: JSON.stringify(favoriteForm) }, (data) => { if (data.hasOwnProperty('callLogList') && !ArrayUtil.isEmpty(data.callLogList)) { HiLog.i(TAG, 'data has callLogList key'); @@ -633,8 +738,16 @@ export default class BatchSelectContactsPresenter { */ initContactsList() { HiLog.i(TAG, 'initContactsList start!'); + let favoriteForm: any = {} + if (0 === this.addFavorite) { + favoriteForm.favorite = 0; + } else { + favoriteForm.favorite = -1; + } + favoriteForm.editContact = this.editContact globalThis.DataWorker.sendRequest("getAllContactWithPhoneNumbers", { - context: globalThis.context + context: globalThis.context, + favoriteForm: JSON.stringify(favoriteForm) }, (resultList) => { HiLog.i(TAG, 'initContactsList resultList success ' + resultList.length); let listTemp: any[] = []; @@ -657,7 +770,10 @@ export default class BatchSelectContactsPresenter { this.contactsSource.refresh(this.contactsList); this.tabInfo.contactsTotal = this.contactsList.length; this.contactsInfo.contactsListTotal = this.contactsList.length; + this.alphabetIndexPresenter.initContactList(this.contactsList); } else { + HiLog.e(TAG, 'select contact list is empty!' + JSON.stringify(this.contactsList)); + this.contactsSource.refresh(this.contactsList); HiLog.e(TAG, 'select contact list is empty!'); } }); diff --git a/entry/src/main/ets/presenter/contact/detail/DetailPresenter.ets b/entry/src/main/ets/presenter/contact/detail/DetailPresenter.ets index bf74844..d82c0b1 100644 --- a/entry/src/main/ets/presenter/contact/detail/DetailPresenter.ets +++ b/entry/src/main/ets/presenter/contact/detail/DetailPresenter.ets @@ -54,8 +54,10 @@ export default class DetailPresenter { topbarBackgroundColor: ResourceColor = Color.Transparent; newNumberBgColor: string = ''; detailsColor: string = ''; + isFavorited : string = '0' /** detail parameters */ contactForm: any = { + contactId: -1, display_name: '', photoFirstName: '', company: '', @@ -71,7 +73,8 @@ export default class DetailPresenter { relationships: [], numRecords: [], portraitColor: '', - detailsBgColor: '' + detailsBgColor: '', + favorite: 0 }; detailCallLogDataSource: DetailCallLogDataSource = new DetailCallLogDataSource(); /** Contact Temporary */ @@ -107,7 +110,8 @@ export default class DetailPresenter { relationships: [], numRecords: [], portraitColor: '', - detailsBgColor: '' + detailsBgColor: '', + favorite: 0 }; let requestData = { contactId: this.contactId @@ -133,7 +137,8 @@ export default class DetailPresenter { relationships: [], numRecords: [], portraitColor: '', - detailsBgColor: '' + detailsBgColor: '', + favorite: 0 }; /** Query the contact ID based on the phone number. If a contact exists,the contact details are displayed based on the first contact ID.*/ @@ -195,6 +200,7 @@ export default class DetailPresenter { this.contactForm.portraitColor = MorandiColor.Color[Math.abs(parseInt(result.data.id, 10)) % 6]; this.contactForm.detailsBgColor = MorandiColor.detailColor[Math.abs(parseInt(result.data.id, 10)) % 6]; this.detailsColor = MorandiColor.detailColor[Math.abs(parseInt(result.data.id, 10)) % 6]; + this.isFavorited = this.contacts.favorite; this.dealDetailsData(); }); } @@ -722,4 +728,33 @@ export default class DetailPresenter { }); return mMoreMenu; } + + updateFavorite(favoriteStatus: number) { + HiLog.i(TAG, 'updateFavorite start.'); + let favoriteForm: any = {} + favoriteForm.id = this.contactId; + favoriteForm.favorite = favoriteStatus; + globalThis.DataWorker.sendRequest("updateFavorite", { + context: globalThis.context, + favoriteForm: JSON.stringify(favoriteForm) + }, (arg) => { + HiLog.i(TAG, 'updateFavorite success.'); + }) + } + + saveExistingContact() { + HiLog.i(TAG, 'updateFavorite start.'); + router.replaceUrl({ + url: 'pages/contacts/batchselectcontacts/SingleSelectContactPage', + params: { + phoneNumberShow: this.phoneNumberShow, + contactForm: this.contactForm, + contactId: this.contactId, + contactsId: this.contacts.id, + phones: this.contactForm.phones, + editContact: 0, + selectType: 0 + } + }); + } } \ No newline at end of file diff --git a/entry/src/main/ets/presenter/dialer/DialerPresenter.ets b/entry/src/main/ets/presenter/dialer/DialerPresenter.ets index 1e1cb5b..523dfe2 100644 --- a/entry/src/main/ets/presenter/dialer/DialerPresenter.ets +++ b/entry/src/main/ets/presenter/dialer/DialerPresenter.ets @@ -20,8 +20,10 @@ import observer from '@ohos.telephony.observer'; import PreferencesUtil from '../../util/PreferencesUtil'; import { PhoneNumber } from '../../../../../../feature/phonenumber/src/main/ets/PhoneNumber'; import IndexPresenter from '../IndexPresenter'; - +import CallRecordListDataSource from '../../model/bean/CallRecordListDataSource'; const TAG = 'DialerPresenter'; +const SECRET_CODE_START: string = '*#*#'; +const SECRET_CODE_END: string = '#*#*'; /** * dialer presenter @@ -33,9 +35,7 @@ export default class DialerPresenter { readonly NUM_TEXT_MAXSIZE_LENGTH = 14; readonly NUM_TEXT_FONT_SIZE_MAX = 38; private timer: any = null; - panelShow: boolean = false; btnShow: boolean = true; - isClickDelete: boolean = false; isEmergencyNum: boolean = false; tele_number: string = ""; tele_num_size: number = this.NUM_TEXT_FONT_SIZE_MAX; @@ -48,7 +48,9 @@ export default class DialerPresenter { dialerRadius = 24; refreshView: boolean; callBtnClick: boolean; - + secretCode: string = ''; + mAllCallRecordListDataSource: CallRecordListDataSource = new CallRecordListDataSource(); + callLogSearchList: any[] = []; static getInstance() { if (this.mPresenter == null) { this.mPresenter = new DialerPresenter(); @@ -80,14 +82,15 @@ export default class DialerPresenter { } editPhoneNumber(phoneNum): void { - if (StringUtil.isEmpty(phoneNum) || this.isClickDelete) { - this.isClickDelete = false; + if (StringUtil.isEmpty(phoneNum)) { return; } + HiLog.i(TAG, 'editPhoneNumber'); AppStorage.SetOrCreate("tele_number", phoneNum); this.all_number = phoneNum; this.viewNumberTextProc(); this.deleteAddSpace(); +// this.callHistorySearch() } onDestroy() { @@ -97,6 +100,7 @@ export default class DialerPresenter { * Change the font size when deleting a number. */ deleteTeleNum() { +// this.callHistorySearch() let number: string = AppStorage.Get("tele_number"); if (this.all_number.length < this.NUM_TEXT_MAX_LENGTH) { AppStorage.SetOrCreate("tele_number", this.all_number); @@ -219,40 +223,40 @@ export default class DialerPresenter { playAudio(number) { switch (number.toString()) { case '1': - this.tonePlayer(1); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_1); break; case '2': - this.tonePlayer(2); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_2); break; case '3': - this.tonePlayer(3); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_3); break; case '4': - this.tonePlayer(4); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_4); break; case '5': - this.tonePlayer(5); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_5); break; case '6': - this.tonePlayer(6); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_6); break; case '7': - this.tonePlayer(7); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_7); break; case '8': - this.tonePlayer(8); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_8); break; case '9': - this.tonePlayer(9); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_9); break; case '0': - this.tonePlayer(0); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_0); break; case '*': - this.tonePlayer(10); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_S); break; case '#': - this.tonePlayer(11); + this.tonePlayer(audio.ToneType.TONE_TYPE_DIAL_P); break; default: HiLog.e(TAG, "keytone src is error"); @@ -302,4 +306,67 @@ export default class DialerPresenter { let formatnum = PhoneNumber.fromString(this.all_number).format(); PhoneNumber.fromString(this.all_number).sendMessage(formatnum, formatnum); } + + saveCallRecordExistingContact() { + HiLog.i(TAG, 'saveCallRecordExistingContact start.'); + router.pushUrl({ + url: 'pages/contacts/batchselectcontacts/SingleSelectContactPage', + params: { + phoneNumberShow: AppStorage.Get("tele_number"), + editContact: 2, + } + }); + } + + dealSecretCode(value: string) { + let length = value.length; + if (length > 8 && value.startsWith(SECRET_CODE_START) && value.endsWith(SECRET_CODE_END)) { + let str = value.substring(SECRET_CODE_START.length, length - SECRET_CODE_END.length); + this.secretCode = str; + } else { + this.secretCode = ''; + } + } + + callHistorySearch() { + let teleNumber: string = AppStorage.Get("tele_number"); + globalThis.DataWorker.sendRequest("getQueryT9PhoneNumbers", { + favoriteForm: JSON.stringify({favorite:{teleNumber:teleNumber.replace(/\s*/g,"")}}), + context: globalThis.context + }, (result) => { + let nameArray = []; + for (let i = 0; i < result.length; i++) { + nameArray.push(result[i].showName); + result[i].displayName = result[i]?.showName || result[i]?.phoneNumbers[0]?.phoneNumber || ''; + result[i].phoneNumber = result[i]?.phoneNumbers[0]?.phoneNumber || ''; + } + const queryCall = {context: globalThis.context,mergeRule: '', actionData: {teleNumber:teleNumber.replace(/\s*/g,""), nameArray}}; + globalThis.DataWorker?.sendRequest("getCallHistorySearch", queryCall, (data) => { + this.callLogSearchList = []; + if(data.callLogList.length > 0 || result.length > 0){ + this.callLogSearchList = [...this.callLogSearchList,...result] + for (let i = 0; i < data.callLogList.length; i++) { + const displayName = data?.callLogList[i]?.displayName || data?.callLogList[i]?.phoneNumber; + if(!nameArray.includes(displayName)){ + nameArray.push(displayName) + this.callLogSearchList.push({...data.callLogList[i], displayName}); + } + } + } + this.mAllCallRecordListDataSource.refreshAll(this.callLogSearchList.sort((a: any, b: any)=> (a.count || 0) - (b.count || 0))); + }) + }) + } + + jumpToContactDetail(phoneNumber) { + router.pushUrl( + { + url: "pages/contacts/details/ContactDetail", + params: { + sourceHasPhone: true, + phoneNumberShow: phoneNumber + } + } + ) + } } \ No newline at end of file diff --git a/entry/src/main/ets/presenter/dialer/callRecord/CallRecordPresenter.ets b/entry/src/main/ets/presenter/dialer/callRecord/CallRecordPresenter.ets index 0d5a1df..eef71a1 100644 --- a/entry/src/main/ets/presenter/dialer/callRecord/CallRecordPresenter.ets +++ b/entry/src/main/ets/presenter/dialer/callRecord/CallRecordPresenter.ets @@ -213,10 +213,13 @@ export default class CallRecordPresenter { let actionData: any = {}; actionData.page = page; actionData.limit = limit; + let favoriteForm: any = {}; + favoriteForm.favorite = -1; this.worker?.sendRequest("getAllCalls", { context: this.context, mergeRule: CallLogSetting.getInstance().getMergeRule(), - actionData: actionData + actionData: actionData, + favoriteForm: JSON.stringify(favoriteForm) }, (data) => { let dateLength = data.callLogList.length; HiLog.i(TAG, `refreshPage ${page} and getAllCalls, length is ` + dateLength); @@ -304,4 +307,16 @@ export default class CallRecordPresenter { } ) } + + saveCallRecordExistingContact(phoneNumber, callId){ + HiLog.i(TAG, 'saveCallRecordExistingContact start.'); + router.pushUrl({ + url: 'pages/contacts/batchselectcontacts/SingleSelectContactPage', + params: { + phoneNumberShow: phoneNumber, + callId: callId, + editContact: 1, + } + }); + } } \ No newline at end of file diff --git a/entry/src/main/ets/presenter/favorite/EditFavoriteListPresenter.ets b/entry/src/main/ets/presenter/favorite/EditFavoriteListPresenter.ets new file mode 100644 index 0000000..d3d5ecc --- /dev/null +++ b/entry/src/main/ets/presenter/favorite/EditFavoriteListPresenter.ets @@ -0,0 +1,196 @@ +/** + * 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 '@ohos.router'; +import { HiLog, sharedPreferencesUtils } from '../../../../../../common'; +import { FavoriteBean } from '../../model/bean/FavoriteBean'; +import FavoriteDataSource from '../../model/bean/FavoriteDataSource'; + +const TAG = 'EditFavoriteListPresenter '; + +/** + * Favorite List Logical Interface Model + */ +export default class EditFavoriteListPresenter { + private static sInstance: EditFavoriteListPresenter; + favoriteList: FavoriteBean[] = []; + usuallySelectList: string[] = []; + tempUsuallySelectList: string[] = []; + favoriteCount: number = 0; + favoriteDataSource: FavoriteDataSource = new FavoriteDataSource(); + isShow: boolean = false; + usuallyList: FavoriteBean[] = [] ; + isEdit: boolean = false; + isEditSelect: string[] = []; + selectFavoriteBean: FavoriteBean; + + private constructor() { + } + + public static getInstance(): EditFavoriteListPresenter { + if (EditFavoriteListPresenter.sInstance == null) { + HiLog.i(TAG, 'EditFavoriteListPresenter getInstance!'); + EditFavoriteListPresenter.sInstance = new EditFavoriteListPresenter(); + } + return EditFavoriteListPresenter.sInstance; + } + + /** + * Remove selected data + * @param selectFavoriteIdList + */ + deleteFavoriteInfo(selectFavoriteIdList: string[]) { + HiLog.i(TAG, 'deleteFavoriteInfo start.'); + let usuallyLength = this.usuallySelectList.length; + if (usuallyLength > 0) { + sharedPreferencesUtils.saveToPreferences('usuallySelectDisplayNameList', JSON.stringify(this.usuallySelectList)); + } + let favoriteLength = selectFavoriteIdList.length; + if (favoriteLength - usuallyLength > 0) { + AppStorage.SetOrCreate>('deleteFavoriteContactData', selectFavoriteIdList); + } + HiLog.i(TAG, 'deleteFavoriteInfo end.'); + } + + /** + * Add Selection Single Data + * @param isEditSelectList + * @param favoriteId + */ + addSingleFavoriteSelectInfo(isEditSelectList: string[], favoriteBean: FavoriteBean): string[] { + HiLog.i(TAG, 'addSingleFavoriteSelectInfo start.'); + isEditSelectList.push(favoriteBean.contactId); + if (1 === favoriteBean.isCommonUseType && !(this.usuallySelectList.indexOf(favoriteBean.displayName) >= 0)) { + this.usuallySelectList.push(favoriteBean.displayName); + } + return isEditSelectList; + } + + /** + * Deselect Single Data + * @param isEditSelectList + * @param favoriteId + */ + cancelSingleFavoriteSelectInfo(isEditSelectList: string[], favoriteBean: FavoriteBean): string[] { + HiLog.i(TAG, 'cancelSingleFavoriteSelectInfo start.'); + let index = isEditSelectList.indexOf(favoriteBean.contactId); + if (index >= 0) { + isEditSelectList.splice(index, 1); + if (1 === favoriteBean.isCommonUseType) { + let indexBean = this.usuallySelectList.indexOf(favoriteBean.displayName); + if (indexBean >= 0) { + this.usuallySelectList.splice(indexBean, 1); + } + } + } + return isEditSelectList; + } + + /** + * Add Select All Data + * @param favoriteId + */ + addAllFavoriteSelectInfo(favoriteList: FavoriteBean[]): string[] { + HiLog.i(TAG, 'addAllFavoriteSelectInfo start.'); + let isEditList: string[] = []; + for (let i = 0;i < favoriteList.length; i++) { + favoriteList[i].isEditSelect = true; + isEditList.push(favoriteList[i].contactId); + if (1 === favoriteList[i].isCommonUseType && !(this.usuallySelectList.indexOf(favoriteList[i].displayName) >= 0)) { + this.usuallySelectList.push(favoriteList[i].displayName); + } + } + this.favoriteDataSource.refresh(favoriteList); + return isEditList; + } + + /** + * Deselect all data + * @param favoriteId + */ + cancelAllFavoriteSelectInfo(favoriteList: FavoriteBean[], isEditSelectList: string[]): string[] { + HiLog.i(TAG, 'cancelAllFavoriteSelectInfo start.'); + for (let i = 0; i < favoriteList.length; i++) { + this.favoriteList[i].isEditSelect = false; + favoriteList[i].isEditSelect = false; + isEditSelectList.splice(i, 1); + if (1 === favoriteList[i].isCommonUseType) { + let indexBean = this.usuallySelectList.indexOf(favoriteList[i].displayName); + this.usuallySelectList.splice(indexBean, 1); + } + } + if (isEditSelectList.length > 0) { + isEditSelectList = []; + } + return isEditSelectList; + } + + /** + * Favorite Move Sort + * @param favoriteList + */ + moveSortFavorite(favoriteList: FavoriteBean[]) { + HiLog.i(TAG, 'moveSortFavorite start.'); + let favoriteLength: number = favoriteList.length; + for (let i = 0; i < favoriteLength; i++) { + let favoriteForm: any = {} + favoriteForm.id = favoriteList[i].contactId; + favoriteForm.favoriteOrder = i + 1; + globalThis.DataWorker.sendRequest('moveSortFavorite', { + context: globalThis.context, + favoriteForm: JSON.stringify(favoriteForm) + }, (result) => { + if (favoriteLength === (i + 1)) { + AppStorage.SetOrCreate('editFavoriteDrag', 1); + router.back(); + } + }) + } + HiLog.i(TAG, 'moveSortFavorite end.'); + } + + /** + * Cancel Editing + */ + cancelEditFavorite() { + AppStorage.SetOrCreate('cancelEditFavorite', 2); + router.back(); + } + + aboutToAppear() { + HiLog.i(TAG, 'EditFavoriteListPresenter aboutToAppear!'); + this.isShow = true; + this.favoriteDataSource.refresh(this.favoriteList); + if (undefined !== this.selectFavoriteBean && 1 === this.selectFavoriteBean.isCommonUseType) { + this.usuallySelectList.push(this.selectFavoriteBean.displayName); + } + } + + aboutToDisappear() { + HiLog.i(TAG, 'EditFavoriteListPresenter aboutToDisappear!'); + this.tempUsuallySelectList = this.usuallySelectList; + this.isShow = false; + } + + onPageShow() { + HiLog.i(TAG, 'EditFavoriteListPresenter onPageShow!'); + this.isShow = true; + } + + onPageHide() { + HiLog.i(TAG, 'EditFavoriteListPresenter onPageHide!'); + this.isShow = false; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/presenter/favorite/FavoriteListPresenter.ets b/entry/src/main/ets/presenter/favorite/FavoriteListPresenter.ets new file mode 100644 index 0000000..5706ffb --- /dev/null +++ b/entry/src/main/ets/presenter/favorite/FavoriteListPresenter.ets @@ -0,0 +1,372 @@ +/** + * 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 '@ohos.router'; +import ArrayList from '@ohos.util.ArrayList'; +import { HiLog, sharedPreferencesUtils } from '../../../../../../common'; +import { FavoriteBean } from '../../model/bean/FavoriteBean'; +import FavoriteDataSource from '../../model/bean/FavoriteDataSource'; +import { ContactRepository } from '../../../../../../feature/contact/src/main/ets/repo/ContactRepository'; +import emitter from '@ohos.events.emitter'; +import ContactAbilityModel from '../../model/ContactAbilityModel'; + +const TAG = 'FavoriteListPresenter '; + +/** + * Favorite List Logical Interface Model + */ +export default class FavoriteListPresenter { + private static sInstance: FavoriteListPresenter; + favoriteList: FavoriteBean[] = []; + usuallyList: FavoriteBean[] = []; + favoriteCount: number = 0; + isEmptyGroup: boolean = true; + favoriteDataSource: FavoriteDataSource = new FavoriteDataSource(); + isShow: boolean = false; + isEditSelectList: string[] = []; + usuallySelectArray = new ArrayList(); + displayNameList: string[] = []; + usuallyDisplayNameParameterList: string[] = []; + usuallyPhoneParameterList: string[] = []; + usuallyTotalCount: number = 0; + onContactChange = () => { + HiLog.i(TAG, 'onFavoriteChange refresh'); + this.requestItem(); + } + + private constructor() { + } + + public static getInstance(): FavoriteListPresenter { + if (FavoriteListPresenter.sInstance == null) { + HiLog.i(TAG, 'FavoriteListPresenter getInstance!'); + FavoriteListPresenter.sInstance = new FavoriteListPresenter(); + } + return FavoriteListPresenter.sInstance; + } + + refreshFavorite() { + HiLog.i(TAG, 'refreshFavorite start.'); + let actionData: any = {}; + actionData.favorite = 1 + globalThis.DataWorker.sendRequest('getAllFavorite', { + actionData: actionData, + context: globalThis.context + }, (result) => { + HiLog.i(TAG, 'refreshFavorite sc.'); + this.favoriteList = []; + this.favoriteList = result; + if (this.favoriteList.length > 0) { + this.favoriteList[0].isUsuallyShow = true; + } + this.favoriteCount = result.length; + this.refreshFavoriteList(this.favoriteList); + }) + HiLog.i(TAG, 'refreshFavorite end.'); + } + + refreshUsually() { + HiLog.i(TAG, 'refreshUsually start.'); + let actionData: any = {}; + actionData.favorite = 0; + globalThis.DataWorker.sendRequest('getAllUsually', { + actionData: actionData, + context: globalThis.context + }, (result) => { + HiLog.i(TAG, 'refreshUsually sc.'); + let resultCount = result.length; + if (resultCount > 0) { + let usuallySelectDisplayNameListCount = this.usuallySelectArray.length; + this.usuallyTotalCount = 0; + let usuallyContainDisplayNameCount: number = 0; + this.displayNameList = []; + this.usuallyDisplayNameParameterList = []; + for (let i = 0; i < resultCount; i++) { + //The name cannot be empty or duplicate. + if ('' !== result[i].displayName && this.displayNameList.indexOf(result[i].displayName) === -1) { + this.displayNameList.push(result[i].displayName); + if (this.usuallySelectArray.has(result[i].displayName)) { + usuallyContainDisplayNameCount++; + } + } + } + if (usuallyContainDisplayNameCount === this.displayNameList.length && usuallyContainDisplayNameCount === usuallySelectDisplayNameListCount) { + this.refreshFavoriteList(this.favoriteList); + return; + } + for (let i = 0; i < resultCount; i++) { + if ('' !== result[i].displayName) { + if (this.usuallyTotalCount < 10) { + if (usuallySelectDisplayNameListCount > 0) { + if (!this.usuallySelectArray.has(result[i].displayName)) { + if ('' !== result[i].phoneNumber) { + this.usuallyTotalCount++; + this.usuallyPhoneParameterList.push(result[i].phoneNumber); + this.usuallyDisplayNameParameterList.push(result[i].displayName); + } + } + } else { + if ('' !== result[i].phoneNumber) { + this.usuallyTotalCount++; + this.usuallyDisplayNameParameterList.push(result[i].displayName); + this.usuallyPhoneParameterList.push(result[i].phoneNumber); + } + } + } else { + //More than 10 + if (this.usuallyDisplayNameParameterList.length > 0) { + this.getDisplayNamesFindUsually(this.usuallyDisplayNameParameterList, this.usuallyPhoneParameterList); + } else { + this.refreshFavoriteList(this.favoriteList); + } + return; + } + } + } + //No more than 10 + if (this.usuallyDisplayNameParameterList.length > 0) { + this.getDisplayNamesFindUsually(this.usuallyDisplayNameParameterList, this.usuallyPhoneParameterList); + } else { + this.refreshFavoriteList(this.favoriteList); + } + } else { + this.refreshFavoriteList(this.favoriteList); + } + }) + HiLog.i(TAG, 'refreshUsually end.'); + } + + getDisplayNamesFindUsually(displayNameList: string[], usuallyPhoneList: string[]) { + HiLog.i(TAG, 'getDisplayNamesFindUsually start.'); + globalThis.DataWorker.sendRequest('getDisplayNamesFindUsually', { + displayName: displayNameList, + usuallyPhone: usuallyPhoneList, + context: globalThis.context + }, (result) => { + let totalCount = result.length; + HiLog.i(TAG, 'getDisplayNamesFindUsually sc'); + if (null != result && totalCount > 0) { + this.favoriteList = this.favoriteList.concat(result); + this.refreshFavoriteList(this.favoriteList); + } else { + this.refreshFavoriteList(this.favoriteList); + } + }) + HiLog.i(TAG, 'getDisplayNamesFindUsually end.'); + } + + /** + * Refresh Favorite List + * @param favoriteList + */ + refreshFavoriteList(favoriteList: FavoriteBean[]) { + if (this.favoriteCount < this.favoriteList.length) { + this.favoriteList[this.favoriteCount].isUsuallyShow = true; + } + this.favoriteDataSource.refresh(favoriteList); + let innerEvent = { + eventId: 100, + priority: emitter.EventPriority.HIGH + }; + emitter.emit(innerEvent, { + data: { + 'favoriteListListLen': this.favoriteList.length + } + }); + } + + goContactDetail(contactId: string) { + HiLog.i(TAG, 'goContactDetail start.'); + router.pushUrl( + { + url: 'pages/contacts/details/ContactDetail', + params: { + sourceHasId: true, + contactId: contactId + } + } + ); + } + + aboutToAppear() { + HiLog.i(TAG, 'FavoriteListPresenter aboutToAppear!'); + this.isShow = true; + ContactRepository.getInstance().registerDataChangeObserver(this.onContactChange); + } + + aboutToDisappear() { + HiLog.i(TAG, 'FavoriteListPresenter aboutToDisappear!'); + this.isShow = false; + ContactRepository.getInstance().unRegisterDataChangeObserver(this.onContactChange); + } + + async onPageShow() { + HiLog.i(TAG, 'onPageShow!'); + this.isShow = true; + let editFavoriteDrag: number = AppStorage.Get('editFavoriteDrag'); + let addFavoriteContactData: string[] = AppStorage.Get>('addFavoriteContactData'); + let deleteFavoriteContactData: string[] = AppStorage.Get>('deleteFavoriteContactData'); + let cancelEditFavorite: number = AppStorage.Get('cancelEditFavorite'); + let that = this; + let usuallySelectData: Promise = new Promise(async (resolve) => { + let usuallySelectDisplayNameList: string = await sharedPreferencesUtils.getFromPreferences('usuallySelectDisplayNameList', ''); + if ('' !== usuallySelectDisplayNameList) { + let usuallySelectDisplayList: string[] = JSON.parse(usuallySelectDisplayNameList); + let count = usuallySelectDisplayList.length; + that.usuallySelectArray.clear(); + for (let i = 0; i < count; i++) { + that.usuallySelectArray.add(usuallySelectDisplayList[i]); + } + } + if(0 !== that.usuallySelectArray.length){ + this.requestItem(); + } + resolve(usuallySelectDisplayNameList) + }); + if (editFavoriteDrag !== undefined && editFavoriteDrag === 1) { + HiLog.i(TAG, 'onPageShow editFavoriteDrag!'); + this.requestItem(); + AppStorage.SetOrCreate('editFavoriteDrag', 0); + } else if (addFavoriteContactData !== undefined && addFavoriteContactData.length > 0) { + HiLog.i(TAG, 'onPageShow addFavoriteContactData!'); + this.addFavoriteInfo(addFavoriteContactData); + AppStorage.SetOrCreate>('addFavoriteContactData', []); + } else if (cancelEditFavorite !== undefined && 2 === cancelEditFavorite) { + HiLog.i(TAG, 'onPageShow cancelEditFavorite!'); + AppStorage.SetOrCreate('cancelEditFavorite', 0); + } else if (deleteFavoriteContactData !== undefined && deleteFavoriteContactData.length > 0) { + HiLog.i(TAG, 'onPageShow deleteFavoriteContactData!'); + this.deleteFavorite(deleteFavoriteContactData); + AppStorage.SetOrCreate>('deleteFavoriteContactData', []); + } else { + HiLog.i(TAG, 'onPageShow else!'); + await usuallySelectData.then(result => { + if ('' === result) { + this.requestItem(); + } + }) + } + } + + onPageHide() { + HiLog.i(TAG, 'onPageHide!'); + this.isShow = false; + } + + requestItem() { + HiLog.i(TAG, 'requestItem start.'); + this.refreshFavorite(); + this.refreshUsually(); + } + + async usuallySelectData(): Promise { + let that = this; + //let usuallySelectData: Promise + return await new Promise(async (resolve) => { + let usuallySelectDisplayNameList: string = await sharedPreferencesUtils.getFromPreferences('usuallySelectDisplayNameList', ''); + if ('' !== usuallySelectDisplayNameList) { + let usuallySelectDisplayList: string[] = JSON.parse(usuallySelectDisplayNameList); + let count = usuallySelectDisplayList.length; + that.usuallySelectArray.clear(); + for (let i = 0; i < count; i++) { + that.usuallySelectArray.add(usuallySelectDisplayList[i]); + } + } + if(0 !== that.usuallySelectArray.length){ + this.requestItem(); + } + resolve(usuallySelectDisplayNameList) + }); + } + + addFavorite() { + router.pushUrl({ + url: 'pages/contacts/batchselectcontacts/BatchSelectContactsPage', + params: { + addFavorite: 0, + selectType: 1 + } + }); + } + + editFavorite() { + router.pushUrl({ + url: 'pages/favorites/editFavoriteList', + params: { + isEdit: true, + favoriteList: this.favoriteList, + favoriteNumber: this.favoriteCount, + usuallyNumber: this.favoriteList.length - this.favoriteCount, + } + }) + } + + addFavoriteInfo(selectFavoriteIdList: string[]) { + HiLog.i(TAG, 'addFavoriteInfo start.'); + let selectLength: number = selectFavoriteIdList.length; + for (let i = 0; i < selectLength; i++) { + let favoriteForm: any = {} + favoriteForm.id = selectFavoriteIdList[i]; + favoriteForm.favorite = 1; + globalThis.DataWorker.sendRequest('updateFavorite', { + context: globalThis.context, + favoriteForm: JSON.stringify(favoriteForm) + }, (arg) => { + if (i === (selectLength - 1)) { + AppStorage.SetOrCreate>('addFavoriteContactData', []) + HiLog.i(TAG, 'addFavoriteInfo success refresh.'); + this.requestItem(); + } + }) + } + HiLog.i(TAG, 'addFavoriteInfo end.'); + } + + deleteFavorite(deleteFavoriteIdList: string[]) { + HiLog.i(TAG, 'deleteFavorite start.'); + let favoriteLength: number = deleteFavoriteIdList.length; + for (let i = 0; i < favoriteLength; i++) { + let favoriteForm: any = {}; + favoriteForm.id = deleteFavoriteIdList[i]; + favoriteForm.favorite = 0; + globalThis.DataWorker.sendRequest("updateFavorite", { + context: globalThis.context, + favoriteForm: JSON.stringify(favoriteForm) + }, (arg) => { + if(i === (favoriteLength - 1)){ + HiLog.i(TAG, 'deleteFavoriteInfo success.'); + AppStorage.SetOrCreate>('deleteFavoriteContactData', []) + this.requestItem(); + } + }) + } + HiLog.i(TAG, 'deleteFavorite end.'); + } + + longItemEditFavorite(favoriteList: FavoriteBean[], isEditSelectList: string[], favoriteBean: FavoriteBean) { + router.pushUrl({ + url: 'pages/favorites/editFavoriteList', + params: { + isEdit: true, + favoriteList: favoriteList, + selectNumber: 1, + favoriteNumber: this.favoriteCount, + usuallyNumber: favoriteList.length - this.favoriteCount, + isEditSelectList: isEditSelectList, + selectFavoriteBean: favoriteBean + } + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/util/StringFormatUtil.ets b/entry/src/main/ets/util/StringFormatUtil.ets index 2b2a36a..9e7e701 100644 --- a/entry/src/main/ets/util/StringFormatUtil.ets +++ b/entry/src/main/ets/util/StringFormatUtil.ets @@ -48,7 +48,7 @@ export default { return $r("app.string.time_morning", hour, minutes); } if (hour >= 11 && hour < 13) { - return $r("app.string.time_noon", hour >= 12 ? (parseInt(hour) - 12).toString() : hour, minutes); + return $r("app.string.time_noon", hour, minutes); } if (hour >= 13 && hour < 17) { return $r("app.string.time_afternoon", (parseInt(hour) - 12).toString(), minutes); diff --git a/entry/src/main/ets/workers/DataWorkerTask.ets b/entry/src/main/ets/workers/DataWorkerTask.ets index 6b30286..5bc1347 100644 --- a/entry/src/main/ets/workers/DataWorkerTask.ets +++ b/entry/src/main/ets/workers/DataWorkerTask.ets @@ -37,6 +37,14 @@ export enum DataWorkerConstant { "getContactById", "updateContact", "getIdByTelephone", + "updateFavorite", + "getAllFavorite", + "getAllUsually", + "getDisplayNamesFindUsually", + "moveSortFavorite", + "getSearchContact", + "getCallHistorySearch", + "getQueryT9PhoneNumbers" } export class DataWorkerTask extends WorkerTask { @@ -58,7 +66,7 @@ export class DataWorkerTask extends WorkerTask { HiLog.i(TAG, `runInWorker ${request}`) switch (request) { case DataWorkerConstant[DataWorkerConstant.getAllCalls]: - CallLog.getAllCalls(param.actionData, param.mergeRule, (data) => { + CallLog.getAllCalls(JSON.parse(param.favoriteForm), param.actionData, param.mergeRule, (data) => { HiLog.i(TAG, `getAllCalls result: ${JSON.stringify(data).length}`) callBack(data); }, param.context); @@ -96,7 +104,7 @@ export class DataWorkerTask extends WorkerTask { case DataWorkerConstant[DataWorkerConstant.getAllContactWithPhoneNumbers]: ContactAbilityModel.getAllContactWithPhoneNumbers((resultList) => { callBack(resultList); - }, param.context) + }, JSON.parse(param.favoriteForm), param.context) break case DataWorkerConstant[DataWorkerConstant.getContactById]: ContactAbilityModel.getContactById(param.contactId, result => { @@ -113,6 +121,46 @@ export class DataWorkerTask extends WorkerTask { callBack(arg); }, param.context) break + case DataWorkerConstant[DataWorkerConstant.updateFavorite]: + ContactAbilityModel.updateFavorite(null, JSON.parse(param.favoriteForm), (arg) => { + callBack(arg); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.getAllFavorite]: + ContactAbilityModel.getAllFavorite(param.actionData, (result) => { + callBack(result); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.getAllUsually]: + ContactAbilityModel.getAllUsually(param.actionData, (result) => { + callBack(result); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.getDisplayNamesFindUsually]: + ContactAbilityModel.getDisplayNamesFindUsually(param.displayName, param.usuallyPhone, (result) => { + callBack(result); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.moveSortFavorite]: + ContactAbilityModel.moveSortFavorite(null, JSON.parse(param.favoriteForm), (result) => { + callBack(result); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.getSearchContact]: + ContactAbilityModel.getSearchContact(param.actionData, (result) => { + callBack(result); + }, param.context) + break + case DataWorkerConstant[DataWorkerConstant.getCallHistorySearch]: + CallLog.getCallHistorySearch(param.actionData, param.mergeRule, (data) => { + callBack(data); + }, param.context); + break + case DataWorkerConstant[DataWorkerConstant.getQueryT9PhoneNumbers]: + ContactAbilityModel.getQueryT9PhoneNumbers((resultList) => { + callBack(resultList); + }, JSON.parse(param.favoriteForm), param.context) + break default: HiLog.w(TAG, `${request} not allow!!!`) break; diff --git a/entry/src/main/ets/workers/DataWorkerWrapper.ets b/entry/src/main/ets/workers/DataWorkerWrapper.ets deleted file mode 100644 index 6fa8f7f..0000000 --- a/entry/src/main/ets/workers/DataWorkerWrapper.ets +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright (c) 2022 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 "../../../../../common" -import { WorkerType } from "./WorkFactory" -import WorkerWrapper from "./base/WorkerWrapper" -import WorkerTask from "./base/WorkerTask" -import { ThreadWorkerGlobalScope } from '@ohos.worker'; -import CallLog from '../model/calllog/CalllogModel'; -import { CallLogRepository } from '../../../../../feature/call'; -import { ContactRepository } from '../../../../../feature/contact'; -import ContactAbilityModel from '../model/ContactAbilityModel'; - -const TAG = "DataWorkerWrapper" - -export default class DataWorkerWrapper extends WorkerWrapper { - private static sInstance: DataWorkerWrapper = undefined; - - private constructor() { - super() - } - - static getInstance() { - HiLog.i(TAG, "getInstance in.") - if (DataWorkerWrapper.sInstance == undefined || DataWorkerWrapper.sInstance.mWorker == undefined) { - HiLog.i(TAG, "make DataWorkerWrapper.") - DataWorkerWrapper.sInstance = new DataWorkerWrapper(); - } - return DataWorkerWrapper.sInstance; - } - - getWorkerType(): WorkerType { - return WorkerType.DataWorker; - } -} - -export enum DataWorkerConstant { - "deleteCallLogsById", - "getAllCalls", - "findByNumberIn", - "deleteContactById", - "addContact", - "getAllContact", - "getAllContactWithPhoneNumbers", - "getContactById", - "updateContact", - "getIdByTelephone", -} - -export class DataWorkerTask extends WorkerTask { - private static sInstance: DataWorkerTask = undefined; - - private constructor(workerPort: ThreadWorkerGlobalScope) { - super(workerPort) - } - - static getInstance(workerPort: ThreadWorkerGlobalScope) { - HiLog.i(TAG, "getInstance in.") - if (DataWorkerTask.sInstance == undefined || DataWorkerTask.sInstance.workerPort == undefined) { - DataWorkerTask.sInstance = new DataWorkerTask(workerPort); - } - return DataWorkerTask.sInstance; - } - - runInWorker(request: string, callBack: (v?: any) => void, param?: any) { - HiLog.i(TAG, `runInWorker ${request}`) - switch (request) { - case DataWorkerConstant[DataWorkerConstant.getAllCalls]: - CallLog.getAllCalls(param.actionData, param.mergeRule, (data) => { - HiLog.i(TAG, `getAllCalls result: ${JSON.stringify(data).length}`) - callBack(data); - }, param.context); - break; - case DataWorkerConstant[DataWorkerConstant.findByNumberIn]: - CallLogRepository.getInstance().init(param.context); - CallLogRepository.getInstance().findByNumberIn(param.numbers, (resultList) => { - callBack(resultList); - }); - break - case DataWorkerConstant[DataWorkerConstant.deleteContactById]: - ContactRepository.getInstance().init(param.context); - ContactRepository.getInstance().deleteById(param.contactId, (result) => { - HiLog.i(TAG, `deleteContactById result ${result}`) - callBack(result); - }); - break; - case DataWorkerConstant[DataWorkerConstant.deleteCallLogsById]: - CallLogRepository.getInstance().init(param.context); - CallLogRepository.getInstance().deleteByIdIn(param.ids, (result) => { - callBack(result); - }) - break; - case DataWorkerConstant[DataWorkerConstant.addContact]: - const contactInfoAfter = JSON.parse(param.contactInfoAfter) - ContactAbilityModel.addContact(contactInfoAfter, (arg) => { - callBack(arg); - }, param.context) - break - case DataWorkerConstant[DataWorkerConstant.getAllContact]: - ContactAbilityModel.getAllContact(param.actionData, (result) => { - callBack(result); - }, param.context) - break - case DataWorkerConstant[DataWorkerConstant.getAllContactWithPhoneNumbers]: - ContactAbilityModel.getAllContactWithPhoneNumbers((resultList) => { - callBack(resultList); - }, param.context) - break - case DataWorkerConstant[DataWorkerConstant.getContactById]: - ContactAbilityModel.getContactById(param.contactId, result => { - callBack(result); - }, param.context) - break - case DataWorkerConstant[DataWorkerConstant.getIdByTelephone]: - ContactAbilityModel.getIdByTelephone(param.phoneNumber, (contactId) => { - callBack(contactId); - }, param.context) - break - case DataWorkerConstant[DataWorkerConstant.updateContact]: - ContactAbilityModel.updateContact(null, JSON.parse(param.contactInfoAfter), (arg) => { - callBack(arg); - }, param.context) - break - default: - HiLog.w(TAG, `${request} not allow!!!`) - break; - } - } -} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index 68e7c37..1db7e83 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -103,6 +103,9 @@ }, { "name": "ohos.permission.GET_NETWORK_INFO" + }, + { + "name": "ohos.permission.SET_TELEPHONY_STATE" } ] } diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index 9814172..ab8a518 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -270,7 +270,7 @@ }, { "name": "contact_list_search", - "value": "搜索联系人" + "value": "搜索..." }, { "name": "contact_list_search_empty", @@ -454,7 +454,7 @@ }, { "name": "no_select", - "value": "未选择" + "value": "未选中" }, { "name": "select_contact", @@ -811,6 +811,39 @@ { "name": "china_unicom", "value": "中国联通" + }, + { + "name": "common_use", + "value": "常用" + }, + { + "name": "no_favorite", + "value": "没有收藏" + }, + { + "name": "favorite_num", + "value": "收藏 %d" + }, + { + "name": "common_use_num", + "value": "常用 %d" + }, + { + "name": "favorite_remove", + "value": "取消" + + }, + { + "name": "contacts_call", + "value": "呼叫" + }, + { + "name": "set_default_values", + "value": "设置默认值" + }, + { + "name": "found_contacts", + "value": "找到 %d 个联系人" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_no_favorites.svg b/entry/src/main/resources/base/media/ic_no_favorites.svg new file mode 100644 index 0000000..85f06fe --- /dev/null +++ b/entry/src/main/resources/base/media/ic_no_favorites.svg @@ -0,0 +1,17 @@ + + + NoFavorites + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_close.svg b/entry/src/main/resources/base/media/ic_public_close.svg new file mode 100644 index 0000000..fb3c695 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_close.svg @@ -0,0 +1,8 @@ + + + ic_public_close + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_close_gray.svg b/entry/src/main/resources/base/media/ic_public_close_gray.svg new file mode 100644 index 0000000..0bb9fd4 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_close_gray.svg @@ -0,0 +1,27 @@ + + + ic_public_close + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_collect.svg b/entry/src/main/resources/base/media/ic_public_collect.svg new file mode 100644 index 0000000..2bd041a --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_collect.svg @@ -0,0 +1,8 @@ + + + ic_public_collect + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_collected.svg b/entry/src/main/resources/base/media/ic_public_collected.svg new file mode 100644 index 0000000..d3fc4cd --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_collected.svg @@ -0,0 +1,8 @@ + + + ic_public_collected + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_drag_handle.svg b/entry/src/main/resources/base/media/ic_public_drag_handle.svg new file mode 100644 index 0000000..18a9c99 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_drag_handle.svg @@ -0,0 +1,8 @@ + + + ic_public_drag_handle + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_more_gray.svg b/entry/src/main/resources/base/media/ic_public_more_gray.svg new file mode 100644 index 0000000..8770068 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_more_gray.svg @@ -0,0 +1,30 @@ + + + Public/ic_public_more + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_phone_dialog.svg b/entry/src/main/resources/base/media/ic_public_phone_dialog.svg new file mode 100644 index 0000000..a88f630 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_phone_dialog.svg @@ -0,0 +1,31 @@ + + + Public/ic_public_phone + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/ic_public_search.svg b/entry/src/main/resources/base/media/ic_public_search.svg new file mode 100644 index 0000000..817e0ab --- /dev/null +++ b/entry/src/main/resources/base/media/ic_public_search.svg @@ -0,0 +1,27 @@ + + + ic_public_search + + + + + \ 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 fce1206..5fa4ab3 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -6,6 +6,7 @@ "pages/contacts/details/ContactDetail", "pages/dialer/DialerTablet", "pages/contacts/batchselectcontacts/BatchSelectContactsPage", - "pages/contacts/batchselectcontacts/SingleSelectContactPage" + "pages/contacts/batchselectcontacts/SingleSelectContactPage", + "pages/favorites/editFavoriteList" ] } diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json index 5580b84..3facd48 100644 --- a/entry/src/main/resources/en_US/element/string.json +++ b/entry/src/main/resources/en_US/element/string.json @@ -270,7 +270,7 @@ }, { "name": "contact_list_search", - "value": "searching for contacts" + "value": "Search..." }, { "name": "contact_list_search_empty", @@ -811,6 +811,38 @@ { "name": "china_unicom", "value": "China Unicom" + }, + { + "name": "common_use", + "value": "Common Use" + }, + { + "name": "no_favorite", + "value": "No favorite" + }, + { + "name": "favorite_num", + "value": "Favorites %d" + }, + { + "name": "common_use_num", + "value": "Common Use %d" + }, + { + "name": "favorite_remove", + "value": "Cancel" + }, + { + "name": "contacts_call", + "value": "call" + }, + { + "name": "set_default_values", + "value": "Set default values" + }, + { + "name": "found_contacts", + "value": "found %d contacts" } ] } \ 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 d7a884c..6a7f6b3 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -270,7 +270,7 @@ }, { "name": "contact_list_search", - "value": "搜索联系人" + "value": "搜索..." }, { "name": "contact_list_search_empty", @@ -454,7 +454,7 @@ }, { "name": "no_select", - "value": "未选择" + "value": "未选中" }, { "name": "select_contact", @@ -811,6 +811,38 @@ { "name": "china_unicom", "value": "中国联通" + }, + { + "name": "common_use", + "value": "常用" + }, + { + "name": "no_favorite", + "value": "没有收藏" + }, + { + "name": "favorite_num", + "value": "收藏 %d" + }, + { + "name": "common_use_num", + "value": "常用 %d" + }, + { + "name": "favorite_remove", + "value": "取消" + }, + { + "name": "contacts_call", + "value": "呼叫" + }, + { + "name": "set_default_values", + "value": "设置默认值" + }, + { + "name": "found_contacts", + "value": "找到 %d 个联系人" } ] } \ No newline at end of file diff --git a/feature/call/src/main/ets/missedcall/MissedCallNotifier.ets b/feature/call/src/main/ets/missedcall/MissedCallNotifier.ets index 89b15de..04488a2 100644 --- a/feature/call/src/main/ets/missedcall/MissedCallNotifier.ets +++ b/feature/call/src/main/ets/missedcall/MissedCallNotifier.ets @@ -15,6 +15,9 @@ import Notification from "@ohos.notificationManager" import WantAgent from '@ohos.app.ability.wantAgent'; import { HiLog, sharedPreferencesUtils } from '../../../../../../common'; +import notificationSubscribe from '@ohos.notificationSubscribe'; +import call from '@ohos.telephony.call'; +import { NotificationSubscriber } from 'notification/notificationSubscriber'; const TAG = "MissedCallNotifier"; @@ -22,6 +25,11 @@ const BUNDLE_NAME: string = 'com.ohos.contacts'; const ABILITY_NAME: string = 'com.ohos.contacts.MainAbility'; const GROUP_NAME: string = "MissedCall" const KEY_MISSED_BADGE_NUM = 'missed_badge_number' +const KEY_ID = 'unread_call_notification_id' +const KEY_DISPLAY_NAME = 'unread_call_notification_displayName' +const KEY_COUNT = 'unread_call_notification_count' +const KEY_CREATE_TIME = 'unread_call_notification_create_time' +const KEY_RING_DURATION = 'unread_call_notification_ring_duration' const actionBtnMaps = { 'notification.event.dialBack': $r('app.string.dial_back'), @@ -42,6 +50,7 @@ export class MissedCallNotifier { private context: Context; private static sInstance: MissedCallNotifier = undefined; private missedBadgeNumber: number = -1; + private UnReadMissedCallData: MissedCallNotifyData; /** * getInstance for MissedCallNotifier @@ -164,16 +173,24 @@ export class MissedCallNotifier { private async sendNotification(missedCallData: MissedCallNotifyData) { const {id, displayName, count, createTime, ringDuration} = missedCallData; + sharedPreferencesUtils.saveToPreferences(KEY_ID, id); + sharedPreferencesUtils.saveToPreferences(KEY_DISPLAY_NAME, displayName); + sharedPreferencesUtils.saveToPreferences(KEY_COUNT, count); + sharedPreferencesUtils.saveToPreferences(KEY_CREATE_TIME, createTime); + sharedPreferencesUtils.saveToPreferences(KEY_RING_DURATION, ringDuration) HiLog.i(TAG, `sendNotification in id:${id}, count:${count}, createTime:${createTime}`) let str_text = this.getContext()?.resourceManager.getStringSync($r("app.string.contacts_ring_times")) + ringDuration + this.getContext()?.resourceManager.getStringSync($r("app.string.contacts_time_sec")); + if (ringDuration === 0) { + str_text = $r("app.string.missed_call") + } const notificationRequest: Notification.NotificationRequest = { content: { contentType: Notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: count > 1 ? `${displayName} (${count})` : displayName, text: str_text, - additionalText: "" + additionalText: missedCallData.phoneNumber }, }, id: id, @@ -204,6 +221,43 @@ export class MissedCallNotifier { HiLog.i(TAG, 'sendNotification end') } + /** + * send Unread Call Notification + */ + public async sendUnreadCallNotification(map: Map) { + let id: number = await sharedPreferencesUtils.getFromPreferences(KEY_ID, -1); + let displayName: string = await sharedPreferencesUtils.getFromPreferences(KEY_DISPLAY_NAME, ""); + let count: number = await sharedPreferencesUtils.getFromPreferences(KEY_COUNT, -1); + let createTime: number = await sharedPreferencesUtils.getFromPreferences(KEY_CREATE_TIME, -1); + let ringDuration: number = await sharedPreferencesUtils.getFromPreferences(KEY_RING_DURATION, -1); + let missCallData = map.get("missedPhoneJson") + const parameters = JSON.parse(JSON.stringify(missCallData)); + for (let i = 0; i < parameters.phoneNumberList.length; i++) { + const missedPhoneNumber = parameters.phoneNumberList[i] + const missedNum = parameters.countList[i] + if (i === (parameters.phoneNumberList.length -1)) { + this.UnReadMissedCallData = { + phoneNumber: missedPhoneNumber, + displayName: missedPhoneNumber, + id: i, + createTime: createTime, + count: count, + ringDuration: ringDuration + } + } else { + this.UnReadMissedCallData = { + phoneNumber: missedPhoneNumber, + displayName: missedPhoneNumber, + id: i, + createTime: createTime, + count: count, + ringDuration: 0 + } + } + this.sendNotification(this.UnReadMissedCallData); + } + } + private setMissedBadgeNumber(newBadgeNum: number) { HiLog.i(TAG, 'setMissedBadgeNumber :' + newBadgeNum); this.missedBadgeNumber = newBadgeNum; @@ -224,7 +278,7 @@ export class MissedCallNotifier { missedCallData: missedCallData }, }], operationType: WantAgent.OperationType.SEND_COMMON_EVENT, - requestCode: 0 // requestCodeWantAgentInfo룬ʹ߶һ˽ֵ + requestCode: 0 }) } diff --git a/feature/call/src/main/ets/missedcall/MissedCallService.ets b/feature/call/src/main/ets/missedcall/MissedCallService.ets index 7aaa6c6..8fb7e34 100644 --- a/feature/call/src/main/ets/missedcall/MissedCallService.ets +++ b/feature/call/src/main/ets/missedcall/MissedCallService.ets @@ -80,6 +80,13 @@ export class MissedCallService { this.sendMissedCallNotify(this.lastMissedId); } + /** + * + * Unread missed call notification + */ + public async unreadCallNotification(map:Map) { + MissedCallNotifier.getInstance().sendUnreadCallNotification(map); + } /** * cancelMissedNotificationAction diff --git a/feature/call/src/main/ets/repo/CallLogRepository.ets b/feature/call/src/main/ets/repo/CallLogRepository.ets index 02bb749..ed686cb 100644 --- a/feature/call/src/main/ets/repo/CallLogRepository.ets +++ b/feature/call/src/main/ets/repo/CallLogRepository.ets @@ -19,6 +19,7 @@ import ICallLogRepository from './ICallLogRepository'; import { CallLog } from '../entity/CallLog'; import CallLogBuilder from '../entity/CallLogBuilder'; import Calls from '../contract/Calls'; +import { RawContacts } from '../../../../../contact/src/main/ets/contract/RawContacts'; import CallLogDelta from './CallLogDelta'; import { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil'; import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; @@ -55,7 +56,7 @@ export class CallLogRepository implements ICallLogRepository { private async getDataAbilityHelper() { if (this.dataShareHelper == undefined) { this.dataShareHelper = await dataShare.createDataShareHelper(this.context ? this.context : globalThis.context, - Calls.CONTENT_URI); + Calls.CONTENT_URI); } return this.dataShareHelper; } @@ -134,10 +135,13 @@ export class CallLogRepository implements ICallLogRepository { return false; } - findAll(actionData, callback) { + findAll(favorite: number, actionData, callback) { this.getDataAbilityHelper().then((dataAbilityHelper) => { let condition = new dataSharePredicates.DataSharePredicates(); let offset = actionData.page < 3 ? (actionData.page - 1) * 50 : (actionData.page - 2) * 500 + 50; + if (0 === favorite) { + condition.equalTo(RawContacts.FAVORITE, favorite) + } condition.limit(actionData.limit, offset); condition.orderByDesc(Calls.CREATE_TIME); dataAbilityHelper.query(Calls.CALL_LOG_URI, condition, null).then(resultSet => { @@ -337,4 +341,36 @@ export class CallLogRepository implements ICallLogRepository { this.unregisterCallLogDataChange(); } } + + findSearch(actionData, callback) { + this.getDataAbilityHelper().then((dataAbilityHelper) => { + let condition = new dataSharePredicates.DataSharePredicates(); + condition.in(Calls.DISPLAY_NAME, actionData.nameArray) + .or() + .like(Calls.PHONE_NUMBER, `%${actionData.teleNumber}%`); + dataAbilityHelper.query(Calls.CALL_LOG_URI, condition, null).then(resultSet => { + let rst: CallLog[] = []; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + let builder = CallLogBuilder.fromResultSet(resultSet); + if (builder.id > 0) { + rst.push(new CallLog(builder)); + } + } while (resultSet.goToNextRow()); + resultSet.close(); + callback(rst); + } + }).catch(error => { + HiLog.w(TAG, 'findAll error:' + JSON.stringify(error.message)); + callback([]); + }); + }).catch(error => { + HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); + callback([]); + }); + } } \ No newline at end of file diff --git a/feature/call/src/main/ets/repo/ICallLogRepository.ets b/feature/call/src/main/ets/repo/ICallLogRepository.ets index cbdc3cf..8c48c71 100644 --- a/feature/call/src/main/ets/repo/ICallLogRepository.ets +++ b/feature/call/src/main/ets/repo/ICallLogRepository.ets @@ -27,7 +27,7 @@ export default interface ICallLogRepository { deleteByLookupUri: (lookupUri: string) => boolean; readByNumber: (number: string) => boolean; readById: (id: number) => boolean; - findAll: (actionData: any, callback) => void; + findAll: (favorite: number, actionData: any, callback) => void; findByFeature: (feature: number) => CallLog[]; findByNumberIn: (numbers: number[], callback) => void; notifyChange: () => void; diff --git a/feature/contact/src/main/ets/contract/DataColumns.ets b/feature/contact/src/main/ets/contract/DataColumns.ets index 455b801..a7a389b 100644 --- a/feature/contact/src/main/ets/contract/DataColumns.ets +++ b/feature/contact/src/main/ets/contract/DataColumns.ets @@ -55,4 +55,6 @@ export default class DataColumns extends BaseColumns { static readonly SYN_1: string = "syn_1"; static readonly SYN_2: string = "syn_2"; static readonly SYN_3: string = "syn_3"; + static readonly FAVORITE: string = "favorite"; + static readonly CONTENT_TYPE: string = "content_type"; } \ No newline at end of file diff --git a/feature/contact/src/main/ets/contract/DataType.ets b/feature/contact/src/main/ets/contract/DataType.ets index 8e1603a..587d80c 100644 --- a/feature/contact/src/main/ets/contract/DataType.ets +++ b/feature/contact/src/main/ets/contract/DataType.ets @@ -44,4 +44,5 @@ export default class DataType { static readonly DATA: string = DataColumns.DETAIL_INFO; static readonly LABEL_ID: string = DataColumns.EXTEND7; static readonly LABEL_NAME: string = DataColumns.CUSTOM_DATA; + static readonly FAVORITE: string = DataColumns.FAVORITE; } \ No newline at end of file diff --git a/feature/contact/src/main/ets/contract/SearchContacts.ets b/feature/contact/src/main/ets/contract/SearchContacts.ets new file mode 100644 index 0000000..604afbd --- /dev/null +++ b/feature/contact/src/main/ets/contract/SearchContacts.ets @@ -0,0 +1,27 @@ +/** + * 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 SearchContactColumns from './SearchContactsColumns'; + +/** + * searchContact data type + */ +export class SearchContacts extends SearchContactColumns { + static readonly CONTENT_URI: string = "datashare:///com.ohos.contactsdataability/contacts/search_contact"; + + constructor() { + super() + } +} \ No newline at end of file diff --git a/feature/contact/src/main/ets/contract/SearchContactsColumns.ets b/feature/contact/src/main/ets/contract/SearchContactsColumns.ets new file mode 100644 index 0000000..81bddb9 --- /dev/null +++ b/feature/contact/src/main/ets/contract/SearchContactsColumns.ets @@ -0,0 +1,39 @@ +/** + * 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 BaseColumns from './BaseColumns'; + +/** + * Field constants in the contact searchcontact table + */ +export default class SearchContactsColumns extends BaseColumns { + static readonly ACCOUNT_ID: string = "account_id"; + static readonly CONTACT_ID: string = "contact_id"; + static readonly ROW_CONTACT_ID: string = "raw_contact_id"; + static readonly SEARCH_NAME: string = "search_name"; + static readonly DISPLAY_NAME: string = "display_name"; + static readonly PHONETIC_NAME: string = "phonetic_name"; + static readonly PHOTO_ID: string = "photo_id"; + static readonly PHOTO_FILE_ID: string = "photo_file_id"; + static readonly IS_DELETED: string = "is_deleted"; + static readonly TYPE_ID: string = "type_id"; + static readonly POSITION: string = "position"; + static readonly DETAIL_INFO: string = "detail_info"; + static readonly PHOTO_FIRST_NAME: string = "photo_first_name"; + static readonly SORT_FIRST_LETTER: string = "sort_first_letter"; + static readonly CUSTOM_DATA: string = "custom_data"; + static readonly CONTENT_TYPE: string = "content_type"; + static readonly HAS_PHONE_NUMBER: string = "has_phone_number"; +} \ No newline at end of file diff --git a/feature/contact/src/main/ets/entity/DataItem.ets b/feature/contact/src/main/ets/entity/DataItem.ets index f5776e6..0bf35df 100644 --- a/feature/contact/src/main/ets/entity/DataItem.ets +++ b/feature/contact/src/main/ets/entity/DataItem.ets @@ -63,6 +63,7 @@ export class DataItem { contentValues.set(Data.SYN_1, resultSet.getString(resultSet.getColumnIndex(Data.SYN_1))); contentValues.set(Data.SYN_2, resultSet.getString(resultSet.getColumnIndex(Data.SYN_2))); contentValues.set(Data.SYN_3, resultSet.getString(resultSet.getColumnIndex(Data.SYN_3))); + contentValues.set(Data.FAVORITE, resultSet.getString(resultSet.getColumnIndex(Data.FAVORITE))); return new DataItem(contentValues); } @@ -85,4 +86,8 @@ export class DataItem { getLabelName() { return this.values.get(DataType.LABEL_NAME); } + + getFavorite() { + return this.values.get(DataType.FAVORITE); + } } \ No newline at end of file diff --git a/feature/contact/src/main/ets/entity/SearchContact.ets b/feature/contact/src/main/ets/entity/SearchContact.ets new file mode 100644 index 0000000..a56142b --- /dev/null +++ b/feature/contact/src/main/ets/entity/SearchContact.ets @@ -0,0 +1,51 @@ +/** + * 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 { SearchContacts } from '../contract/SearchContacts'; +import { Data } from '../contract/Data'; +import { DataItem } from '../entity/DataItem'; + +export default class SearchContact { + readonly id: number; + readonly values: Map; + readonly dataItems: DataItem[]; + constructor(id: number, values: Map) { + this.id = id; + this.values = values; + this.dataItems = []; + } + + static fromResultSet(resultSet: any): SearchContact{ + let contentValues: Map = new Map(); + contentValues.set(SearchContacts.ACCOUNT_ID, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.ACCOUNT_ID))); + contentValues.set(SearchContacts.CONTACT_ID, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.CONTACT_ID))); + contentValues.set(SearchContacts.RAW_CONTACT_ID, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.RAW_CONTACT_ID))); + contentValues.set(SearchContacts.SEARCH_NAME, resultSet.getString(resultSet.getColumnIndex(SearchContacts.SEARCH_NAME))); + contentValues.set(SearchContacts.DISPLAY_NAME, resultSet.getString(resultSet.getColumnIndex(SearchContacts.DISPLAY_NAME))); + contentValues.set(SearchContacts.PHONETIC_NAME, resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHONETIC_NAME))); + contentValues.set(SearchContacts.PHOTO_ID, resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHOTO_ID))); + contentValues.set(SearchContacts.PHOTO_FILE_ID, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.PHOTO_FILE_ID))); + contentValues.set(SearchContacts.IS_DELETED, resultSet.getString(resultSet.getColumnIndex(SearchContacts.IS_DELETED))); + contentValues.set(SearchContacts.POSITION, resultSet.getString(resultSet.getColumnIndex(SearchContacts.POSITION))); + contentValues.set(SearchContacts.PHOTO_FIRST_NAME, resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHOTO_FIRST_NAME))); + contentValues.set(SearchContacts.SORT_FIRST_LETTER, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.SORT_FIRST_LETTER))); + contentValues.set(SearchContacts.CUSTOM_DATA, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.CUSTOM_DATA))); + contentValues.set(SearchContacts.DETAIL_INFO, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.DETAIL_INFO))); + contentValues.set(SearchContacts.CONTENT_TYPE, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.CONTENT_TYPE))); + contentValues.set(SearchContacts.HAS_PHONE_NUMBER, resultSet.getLong(resultSet.getColumnIndex(SearchContacts.HAS_PHONE_NUMBER))); + return new SearchContact(resultSet.getLong(resultSet.getColumnIndex(SearchContacts.ID)), contentValues); + } +} + diff --git a/feature/contact/src/main/ets/repo/ContactListItem.ets b/feature/contact/src/main/ets/repo/ContactListItem.ets index d15ffe4..64e53b4 100644 --- a/feature/contact/src/main/ets/repo/ContactListItem.ets +++ b/feature/contact/src/main/ets/repo/ContactListItem.ets @@ -20,7 +20,7 @@ import ContactBuilder from '../entity/ContactBuilder'; export default class ContactListItem { static readonly COLUMNS: string[] = [Contacts.ID, Contacts.QUICK_SEARCH_KEY, RawContacts.DISPLAY_NAME, - RawContacts.SORT_FIRST_LETTER, RawContacts.PHOTO_FIRST_NAME, Contacts.COMPANY, Contacts.POSITION]; + RawContacts.SORT_FIRST_LETTER, RawContacts.PHOTO_FIRST_NAME, Contacts.COMPANY, Contacts.POSITION, RawContacts.FAVORITE_ORDER]; readonly id: number; readonly displayName: string; readonly sortFirstLetter: string; @@ -28,6 +28,7 @@ export default class ContactListItem { readonly quickSearchKey: string; readonly company: string; readonly position: string; + readonly favoriteOrder: string; phoneNumbers: [] = []; constructor(resultSet: any) { @@ -38,6 +39,7 @@ export default class ContactListItem { this.quickSearchKey = resultSet.getString(resultSet.getColumnIndex(Contacts.QUICK_SEARCH_KEY)); this.company = resultSet.getString(resultSet.getColumnIndex(Contacts.COMPANY)); this.position = resultSet.getString(resultSet.getColumnIndex(Contacts.POSITION)); + this.favoriteOrder = resultSet.getString(resultSet.getColumnIndex(RawContacts.FAVORITE_ORDER)); } createContact() { diff --git a/feature/contact/src/main/ets/repo/ContactRepository.ets b/feature/contact/src/main/ets/repo/ContactRepository.ets index c57a2f3..beebe5f 100644 --- a/feature/contact/src/main/ets/repo/ContactRepository.ets +++ b/feature/contact/src/main/ets/repo/ContactRepository.ets @@ -29,6 +29,12 @@ import { Data } from '../contract/Data'; import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; import ContactListItem from './ContactListItem'; import { DataItemType } from '../contract/DataType'; +import Calls from '../../../../../call/src/main/ets/contract/Calls'; +import { CallLog } from '../../../../../call/src/main/ets/entity/CallLog'; +import CallLogBuilder from '../../../../../call/src/main/ets/entity/CallLogBuilder'; +import { SearchContacts } from '../contract/SearchContacts'; +import SearchContactListItem from './SearchContactListItem'; +import ContactUsuallyListItem from './ContactUsuallyListItem'; const TAG = "ContactRepository"; @@ -207,14 +213,24 @@ export class ContactRepository implements IContactRepository { return new ContactList({}); } - findByPhoneIsNotNull(callback) { - this.getAllContactNumbers((contactNumberMap) => { + findByPhoneIsNotNull(favorite: number, editContact: number, callback) { + HiLog.i(TAG, 'initContactsList resultList success favoriteForm favorite :' + favorite + "---" + editContact); + this.getAllContactNumbers(favorite, editContact, (contactNumberMap) => { this.getDataAbilityHelper().then((dataAbilityHelper) => { let conditionArgs = new dataSharePredicates.DataSharePredicates(); - conditionArgs.equalTo(RawContacts.IS_DELETED, '0') - .and() - .equalTo(Contacts.HAS_PHONE_NUMBER, '1') - .orderByAsc(RawContacts.SORT_FIRST_LETTER); + if (-1 !== editContact || 0 === favorite) { + conditionArgs.equalTo(RawContacts.IS_DELETED, '0'); + if (0 === favorite) { + conditionArgs.and(); + conditionArgs.equalTo(RawContacts.FAVORITE, 0); + } + conditionArgs.orderByAsc(RawContacts.SORT_FIRST_LETTER); + } else { + conditionArgs.equalTo(RawContacts.IS_DELETED, '0') + .and() + .equalTo(Contacts.HAS_PHONE_NUMBER, '1') + .orderByAsc(RawContacts.SORT_FIRST_LETTER); + } dataAbilityHelper.query(Contacts.CONTACT_URI, conditionArgs, ContactListItem.COLUMNS) .then(resultSet => { let rst: ContactListItem[] = []; @@ -251,11 +267,15 @@ export class ContactRepository implements IContactRepository { /** * 查询所有联系人手机号 */ - private getAllContactNumbers(callback) { + private getAllContactNumbers(favorite: number, editContact: number, callback) { this.getDataAbilityHelper().then((dataAbilityHelper) => { let resultColumns = [RawContacts.CONTACT_ID, Data.DETAIL_INFO, Data.EXTEND7, Data.CUSTOM_DATA]; let conditionArgs = new dataSharePredicates.DataSharePredicates(); - conditionArgs.equalTo(Data.TYPE_ID, DataItemType.PHONE).orderByAsc(RawContacts.CONTACT_ID); + if (-1 !== editContact || 0 === favorite) { + conditionArgs.orderByAsc(RawContacts.CONTACT_ID); + } else { + conditionArgs.equalTo(Data.TYPE_ID, DataItemType.PHONE).orderByAsc(RawContacts.CONTACT_ID); + } dataAbilityHelper.query(Data.CONTENT_URI, conditionArgs, resultColumns).then(resultSet => { // 用于存储联系人及其电话号码的对应关系 let contactNumberMap = new Map(); @@ -442,4 +462,236 @@ export class ContactRepository implements IContactRepository { } return contacts; } + + findAllFavorite(actionData, callback) { + HiLog.i(TAG, 'refreshUsually findAllFavorite start.'); + let conditionArgs = new dataSharePredicates.DataSharePredicates(); + conditionArgs.equalTo(RawContacts.IS_DELETED, '0'); + conditionArgs.and(); + conditionArgs.equalTo(RawContacts.FAVORITE, 1); + conditionArgs.and(); + conditionArgs.orderByAsc(RawContacts.FAVORITE_ORDER); + this.getDataAbilityHelper().then((dataAbilityHelper) => { + dataAbilityHelper.query(Contacts.CONTACT_URI, conditionArgs, ContactListItem.COLUMNS) + .then(resultSet => { + let rst: ContactListItem[] = []; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + rst.push(new ContactListItem(resultSet)); + } while (resultSet.goToNextRow()); + resultSet.close(); + HiLog.i(TAG, 'findAllFavorite query data success.'); + callback(rst); + } + }) + .catch(error => { + HiLog.e(TAG, 'findAllFavorite error:%s' + JSON.stringify(error.message)); + callback([]); + }); + }).catch(error => { + HiLog.e(TAG, 'findAllFavorite error:%s' + JSON.stringify(error.message)); + callback([]); + }); + } + + findAllUsually(actionData, callback) { + HiLog.i(TAG, 'refreshUsually findAllUsually start.'); + let conditionArgs = new dataSharePredicates.DataSharePredicates(); + conditionArgs.groupBy([Calls.DISPLAY_NAME, Calls.PHONE_NUMBER]).distinct(); + conditionArgs.and(); + conditionArgs.orderByDesc(Calls.TALK_DURATION); + conditionArgs.and(); + conditionArgs.orderByDesc(Calls.CREATE_TIME); + this.getDataAbilityHelper().then((dataAbilityHelper) => { + dataAbilityHelper.query(Calls.CALL_LOG_URI, conditionArgs, null) + .then(resultSet => { + let rst: CallLog[] = []; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + let builder = CallLogBuilder.fromResultSet(resultSet); + if (builder.id > 0) { + rst.push(new CallLog(builder)); + } + } while (resultSet.goToNextRow()); + resultSet.close(); + HiLog.i(TAG, 'findAllUsually query data success.'); + callback(rst); + } + }) + .catch(error => { + HiLog.e(TAG, 'findAllUsually error:%s' + JSON.stringify(error.message)); + callback([]); + }); + }).catch(error => { + HiLog.e(TAG, 'findAllUsually error:%s' + JSON.stringify(error.message)); + callback([]); + }); + } + + getDisplayNameByFavorite(displayName, usuallyPhone, callback) { + HiLog.i(TAG, 'getDisplayNameByFavorite start.'); + let conditionArgs = new dataSharePredicates.DataSharePredicates(); + conditionArgs.equalTo(RawContacts.IS_DELETED, '0'); + conditionArgs.and(); + conditionArgs.in(RawContacts.DISPLAY_NAME, displayName); + conditionArgs.and(); + conditionArgs.in(Data.DETAIL_INFO, usuallyPhone); + conditionArgs.and(); + conditionArgs.equalTo(RawContacts.FAVORITE, 0); + conditionArgs.and(); + conditionArgs.equalTo(Data.CONTENT_TYPE, 'phone'); + this.getDataAbilityHelper().then((dataAbilityHelper) => { + dataAbilityHelper.query(Data.CONTENT_URI, conditionArgs, ContactUsuallyListItem.COLUMNS) + .then(resultSet => { + let rst: ContactUsuallyListItem[] = []; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + rst.push(new ContactUsuallyListItem(resultSet)); + } while (resultSet.goToNextRow()); + resultSet.close(); + HiLog.i(TAG, 'getDisplayNameByFavorite query data sc.'); + callback(rst); + } + }) + .catch(error => { + HiLog.e(TAG, 'getDisplayNameByFavorite error:%s' + JSON.stringify(error.message)); + callback([]); + }); + }).catch(error => { + HiLog.e(TAG, 'getDisplayNameByFavorite error:%s' + JSON.stringify(error.message)); + callback([]); + }); + } + + searchContact(actionData, callback) { + HiLog.i(TAG, "searchContact start."); + let conditionArgs = new dataSharePredicates.DataSharePredicates(); + let searchValue: string = actionData.value; + conditionArgs.beginWrap() + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'phone') + conditionArgs.and() + conditionArgs.beginWrap() + conditionArgs.like(SearchContacts.DETAIL_INFO, '%' + searchValue + '%') + conditionArgs.endWrap() + conditionArgs.or() + conditionArgs.beginWrap() + conditionArgs.equalTo(SearchContacts.SORT_FIRST_LETTER, '#') + conditionArgs.and() + conditionArgs.like(SearchContacts.SEARCH_NAME, '%' + searchValue + '%') + conditionArgs.endWrap() + conditionArgs.endWrap() + conditionArgs.or() + conditionArgs.beginWrap() + conditionArgs.equalTo(SearchContacts.SORT_FIRST_LETTER, searchValue[0].toUpperCase()) + conditionArgs.and() + conditionArgs.like(SearchContacts.SEARCH_NAME, '%' + searchValue + '%') + conditionArgs.and() + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'phone') + conditionArgs.endWrap() + conditionArgs.or() + conditionArgs.beginWrap() + conditionArgs.equalTo(SearchContacts.SORT_FIRST_LETTER, searchValue.toUpperCase()) + conditionArgs.and() + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'phone') + conditionArgs.endWrap() + conditionArgs.or() + conditionArgs.beginWrap() + conditionArgs.like(SearchContacts.SEARCH_NAME, '%' + actionData.value + '%') + conditionArgs.and() + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'phone') + conditionArgs.endWrap() + conditionArgs.or() + conditionArgs.beginWrap() + conditionArgs.like(SearchContacts.SEARCH_NAME, '%' + actionData.value + '%') + conditionArgs.and() + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'name') + conditionArgs.and() + conditionArgs.equalTo(SearchContacts.HAS_PHONE_NUMBER, '0') + conditionArgs.endWrap() + conditionArgs.and() + conditionArgs.groupBy([SearchContacts.ROW_CONTACT_ID]).distinct(); + this.getDataAbilityHelper().then((dataAbilityHelper) => { + dataAbilityHelper.query(SearchContacts.CONTENT_URI, conditionArgs, SearchContactListItem.COLUMNS) + .then(resultSet => { + HiLog.i(TAG, "searchContact resultSet.rowCount : " + JSON.stringify(resultSet.rowCount) ); + let rst: SearchContactListItem[] = []; + let goTo: boolean = false; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + rst.push(new SearchContactListItem(resultSet)); + goTo = resultSet.goToNextRow(); + } while (goTo); + resultSet.close(); + HiLog.i(TAG, 'searchContact query data success.'); + callback(rst); + } + }) + .catch(error => { + HiLog.i(TAG, 'searchContact error:%s' + JSON.stringify(error.message)); + callback([]); + }); + }).catch(error => { + HiLog.i(TAG, 'searchContact:%s' + JSON.stringify(error.message)); + callback([]); + }); + } + + queryT9PhoneIsNotNull(favorite: any, callback) { + this.getAllContactNumbers(0, 0, (contactNumberMap) => { + this.getDataAbilityHelper().then((dataAbilityHelper) => { + let conditionArgs = new dataSharePredicates.DataSharePredicates(); + conditionArgs.like(SearchContacts.DETAIL_INFO, `%${favorite.teleNumber}%`); + conditionArgs.equalTo(SearchContacts.CONTENT_TYPE, 'phone'); + conditionArgs.orderByAsc(SearchContacts.DISPLAY_NAME) + dataAbilityHelper.query( + SearchContacts.CONTENT_URI, + conditionArgs, + SearchContactListItem.COLUMNS + ).then(resultSet => { + let rst: ContactListItem[] = []; + if (resultSet.rowCount === 0) { + resultSet.close(); + callback(rst); + } else { + resultSet.goToFirstRow(); + do { + let id = resultSet.getLong(resultSet.getColumnIndex(Contacts.ID)); + if (!contactNumberMap.has(id)) { + HiLog.w(TAG, 'findAll: contact id is invalid or contact has no phone number.'); + continue; + } + let contactListItem = new ContactListItem(resultSet); + contactListItem.phoneNumbers = contactNumberMap.get(id); + rst.push(contactListItem); + } while (resultSet.goToNextRow()); + resultSet.close(); + callback(rst); + } + }) + .catch(error => { + HiLog.w(TAG, 'findAll error:%s' + JSON.stringify(error.message)); + callback(); + }); + }).catch(error => { + HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); + callback(); + }); + }); + } } \ No newline at end of file diff --git a/feature/contact/src/main/ets/repo/ContactUsuallyListItem.ets b/feature/contact/src/main/ets/repo/ContactUsuallyListItem.ets new file mode 100644 index 0000000..495d9ae --- /dev/null +++ b/feature/contact/src/main/ets/repo/ContactUsuallyListItem.ets @@ -0,0 +1,53 @@ +/** + * 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 Contact from '../entity/Contact'; +import { Contacts } from '../contract/Contacts'; +import { RawContacts } from '../contract/RawContacts'; +import ContactBuilder from '../entity/ContactBuilder'; +import { Data } from '../contract/Data'; + +export default class ContactUsuallyListItem { + static readonly COLUMNS: string[] = [Data.RAW_CONTACT_ID, Contacts.QUICK_SEARCH_KEY, RawContacts.DISPLAY_NAME, + RawContacts.SORT_FIRST_LETTER, RawContacts.PHOTO_FIRST_NAME, Contacts.COMPANY, Contacts.POSITION, + RawContacts.FAVORITE_ORDER, Data.DETAIL_INFO, Data.CONTENT_TYPE]; + readonly id: number; + readonly displayName: string; + readonly sortFirstLetter: string; + readonly photoFirstName: string; + readonly quickSearchKey: string; + readonly company: string; + readonly position: string; + readonly favoriteOrder: string; + readonly detailInfo: string; + readonly contentType: string; + + constructor(resultSet: any) { + this.id = resultSet.getLong(resultSet.getColumnIndex(Data.RAW_CONTACT_ID)); + this.displayName = resultSet.getString(resultSet.getColumnIndex(RawContacts.DISPLAY_NAME)); + this.sortFirstLetter = resultSet.getString(resultSet.getColumnIndex(RawContacts.SORT_FIRST_LETTER)); + this.photoFirstName = resultSet.getString(resultSet.getColumnIndex(RawContacts.PHOTO_FIRST_NAME)); + this.quickSearchKey = resultSet.getString(resultSet.getColumnIndex(Contacts.QUICK_SEARCH_KEY)); + this.company = resultSet.getString(resultSet.getColumnIndex(Contacts.COMPANY)); + this.position = resultSet.getString(resultSet.getColumnIndex(Contacts.POSITION)); + this.favoriteOrder = resultSet.getString(resultSet.getColumnIndex(RawContacts.FAVORITE_ORDER)); + this.detailInfo = resultSet.getString(resultSet.getColumnIndex(Data.DETAIL_INFO)); + this.contentType = resultSet.getString(resultSet.getColumnIndex(Data.CONTENT_TYPE)); + } + +// createContact() { +// return new Contact(new ContactBuilder(this.id)); +// } +} \ No newline at end of file diff --git a/feature/contact/src/main/ets/repo/FavoriteListItem.ets b/feature/contact/src/main/ets/repo/FavoriteListItem.ets new file mode 100644 index 0000000..ab5ee28 --- /dev/null +++ b/feature/contact/src/main/ets/repo/FavoriteListItem.ets @@ -0,0 +1,49 @@ +/** + * 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 Contact from '../entity/Contact'; +import { Contacts } from '../contract/Contacts'; +import { RawContacts } from '../contract/RawContacts'; +import ContactBuilder from '../entity/ContactBuilder'; + +export default class FavoriteListItem { + static readonly COLUMNS: string[] = [Contacts.ID, Contacts.QUICK_SEARCH_KEY, RawContacts.DISPLAY_NAME, + RawContacts.SORT_FIRST_LETTER, RawContacts.PHOTO_FIRST_NAME, Contacts.COMPANY, Contacts.POSITION]; + + + + readonly id: number; + readonly displayName: string; + readonly sortFirstLetter: string; + readonly photoFirstName: string; + readonly quickSearchKey: string; + readonly company: string; + readonly position: string; + phoneNumbers: [] = []; + + constructor(resultSet: any) { + this.id = resultSet.getLong(resultSet.getColumnIndex(Contacts.ID)); + this.displayName = resultSet.getString(resultSet.getColumnIndex(RawContacts.DISPLAY_NAME)); + this.sortFirstLetter = resultSet.getString(resultSet.getColumnIndex(RawContacts.SORT_FIRST_LETTER)); + this.photoFirstName = resultSet.getString(resultSet.getColumnIndex(RawContacts.PHOTO_FIRST_NAME)); + this.quickSearchKey = resultSet.getString(resultSet.getColumnIndex(Contacts.QUICK_SEARCH_KEY)); + this.company = resultSet.getString(resultSet.getColumnIndex(Contacts.COMPANY)); + this.position = resultSet.getString(resultSet.getColumnIndex(Contacts.POSITION)); + } + + createContact() { + return new Contact(new ContactBuilder(this.id)); + } +} \ No newline at end of file diff --git a/feature/contact/src/main/ets/repo/IContactRepository.ets b/feature/contact/src/main/ets/repo/IContactRepository.ets index 0128049..22b8e7e 100644 --- a/feature/contact/src/main/ets/repo/IContactRepository.ets +++ b/feature/contact/src/main/ets/repo/IContactRepository.ets @@ -26,10 +26,10 @@ export default interface IContactRepository { save: (contact: ContactDelta, callback) => void; findById: (id: number, callback) => void; findByQuickSearchKey: (searchKey: string, callback) => void; - findAll: (actionData: any, callback) => void; + findAll: (favorite: number, actionData: any, callback) => void; findAllWithBookIndex: () => ContactList; search: (queryStr: string) => ContactList; - findByPhoneIsNotNull: (callback) => void; + findByPhoneIsNotNull: (favorite: number, editContact: number, callback) => void; findByMailIsNotNull: () => ContactList; deleteByIdIn: (ids: number[]) => boolean; deleteById: (id: number, callback) => void; diff --git a/feature/contact/src/main/ets/repo/SearchContactListItem.ets b/feature/contact/src/main/ets/repo/SearchContactListItem.ets new file mode 100644 index 0000000..7206e1d --- /dev/null +++ b/feature/contact/src/main/ets/repo/SearchContactListItem.ets @@ -0,0 +1,63 @@ +/** + * 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 Contact from '../entity/Contact'; +import ContactBuilder from '../entity/ContactBuilder'; +import { SearchContacts } from '../contract/SearchContacts'; + +export default class SearchContactListItem { + static readonly COLUMNS: string[] = [SearchContacts.ID, SearchContacts.ACCOUNT_ID, SearchContacts.CONTACT_ID, + SearchContacts.ROW_CONTACT_ID, SearchContacts.SEARCH_NAME, SearchContacts.DISPLAY_NAME, SearchContacts.PHONETIC_NAME, + SearchContacts.PHOTO_ID, SearchContacts.PHOTO_FILE_ID, SearchContacts.IS_DELETED, SearchContacts.POSITION, + SearchContacts.PHOTO_FIRST_NAME, SearchContacts.SORT_FIRST_LETTER, SearchContacts.CUSTOM_DATA, + SearchContacts.DETAIL_INFO, SearchContacts.CONTENT_TYPE, SearchContacts.HAS_PHONE_NUMBER]; + readonly id: number; + readonly accountId: string; + readonly contactId: string; + readonly rawContactId: string; + readonly searchName: string; + readonly displayName: string; + readonly phoneticName: string; + readonly photoId: string; + readonly photoFileId: string; + readonly isDeleted: number; + readonly position: string; + readonly photoFirstName: string; + readonly sortFirstLetter: string; + readonly customData: string; + readonly detailInfo: string; + readonly contentType: string; + readonly hasPhoneNumber: string; + + constructor(resultSet: any) { + this.id = resultSet.getLong(resultSet.getColumnIndex(SearchContacts.ID)); + this.accountId = resultSet.getString(resultSet.getColumnIndex(SearchContacts.ACCOUNT_ID)); + this.contactId = resultSet.getString(resultSet.getColumnIndex(SearchContacts.CONTACT_ID)); + this.rawContactId = resultSet.getString(resultSet.getColumnIndex(SearchContacts.ROW_CONTACT_ID)); + this.searchName = resultSet.getString(resultSet.getColumnIndex(SearchContacts.SEARCH_NAME)); + this.displayName = resultSet.getString(resultSet.getColumnIndex(SearchContacts.DISPLAY_NAME)); + this.phoneticName = resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHONETIC_NAME)); + this.photoId = resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHOTO_ID)); + this.photoFileId = resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHOTO_FILE_ID)); + this.isDeleted = resultSet.getString(resultSet.getColumnIndex(SearchContacts.IS_DELETED)); + this.position = resultSet.getString(resultSet.getColumnIndex(SearchContacts.POSITION)); + this.photoFirstName = resultSet.getString(resultSet.getColumnIndex(SearchContacts.PHOTO_FIRST_NAME)); + this.sortFirstLetter = resultSet.getString(resultSet.getColumnIndex(SearchContacts.SORT_FIRST_LETTER)); + this.customData = resultSet.getString(resultSet.getColumnIndex(SearchContacts.CUSTOM_DATA)); + this.detailInfo = resultSet.getString(resultSet.getColumnIndex(SearchContacts.DETAIL_INFO)); + this.contentType = resultSet.getString(resultSet.getColumnIndex(SearchContacts.CONTENT_TYPE)); + this.hasPhoneNumber = resultSet.getString(resultSet.getColumnIndex(SearchContacts.HAS_PHONE_NUMBER)); + } +} \ No newline at end of file diff --git a/feature/phonenumber/src/main/ets/PhoneNumber.ets b/feature/phonenumber/src/main/ets/PhoneNumber.ets index 853dd7e..6628600 100644 --- a/feature/phonenumber/src/main/ets/PhoneNumber.ets +++ b/feature/phonenumber/src/main/ets/PhoneNumber.ets @@ -69,6 +69,20 @@ export class PhoneNumber { }); } + dialerSpecialCode() { + if (this.number.length === 0) { + return; + } + let phoneNumber = this.number; + call.inputDialerSpecialCode(phoneNumber, (err) => { + if (err) { + HiLog.e(TAG, 'inputDialerSpecialCode error: ' + JSON.stringify(err)); + } else { + HiLog.e(TAG, 'inputDialerSpecialCode success'); + } + }); + } + sendMessage(formatnum?:string, name?: string) { HiLog.i(TAG, 'send message.'); MmsService.sendMessage(this.number, formatnum, name); diff --git a/sign/contacts.p7b b/sign/contacts.p7b index 72d2fbfd1ef84c07ba168289546450074905bed0..a7afa2d78083ee1a889168212e9636c39b0a1778 100644 GIT binary patch delta 262 zcmX>j_esvdpou?^jZ>@5qwPB{BRkWACjNM!P&5-Gqam*WHydX{n+IbmGYb==K@&Sa zLLuXVCUypvCbmBt9s1c+b(Hcm@{9EfQj2mki;FY!^YnsUL*heReOv=P{QV;1gF_rc zTsNO%d&gdHYG`U`U}9ik5M`hbH;$9VP>Dr>htIY1eV>DdP1))u)fZ3H6jE$YmD*2l zSo;4!llI}Qd96*Pjvt?msVsSTcWm2e|v}8fUF_y{Y=T&a*lo#8~v)bFDKW!^po!mujZ>@5qwPB{BRkWACVoqxkSP-*qam*WHydX{n+IbmGYb==K@;0c zghIvzO>DPVn%J&wbm(W>{FUu3d%dx-fq|i^si|3%fj(RzCySvHi-NF2&TpkH|2dA< zC|>hDx}Cpfx2f!T#ZyxSZPgW9?e70E;9}zdI^mox3o{doyMZf{BE#Wl2hx-;Neip! znx0TG{K8TV%tc&w^R6x2cGHx}v>u Mxt!XjIX_EU0gu5;8~^|S