Просмотр исходного кода

[feat]新增联系人重复页

Destiny 11 месяцев назад
Родитель
Сommit
4aed19b068

+ 8 - 13
lib/module/contact/all/all_controller.dart

@@ -28,28 +28,23 @@ class AllController extends BaseController {
 
 
   void scrollToInitial(String initial) {
   void scrollToInitial(String initial) {
     int index = ContactState.initials.indexOf(initial);
     int index = ContactState.initials.indexOf(initial);
-    var isScroll = true;
-    itemPositionsListener.itemPositions.addListener(() {
+    if (index != -1) {
+
       final positions = itemPositionsListener.itemPositions.value;
       final positions = itemPositionsListener.itemPositions.value;
-      for (var position in positions) {
-        if (index == position.index) {
-          isScroll = false;
-        }
-        print('Item ${position.index} is visible at position ${position.itemLeadingEdge}');
-      }
+      final isVisible = positions.any((position) => position.index == index);
 
 
-      if (index != -1 && isScroll) {
+      if (!isVisible) {
         itemScrollController.scrollTo(
         itemScrollController.scrollTo(
-          index: index, duration: Duration(milliseconds: 300),
-          // curve: Curves,
+          index: index,
+          duration: Duration(milliseconds: 300),
         );
         );
       }
       }
-    });
+    }
   }
   }
 
 
   // 选择/取消选择联系人
   // 选择/取消选择联系人
   void toggleSelectContact(Contact selectContact) {
   void toggleSelectContact(Contact selectContact) {
-    final asset = ContactState.contactList.firstWhere((contact) => contact.id == selectContact.id);
+    // final asset = ContactState.contactList.firstWhere((contact) => contact.id == selectContact.id);
 
 
     if (selectedContacts.contains(selectContact.id)) {
     if (selectedContacts.contains(selectContact.id)) {
       selectedContacts.remove(selectContact.id);
       selectedContacts.remove(selectContact.id);

+ 0 - 2
lib/module/contact/all/all_view.dart

@@ -151,8 +151,6 @@ class AllPage extends BasePage<AllController> {
                               .itemPositionsListener,
                               .itemPositionsListener,
                           itemBuilder: (context, index) {
                           itemBuilder: (context, index) {
                             String initial = ContactState.initials[index];
                             String initial = ContactState.initials[index];
-                            var groupedContacts =
-                            ContactState.groupedContacts[initial];
                             return Column(
                             return Column(
                               crossAxisAlignment: CrossAxisAlignment.start,
                               crossAxisAlignment: CrossAxisAlignment.start,
                               children: [
                               children: [

+ 207 - 0
lib/module/contact/duplicate/controller.dart

@@ -1,5 +1,212 @@
 import 'package:clean/base/base_controller.dart';
 import 'package:clean/base/base_controller.dart';
+import 'package:clean/module/contact/contact_state.dart';
+import 'package:clean/utils/toast_util.dart';
+import 'package:flutter_contacts/contact.dart';
+import 'package:flutter_contacts/flutter_contacts.dart';
+import 'package:get/get_rx/src/rx_types/rx_types.dart';
 
 
 class ContactDuplicateController extends BaseController {
 class ContactDuplicateController extends BaseController {
 
 
+  // 是否为编辑状态
+  RxBool isEdit = false.obs;
+
+  // 是否全选
+  RxBool isAllSelected = false.obs;
+
+  RxMap<String, List<Contact>> contactsByPhoneNumber = <String, List<Contact>>{}.obs;
+
+  var contactCount = 0;
+
+  // RxMap<String, List<Contact>> contactsByPhoneNumber = <String, List<Contact>>{}.obs;
+
+  // 存储选中的联系人ID
+  final RxSet<String> selectedContacts = <String>{}.obs;
+
+  @override
+  void onInit() {
+    // TODO: implement onInit
+    super.onInit();
+
+    loadDuplicateContacts();
+  }
+
+  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 ?? ''));
+
+    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 = tempContacts;
+
+    contactCount = 0;
+    for (var key in contactsByPhoneNumber.keys) {
+      if (contactsByPhoneNumber[key] != null) {
+        contactCount += contactsByPhoneNumber[key]!.length;
+      }
+    }
+  }
+
+
+  // 选择/取消选择联系人
+  void toggleSelectContact(String phoneNumber) {
+    // final asset = ContactState.contactList.firstWhere((contact) => contact.id == selectContact.id);
+
+    final contacts = contactsByPhoneNumber[phoneNumber];
+    if (contacts != null) {
+      for (var contact in contacts) {
+        if (selectedContacts.contains(contact.id)) {
+          selectedContacts.remove(contact.id);
+        } else {
+          selectedContacts.add(contact.id);
+        }
+      }
+    }
+
+    // 更新全选状态
+    isAllSelected.value = selectedContacts.length == contactCount;
+  }
+
+  // 全选/取消全选
+  void toggleSelectAll() {
+    if (isAllSelected.value) {
+      selectedContacts.clear();
+    } else {
+      for (var key in contactsByPhoneNumber.keys) {
+        if (contactsByPhoneNumber[key] != null) {
+          for (var contact in contactsByPhoneNumber[key]!) {
+            selectedContacts.add(contact.id);
+          }
+        }
+      }
+    }
+    isAllSelected.value = !isAllSelected.value;
+  }
+
+  // 退出编辑模式时清空选择
+  void exitEditMode() {
+    isEdit.value = false;
+    selectedContacts.clear();
+    isAllSelected.value = false;
+  }
+
+  Future<void> mergeBtnClick(List<Contact> contacts) async {
+    Contact? contactWithMostInfo;
+    int maxInfoCount = 0;
+
+    for (var contact in contacts) {
+      final infoCount = _countContactInfo(contact);
+      if (infoCount > maxInfoCount) {
+        maxInfoCount = infoCount;
+        contactWithMostInfo = contact;
+      }
+    }
+
+    for (var contact in contacts) {
+      if (contact.id != contactWithMostInfo?.id) {
+        await contact.delete();
+      }
+    }
+
+    ToastUtil.show("Successful");
+    exitEditMode();
+    await loadDuplicateContacts();
+  }
+
+  void deleteBtnClick() {
+    // 获取要删除的资产
+    final contactToDelete =
+    ContactState.contactList.where((contact) => selectedContacts.contains(contact.id)).toList();
+
+    for (var contact in contactToDelete) {
+      contact.delete();
+    }
+
+    ToastUtil.show("Successful");
+    exitEditMode();
+    ContactState.loadContacts();
+  }
+
+  // 统计联系人的信息数量
+  int _countContactInfo(Contact contact) {
+    int count = 0;
+
+    // 统计姓名
+    if (contact.name.first.isNotEmpty || contact.name.last.isNotEmpty) {
+      count++;
+    }
+
+    // 统计电话号码
+    if (contact.phones.isNotEmpty) {
+      count += contact.phones.length;
+    }
+
+    // 统计电子邮件
+    if (contact.emails.isNotEmpty) {
+      count += contact.emails.length;
+    }
+
+    // 统计地址
+    if (contact.addresses.isNotEmpty) {
+      count += contact.addresses.length;
+    }
+
+    if (contact.events.isNotEmpty) {
+      count += contact.events.length;
+    }
+
+    if (contact.notes.isNotEmpty) {
+      count += contact.notes.length;
+    }
+
+    if (contact.socialMedias.isNotEmpty) {
+      count += contact.socialMedias.length;
+    }
+
+    if (contact.websites.isNotEmpty) {
+      count += contact.websites.length;
+    }
+
+    if (contact.organizations.isNotEmpty) {
+      count += contact.organizations.length;
+    }
+
+    // 统计图片
+    if (contact.photo != null) {
+      if (contact.photo!.isNotEmpty) {
+        count++;
+      }
+    }
+
+    return count;
+  }
 }
 }

+ 334 - 2
lib/module/contact/duplicate/view.dart

@@ -1,8 +1,12 @@
 import 'package:clean/base/base_page.dart';
 import 'package:clean/base/base_page.dart';
 import 'package:clean/module/contact/duplicate/controller.dart';
 import 'package:clean/module/contact/duplicate/controller.dart';
 import 'package:clean/resource/assets.gen.dart';
 import 'package:clean/resource/assets.gen.dart';
+import 'package:clean/utils/expand.dart';
 import 'package:flutter/Material.dart';
 import 'package:flutter/Material.dart';
+import 'package:flutter_contacts/contact.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:path/path.dart';
 
 
 class ContactDuplicatePage extends BasePage<ContactDuplicateController> {
 class ContactDuplicatePage extends BasePage<ContactDuplicateController> {
   const ContactDuplicatePage({super.key});
   const ContactDuplicatePage({super.key});
@@ -19,7 +23,7 @@ class ContactDuplicatePage extends BasePage<ContactDuplicateController> {
   Widget buildBody(BuildContext context) {
   Widget buildBody(BuildContext context) {
     return Stack(
     return Stack(
       children: [
       children: [
-        // buildMain(context),
+        buildMain(context),
         IgnorePointer(
         IgnorePointer(
           child: Assets.images.bgHome.image(
           child: Assets.images.bgHome.image(
             width: 360.w,
             width: 360.w,
@@ -28,4 +32,332 @@ class ContactDuplicatePage extends BasePage<ContactDuplicateController> {
       ],
       ],
     );
     );
   }
   }
-}
+
+  Widget buildMain(BuildContext context) {
+    return SafeArea(
+      child: Container(
+        padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w),
+        child: Obx(() {
+          return Column(
+            mainAxisAlignment: MainAxisAlignment.start,
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              !controller.isEdit.value
+                  ? Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      children: [
+                        GestureDetector(
+                          onTap: () {
+                            Get.back();
+                          },
+                          child: Assets.images.iconCommonBack
+                              .image(width: 28.w, height: 28.w),
+                        ),
+                        GestureDetector(
+                          onTap: () {
+                            controller.isEdit.value = true;
+                          },
+                          child: Container(
+                            width: 71.w,
+                            height: 30.h,
+                            decoration: BoxDecoration(
+                              color: "#1F2D3F".color,
+                              borderRadius: BorderRadius.all(
+                                Radius.circular(15.h),
+                              ),
+                            ),
+                            child: Center(
+                              child: Text(
+                                "Select",
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 14.sp,
+                                ),
+                              ),
+                            ),
+                          ),
+                        ),
+                      ],
+                    )
+                  : Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      children: [
+                        GestureDetector(
+                          onTap: () {
+                            controller.isEdit.value = false;
+                          },
+                          child: Container(
+                            width: 71.w,
+                            height: 30.h,
+                            decoration: BoxDecoration(
+                              color: "#1F2D3F".color,
+                              borderRadius: BorderRadius.all(
+                                Radius.circular(15.h),
+                              ),
+                            ),
+                            child: Center(
+                              child: Text(
+                                "Cancel",
+                                style: TextStyle(
+                                  color: Colors.white,
+                                  fontSize: 14.sp,
+                                ),
+                              ),
+                            ),
+                          ),
+                        ),
+                        GestureDetector(
+                          onTap: () {
+                            controller.toggleSelectAll();
+                          },
+                          child: Text(
+                            controller.isAllSelected.value
+                                ? "Deselect all"
+                                : "Select All",
+                            style: TextStyle(
+                              color: Colors.white.withOpacity(0.65),
+                              fontSize: 14.sp,
+                            ),
+                          ),
+                        ),
+                      ],
+                    ),
+              SizedBox(
+                height: 12.h,
+              ),
+              Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  Text(
+                    "Duplicate Contacts",
+                    style: TextStyle(
+                      color: Colors.white,
+                      fontWeight: FontWeight.w700,
+                      fontSize: 24.sp,
+                    ),
+                  ),
+                ],
+              ),
+              SizedBox(
+                height: 20.h,
+              ),
+              Text(
+                "Suggested Merge Contacts",
+                style: TextStyle(
+                  color: Colors.white.withOpacity(0.7),
+                  fontWeight: FontWeight.w500,
+                  fontSize: 16.sp,
+                ),
+              ),
+              _buildDuplicateContact(),
+            ],
+          );
+        }),
+      ),
+    );
+  }
+
+  _buildDuplicateContact() {
+    return Expanded(
+      child: Row(
+        children: [
+          Expanded(
+            child: ListView.builder(
+                itemCount: controller.contactsByPhoneNumber.length,
+                itemBuilder: (context, index) {
+                  final phoneNumber =
+                      controller.contactsByPhoneNumber.keys.toList()[index];
+                  final contact =
+                      controller.contactsByPhoneNumber[phoneNumber]?.first;
+                  final contacts =
+                      controller.contactsByPhoneNumber[phoneNumber];
+                  return Column(
+                    children: [
+                      SizedBox(
+                        height: 24.h,
+                      ),
+                      Container(
+                        padding: EdgeInsets.only(left: 10.w, right: 10.w),
+                        height: 62.h,
+                        width: double.infinity,
+                        decoration: BoxDecoration(
+                          color: Colors.white.withOpacity(0.15),
+                          borderRadius: BorderRadius.all(
+                            Radius.circular(10.r),
+                          ),
+                        ),
+                        child: Row(
+                          children: [
+                            Column(
+                              crossAxisAlignment: CrossAxisAlignment.start,
+                              mainAxisAlignment: MainAxisAlignment.center,
+                              children: [
+                                Text(
+                                  contact?.displayName ?? "",
+                                  style: TextStyle(
+                                    color: Colors.white,
+                                    fontSize: 14.sp,
+                                    fontWeight: FontWeight.w500,
+                                  ),
+                                ),
+                                Text(
+                                  contact?.phones.first.number ?? "",
+                                  style: TextStyle(
+                                    color: Colors.white.withOpacity(0.8),
+                                    fontSize: 12.sp,
+                                  ),
+                                ),
+                              ],
+                            ),
+                            Spacer(),
+                            Visibility(
+                              visible: controller.selectedContacts.contains(contact?.id),
+                              child: GestureDetector(
+                                onTap: () {
+                                  if (contacts != null) {
+                                    controller.mergeBtnClick(contacts);
+                                  }
+                                },
+                                child: Container(
+                                  height: 34.h,
+                                  padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 17.w),
+                                  decoration: BoxDecoration(
+                                    color: "#0279FB".color,
+                                    borderRadius: BorderRadius.all(
+                                      Radius.circular(8.r),
+                                    ),
+                                  ),
+                                  child: Center(
+                                    child: Text(
+                                      "Merge ${contacts?.length}",
+                                      style: TextStyle(
+                                        color: Colors.white,
+                                        fontSize: 16.sp,
+                                        fontWeight: FontWeight.w500,
+                                      ),
+                                    ),
+                                  ),
+                                ),
+                              ),
+                            )
+                          ],
+                        ),
+                      ),
+                      SizedBox(
+                        height: 12.h,
+                      ),
+                      ...controller.contactsByPhoneNumber[phoneNumber]!
+                          .asMap()
+                          .entries
+                          .map((entry) {
+                        int index = entry.key; // 当前联系人的索引
+                        Contact contact = entry.value; // 当前联系人
+                        bool isFirst = index == 0; // 是否是第一个
+                        bool isLast = index ==
+                            (controller.contactsByPhoneNumber[phoneNumber]
+                                        ?.length ??
+                                    0) -
+                                1;
+                        return Container(
+                          padding: EdgeInsets.only(
+                              left: 20.w, top: 10.h, right: 20.w),
+                          width: double.infinity,
+                          decoration: BoxDecoration(
+                            borderRadius: BorderRadius.vertical(
+                              top: isFirst
+                                  ? Radius.circular(12)
+                                  : Radius.zero, // 第一个设置上圆角
+                              bottom: isLast
+                                  ? Radius.circular(12)
+                                  : Radius.zero, // 最后一个设置下圆角
+                            ),
+                            color: Colors.white.withOpacity(0.09),
+                          ),
+                          child: Column(
+                            children: [
+                              Row(
+                                mainAxisAlignment:
+                                    MainAxisAlignment.spaceBetween,
+                                children: [
+                                  Column(
+                                    mainAxisAlignment: MainAxisAlignment.start,
+                                    crossAxisAlignment:
+                                        CrossAxisAlignment.start,
+                                    children: [
+                                      Text(
+                                        contact.displayName ?? '未命名',
+                                        style: TextStyle(
+                                          color: Colors.white,
+                                          fontSize: 14.sp,
+                                          fontWeight: FontWeight.w500,
+                                        ),
+                                      ),
+                                      SizedBox(
+                                        height: 5.h,
+                                      ),
+                                      Text(
+                                        contact.phones.first.number ?? '无号码',
+                                        style: TextStyle(
+                                          color: Colors.white.withOpacity(0.8),
+                                          fontSize: 12.sp,
+                                        ),
+                                      ),
+                                    ],
+                                  ),
+                                  // 删除按钮
+                                  Visibility(
+                                    visible: controller.isEdit.value,
+                                    child: GestureDetector(
+                                      onTap: () {
+                                        controller.toggleSelectContact(
+                                            phoneNumber);
+                                      },
+                                      child: Container(
+                                        child: controller.selectedContacts
+                                                .contains(contact.id)
+                                            ? Center(
+                                                child: Assets
+                                                    .images.iconSelected
+                                                    .image(
+                                                  width: 16.w,
+                                                  height: 16.h,
+                                                ),
+                                              )
+                                            : Center(
+                                                child: Assets
+                                                    .images.iconUnselected
+                                                    .image(
+                                                  width: 16.w,
+                                                  height: 16.h,
+                                                ),
+                                              ),
+                                      ),
+                                    ),
+                                  ),
+                                ],
+                              ),
+                              SizedBox(
+                                height: 10.h,
+                              ),
+                              Visibility(
+                                  visible: !isLast,
+                                  child: Container(
+                                    height: 1.h,
+                                    color: "#3E3E47".color,
+                                  )),
+                            ],
+                          ),
+                        );
+                      }),
+                    ],
+                  );
+                }),
+          ),
+          Container(
+            width: 10.w,
+          )
+        ],
+      ),
+    );
+  }
+}

+ 2 - 0
pubspec.yaml

@@ -114,8 +114,10 @@ dependencies:
   #网页跳转
   #网页跳转
   webview_flutter: ^4.10.0
   webview_flutter: ^4.10.0
 
 
+  # 获取联系人
   flutter_contacts: ^1.1.9
   flutter_contacts: ^1.1.9
 
 
+  # 实现点击跳转列表
   scrollable_positioned_list: ^0.3.8
   scrollable_positioned_list: ^0.3.8
 
 
   # 照片
   # 照片