diff --git a/OAT.xml b/OAT.xml
index 44bbe5740d7194151c537ea593cc6fe6c9b6ff99..fb0989a96f507fd88201f89cf2a926c9f9493c3f 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -96,6 +96,10 @@
desc="self developed image"
name="entry/src/main/resources/base/media/.*.png"
type="filepath"/>
+
{ this.onCancel() },
+ confirm: ()=> { this.onAccept() },
+ }),
+ cancel: this.exitApp,
+ autoCancel: true,
+ alignment: DialogAlignment.Bottom,
+ offset: { dx: 0, dy: -20 },
+ gridCount: 4,
+ customStyle: false,
+ backgroundColor: 0xd9ffffff,
+ cornerRadius: 10,
+ })
+
+ aboutToDisappear() {
+ this.dialogController = null
+ }
+
+ onCancel() {
+ console.info('Callback when the first button is clicked')
+ }
+
+ onAccept() {
+ console.info('Callback when the second button is clicked')
+ }
+
+ exitApp() {
+ console.info('Click the callback in the blank area')
+ }
+
+ aboutToAppear() {
+ this.importContactsPresenter.aboutToAppear();
+ }
+
+ @Builder
+ MoreMenu() {
+ Menu() {
+ MenuItem({ content: '从内部存储导入联系人文件' })
+ .onChange((selected) => {
+ if (selected) {
+ this.importContactsPresenter.dialogController = this.dialogController;
+ this.importContactsPresenter.importFile();
+ }
+ })
+ }
+ }
build() {
Row() {
@@ -104,6 +157,7 @@ struct TitleGuide {
.height($r("app.float.id_card_image_small"))
.objectFit(ImageFit.Contain)
.opacity(0.4)
+ .bindMenu(this.MoreMenu)
}
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Center)
@@ -112,6 +166,61 @@ struct TitleGuide {
}
}
+@CustomDialog
+struct CustomDialogExample {
+ importContactsPresenter: ImportContactsPresenter = ImportContactsPresenter.getInstance();
+ controller?: CustomDialogController
+ cancel: () => void = () => {
+ }
+ confirm: () => void = () => {
+ }
+
+ JumpFileUpload() {
+ let context = getContext(this) as common.UIAbilityContext;
+ let want: Want = {
+ deviceId: '',
+ bundleName: 'com.pengju.minihttpserver',
+ abilityName: 'EntryAbility',
+ };
+ context.startAbility(want, (err: BusinessError) => {
+ if (err.code) {
+ console.error(`Failed to startAbility. Code: ${err.code}, message: ${err.message}`);
+ }
+ });
+ }
+
+ build() {
+ Column() {
+ Column() {
+ Text('没有找到.vcf文件!').fontSize(20).margin({ top: 10, bottom: 10 })
+ Text('可通过以下两种方式之一上传.vcf文件到指定目录:').fontSize(18).margin({ bottom: 10 })
+ Text('1、使用文件传输助手:\n(1)打开文件传输助手\n(2)点击打开服务按钮\n(3)手机连接wifi后会显示http地址\n(4)另一台手机连上相同wifi并通过浏览器访问文件传输助手上的http地址\n(5)在进入的网页中选择需要上传到的开发者手机目录/app/el2/0/base/com.ohos.contacts/haps/entry/files/\n(6)点击选择文件,选取手机中存储的.vcf文件(一次选取一个.vcf文件上传)后点击Upload file').fontSize(16).margin({ bottom: 10 })
+ Text('2、使用hdc file send推送.vcf文件到/data/app/el2/0/base/com.ohos.contacts/haps/entry/files/目录,例如:hdc file send D:/\demo.vcf /data/app/el2/0/base/com.ohos.contacts/haps/entry/files/').fontSize(16).margin({ bottom: 10 })
+ }
+ .alignItems(HorizontalAlign.Start)
+ .padding(20)
+
+ Flex({ justifyContent: FlexAlign.SpaceAround }) {
+ Button('知道了')
+ .onClick(() => {
+ if (this.controller != undefined) {
+ this.controller.close()
+ this.cancel()
+ }
+ }).backgroundColor(0xffffff).fontColor(Color.Gray)
+ Button('跳转到文件传输助手')
+ .onClick(() => {
+ if (this.controller != undefined) {
+ this.JumpFileUpload()
+ this.controller.close()
+ this.confirm()
+ }
+ }).backgroundColor(0xffffff).fontColor(Color.Blue)
+ }.margin({ bottom: 10 })
+ }.borderRadius(10)
+ }
+}
+
@Component
struct ContactContent {
@Link private presenter: ContactListPresenter;
diff --git a/entry/src/main/ets/pages/index.ets b/entry/src/main/ets/pages/index.ets
index d3c2d97658572479a7a7760539017f74644ec0ea..ef9384fd796306551eb114aa60aa7f223c4aeb83 100644
--- a/entry/src/main/ets/pages/index.ets
+++ b/entry/src/main/ets/pages/index.ets
@@ -26,6 +26,9 @@ import CallRecordPresenter from '../presenter/dialer/callRecord/CallRecordPresen
import FavoriteListPresenter from '../presenter/favorite/FavoriteListPresenter';
import device from '@system.device';
import emitter from '@ohos.events.emitter';
+import common from '@ohos.app.ability.common';
+import fs from '@ohos.file.fs';
+import { BusinessError } from '@ohos.base';
const TAG = 'Index ';
@@ -118,6 +121,7 @@ struct Index {
emitter.on(innerEvent, (data) => {
this.isContactSearch = data.data['isSearchPage'];
})
+ this.createVcf();
}
aboutToDisappear() {
@@ -132,6 +136,19 @@ struct Index {
}
}
+ createVcf() {
+ let filesDir = (getContext(this) as common.UIAbilityContext).filesDir;
+ let uri = filesDir + '/' + 'contactsCacheFile.vcf';
+ HiLog.i(TAG, `indexuri: ${uri}`);
+ fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE, (err: BusinessError, file: fs.File) => {
+ if (err) {
+ HiLog.e(TAG, `open failed with error message: ${JSON.stringify(err)}`);
+ } else {
+ fs.closeSync(file);
+ }
+ });
+ }
+
getInfo() {
device.getInfo({
success: function (data) {
diff --git a/entry/src/main/ets/presenter/contact/importcontacts/ImportContactsPresenter.ets b/entry/src/main/ets/presenter/contact/importcontacts/ImportContactsPresenter.ets
new file mode 100644
index 0000000000000000000000000000000000000000..dde8c058f017db9e96671aeb29734a0234b12d7e
--- /dev/null
+++ b/entry/src/main/ets/presenter/contact/importcontacts/ImportContactsPresenter.ets
@@ -0,0 +1,414 @@
+import fs, { Filter } from '@ohos.file.fs';
+import { VCardParserImpl_V21, VCardEntry, VCardInterpreter, VCardProperty, contact } from '@ohos/vcard/';
+import { BusinessError } from '@ohos.base';
+import promptAction from '@ohos.promptAction';
+import { ContactInfo } from '../../../model/bean/ContactInfo';
+import { PhoneNumBean } from '../../../model/bean/PhoneNumBean';
+import { EmailBean } from '../../../model/bean/EmailBean';
+import { AIMBean } from '../../../model/bean/AIMBean';
+import { HouseBean } from '../../../model/bean/HouseBean';
+import { AssociatedPersonBean } from '../../../model/bean/AssociatedPersonBean';
+import { EventBean } from '../../../model/bean/EventBean';
+import common from '@ohos.app.ability.common';
+import ohosContact from '@ohos.contact';
+import { Aim, Birthday, Phone } from '../../../../../../../feature/contact';
+import { HiLog } from '../../../../../../../common/src/main/ets/util/HiLog';
+
+const DURATION_TIME = 2000;
+const PROMPT_BOTTOM = 100;
+const TAG = 'ImportContactsPresenter';
+
+export default class ImportContactsPresenter {
+ public uri: string = '';
+ public contactInfoAfters: ContactInfo[] = [];
+ public allContact: ohosContact.Contact[] = [];
+ public filesDir = (getContext(this) as common.UIAbilityContext).filesDir;
+ public srcPath: string = '';
+ private static sInstance: ImportContactsPresenter;
+
+ public dialogController: CustomDialogController | null;
+
+ public static getInstance(): ImportContactsPresenter {
+ if (ImportContactsPresenter.sInstance == null) {
+ ImportContactsPresenter.sInstance = new ImportContactsPresenter();
+ }
+ return ImportContactsPresenter.sInstance;
+ }
+
+ aboutToAppear() {
+ this.getUri();
+ }
+
+ getUri() {
+ let filesDir = (getContext(this) as common.UIAbilityContext).filesDir;
+ this.uri = filesDir + '/' + 'contactsCacheFile.vcf';
+ HiLog.i(TAG, `importuri: ${this.uri}`);
+ }
+
+ importFile() {
+ this.getLatestFile()
+ }
+
+ handleContact() {
+ ohosContact.queryContacts(globalThis.context, {
+ attributes: [ohosContact.Attribute.ATTR_NAME, ohosContact.Attribute.ATTR_PHONE]
+ }, (err: BusinessError, allContact) => {
+ if (err) {
+ HiLog.e(TAG, `queryContacts callback: err-> ${JSON.stringify(err)}`);
+ return;
+ }
+ let filterContactInfoAfters = this.filterContactInfoAfters(this.contactInfoAfters);
+ let contactInfoMatched = this.filterContacts(filterContactInfoAfters, allContact);
+ if (contactInfoMatched != null && contactInfoMatched.length > 0) {
+ contactInfoMatched.forEach((contactInfoAfter) => {
+ this.addContact(contactInfoAfter);
+ })
+ } else {
+ setTimeout(() => {
+ promptAction.showToast({
+ message: '已导入',
+ duration: DURATION_TIME,
+ bottom: PROMPT_BOTTOM
+ });
+ }, 1500);
+ }
+ this.contactInfoAfters = [];
+ });
+ }
+
+ addContact(contactInfoAfter: ContactInfo) {
+ globalThis.DataWorker.sendRequest('addContact',
+ {
+ context: globalThis.context,
+ contactInfoAfter: JSON.stringify(contactInfoAfter)
+ }
+ , (arg) => {
+ HiLog.i(TAG, `addContact arg: ${JSON.stringify(arg)}`);
+ })
+ }
+
+ filterContacts(contactInfoAfters: ContactInfo[], allContact: ohosContact.Contact[]): ContactInfo[] {
+ if (allContact != null && allContact.length > 0) {
+ let filteredArr = contactInfoAfters.filter((itemA) => {
+ let isExisted = false;
+ for (let i = 0; i < allContact.length; i++) {
+ if (allContact[i]?.name?.fullName === itemA?.display_name) {
+ isExisted = true;
+ break;
+ }
+ }
+ return !isExisted;
+ });
+ HiLog.i(TAG, `filteredArr: ${JSON.stringify(filteredArr)}`);
+ return filteredArr;
+ } else {
+ return contactInfoAfters;
+ }
+ }
+
+ filterContactInfoAfters(contactInfoAfters: ContactInfo[]): ContactInfo[] {
+ let seenNames: { [key: string]: boolean } = {};
+ let contactInfofilter = contactInfoAfters.filter(item => {
+ if (seenNames[item?.display_name]) {
+ return false;
+ } else {
+ seenNames[item?.display_name] = true;
+ return true;
+ }
+ });
+ HiLog.i(TAG, `contactInfofilter: ${JSON.stringify(contactInfofilter)}`);
+ return contactInfofilter
+ }
+
+ getLatestFile() {
+ class ListFileOption {
+ public recursion: boolean = false;
+ public listNum: number = 0;
+ public filter: Filter = {};
+ }
+ let option = new ListFileOption();
+ option.filter.suffix = ['.vcf'];
+ fs.listFile(this.filesDir, option).then((filenames: Array) => {
+ console.info('listFile succeed');
+ console.info(`srcPathgetfilenames: ${JSON.stringify(filenames)}`);
+ let timeList = new Map()
+ let maxKey = 0
+ let maxValue = 0;
+ function findMaxIndex(timeList: Map) {
+ for (let [key, value] of timeList) {
+ if (value > maxValue) {
+ maxValue = value;
+ maxKey = key;
+ }
+ }
+ }
+ filenames.forEach((item,index) => {
+ if (item !== 'contactsCacheFile.vcf') {
+ let srcPath = this.filesDir + '/' + item;
+ console.info('timeListsrcPath: ', srcPath);
+ fs.stat(srcPath).then((stat: fs.Stat) => {
+ HiLog.i(TAG, `srcPathget file info succeed, the ctime of file is: ${JSON.stringify(stat.ctime)}`);
+ timeList.set(index,stat.ctime)
+ if (index === filenames.length-1) {
+ findMaxIndex(timeList);
+ this.srcPath = this.filesDir + '/' + filenames[maxKey];
+ this.parseFile();
+ }
+ }).catch((err: BusinessError) => {
+ HiLog.e(TAG, `srcPathget file info failed with error message: ${JSON.stringify(err)}`);
+ });
+ }
+ if (filenames.length === 1 && item === 'contactsCacheFile.vcf') {
+ this.dialogController.open()
+ }
+ })
+ }).catch((err: BusinessError) => {
+ console.error('list file failed with error message: ' + err.message + ', error code: ' + err.code);
+ });
+ }
+
+ parseFile() {
+ promptAction.showToast({
+ message: '系统将在稍后导入该文件,请勿重复点击',
+ duration: 1500,
+ bottom: PROMPT_BOTTOM
+ });
+ class MyVCardInterpreter implements VCardInterpreter {
+ public vCardEntries: VCardEntry[] = [];
+ public vCardEntry: VCardEntry = new VCardEntry();
+
+ onVCardStarted(): void {
+ }
+
+ onVCardEnded(): void {
+ }
+
+ onEntryStarted(): void {
+ this.vCardEntry = new VCardEntry();
+ }
+
+ onEntryEnded(): void {
+ this.vCardEntries.push(this.vCardEntry);
+ }
+
+ onPropertyCreated(property: VCardProperty): void {
+ this.vCardEntry.addProperty(property);
+ }
+ }
+
+ let myParser = new VCardParserImpl_V21();
+ let myInterpreter = new MyVCardInterpreter();
+ myParser.addInterpreter(myInterpreter);
+
+ let dstPath = this.filesDir + '/' + 'contactsCacheFile.vcf';
+ fs.copyFile(this.srcPath, dstPath, 0).then(() => {
+
+ fs.open(this.uri, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE , (err: BusinessError, file: fs.File) => {
+ if (err) {
+ promptAction.showToast({
+ message: '未读取到.vcf文件',
+ duration: DURATION_TIME,
+ bottom: PROMPT_BOTTOM
+ });
+ HiLog.e(TAG, `open failed with error message: ${JSON.stringify(err)}`);
+ } else {
+ myParser.parse(file.fd);
+ myInterpreter.vCardEntries.forEach((vCardEntry: VCardEntry, index: number) => {
+ let contactInfoAfter: ContactInfo = new ContactInfo('', '', '', [], [], '', '', '', [], [], [], [], [], [], 0);
+
+ HiLog.i(TAG, `getPhoneList: ${JSON.stringify(vCardEntry.getPhoneList())}`);
+ if (vCardEntry.getPhoneList() != null && vCardEntry.getPhoneList().length > 0) {
+ vCardEntry.getPhoneList().forEach((item: contact.PhoneNumber) => {
+ let newItem = new PhoneNumBean('', '', '', '', '')
+ newItem.num = item.phoneNumber
+ newItem.numType = this.getPhoneNumberType(item.labelId).toString();
+ contactInfoAfter.phones.push(newItem)
+ });
+ } else {
+ return
+ }
+
+ let displayName = vCardEntry.getDisplayName();
+ HiLog.i(TAG, `getDisplayName: ${JSON.stringify(displayName)}`);
+ if (displayName != null) {
+ if (displayName == '') {
+ contactInfoAfter.display_name = vCardEntry.getPhoneList()[0].phoneNumber
+ } else {
+ contactInfoAfter.display_name = displayName;
+ }
+ }
+
+ let organizationList = vCardEntry.getOrganizationList();
+ HiLog.i(TAG, `organizationList: ${JSON.stringify(vCardEntry.getOrganizationList())}`);
+ if (organizationList != null && organizationList.length > 0) {
+ const newObj = organizationList.reduce(
+ (prev, cur) => ({
+ name: cur.name ? cur.name : prev.name,
+ title: cur.title ? cur.title : prev.title,
+ }),
+ {
+ name: '', title: ''
+ },
+ );
+ HiLog.i(TAG, `newObj: ${JSON.stringify(newObj)}`);
+ contactInfoAfter.company = newObj.name;
+ contactInfoAfter.position = newObj.title;
+ }
+
+ HiLog.i(TAG, `getEmailList: ${JSON.stringify(vCardEntry.getEmailList())}`);
+ if (vCardEntry.getEmailList() != null && vCardEntry.getEmailList().length > 0) {
+ vCardEntry.getEmailList().forEach((item: contact.Email) => {
+ let newItem = new EmailBean('', '', '');
+ newItem.address = item.email;
+ newItem.emailType = item.labelId.toString();
+ contactInfoAfter.emails.push(newItem);
+ });
+ }
+
+ let notes = vCardEntry.getNotes();
+ if (notes != null && notes.length > 0) {
+ contactInfoAfter.remarks = notes[0].noteContent;
+ }
+
+ if (vCardEntry.getImList() != null && vCardEntry.getImList().length > 0) {
+ vCardEntry.getImList().forEach((item: contact.ImAddress) => {
+ let newItem = new AIMBean('', '', '', '');
+ newItem.aimName = item.imAddress;
+ newItem.aimType = this.getAIMType(item.labelId).toString();
+ contactInfoAfter.aims.push(newItem);
+ });
+ }
+
+ if (vCardEntry.getPostalList() != null && vCardEntry.getPostalList().length > 0) {
+ HiLog.i(TAG, `getPostalList: ${JSON.stringify(vCardEntry.getPostalList())}`);
+ vCardEntry.getPostalList().forEach((item: contact.PostalAddress) => {
+ let newItem = new HouseBean('', '', '', '');
+ if (item.city == null) {
+ item.city = '';
+ }
+ newItem.houseName = item.country + item.city + item.region +
+ item.neighborhood + item.street + item.postalAddress + item.pobox;
+ newItem.houseType = item.labelId.toString();
+ contactInfoAfter.houses.push(newItem);
+ })
+ }
+
+ if (vCardEntry.getNickNameList() != null && vCardEntry.getNickNameList().length > 0) {
+ HiLog.i(TAG, `nickNameList: ${JSON.stringify(vCardEntry.getNickNameList())}`);
+ contactInfoAfter.nickname = vCardEntry.getNickNameList()[0].nickName;
+ }
+
+ if (vCardEntry.getWebsiteList() != null && vCardEntry.getWebsiteList().length > 0) {
+ HiLog.i(TAG, `getWebsiteList: ${JSON.stringify(vCardEntry.getWebsiteList())}`);
+ contactInfoAfter.websites[0] = vCardEntry.getWebsiteList()[0].website;
+ }
+
+ HiLog.i(TAG, `getCustomData: ${JSON.stringify(vCardEntry.getCustomData())}`);
+ if (vCardEntry.getCustomData() != null && vCardEntry.getCustomData().length > 0) {
+ vCardEntry.getCustomData().forEach((item: contact.Relation) => {
+ let newItem = new AssociatedPersonBean('', '', '', '');
+ newItem.name = item.relationName;
+ newItem.associatedType = item.labelId.toString();
+ contactInfoAfter.relationships.push(newItem);
+ })
+ }
+
+ let bday = vCardEntry.getBirthday();
+ HiLog.i(TAG, `bday: ${JSON.stringify(bday)}`);
+ if (bday != null) {
+ let newItem = new EventBean('', '', '', '');
+ newItem.data = bday;
+ newItem.eventType = this.getEventType(contact.Event.EVENT_BIRTHDAY).toString();
+ contactInfoAfter.events.push(newItem);
+ }
+
+ let anniversaryList = vCardEntry.getAnniversaryList();
+ HiLog.i(TAG, `anniversaryList: ${JSON.stringify(anniversaryList)}`);
+ if (anniversaryList != null && anniversaryList.length > 0) {
+ anniversaryList.forEach((item: contact.Event) => {
+ let newItem = new EventBean('', '', '', '');
+ newItem.data = item.eventDate;
+ newItem.eventType = this.getEventType(item.labelId).toString();
+ contactInfoAfter.events.push(newItem);
+ })
+ }
+ this.contactInfoAfters.push(contactInfoAfter);
+ })
+ this.handleContact();
+ fs.closeSync(file);
+ }
+ });
+ fs.unlink(this.srcPath).then(() => {
+ console.info('remove file succeed');
+ }).catch((err: BusinessError) => {
+ console.error('remove file failed with error message: ' + err.message + ', error code: ' + err.code);
+ });
+ console.info('copy file succeed');
+ }).catch((err: BusinessError) => {
+ console.error('copy file failed with error message: ' + err.message + ', error code: ' + err.code);
+ });
+ }
+
+ getPhoneNumberType(type: number): number {
+ switch (type) {
+ case contact.PhoneNumber.CUSTOM_LABEL:
+ return Phone.TYPE_CUSTOM;
+ case contact.PhoneNumber.NUM_HOME:
+ return Phone.TYPE_HOME;
+ case contact.PhoneNumber.NUM_MOBILE:
+ return Phone.TYPE_MOBILE;
+ case contact.PhoneNumber.NUM_WORK:
+ return Phone.TYPE_WORK;
+ case contact.PhoneNumber.NUM_FAX_WORK:
+ return Phone.TYPE_FAX_WORK;
+ case contact.PhoneNumber.NUM_FAX_HOME:
+ return Phone.TYPE_FAX_HOME;
+ case contact.PhoneNumber.NUM_PAGER:
+ return Phone.TYPE_PAGER;
+ case contact.PhoneNumber.NUM_OTHER:
+ return Phone.TYPE_OTHER;
+ case contact.PhoneNumber.NUM_MAIN:
+ return Phone.TYPE_MAIN;
+ default:
+ return Phone.TYPE_CUSTOM;
+ }
+ }
+
+ getAIMType(type: number): number {
+ switch (type) {
+ case contact.ImAddress.IM_AIM:
+ return Aim.TYPE_AIM;
+ case contact.ImAddress.IM_MSN:
+ return Aim.TYPE_WINDOWSLIVE;
+ case contact.ImAddress.IM_YAHOO:
+ return Aim.TYPE_YAHOO;
+ case contact.ImAddress.IM_SKYPE:
+ return Aim.TYPE_SKYPE;
+ case contact.ImAddress.IM_QQ:
+ return Aim.TYPE_QQ;
+ case contact.ImAddress.IM_ICQ:
+ return Aim.TYPE_ICQ;
+ case contact.ImAddress.IM_JABBER:
+ return Aim.TYPE_JABBER;
+ case contact.ImAddress.CUSTOM_LABEL:
+ return Aim.TYPE_CUSTOM;
+ default:
+ return Aim.TYPE_CUSTOM;
+ }
+ }
+
+ getEventType(type: number): number {
+ switch (type) {
+ case contact.Event.EVENT_ANNIVERSARY:
+ return Birthday.TYPE_ANNIVERSARIES;
+ case contact.Event.EVENT_OTHER:
+ return Birthday.TYPE_OTHER;
+ case contact.Event.EVENT_BIRTHDAY:
+ return Birthday.TYPE_GREBIRTHDAY;
+ case contact.Event.CUSTOM_LABEL:
+ return Birthday.TYPE_OTHER;
+ default:
+ return Birthday.TYPE_OTHER;
+ }
+ }
+}
\ No newline at end of file