From 799f2a7165a1c42892a7070f6a8af1dbc163aeee Mon Sep 17 00:00:00 2001 From: Ruchi71 Date: Mon, 18 Dec 2023 13:27:43 +0530 Subject: [PATCH 01/11] contact list --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 2 + ios/Runner/Info.plist | 2 + lib/contact_screen.dart | 78 +++++++++++++++++++ lib/import_expense_screen.dart | 42 ++++++---- lib/tag_edit_screen.dart | 6 +- pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 9 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 lib/contact_screen.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 27cdd65..30dc54f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c040eba..08ddc23 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + + LaunchScreen UIMainStoryboardFile Main + NSContactsUsageDescription + This app requires contacts access to function properly. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart new file mode 100644 index 0000000..6ae6e9c --- /dev/null +++ b/lib/contact_screen.dart @@ -0,0 +1,78 @@ +import 'package:contacts_service/contacts_service.dart'; +import 'package:flutter/material.dart'; +import 'package:kilvish/common_widgets.dart'; +import 'package:kilvish/style.dart'; +import 'package:permission_handler/permission_handler.dart'; + + + +class ContactScreen extends StatefulWidget { + const ContactScreen({Key? key}) : super(key: key); + + @override + State createState() => _ContactScreenState(); +} + +class _ContactScreenState extends State { + + List contacts = []; + bool isLoading = true; + + @override + void initState() { + // TODO: implement initState + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await getContactPermission(); + }); + } + + Future getContactPermission() async { + if (await Permission.contacts.request().isGranted) { + // Permission is granted, fetch contacts + fetchContacts(); + } else { + await Permission.contacts.request(); + // Permission is denied + print('Contact permission is denied'); + } + } + + Future fetchContacts() async { + contacts = await ContactsService.getContacts(); + setState(() { + isLoading = false; + }); + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: appBarTitleText('Contact List'), + ), + body: + isLoading?const Center(child: CircularProgressIndicator()):ListView.builder( + itemCount: contacts.length, + itemBuilder: (BuildContext context, int index) { + print("con-${contacts[index].phones}"); + return + contacts[index].displayName == null?const SizedBox(): + ListTile( + leading: CircleAvatar( + child: Text(contacts[index].displayName![0]??''), + ), + title: Text(contacts[index].displayName??''), + subtitle: Text( + contacts[index].phones?.isNotEmpty == true + ? contacts[index].phones![0].value ?? '' + : 'No phone number', + ), + + ); + }, + ), + ); + } +} diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index 50d6222..94ed128 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -4,6 +4,7 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:kilvish/constants/dimens_constants.dart'; import 'package:flutter/scheduler.dart'; +import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/home_screen.dart'; import 'package:kilvish/platform_functions.dart'; import 'package:kilvish/style.dart'; @@ -92,20 +93,23 @@ class _ImportExpensePageState extends State { void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((timeStamp) { - if (widget.files!.isNotEmpty) { - setState(() { - _imageFile = XFile(widget.files!.first.path); - // var i = 0; - // widget.files?.forEach((element) { - // _galleryItems.add(MediaPreviewItem( - // id: i, - // resource: element, - // controller: TextEditingController(), - // isSelected: i == 0 ? true : false)); - // i++; - // }); - }); + if(widget.files != null){ + if (widget.files!.isNotEmpty) { + setState(() { + _imageFile = XFile(widget.files!.first.path); + // var i = 0; + // widget.files?.forEach((element) { + // _galleryItems.add(MediaPreviewItem( + // id: i, + // resource: element, + // controller: TextEditingController(), + // isSelected: i == 0 ? true : false)); + // i++; + // }); + }); + } } + }); } @@ -160,7 +164,12 @@ class _ImportExpensePageState extends State { }); }), const Spacer(), - customContactUi(onTap: _contactFetchFn), + customContactUi( + onTap: (){ + Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen())); + } + // onTap: _contactFetchFn + ), ], ) : TextFormField( @@ -170,7 +179,10 @@ class _ImportExpensePageState extends State { decoration: customUnderlineInputdecoration( hintText: 'Enter Name or select from contact', bordersideColor: primaryColor, - suffixicon: customContactUi(onTap: _contactFetchFn), + suffixicon: customContactUi(onTap:(){ + Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen()));} + //_contactFetchFn + ), )), /* render Receipt/Screenshot diff --git a/lib/tag_edit_screen.dart b/lib/tag_edit_screen.dart index 9373f1d..d4e8008 100644 --- a/lib/tag_edit_screen.dart +++ b/lib/tag_edit_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:fluttercontactpicker/fluttercontactpicker.dart'; import 'package:kilvish/common_widgets.dart'; import 'package:kilvish/constants/dimens_constants.dart'; +import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/models.dart'; import 'package:kilvish/platform_functions.dart'; import 'package:kilvish/style.dart'; @@ -87,7 +88,10 @@ class _TagEditPageState extends State { }); }), const Spacer(), - customContactUi(onTap: _contactFetchFn), + customContactUi(onTap: () { + Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen())); + // _contactFetchFn + }), ], ) ]))), diff --git a/pubspec.yaml b/pubspec.yaml index afd4f7a..3fb9376 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: fluttercontactpicker: ^4.7.0 image_picker: ^1.0.5 fluttertoast: ^8.2.4 + contacts_service: ^0.6.3 + permission_handler: ^11.1.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 77ab7a0..2c256bd 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a423a02..230eabf 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 9ff7f08b3b5fe2bb3c038775874d89b4c1778154 Mon Sep 17 00:00:00 2001 From: radheyshyam Date: Thu, 1 Feb 2024 21:13:43 +0530 Subject: [PATCH 02/11] Handling Contacts in UI --- lib/contact_screen.dart | 318 ++++++++++++++++++++++++++---- lib/import_expense_screen.dart | 63 +++--- lib/models/ContactModel.dart | 8 + lib/platform_functions.dart | 12 -- lib/provider/search_provider.dart | 17 ++ lib/tag_edit_screen.dart | 19 +- pubspec.yaml | 3 +- 7 files changed, 349 insertions(+), 91 deletions(-) create mode 100644 lib/models/ContactModel.dart delete mode 100644 lib/platform_functions.dart create mode 100644 lib/provider/search_provider.dart diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index 6ae6e9c..2b759de 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -1,26 +1,52 @@ -import 'package:contacts_service/contacts_service.dart'; +import 'package:fast_contacts/fast_contacts.dart'; import 'package:flutter/material.dart'; -import 'package:kilvish/common_widgets.dart'; -import 'package:kilvish/style.dart'; +import 'package:kilvish/models/ContactModel.dart'; +import 'package:kilvish/provider/search_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:collection/collection.dart'; - +enum ContactSelection { singleSelect, multiSelect } class ContactScreen extends StatefulWidget { - const ContactScreen({Key? key}) : super(key: key); + final ContactSelection contactSelection; + + const ContactScreen( + {Key? key, this.contactSelection = ContactSelection.singleSelect}) + : super(key: key); @override State createState() => _ContactScreenState(); } class _ContactScreenState extends State { - - List contacts = []; + List selectedContactsList = []; + List contactsList = [ + ContactModel( + kilvishId: "Kelvish ID 1", + contact: const Contact( + id: 'Kilvish User 1', + emails: [], + structuredName: StructuredName( + displayName: "kilvish user", + familyName: "no", + givenName: "no", + middleName: "", + namePrefix: "", + nameSuffix: ""), + organization: null, + phones: [Phone(label: "my", number: "65656-52452")])), + ]; bool isLoading = true; + SearchNotifier searchNotifier = SearchNotifier(); + String filterOn = "name"; + final ValueNotifier _searchNotifier = ValueNotifier(true); + final TextEditingController searchController = TextEditingController(); + final String hintText = "Enter name"; + final String appbarTitle = "Contact List"; + @override void initState() { - // TODO: implement initState super.initState(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await getContactPermission(); @@ -39,40 +65,264 @@ class _ContactScreenState extends State { } Future fetchContacts() async { - contacts = await ContactsService.getContacts(); - setState(() { - isLoading = false; - }); - + List contacts = await FastContacts.getAllContacts(batchSize: 5); + if (contacts.length > 5) { + contacts = contacts.take(5).toList(); + } + setState(() { + isLoading = false; + contacts.forEach((contactsElement) { + final localContact = contactsList.firstWhereOrNull((element) => + element.contact.displayName == contactsElement.displayName); + if (localContact == null) { + contactsList.add(ContactModel(contact: contactsElement)); + } + }); + searchNotifier.updateSearchValue(contactsList); + }); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: appBarTitleText('Contact List'), + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + Navigator.pop(context,selectedContactsList); + }, + label: const Text('Done'), + icon: const Icon(Icons.check,color: Colors.green), + backgroundColor: Colors.pink, + ) , + appBar: PreferredSize( + preferredSize: const Size(double.infinity, kToolbarHeight), + child: ValueListenableBuilder( + valueListenable: _searchNotifier, + builder: (context, value, child) { + return _searchNotifier.value + ? appBarForShowOnlyTitle() + : appBarForSearch(); + }, + ), ), - body: - isLoading?const Center(child: CircularProgressIndicator()):ListView.builder( - itemCount: contacts.length, - itemBuilder: (BuildContext context, int index) { - print("con-${contacts[index].phones}"); - return - contacts[index].displayName == null?const SizedBox(): - ListTile( - leading: CircleAvatar( - child: Text(contacts[index].displayName![0]??''), - ), - title: Text(contacts[index].displayName??''), - subtitle: Text( - contacts[index].phones?.isNotEmpty == true - ? contacts[index].phones![0].value ?? '' - : 'No phone number', + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : ValueListenableBuilder>( + valueListenable: searchNotifier.contactNotifier, + builder: (context, filterList, child) { + return ListView.builder( + itemCount: filterList.length, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return filterList[index].contact.displayName == null + ? const SizedBox() + : InkWell( + onTap: () { + if (widget.contactSelection == + ContactSelection.singleSelect) { + Navigator.pop(context, filterList[index]); + } else { + final localContact = selectedContactsList + .firstWhereOrNull((element) => + element.contact.displayName == + filterList[index].contact.displayName); + if (localContact == null) { + selectedContactsList.add(filterList[index]); + } else { + selectedContactsList.remove(localContact); + } + print("Selected value is $selectedContactsList"); + print("Selected value is ${selectedContactsList.length}"); + setState(() {}); + } + }, + child: Column( + children: [ + (index > 0 && + filterList[index].kilvishId == null && + filterList[index].kilvishId != null) + ? const Divider( + height: 1, color: Colors.grey) + : const SizedBox(), + ListTile( + leading: Stack( + children: [ + CircleAvatar( + child: Text(filterList[index] + .contact + .displayName[0]), + ), + if (selectedContactsList.firstWhereOrNull( + (element) => + element.contact.displayName == + filterList[index] + .contact + .displayName) != + null) + const CircleAvatar( + child: Center( + child: Icon(Icons.check, + color: Colors.green)), + ) + ], + ), + title: Text( + filterList[index].contact.displayName), + subtitle: Text( + filterList[index].contact.phones.isNotEmpty + ? filterList[index] + .contact + .phones[0] + .number + : 'No phone number', + ), + trailing: + Text((filterList[index].kilvishId ?? "")), + ), + ], + ), + ); + }, + ); + }, ), + ); + } - ); - }, - ), + /// App bar for show only title and icon + AppBar appBarForShowOnlyTitle() { + return AppBar( + centerTitle: true, + automaticallyImplyLeading: false, + leading: Container( + margin: const EdgeInsets.only(left: 8), + child: IconButton( + icon: Icon(Icons.arrow_back_ios, + color: Theme.of(context).textSelectionTheme.selectionColor), + onPressed: () { + Navigator.pop(context); + }, + )), + title: titleWidget(), + actions: [ + IconButton( + icon: Icon(Icons.search_rounded, + color: Theme.of(context).textSelectionTheme.selectionColor), + onPressed: () { + _searchNotifier.value = !_searchNotifier.value; + }) + ], + ); + } + + /// AppBar for Search bhajan + AppBar appBarForSearch() { + return AppBar( + centerTitle: true, + automaticallyImplyLeading: false, + leading: Container( + margin: const EdgeInsets.only(left: 8), + child: IconButton( + icon: Icon(Icons.arrow_back_ios, + color: Theme.of(context).textSelectionTheme.selectionColor), + onPressed: () { + _searchNotifier.value = !_searchNotifier.value; + }, + )), + title: titleSearchWidget(), + actions: [ + IconButton( + icon: Icon(Icons.close, + color: Theme.of(context).textSelectionTheme.selectionColor), + onPressed: () { + _searchNotifier.value = !_searchNotifier.value; + }, + ), + ]); + } + + void searchFromContactList() { + if (searchController.text.isNotEmpty) { + print("Value is ${searchController.text}"); + if (filterOn == "name") { + final list = contactsList.where((element) { + return element.contact.displayName + .toLowerCase() + .contains(searchController.text.toLowerCase()); + }).toList(); + searchNotifier.updateSearchValue(list); + } else if (filterOn == "phoneNumber") { + final list = contactsList.where((element) { + if (element.contact.phones.isNotEmpty) { + return element.contact.phones[0].number + .toLowerCase() + .contains(searchController.text.toLowerCase()); + } else { + return false; + } + }).toList(); + searchNotifier.updateSearchValue(list); + } else if (filterOn == "kilvishId") { + final list = contactsList.where((element) { + if (element.kilvishId != null) { + return element.kilvishId! + .toLowerCase() + .contains(searchController.text.toLowerCase()); + } else { + return false; + } + }).toList(); + searchNotifier.updateSearchValue(list); + } + } else { + searchNotifier.updateSearchValue(contactsList); + } + } + + /// Showing title widget for app bar Search title + Widget titleSearchWidget() { + return TextField( + controller: searchController, + onChanged: (value) { + searchFromContactList(); + }, + decoration: InputDecoration( + prefixIcon: Icon(Icons.search, + color: Theme.of(context).textSelectionTheme.selectionColor), + hintText: hintText, + suffixIcon: PopupMenuButton( + icon: const Icon(Icons.filter_list), + onSelected: (String result) { + filterOn = result; + searchFromContactList(); + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: 'name', + child: Text('Name'), + ), + const PopupMenuItem( + value: 'phoneNumber', + child: Text('Phone Number'), + ), + const PopupMenuItem( + value: 'kilvishId', + child: Text('Kilvish Id'), + ), + ], + ), + hintStyle: TextStyle( + color: Theme.of(context).textSelectionTheme.selectionColor)), ); } + + /// Showing title widget for app bar + Widget titleWidget() { + return Material( + type: MaterialType.transparency, + child: Text( + appbarTitle, + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.w500, color: Colors.white), + )); + } } diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index 94ed128..44dcaa6 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:fluttercontactpicker/fluttercontactpicker.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:kilvish/constants/dimens_constants.dart'; import 'package:flutter/scheduler.dart'; import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/home_screen.dart'; -import 'package:kilvish/platform_functions.dart'; +import 'package:kilvish/models/ContactModel.dart'; import 'package:kilvish/style.dart'; import 'package:kilvish/tag_selection_screen.dart'; import '../common_widgets.dart'; @@ -18,6 +16,7 @@ class MediaPreviewItem { File? resource; bool isSelected; TextEditingController? controller; + MediaPreviewItem( {this.id, this.resource, this.controller, this.isSelected = false}); } @@ -38,7 +37,6 @@ class _ImportExpensePageState extends State { PageController(initialPage: 0, viewportFraction: 0.95, keepPage: false); final List _galleryItems = []; int _initialIndex = 0; - PhoneContact? _phoneContact; XFile? _imageFile; TextEditingController amountcon = TextEditingController(); TextEditingController namecon = TextEditingController(); @@ -55,17 +53,6 @@ class _ImportExpensePageState extends State { }); } - void _contactFetchFn() async { - _phoneContact = await fetchContactFromPhonebook(); - - if (_phoneContact != null && _phoneContact!.fullName!.isNotEmpty) { - setState(() { - pickedname = _phoneContact!.fullName.toString(); - namecon.text = _phoneContact!.fullName.toString(); - }); - } - } - Widget crossButtonTopRightForImage() { return Positioned( right: 10, @@ -93,7 +80,7 @@ class _ImportExpensePageState extends State { void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((timeStamp) { - if(widget.files != null){ + if (widget.files != null) { if (widget.files!.isNotEmpty) { setState(() { _imageFile = XFile(widget.files!.first.path); @@ -109,7 +96,6 @@ class _ImportExpensePageState extends State { }); } } - }); } @@ -127,7 +113,7 @@ class _ImportExpensePageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - /* + /* Amount */ renderPrimaryColorLabel( @@ -146,7 +132,7 @@ class _ImportExpensePageState extends State { hintText: 'Enter Amount', bordersideColor: primaryColor)), ), - /* + /* To */ renderPrimaryColorLabel(text: "To"), @@ -164,12 +150,14 @@ class _ImportExpensePageState extends State { }); }), const Spacer(), - customContactUi( - onTap: (){ - Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen())); - } - // onTap: _contactFetchFn - ), + customContactUi(onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ContactScreen())); + } + // onTap: _contactFetchFn + ), ], ) : TextFormField( @@ -179,10 +167,27 @@ class _ImportExpensePageState extends State { decoration: customUnderlineInputdecoration( hintText: 'Enter Name or select from contact', bordersideColor: primaryColor, - suffixicon: customContactUi(onTap:(){ - Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen()));} - //_contactFetchFn - ), + suffixicon: customContactUi(onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ContactScreen( + contactSelection: ContactSelection + .multiSelect))).then((value) { + if (value != null) { + if (value is ContactModel) { + namecon.text = value.contact.displayName; + } else if (value is List) { + List temp=[]; + value.forEach((element) { + temp.add(element.contact.displayName); + }); + namecon.text = temp.join(","); + } + } + print("value: $value"); + }); + }), )), /* render Receipt/Screenshot diff --git a/lib/models/ContactModel.dart b/lib/models/ContactModel.dart new file mode 100644 index 0000000..243cede --- /dev/null +++ b/lib/models/ContactModel.dart @@ -0,0 +1,8 @@ +import 'package:fast_contacts/fast_contacts.dart'; + +class ContactModel { + ContactModel({required this.contact, this.kilvishId}); + + final Contact contact; + final String? kilvishId; +} diff --git a/lib/platform_functions.dart b/lib/platform_functions.dart deleted file mode 100644 index 258e642..0000000 --- a/lib/platform_functions.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:fluttercontactpicker/fluttercontactpicker.dart'; - -Future fetchContactFromPhonebook() async { - bool permission = await FlutterContactPicker.requestPermission(); - if (permission) { - if (await FlutterContactPicker.hasPermission()) { - PhoneContact phoneContact = await FlutterContactPicker.pickPhoneContact(); - return phoneContact; - } - } - return null; -} diff --git a/lib/provider/search_provider.dart b/lib/provider/search_provider.dart new file mode 100644 index 0000000..a952984 --- /dev/null +++ b/lib/provider/search_provider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:kilvish/models/ContactModel.dart'; + +class SearchNotifier { + ValueNotifier> contactNotifier = ValueNotifier([]); + ValueNotifier contactStateNotifier = ValueNotifier(false); + + /// Update list search item + void updateSearchValue(List list) { + contactNotifier.value = list; + } + + /// Update state of search bar + void updateSearchState(bool isSearch) { + contactStateNotifier.value = isSearch; + } +} diff --git a/lib/tag_edit_screen.dart b/lib/tag_edit_screen.dart index d4e8008..e17a9d2 100644 --- a/lib/tag_edit_screen.dart +++ b/lib/tag_edit_screen.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:fluttercontactpicker/fluttercontactpicker.dart'; import 'package:kilvish/common_widgets.dart'; import 'package:kilvish/constants/dimens_constants.dart'; import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/models.dart'; -import 'package:kilvish/platform_functions.dart'; import 'package:kilvish/style.dart'; class TagEditPage extends StatefulWidget { const TagEditPage({Key? key}) : super(key: key); + @override createState() => _TagEditPageState(); } @@ -29,16 +27,6 @@ class _TagEditPageState extends State { super.dispose(); } - void _contactFetchFn() async { - PhoneContact? phoneContact = await fetchContactFromPhonebook(); - - if (phoneContact != null && phoneContact.fullName!.isNotEmpty) { - setState(() { - _peopleList.add(Tag(name: phoneContact.fullName.toString())); - }); - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -89,7 +77,10 @@ class _TagEditPageState extends State { }), const Spacer(), customContactUi(onTap: () { - Navigator.push(context,MaterialPageRoute(builder: (context)=> ContactScreen())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ContactScreen())); // _contactFetchFn }), ], diff --git a/pubspec.yaml b/pubspec.yaml index 3fb9376..dc310d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,11 +34,10 @@ dependencies: cupertino_icons: ^1.0.2 jiffy: ^5.0.0 receive_sharing_intent: ^1.4.5 - fluttercontactpicker: ^4.7.0 image_picker: ^1.0.5 fluttertoast: ^8.2.4 - contacts_service: ^0.6.3 permission_handler: ^11.1.0 + fast_contacts: ^3.1.3 dev_dependencies: flutter_test: From 50519b482d7e618d9d709e41d7a0f2180d989ff5 Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Thu, 1 Feb 2024 21:21:52 +0530 Subject: [PATCH 03/11] Handling Contacts in UI --- lib/contact_screen.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index 2b759de..a3ebfdc 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -58,9 +58,11 @@ class _ContactScreenState extends State { // Permission is granted, fetch contacts fetchContacts(); } else { - await Permission.contacts.request(); - // Permission is denied - print('Contact permission is denied'); + final status = await Permission.contacts.request(); + if(status.isGranted){ + // Permission is granted, fetch contacts + fetchContacts(); + } } } @@ -130,8 +132,6 @@ class _ContactScreenState extends State { } else { selectedContactsList.remove(localContact); } - print("Selected value is $selectedContactsList"); - print("Selected value is ${selectedContactsList.length}"); setState(() {}); } }, @@ -242,7 +242,6 @@ class _ContactScreenState extends State { void searchFromContactList() { if (searchController.text.isNotEmpty) { - print("Value is ${searchController.text}"); if (filterOn == "name") { final list = contactsList.where((element) { return element.contact.displayName From 40c2fe0fcafd49385db1157184e99ccf64fcb40f Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Thu, 1 Feb 2024 21:24:01 +0530 Subject: [PATCH 04/11] Handling Contacts in UI --- lib/import_expense_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index 44dcaa6..dbd1f96 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -185,7 +185,6 @@ class _ImportExpensePageState extends State { namecon.text = temp.join(","); } } - print("value: $value"); }); }), )), From 1bde94282f4a980b2258d15bec95188d0b1bee17 Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 09:27:09 +0530 Subject: [PATCH 05/11] Handling Contacts in UI --- lib/contact_screen.dart | 230 ++++++++++++++++----------------- lib/import_expense_screen.dart | 4 +- lib/models/ContactModel.dart | 7 +- lib/tag_edit_screen.dart | 20 ++- 4 files changed, 133 insertions(+), 128 deletions(-) diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index a3ebfdc..9b09f7e 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -19,31 +19,21 @@ class ContactScreen extends StatefulWidget { } class _ContactScreenState extends State { - List selectedContactsList = []; - List contactsList = [ + List _selectedContactsList = []; + List _contactsList = [ ContactModel( kilvishId: "Kelvish ID 1", - contact: const Contact( - id: 'Kilvish User 1', - emails: [], - structuredName: StructuredName( - displayName: "kilvish user", - familyName: "no", - givenName: "no", - middleName: "", - namePrefix: "", - nameSuffix: ""), - organization: null, - phones: [Phone(label: "my", number: "65656-52452")])), + name: 'Kilvish User 1', + phoneNumber: "65656-52452"), ]; - bool isLoading = true; + bool _isLoading = true; - SearchNotifier searchNotifier = SearchNotifier(); - String filterOn = "name"; - final ValueNotifier _searchNotifier = ValueNotifier(true); - final TextEditingController searchController = TextEditingController(); - final String hintText = "Enter name"; - final String appbarTitle = "Contact List"; + final SearchNotifier _searchNotifier = SearchNotifier(); + final ValueNotifier _valueNotifier = ValueNotifier(true); + final TextEditingController _searchController = TextEditingController(); + String _filterOn = "name"; + final String _hintText = "Enter name"; + final String _appbarTitle = "Contact List"; @override void initState() { @@ -53,13 +43,20 @@ class _ContactScreenState extends State { }); } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + Future getContactPermission() async { if (await Permission.contacts.request().isGranted) { // Permission is granted, fetch contacts fetchContacts(); } else { final status = await Permission.contacts.request(); - if(status.isGranted){ + if (status.isGranted) { // Permission is granted, fetch contacts fetchContacts(); } @@ -72,15 +69,19 @@ class _ContactScreenState extends State { contacts = contacts.take(5).toList(); } setState(() { - isLoading = false; + _isLoading = false; contacts.forEach((contactsElement) { - final localContact = contactsList.firstWhereOrNull((element) => - element.contact.displayName == contactsElement.displayName); + final localContact = _contactsList.firstWhereOrNull( + (element) => element.name == contactsElement.displayName); if (localContact == null) { - contactsList.add(ContactModel(contact: contactsElement)); + _contactsList.add(ContactModel( + name: contactsElement.displayName, + phoneNumber: contactsElement.phones.isNotEmpty + ? contactsElement.phones[0].number + : "")); } }); - searchNotifier.updateSearchValue(contactsList); + _searchNotifier.updateSearchValue(_contactsList); }); } @@ -89,98 +90,87 @@ class _ContactScreenState extends State { return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () { - Navigator.pop(context,selectedContactsList); + Navigator.pop(context, _selectedContactsList); }, label: const Text('Done'), - icon: const Icon(Icons.check,color: Colors.green), + icon: const Icon(Icons.check, color: Colors.green), backgroundColor: Colors.pink, - ) , + ), appBar: PreferredSize( preferredSize: const Size(double.infinity, kToolbarHeight), child: ValueListenableBuilder( - valueListenable: _searchNotifier, + valueListenable: _valueNotifier, builder: (context, value, child) { - return _searchNotifier.value + return _valueNotifier.value ? appBarForShowOnlyTitle() : appBarForSearch(); }, ), ), - body: isLoading + body: _isLoading ? const Center(child: CircularProgressIndicator()) : ValueListenableBuilder>( - valueListenable: searchNotifier.contactNotifier, + valueListenable: _searchNotifier.contactNotifier, builder: (context, filterList, child) { return ListView.builder( itemCount: filterList.length, shrinkWrap: true, itemBuilder: (BuildContext context, int index) { - return filterList[index].contact.displayName == null - ? const SizedBox() - : InkWell( - onTap: () { - if (widget.contactSelection == - ContactSelection.singleSelect) { - Navigator.pop(context, filterList[index]); - } else { - final localContact = selectedContactsList - .firstWhereOrNull((element) => - element.contact.displayName == - filterList[index].contact.displayName); - if (localContact == null) { - selectedContactsList.add(filterList[index]); - } else { - selectedContactsList.remove(localContact); - } - setState(() {}); - } - }, - child: Column( + return InkWell( + onTap: () { + if (widget.contactSelection == + ContactSelection.singleSelect) { + Navigator.pop(context, filterList[index]); + } else { + final localContact = + _selectedContactsList.firstWhereOrNull((element) => + element.name == filterList[index].name); + if (localContact == null) { + _selectedContactsList.add(filterList[index]); + } else { + _selectedContactsList.remove(localContact); + } + setState(() {}); + } + }, + child: Column( + children: [ + (index > 0 && + filterList[index].kilvishId == null && + filterList[index].kilvishId != null) + ? const Divider(height: 1, color: Colors.grey) + : const SizedBox(), + ListTile( + leading: Stack( children: [ - (index > 0 && - filterList[index].kilvishId == null && - filterList[index].kilvishId != null) - ? const Divider( - height: 1, color: Colors.grey) - : const SizedBox(), - ListTile( - leading: Stack( - children: [ - CircleAvatar( - child: Text(filterList[index] - .contact - .displayName[0]), - ), - if (selectedContactsList.firstWhereOrNull( - (element) => - element.contact.displayName == - filterList[index] - .contact - .displayName) != - null) - const CircleAvatar( - child: Center( - child: Icon(Icons.check, - color: Colors.green)), - ) - ], - ), - title: Text( - filterList[index].contact.displayName), - subtitle: Text( - filterList[index].contact.phones.isNotEmpty - ? filterList[index] - .contact - .phones[0] - .number - : 'No phone number', - ), - trailing: - Text((filterList[index].kilvishId ?? "")), + CircleAvatar( + child: Text(filterList[index].name.isNotEmpty + ? filterList[index].name[0] + : ""), ), + if (_selectedContactsList.firstWhereOrNull( + (element) => + element.name == + filterList[index].name) != + null) + const CircleAvatar( + child: Center( + child: Icon(Icons.check, + color: Colors.green)), + ) ], ), - ); + title: Text(filterList[index].name), + subtitle: Text( + filterList[index].phoneNumber.isNotEmpty + ? filterList[index].phoneNumber + : 'No phone number', + ), + trailing: Text((filterList[index].kilvishId ?? "")), + ), + ], + ), + ); }, ); }, @@ -208,7 +198,7 @@ class _ContactScreenState extends State { icon: Icon(Icons.search_rounded, color: Theme.of(context).textSelectionTheme.selectionColor), onPressed: () { - _searchNotifier.value = !_searchNotifier.value; + _valueNotifier.value = !_valueNotifier.value; }) ], ); @@ -225,7 +215,7 @@ class _ContactScreenState extends State { icon: Icon(Icons.arrow_back_ios, color: Theme.of(context).textSelectionTheme.selectionColor), onPressed: () { - _searchNotifier.value = !_searchNotifier.value; + _valueNotifier.value = !_valueNotifier.value; }, )), title: titleSearchWidget(), @@ -234,64 +224,64 @@ class _ContactScreenState extends State { icon: Icon(Icons.close, color: Theme.of(context).textSelectionTheme.selectionColor), onPressed: () { - _searchNotifier.value = !_searchNotifier.value; + _valueNotifier.value = !_valueNotifier.value; }, ), ]); } void searchFromContactList() { - if (searchController.text.isNotEmpty) { - if (filterOn == "name") { - final list = contactsList.where((element) { - return element.contact.displayName + if (_searchController.text.isNotEmpty) { + if (_filterOn == "name") { + final list = _contactsList.where((element) { + return element.name .toLowerCase() - .contains(searchController.text.toLowerCase()); + .contains(_searchController.text.toLowerCase()); }).toList(); - searchNotifier.updateSearchValue(list); - } else if (filterOn == "phoneNumber") { - final list = contactsList.where((element) { - if (element.contact.phones.isNotEmpty) { - return element.contact.phones[0].number + _searchNotifier.updateSearchValue(list); + } else if (_filterOn == "phoneNumber") { + final list = _contactsList.where((element) { + if (element.phoneNumber.isNotEmpty) { + return element.phoneNumber .toLowerCase() - .contains(searchController.text.toLowerCase()); + .contains(_searchController.text.toLowerCase()); } else { return false; } }).toList(); - searchNotifier.updateSearchValue(list); - } else if (filterOn == "kilvishId") { - final list = contactsList.where((element) { + _searchNotifier.updateSearchValue(list); + } else if (_filterOn == "kilvishId") { + final list = _contactsList.where((element) { if (element.kilvishId != null) { return element.kilvishId! .toLowerCase() - .contains(searchController.text.toLowerCase()); + .contains(_searchController.text.toLowerCase()); } else { return false; } }).toList(); - searchNotifier.updateSearchValue(list); + _searchNotifier.updateSearchValue(list); } } else { - searchNotifier.updateSearchValue(contactsList); + _searchNotifier.updateSearchValue(_contactsList); } } /// Showing title widget for app bar Search title Widget titleSearchWidget() { return TextField( - controller: searchController, + controller: _searchController, onChanged: (value) { searchFromContactList(); }, decoration: InputDecoration( prefixIcon: Icon(Icons.search, color: Theme.of(context).textSelectionTheme.selectionColor), - hintText: hintText, + hintText: _hintText, suffixIcon: PopupMenuButton( icon: const Icon(Icons.filter_list), onSelected: (String result) { - filterOn = result; + _filterOn = result; searchFromContactList(); }, itemBuilder: (BuildContext context) => >[ @@ -319,7 +309,7 @@ class _ContactScreenState extends State { return Material( type: MaterialType.transparency, child: Text( - appbarTitle, + _appbarTitle, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.white), )); diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index dbd1f96..db6ee4b 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -176,11 +176,11 @@ class _ImportExpensePageState extends State { .multiSelect))).then((value) { if (value != null) { if (value is ContactModel) { - namecon.text = value.contact.displayName; + namecon.text = value.name; } else if (value is List) { List temp=[]; value.forEach((element) { - temp.add(element.contact.displayName); + temp.add(element.name); }); namecon.text = temp.join(","); } diff --git a/lib/models/ContactModel.dart b/lib/models/ContactModel.dart index 243cede..a9807c2 100644 --- a/lib/models/ContactModel.dart +++ b/lib/models/ContactModel.dart @@ -1,8 +1,7 @@ -import 'package:fast_contacts/fast_contacts.dart'; - class ContactModel { - ContactModel({required this.contact, this.kilvishId}); + ContactModel({required this.name, required this.phoneNumber, this.kilvishId}); - final Contact contact; + final String name; final String? kilvishId; + final String phoneNumber; } diff --git a/lib/tag_edit_screen.dart b/lib/tag_edit_screen.dart index e17a9d2..588f99f 100644 --- a/lib/tag_edit_screen.dart +++ b/lib/tag_edit_screen.dart @@ -3,6 +3,7 @@ import 'package:kilvish/common_widgets.dart'; import 'package:kilvish/constants/dimens_constants.dart'; import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/models.dart'; +import 'package:kilvish/models/ContactModel.dart'; import 'package:kilvish/style.dart'; class TagEditPage extends StatefulWidget { @@ -80,8 +81,23 @@ class _TagEditPageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => ContactScreen())); - // _contactFetchFn + builder: (context) => const ContactScreen( + contactSelection: + ContactSelection.multiSelect, + ))).then((value) { + if (value != null) { + if (value is ContactModel) { + _tagNameController.text = + value.name; + } else if (value is List) { + List temp = []; + value.forEach((element) { + temp.add(element.name); + }); + _tagNameController.text = temp.join(","); + } + } + }); }), ], ) From bccc70e50cdaace7b75dadc33e82b424a18d6073 Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 09:30:48 +0530 Subject: [PATCH 06/11] Handling Contacts in UI --- lib/contact_screen.dart | 2 +- lib/models.dart | 8 ++++++++ lib/models/ContactModel.dart | 7 ------- lib/provider/search_provider.dart | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 lib/models/ContactModel.dart diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index 9b09f7e..e766fed 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -1,6 +1,6 @@ import 'package:fast_contacts/fast_contacts.dart'; import 'package:flutter/material.dart'; -import 'package:kilvish/models/ContactModel.dart'; +import 'package:kilvish/models.dart'; import 'package:kilvish/provider/search_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:collection/collection.dart'; diff --git a/lib/models.dart b/lib/models.dart index e64f218..6874ca5 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -37,3 +37,11 @@ class ExpenseTag { const ExpenseTag( {required this.tag, required this.expense, this.isSaved = true}); } + +class ContactModel { + ContactModel({required this.name, required this.phoneNumber, this.kilvishId}); + + final String name; + final String? kilvishId; + final String phoneNumber; +} \ No newline at end of file diff --git a/lib/models/ContactModel.dart b/lib/models/ContactModel.dart deleted file mode 100644 index a9807c2..0000000 --- a/lib/models/ContactModel.dart +++ /dev/null @@ -1,7 +0,0 @@ -class ContactModel { - ContactModel({required this.name, required this.phoneNumber, this.kilvishId}); - - final String name; - final String? kilvishId; - final String phoneNumber; -} diff --git a/lib/provider/search_provider.dart b/lib/provider/search_provider.dart index a952984..1455be0 100644 --- a/lib/provider/search_provider.dart +++ b/lib/provider/search_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:kilvish/models/ContactModel.dart'; +import 'package:kilvish/models.dart'; class SearchNotifier { ValueNotifier> contactNotifier = ValueNotifier([]); From 2e6116c801ede2e0e93d08dd4c79eb11fdb5a6e9 Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 20:35:10 +0530 Subject: [PATCH 07/11] Handling Contacts in UI --- lib/contact_screen.dart | 229 ++++++++++++--------------------- lib/import_expense_screen.dart | 1 - lib/tag_edit_screen.dart | 1 - pubspec.yaml | 2 +- 4 files changed, 86 insertions(+), 147 deletions(-) diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index e766fed..13234e9 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -1,4 +1,4 @@ -import 'package:fast_contacts/fast_contacts.dart'; +import 'package:contacts_service/contacts_service.dart'; import 'package:flutter/material.dart'; import 'package:kilvish/models.dart'; import 'package:kilvish/provider/search_provider.dart'; @@ -19,8 +19,8 @@ class ContactScreen extends StatefulWidget { } class _ContactScreenState extends State { - List _selectedContactsList = []; - List _contactsList = [ + final List _selectedContactsList = []; + final List _kilvishContactsList = [ ContactModel( kilvishId: "Kelvish ID 1", name: 'Kilvish User 1', @@ -29,11 +29,9 @@ class _ContactScreenState extends State { bool _isLoading = true; final SearchNotifier _searchNotifier = SearchNotifier(); - final ValueNotifier _valueNotifier = ValueNotifier(true); final TextEditingController _searchController = TextEditingController(); - String _filterOn = "name"; - final String _hintText = "Enter name"; - final String _appbarTitle = "Contact List"; + final String _hintText = "Enter Name, Contact"; + bool _permissionDenied = false; @override void initState() { @@ -43,51 +41,63 @@ class _ContactScreenState extends State { }); } - @override void dispose() { _searchController.dispose(); super.dispose(); } + /// Ask Contact permission Future getContactPermission() async { if (await Permission.contacts.request().isGranted) { + setState(() { + _permissionDenied = false; + }); // Permission is granted, fetch contacts - fetchContacts(); + searchFromContactList(); } else { final status = await Permission.contacts.request(); if (status.isGranted) { + setState(() { + _permissionDenied = false; + }); // Permission is granted, fetch contacts - fetchContacts(); + searchFromContactList(); + } else { + setState(() { + _permissionDenied = true; + }); } } } - Future fetchContacts() async { - List contacts = await FastContacts.getAllContacts(batchSize: 5); - if (contacts.length > 5) { - contacts = contacts.take(5).toList(); - } - setState(() { - _isLoading = false; - contacts.forEach((contactsElement) { - final localContact = _contactsList.firstWhereOrNull( - (element) => element.name == contactsElement.displayName); - if (localContact == null) { - _contactsList.add(ContactModel( - name: contactsElement.displayName, - phoneNumber: contactsElement.phones.isNotEmpty - ? contactsElement.phones[0].number - : "")); - } - }); - _searchNotifier.updateSearchValue(_contactsList); - }); - } - @override Widget build(BuildContext context) { return Scaffold( + bottomNavigationBar: _permissionDenied + ? SizedBox( + height: 72, + child: Card( + child: Container( + margin: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: Text( + "Contact permission isn't provided so contacts are not fetched followed by a button to provide permission.")), + const SizedBox(width: 8), + InkWell( + onTap: () { + getContactPermission(); + }, + child: const Text("Grant")) + ], + ), + ), + ), + ) + : const SizedBox(), floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.pop(context, _selectedContactsList); @@ -98,14 +108,7 @@ class _ContactScreenState extends State { ), appBar: PreferredSize( preferredSize: const Size(double.infinity, kToolbarHeight), - child: ValueListenableBuilder( - valueListenable: _valueNotifier, - builder: (context, value, child) { - return _valueNotifier.value - ? appBarForShowOnlyTitle() - : appBarForSearch(); - }, - ), + child: appBarForSearch(), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) @@ -122,8 +125,8 @@ class _ContactScreenState extends State { ContactSelection.singleSelect) { Navigator.pop(context, filterList[index]); } else { - final localContact = - _selectedContactsList.firstWhereOrNull((element) => + final localContact = _selectedContactsList + .firstWhereOrNull((element) => element.name == filterList[index].name); if (localContact == null) { _selectedContactsList.add(filterList[index]); @@ -178,8 +181,8 @@ class _ContactScreenState extends State { ); } - /// App bar for show only title and icon - AppBar appBarForShowOnlyTitle() { + /// AppBar for Search bhajan + AppBar appBarForSearch() { return AppBar( centerTitle: true, automaticallyImplyLeading: false, @@ -192,79 +195,49 @@ class _ContactScreenState extends State { Navigator.pop(context); }, )), - title: titleWidget(), - actions: [ - IconButton( - icon: Icon(Icons.search_rounded, - color: Theme.of(context).textSelectionTheme.selectionColor), - onPressed: () { - _valueNotifier.value = !_valueNotifier.value; - }) - ], + title: titleSearchWidget(), ); } - /// AppBar for Search bhajan - AppBar appBarForSearch() { - return AppBar( - centerTitle: true, - automaticallyImplyLeading: false, - leading: Container( - margin: const EdgeInsets.only(left: 8), - child: IconButton( - icon: Icon(Icons.arrow_back_ios, - color: Theme.of(context).textSelectionTheme.selectionColor), - onPressed: () { - _valueNotifier.value = !_valueNotifier.value; - }, - )), - title: titleSearchWidget(), - actions: [ - IconButton( - icon: Icon(Icons.close, - color: Theme.of(context).textSelectionTheme.selectionColor), - onPressed: () { - _valueNotifier.value = !_valueNotifier.value; - }, - ), - ]); - } - - void searchFromContactList() { - if (_searchController.text.isNotEmpty) { - if (_filterOn == "name") { - final list = _contactsList.where((element) { - return element.name + /// Search Contact from Kilvish Contact & Phone Contact List + Future searchFromContactList() async { + setState(() { + _isLoading = true; + }); + final kilvishSearchResult = _kilvishContactsList.where((element) { + return element.name .toLowerCase() - .contains(_searchController.text.toLowerCase()); - }).toList(); - _searchNotifier.updateSearchValue(list); - } else if (_filterOn == "phoneNumber") { - final list = _contactsList.where((element) { - if (element.phoneNumber.isNotEmpty) { - return element.phoneNumber - .toLowerCase() - .contains(_searchController.text.toLowerCase()); - } else { - return false; - } - }).toList(); - _searchNotifier.updateSearchValue(list); - } else if (_filterOn == "kilvishId") { - final list = _contactsList.where((element) { - if (element.kilvishId != null) { - return element.kilvishId! - .toLowerCase() - .contains(_searchController.text.toLowerCase()); - } else { - return false; - } - }).toList(); - _searchNotifier.updateSearchValue(list); - } - } else { - _searchNotifier.updateSearchValue(_contactsList); + .contains(_searchController.text.toLowerCase()) || + element.phoneNumber + .toLowerCase() + .contains(_searchController.text.toLowerCase()) || + (element.kilvishId != null && + element.kilvishId! + .toLowerCase() + .contains(_searchController.text.toLowerCase())); + }).toList(); + + List contactSearchResult = + await ContactsService.getContacts(query: _searchController.text); + if (contactSearchResult.length > 5) { + contactSearchResult = contactSearchResult.take(5).toList(); } + contactSearchResult.forEach((contactsElement) { + final localContact = kilvishSearchResult.firstWhereOrNull( + (element) => element.name == contactsElement.displayName); + if (localContact == null) { + kilvishSearchResult.add(ContactModel( + name: (contactsElement.displayName ?? ""), + phoneNumber: (contactsElement.phones != null && + contactsElement.phones!.isNotEmpty) + ? (contactsElement.phones![0].value ?? "") + : "")); + } + }); + _searchNotifier.updateSearchValue(kilvishSearchResult); + setState(() { + _isLoading = false; + }); } /// Showing title widget for app bar Search title @@ -278,40 +251,8 @@ class _ContactScreenState extends State { prefixIcon: Icon(Icons.search, color: Theme.of(context).textSelectionTheme.selectionColor), hintText: _hintText, - suffixIcon: PopupMenuButton( - icon: const Icon(Icons.filter_list), - onSelected: (String result) { - _filterOn = result; - searchFromContactList(); - }, - itemBuilder: (BuildContext context) => >[ - const PopupMenuItem( - value: 'name', - child: Text('Name'), - ), - const PopupMenuItem( - value: 'phoneNumber', - child: Text('Phone Number'), - ), - const PopupMenuItem( - value: 'kilvishId', - child: Text('Kilvish Id'), - ), - ], - ), hintStyle: TextStyle( color: Theme.of(context).textSelectionTheme.selectionColor)), ); } - - /// Showing title widget for app bar - Widget titleWidget() { - return Material( - type: MaterialType.transparency, - child: Text( - _appbarTitle, - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.w500, color: Colors.white), - )); - } } diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index db6ee4b..4cc70ba 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -4,7 +4,6 @@ import 'package:kilvish/constants/dimens_constants.dart'; import 'package:flutter/scheduler.dart'; import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/home_screen.dart'; -import 'package:kilvish/models/ContactModel.dart'; import 'package:kilvish/style.dart'; import 'package:kilvish/tag_selection_screen.dart'; import '../common_widgets.dart'; diff --git a/lib/tag_edit_screen.dart b/lib/tag_edit_screen.dart index 588f99f..85f3456 100644 --- a/lib/tag_edit_screen.dart +++ b/lib/tag_edit_screen.dart @@ -3,7 +3,6 @@ import 'package:kilvish/common_widgets.dart'; import 'package:kilvish/constants/dimens_constants.dart'; import 'package:kilvish/contact_screen.dart'; import 'package:kilvish/models.dart'; -import 'package:kilvish/models/ContactModel.dart'; import 'package:kilvish/style.dart'; class TagEditPage extends StatefulWidget { diff --git a/pubspec.yaml b/pubspec.yaml index dc310d6..0bd2e7d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: image_picker: ^1.0.5 fluttertoast: ^8.2.4 permission_handler: ^11.1.0 - fast_contacts: ^3.1.3 + contacts_service: ^0.6.3 dev_dependencies: flutter_test: From ab310337534de8998d2cea548d42503c6b2a813e Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 20:43:05 +0530 Subject: [PATCH 08/11] Handling Contacts in UI --- lib/contact_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index 13234e9..53c5c5a 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -140,7 +140,7 @@ class _ContactScreenState extends State { children: [ (index > 0 && filterList[index].kilvishId == null && - filterList[index].kilvishId != null) + filterList[index - 1].kilvishId != null) ? const Divider(height: 1, color: Colors.grey) : const SizedBox(), ListTile( From 2e33e8956ec312714fe2444d0213417ad1a756ac Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 20:50:19 +0530 Subject: [PATCH 09/11] Handling Contacts in UI --- lib/contact_screen.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/contact_screen.dart b/lib/contact_screen.dart index 53c5c5a..bb351db 100644 --- a/lib/contact_screen.dart +++ b/lib/contact_screen.dart @@ -223,8 +223,14 @@ class _ContactScreenState extends State { contactSearchResult = contactSearchResult.take(5).toList(); } contactSearchResult.forEach((contactsElement) { - final localContact = kilvishSearchResult.firstWhereOrNull( - (element) => element.name == contactsElement.displayName); + final localContact = kilvishSearchResult.firstWhereOrNull((element) { + if (contactsElement.phones != null && + contactsElement.phones?[0].value != null) { + return (element.phoneNumber == contactsElement.phones![0].value); + } else { + return false; + } + }); if (localContact == null) { kilvishSearchResult.add(ContactModel( name: (contactsElement.displayName ?? ""), From ddb65a41181ad928f313e6e741a90eb2f268acc7 Mon Sep 17 00:00:00 2001 From: radheyshyamjat Date: Fri, 2 Feb 2024 20:51:48 +0530 Subject: [PATCH 10/11] Handling Contacts in UI --- lib/import_expense_screen.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index 4cc70ba..933a977 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -170,14 +170,13 @@ class _ImportExpensePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const ContactScreen( - contactSelection: ContactSelection - .multiSelect))).then((value) { + builder: (context) => + const ContactScreen())).then((value) { if (value != null) { if (value is ContactModel) { namecon.text = value.name; } else if (value is List) { - List temp=[]; + List temp = []; value.forEach((element) { temp.add(element.name); }); From fdbe29ec419f8533fecd1cfdbfab22d447269906 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Sun, 4 Feb 2024 12:08:40 +0530 Subject: [PATCH 11/11] on contact selection for import_expense screen should be uniform when contact is selected or not selected --- lib/import_expense_screen.dart | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/import_expense_screen.dart b/lib/import_expense_screen.dart index 933a977..7fbbd48 100644 --- a/lib/import_expense_screen.dart +++ b/lib/import_expense_screen.dart @@ -153,10 +153,15 @@ class _ImportExpensePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => ContactScreen())); - } - // onTap: _contactFetchFn - ), + builder: (context) => + const ContactScreen())).then((value) { + if (value != null && value is ContactModel) { + namecon.text = value.name; + } else { + //TOOD - show toast to notify user of an error + } + }); + }), ], ) : TextFormField( @@ -172,16 +177,10 @@ class _ImportExpensePageState extends State { MaterialPageRoute( builder: (context) => const ContactScreen())).then((value) { - if (value != null) { - if (value is ContactModel) { - namecon.text = value.name; - } else if (value is List) { - List temp = []; - value.forEach((element) { - temp.add(element.name); - }); - namecon.text = temp.join(","); - } + if (value != null && value is ContactModel) { + namecon.text = value.name; + } else { + //TOOD - show toast to notify user of an error } }); }),