|
|
@@ -1,5 +1,6 @@
|
|
|
import 'package:clean/base/base_controller.dart';
|
|
|
import 'package:clean/data/repositories/user_repository.dart';
|
|
|
+import 'package:clean/dialog/loading_dialog.dart';
|
|
|
import 'package:clean/module/contact/contact_state.dart';
|
|
|
import 'package:clean/module/store/store_view.dart';
|
|
|
import 'package:clean/utils/toast_util.dart';
|
|
|
@@ -8,14 +9,14 @@ import 'package:flutter_contacts/flutter_contacts.dart';
|
|
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
|
|
|
|
|
class ContactDuplicateController extends BaseController {
|
|
|
-
|
|
|
// 是否为编辑状态
|
|
|
RxBool isEdit = false.obs;
|
|
|
|
|
|
// 是否全选
|
|
|
RxBool isAllSelected = false.obs;
|
|
|
|
|
|
- RxMap<String, List<Contact>> contactsByPhoneNumber = <String, List<Contact>>{}.obs;
|
|
|
+ RxMap<Contact, List<Contact>> contactsByPhoneNumber =
|
|
|
+ <Contact, List<Contact>>{}.obs;
|
|
|
|
|
|
var contactCount = 0;
|
|
|
|
|
|
@@ -32,35 +33,41 @@ class ContactDuplicateController extends BaseController {
|
|
|
loadDuplicateContacts();
|
|
|
}
|
|
|
|
|
|
- Future<void> loadDuplicateContacts() async {
|
|
|
+// 按共享电话号码分组联系人
|
|
|
+ Map<Contact, List<Contact>> groupContactsBySharedPhones(
|
|
|
+ List<Contact> contacts) {
|
|
|
+ final UnionFind uf = UnionFind();
|
|
|
+ final Map<String, List<Contact>> phoneToContactsMap = {};
|
|
|
|
|
|
- Map<String, List<Contact>> groupedContacts = {};
|
|
|
-
|
|
|
- // 获取所有联系人
|
|
|
- List<Contact> contacts = await FlutterContacts.getContacts(
|
|
|
- withProperties: true,
|
|
|
- withPhoto: true,
|
|
|
- );
|
|
|
+ // 遍历所有联系人,建立电话号码与联系人的映射
|
|
|
+ for (var contact in contacts) {
|
|
|
+ for (var phone in contact.phones) {
|
|
|
+ if (!phoneToContactsMap.containsKey(phone.number)) {
|
|
|
+ phoneToContactsMap[phone.number] = [];
|
|
|
+ }
|
|
|
+ phoneToContactsMap[phone.number]!.add(contact);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 按名字的首字母排序
|
|
|
- contacts.sort((a, b) => (a.displayName ?? '').compareTo(b.displayName ?? ''));
|
|
|
+ // 合并拥有相同电话号码的联系人
|
|
|
+ for (var phone in phoneToContactsMap.keys) {
|
|
|
+ final contactList = phoneToContactsMap[phone]!;
|
|
|
+ for (var i = 1; i < contactList.length; i++) {
|
|
|
+ uf.union(contactList[0], contactList[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ // 整理分组结果
|
|
|
+ final Map<Contact, List<Contact>> groupedContacts = {};
|
|
|
for (var contact in contacts) {
|
|
|
- if (contact.phones.isNotEmpty) {
|
|
|
- for (var phone in contact.phones) {
|
|
|
- final phoneNumber = phone.number;
|
|
|
-
|
|
|
- if (phoneNumber.isNotEmpty) {
|
|
|
- if (!groupedContacts.containsKey(phoneNumber)) {
|
|
|
- groupedContacts[phoneNumber] = [];
|
|
|
- }
|
|
|
- groupedContacts[phoneNumber]!.add(contact);
|
|
|
- }
|
|
|
- }
|
|
|
+ final root = uf.find(contact);
|
|
|
+ if (!groupedContacts.containsKey(root)) {
|
|
|
+ groupedContacts[root] = [];
|
|
|
}
|
|
|
+ groupedContacts[root]!.add(contact);
|
|
|
}
|
|
|
|
|
|
- Map<String, List<Contact>> tempContacts = {};
|
|
|
+ Map<Contact, List<Contact>> tempContacts = {};
|
|
|
tempContacts.addAll(groupedContacts);
|
|
|
for (var key in groupedContacts.keys) {
|
|
|
if (groupedContacts[key]?.length == 1) {
|
|
|
@@ -68,7 +75,48 @@ class ContactDuplicateController extends BaseController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- contactsByPhoneNumber.value = tempContacts;
|
|
|
+ return tempContacts;
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> loadDuplicateContacts() async {
|
|
|
+ Map<String, List<Contact>> groupedContacts = {};
|
|
|
+
|
|
|
+ // 获取所有联系人
|
|
|
+ List<Contact> contacts = await FlutterContacts.getContacts(
|
|
|
+ withProperties: true,
|
|
|
+ withPhoto: true,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 按名字的首字母排序
|
|
|
+ contacts
|
|
|
+ .sort((a, b) => (a.displayName ?? '').compareTo(b.displayName ?? ''));
|
|
|
+
|
|
|
+ final duplicateContact = groupContactsBySharedPhones(contacts);
|
|
|
+
|
|
|
+ // for (var contact in contacts) {
|
|
|
+ // if (contact.phones.isNotEmpty) {
|
|
|
+ // for (var phone in contact.phones) {
|
|
|
+ // final phoneNumber = phone.number;
|
|
|
+ //
|
|
|
+ // if (phoneNumber.isNotEmpty) {
|
|
|
+ // if (!groupedContacts.containsKey(phoneNumber)) {
|
|
|
+ // groupedContacts[phoneNumber] = [];
|
|
|
+ // }
|
|
|
+ // groupedContacts[phoneNumber]!.add(contact);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ //
|
|
|
+ // Map<String, List<Contact>> tempContacts = {};
|
|
|
+ // tempContacts.addAll(groupedContacts);
|
|
|
+ // for (var key in groupedContacts.keys) {
|
|
|
+ // if (groupedContacts[key]?.length == 1) {
|
|
|
+ // tempContacts.remove(key);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ contactsByPhoneNumber.value = duplicateContact;
|
|
|
|
|
|
contactCount = 0;
|
|
|
for (var key in contactsByPhoneNumber.keys) {
|
|
|
@@ -78,12 +126,11 @@ class ContactDuplicateController extends BaseController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// 选择/取消选择联系人
|
|
|
- void toggleSelectContact(String phoneNumber) {
|
|
|
+ void toggleSelectContact(Contact contact) {
|
|
|
// final asset = ContactState.contactList.firstWhere((contact) => contact.id == selectContact.id);
|
|
|
|
|
|
- final contacts = contactsByPhoneNumber[phoneNumber];
|
|
|
+ final contacts = contactsByPhoneNumber[contact];
|
|
|
if (contacts != null) {
|
|
|
for (var contact in contacts) {
|
|
|
if (selectedContacts.contains(contact.id)) {
|
|
|
@@ -121,38 +168,116 @@ class ContactDuplicateController extends BaseController {
|
|
|
isAllSelected.value = false;
|
|
|
}
|
|
|
|
|
|
+ // 合并联系人
|
|
|
Future<void> mergeBtnClick(List<Contact> contacts) async {
|
|
|
if (!userRepository.isVip()) {
|
|
|
StorePage.start();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- Contact? contactWithMostInfo;
|
|
|
- int maxInfoCount = 0;
|
|
|
+ LoadingDialog.show(displayTime: 100);
|
|
|
+
|
|
|
+ Contact newContact = contacts.first;
|
|
|
|
|
|
for (var contact in contacts) {
|
|
|
- final infoCount = _countContactInfo(contact);
|
|
|
- if (infoCount > maxInfoCount) {
|
|
|
- maxInfoCount = infoCount;
|
|
|
- contactWithMostInfo = contact;
|
|
|
+ if (contact != newContact) {
|
|
|
+ for (var phone in contact.phones) {
|
|
|
+ var canAdd = true;
|
|
|
+ for (var newPhone in newContact.phones) {
|
|
|
+ if (phone.number == newPhone.number) {
|
|
|
+ canAdd = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (canAdd) {
|
|
|
+ newContact.phones.add(phone);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newContact.thumbnail == null) {
|
|
|
+ if (contact.thumbnail != null) {
|
|
|
+ newContact.thumbnail = contact.thumbnail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newContact.photo == null) {
|
|
|
+ if (contact.photo != null) {
|
|
|
+ newContact.photo = contact.photo;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newContact.name.first.isEmpty) {
|
|
|
+ if (contact.name.first.isNotEmpty) {
|
|
|
+ newContact.name.first = contact.name.first;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newContact.name.last.isEmpty) {
|
|
|
+ if (contact.name.last.isNotEmpty) {
|
|
|
+ newContact.name.last = contact.name.last;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newContact.name.last.isEmpty) {
|
|
|
+ if (contact.name.last.isNotEmpty) {
|
|
|
+ newContact.name.last = contact.name.last;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.emails.isNotEmpty) {
|
|
|
+ for (var email in contact.emails) {
|
|
|
+ newContact.emails.add(email);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.addresses.isNotEmpty) {
|
|
|
+ for (var address in contact.addresses) {
|
|
|
+ newContact.addresses.add(address);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.organizations.isNotEmpty) {
|
|
|
+ for (var organization in contact.organizations) {
|
|
|
+ newContact.organizations.add(organization);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.websites.isNotEmpty) {
|
|
|
+ for (var website in contact.websites) {
|
|
|
+ newContact.websites.add(website);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.socialMedias.isNotEmpty) {
|
|
|
+ for (var socialMedia in contact.socialMedias) {
|
|
|
+ newContact.socialMedias.add(socialMedia);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.events.isNotEmpty) {
|
|
|
+ for (var event in contact.events) {
|
|
|
+ newContact.events.add(event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.notes.isNotEmpty) {
|
|
|
+ for (var note in contact.notes) {
|
|
|
+ newContact.notes.add(note);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (contact.groups.isNotEmpty) {
|
|
|
+ for (var group in contact.groups) {
|
|
|
+ newContact.groups.add(group);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var contact in contacts) {
|
|
|
- if (contact.id != contactWithMostInfo?.id) {
|
|
|
- await contact.delete();
|
|
|
- }
|
|
|
+ await contact.delete();
|
|
|
}
|
|
|
|
|
|
- ToastUtil.show("Successful");
|
|
|
+ newContact.id = "";
|
|
|
+ await FlutterContacts.insertContact(newContact);
|
|
|
+
|
|
|
exitEditMode();
|
|
|
await loadDuplicateContacts();
|
|
|
+
|
|
|
+ ToastUtil.show("Merge Successful");
|
|
|
}
|
|
|
|
|
|
void deleteBtnClick() {
|
|
|
// 获取要删除的资产
|
|
|
- final contactToDelete =
|
|
|
- ContactState.contactList.where((contact) => selectedContacts.contains(contact.id)).toList();
|
|
|
+ final contactToDelete = ContactState.contactList
|
|
|
+ .where((contact) => selectedContacts.contains(contact.id))
|
|
|
+ .toList();
|
|
|
|
|
|
for (var contact in contactToDelete) {
|
|
|
contact.delete();
|
|
|
@@ -216,4 +341,28 @@ class ContactDuplicateController extends BaseController {
|
|
|
|
|
|
return count;
|
|
|
}
|
|
|
-}
|
|
|
+}
|
|
|
+
|
|
|
+// 并查集类
|
|
|
+class UnionFind {
|
|
|
+ final Map<Contact, Contact> _parent = {};
|
|
|
+
|
|
|
+ // 查找根节点
|
|
|
+ Contact find(Contact contact) {
|
|
|
+ if (!_parent.containsKey(contact)) {
|
|
|
+ _parent[contact] = contact;
|
|
|
+ } else if (_parent[contact] != contact) {
|
|
|
+ _parent[contact] = find(_parent[contact]!); // 路径压缩
|
|
|
+ }
|
|
|
+ return _parent[contact]!;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合并两个集合
|
|
|
+ void union(Contact a, Contact b) {
|
|
|
+ final rootA = find(a);
|
|
|
+ final rootB = find(b);
|
|
|
+ if (rootA != rootB) {
|
|
|
+ _parent[rootB] = rootA;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|