From 6bed3f5ad9ccbdfa60ba685f8e5c1608d63d3f65 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Wed, 11 Dec 2024 21:54:20 +0100 Subject: [PATCH 1/6] feat: WIP contact-modal --- .../widgets/alphabet_scroll_page.dart | 180 +++++++++++++++++- 1 file changed, 175 insertions(+), 5 deletions(-) diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index 7453181..e0b8b76 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -5,11 +5,14 @@ import '../contact_state.dart'; import '../../../widgets/color_darkener.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; +import 'contact_modal.dart'; import 'share_own_qr.dart'; class AlphabetScrollPage extends StatefulWidget { final double scrollOffset; + const AlphabetScrollPage( + {super.key, required this.contacts, required this.scrollOffset}); const AlphabetScrollPage({super.key, required this.scrollOffset}); @override @@ -18,10 +21,14 @@ class AlphabetScrollPage extends StatefulWidget { class _AlphabetScrollPageState extends State { late ScrollController _scrollController; + List _contacts = []; // Local copy of contacts for updating @override void initState() { super.initState(); + _scrollController = + ScrollController(initialScrollOffset: widget.scrollOffset); + _contacts = widget.contacts; // Initialize with the provided contacts _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); _scrollController.addListener(_onScroll); @@ -69,6 +76,142 @@ class _AlphabetScrollPageState extends State { } } + Future _refreshContacts() async { + if (await FlutterContacts.requestPermission()) { + final updatedContacts = await FlutterContacts.getContacts( + withProperties: true, withThumbnail: true); + setState(() { + _contacts = updatedContacts; + }); + } + } + + // void _toggleFavorite(Contact contact) async { + // print(contact.id); + + // if (await FlutterContacts.requestPermission()) { + // try { + // // Fetch all contacts (this can be slow if there are many contacts) + // List allContacts = await FlutterContacts.getContacts( + // withProperties: true, + // withThumbnail: true, + // withAccounts: true, + // withPhoto: true + // ); + + // // Find the specific contact by matching contact.id + // // Use `orElse` to return a nullable Contact? or throw an exception if not found + // Contact? contactToUpdate = allContacts.firstWhere( + // (c) => c.id == contact.id, + // orElse: () => throw Exception( + // "Contact not found"), // Throw an exception if not found + // ); + + // if (contactToUpdate != null) { + // print("Contact fetched: ${contactToUpdate.displayName}"); + // print("Current isStarred status: ${contactToUpdate.isStarred}"); + + // // Toggle the favorite status + // contactToUpdate.isStarred = !contactToUpdate.isStarred; + // print("Updated isStarred status: ${contactToUpdate.isStarred}"); + + // // Update the contact + // await FlutterContacts.updateContact(contactToUpdate); + // print("Contact updated successfully"); + + // // Refresh the UI by updating the state + // setState(() { + // contact.isStarred = contactToUpdate.isStarred; + // }); + // } + // } catch (e) { + // print("Error updating favorite status: $e"); + // } + // } + // } + + // void _toggleFavorite(Contact contact) async { + // print(contact.id); + + // try { + // // Fetch the contact with details + // final contactWithDetails = await FlutterContacts.getContact(contact.id, + // withProperties: true, withThumbnail: true, withAccounts: true); + + // if (contactWithDetails != null) { + // print("Contact fetched: ${contactWithDetails.displayName}"); + // print("Current isStarred status: ${contactWithDetails.isStarred}"); + + // // Toggle the favorite status + // contactWithDetails.isStarred = !contactWithDetails.isStarred; + // print("Updated isStarred status: ${contactWithDetails.isStarred}"); + + // // Update the contact + // await FlutterContacts.updateContact(contactWithDetails); + // print("Contact updated successfully"); + + // // Reset the contact state (optional) + // setState(() { + // contact.isStarred = contactWithDetails.isStarred; + // }); + + // // Re-fetch the contact to ensure UI reflects changes + // final updatedContact = await FlutterContacts.getContact(contact.id, + // withProperties: true, withThumbnail: true, withAccounts: true); + + // if (updatedContact != null) { + // print("Re-fetched updated contact: ${updatedContact.displayName}"); + // print("Re-fetched isStarred status: ${updatedContact.isStarred}"); + // } else { + // print("Re-fetching contact failed."); + // } + // } else { + // print("Contact details are not available"); + // } + // } catch (e) { + // print("Error updating favorite status: $e"); + // } + // } + + void _toggleFavorite(Contact contact) async { + print(contact.id); + + try { + if (contact != null) { + print("Contact fetched: ${contact.displayName}"); + print("Current isStarred status: ${contact.isStarred}"); + + // Toggle the favorite status + contact.isStarred = !contact.isStarred; + print("Updated isStarred status: ${contact.isStarred}"); + + // Update the contact + await FlutterContacts.updateContact(contact); + print("Contact updated successfully"); + + // Reset the contact state (optional) + setState(() { + contact.isStarred = contact.isStarred; + }); + + // Re-fetch the contact to ensure UI reflects changes + final updatedContact = await FlutterContacts.getContact(contact.id, + withProperties: true, withThumbnail: true, withAccounts: true); + + if (updatedContact != null) { + print("Re-fetched updated contact: ${updatedContact.displayName}"); + print("Re-fetched isStarred status: ${updatedContact.isStarred}"); + } else { + print("Re-fetching contact failed."); + } + } else { + print("Contact details are not available"); + } + } catch (e) { + print("Error updating favorite status: $e"); + } + } + @override Widget build(BuildContext context) { final contactState = ContactState.of(context); @@ -76,7 +219,7 @@ class _AlphabetScrollPageState extends State { final selfContact = contactState.selfContact; Map> alphabetizedContacts = {}; - for (var contact in contacts) { + for (var contact in _contacts) { String firstLetter = contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '#'; @@ -91,16 +234,21 @@ class _AlphabetScrollPageState extends State { return Scaffold( backgroundColor: Colors.black, body: Column( + children: [ + // Top buttons row children: [ // Top buttons row Container( color: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AddContactButton(), - QRCodeButton(contacts: contacts, selfContact: selfContact), + QRCodeButton( + contacts: _contacts, + selfContact: ContactState.of(context).selfContact), ], ), ), @@ -117,6 +265,8 @@ class _AlphabetScrollPageState extends State { children: [ // Alphabet Letter Header Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 16.0), padding: const EdgeInsets.symmetric( vertical: 8.0, horizontal: 16.0), child: Text( @@ -129,19 +279,24 @@ class _AlphabetScrollPageState extends State { ), ), // Contact Entries - ...contactsForLetter.map((contact) { + ...contacts.map((contact) { String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number'; Color avatarColor = generateColorFromName(contact.displayName); return ListTile( + leading: (contact.thumbnail != null && + contact.thumbnail!.isNotEmpty) leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) ? CircleAvatar( backgroundImage: MemoryImage(contact.thumbnail!), ) + backgroundImage: + MemoryImage(contact.thumbnail!), + ) : CircleAvatar( backgroundColor: avatarColor, child: Text( @@ -154,6 +309,19 @@ class _AlphabetScrollPageState extends State { ), title: Text(contact.displayName, style: TextStyle(color: Colors.white)), + subtitle: Text(phoneNumber, + style: TextStyle(color: Colors.white70)), + backgroundColor: avatarColor, + child: Text( + contact.displayName.isNotEmpty + ? contact.displayName[0].toUpperCase() + : '?', + style: TextStyle( + color: darken(avatarColor, 0.4)), + ), + ), + title: Text(contact.displayName, + style: TextStyle(color: Colors.white)), subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)), onTap: () { @@ -165,7 +333,9 @@ class _AlphabetScrollPageState extends State { return ContactModal( contact: contact, onEdit: () async { - if (await FlutterContacts.requestPermission()) { + // Trigger edit logic and refresh contacts + if (await FlutterContacts + .requestPermission()) { final updatedContact = await FlutterContacts.openExternalEdit( contact.id); -- 2.45.2 From 7638c23c4078c5f6d8ba8b216bb3957c6e6cb840 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Thu, 12 Dec 2024 17:47:49 +0100 Subject: [PATCH 2/6] feat: all getContacts use the contactService | can favorite a contact --- .../lib/features/composition/composition.dart | 10 +- .../widgets/alphabet_scroll_page.dart | 162 ++-------------- .../features/favorites/favorites_page.dart | 180 ++++++++++++++++++ 3 files changed, 206 insertions(+), 146 deletions(-) diff --git a/dialer/lib/features/composition/composition.dart b/dialer/lib/features/composition/composition.dart index b224f05..b2dfb7d 100644 --- a/dialer/lib/features/composition/composition.dart +++ b/dialer/lib/features/composition/composition.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../../widgets/contact_service.dart'; +import '../../widgets/contact_service.dart'; class CompositionPage extends StatefulWidget { const CompositionPage({super.key}); @@ -14,6 +15,7 @@ class _CompositionPageState extends State { List _allContacts = []; List _filteredContacts = []; final ContactService _contactService = ContactService(); + final ContactService _contactService = ContactService(); @override void initState() { @@ -22,9 +24,11 @@ class _CompositionPageState extends State { } Future _fetchContacts() async { - _allContacts = await _contactService.fetchContacts(); - _filteredContacts = _allContacts; - setState(() {}); + if (await FlutterContacts.requestPermission()) { + _allContacts = await _contactService.fetchContacts(); + _filteredContacts = _allContacts; + setState(() {}); + } } void _filterContacts() { diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index e0b8b76..f5ddc0a 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../contact_state.dart'; import '../../../widgets/color_darkener.dart'; +import '../contact_state.dart'; +import '../../../widgets/contact_service.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; import 'contact_modal.dart'; @@ -22,6 +24,7 @@ class AlphabetScrollPage extends StatefulWidget { class _AlphabetScrollPageState extends State { late ScrollController _scrollController; List _contacts = []; // Local copy of contacts for updating + final ContactService _contactService = ContactService(); @override void initState() { @@ -40,20 +43,28 @@ class _AlphabetScrollPageState extends State { } Future _refreshContacts() async { - final contactState = ContactState.of(context); try { - await contactState.fetchContacts(); + // Use the fetchContacts method from ContactService + final updatedContacts = await _contactService.fetchContacts(); + + if (mounted) { + setState(() { + _contacts = updatedContacts; + }); + } } catch (e) { print('Error refreshing contacts: $e'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to refresh contacts')), - ); + // Optionally show a user-friendly error message + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('Failed to refresh contacts'))); } } void _toggleFavorite(Contact contact) async { try { + // Request permission first if (await FlutterContacts.requestPermission()) { + // Fetch the full contact details with all available properties Contact? fullContact = await FlutterContacts.getContact(contact.id, withProperties: true, withAccounts: true, @@ -61,6 +72,7 @@ class _AlphabetScrollPageState extends State { withThumbnail: true); if (fullContact != null) { + // Update the contact fullContact.isStarred = !fullContact.isStarred; await FlutterContacts.updateContact(fullContact); } @@ -70,145 +82,9 @@ class _AlphabetScrollPageState extends State { } } catch (e) { print("Error updating favorite status: $e"); + // Optional: Show a user-friendly error message ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to update contact favorite status')), - ); - } - } - - Future _refreshContacts() async { - if (await FlutterContacts.requestPermission()) { - final updatedContacts = await FlutterContacts.getContacts( - withProperties: true, withThumbnail: true); - setState(() { - _contacts = updatedContacts; - }); - } - } - - // void _toggleFavorite(Contact contact) async { - // print(contact.id); - - // if (await FlutterContacts.requestPermission()) { - // try { - // // Fetch all contacts (this can be slow if there are many contacts) - // List allContacts = await FlutterContacts.getContacts( - // withProperties: true, - // withThumbnail: true, - // withAccounts: true, - // withPhoto: true - // ); - - // // Find the specific contact by matching contact.id - // // Use `orElse` to return a nullable Contact? or throw an exception if not found - // Contact? contactToUpdate = allContacts.firstWhere( - // (c) => c.id == contact.id, - // orElse: () => throw Exception( - // "Contact not found"), // Throw an exception if not found - // ); - - // if (contactToUpdate != null) { - // print("Contact fetched: ${contactToUpdate.displayName}"); - // print("Current isStarred status: ${contactToUpdate.isStarred}"); - - // // Toggle the favorite status - // contactToUpdate.isStarred = !contactToUpdate.isStarred; - // print("Updated isStarred status: ${contactToUpdate.isStarred}"); - - // // Update the contact - // await FlutterContacts.updateContact(contactToUpdate); - // print("Contact updated successfully"); - - // // Refresh the UI by updating the state - // setState(() { - // contact.isStarred = contactToUpdate.isStarred; - // }); - // } - // } catch (e) { - // print("Error updating favorite status: $e"); - // } - // } - // } - - // void _toggleFavorite(Contact contact) async { - // print(contact.id); - - // try { - // // Fetch the contact with details - // final contactWithDetails = await FlutterContacts.getContact(contact.id, - // withProperties: true, withThumbnail: true, withAccounts: true); - - // if (contactWithDetails != null) { - // print("Contact fetched: ${contactWithDetails.displayName}"); - // print("Current isStarred status: ${contactWithDetails.isStarred}"); - - // // Toggle the favorite status - // contactWithDetails.isStarred = !contactWithDetails.isStarred; - // print("Updated isStarred status: ${contactWithDetails.isStarred}"); - - // // Update the contact - // await FlutterContacts.updateContact(contactWithDetails); - // print("Contact updated successfully"); - - // // Reset the contact state (optional) - // setState(() { - // contact.isStarred = contactWithDetails.isStarred; - // }); - - // // Re-fetch the contact to ensure UI reflects changes - // final updatedContact = await FlutterContacts.getContact(contact.id, - // withProperties: true, withThumbnail: true, withAccounts: true); - - // if (updatedContact != null) { - // print("Re-fetched updated contact: ${updatedContact.displayName}"); - // print("Re-fetched isStarred status: ${updatedContact.isStarred}"); - // } else { - // print("Re-fetching contact failed."); - // } - // } else { - // print("Contact details are not available"); - // } - // } catch (e) { - // print("Error updating favorite status: $e"); - // } - // } - - void _toggleFavorite(Contact contact) async { - print(contact.id); - - try { - if (contact != null) { - print("Contact fetched: ${contact.displayName}"); - print("Current isStarred status: ${contact.isStarred}"); - - // Toggle the favorite status - contact.isStarred = !contact.isStarred; - print("Updated isStarred status: ${contact.isStarred}"); - - // Update the contact - await FlutterContacts.updateContact(contact); - print("Contact updated successfully"); - - // Reset the contact state (optional) - setState(() { - contact.isStarred = contact.isStarred; - }); - - // Re-fetch the contact to ensure UI reflects changes - final updatedContact = await FlutterContacts.getContact(contact.id, - withProperties: true, withThumbnail: true, withAccounts: true); - - if (updatedContact != null) { - print("Re-fetched updated contact: ${updatedContact.displayName}"); - print("Re-fetched isStarred status: ${updatedContact.isStarred}"); - } else { - print("Re-fetching contact failed."); - } - } else { - print("Contact details are not available"); - } - } catch (e) { - print("Error updating favorite status: $e"); + SnackBar(content: Text('Failed to update contact favorite status'))); } } diff --git a/dialer/lib/features/favorites/favorites_page.dart b/dialer/lib/features/favorites/favorites_page.dart index 48227c5..4cfda5f 100644 --- a/dialer/lib/features/favorites/favorites_page.dart +++ b/dialer/lib/features/favorites/favorites_page.dart @@ -1,3 +1,183 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_contacts/flutter_contacts.dart'; +// import '../../../widgets/color_darkener.dart'; +// import '../contacts/contact_state.dart'; +// import '../contacts/widgets/add_contact_button.dart'; +// import '../contacts/widgets/contact_modal.dart'; +// import '../contacts/widgets/share_own_qr.dart'; +// import 'package:dialer/widgets/username_color_generator.dart'; + +// class FavoritePage extends StatefulWidget { +// final List contacts; +// final double scrollOffset; + +// const FavoritePage({ +// super.key, +// required this.contacts, +// required this.scrollOffset, +// }); + +// @override +// _FavoritePageState createState() => _FavoritePageState(); +// } + +// class _FavoritePageState extends State { +// late ScrollController _scrollController; +// List _favoriteContacts = []; // Local list of favorite contacts + +// @override +// void initState() { +// super.initState(); +// _favoriteContacts = widget.contacts.where((contact) => contact.isStarred).toList(); // Filter only favorites +// _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); +// _scrollController.addListener(_onScroll); +// } + +// void _onScroll() { +// final contactState = ContactState.of(context); +// contactState.setScrollOffset(_scrollController.offset); +// } + +// Future _refreshContacts() async { +// if (await FlutterContacts.requestPermission()) { +// final updatedContacts = await FlutterContacts.getContacts( +// withProperties: true, +// withThumbnail: true, +// ); +// setState(() { +// _favoriteContacts = updatedContacts.where((contact) => contact.isStarred).toList(); +// }); +// } +// } + +// void _toggleFavorite(Contact contact) async { +// if (await FlutterContacts.requestPermission()) { +// try { +// // Fetch all contacts (this can be slow if there are many contacts) +// List allContacts = await FlutterContacts.getContacts( +// withProperties: true, +// withThumbnail: true, +// withAccounts: true, +// ); + +// // Find the specific contact by matching contact.id +// Contact? contactToUpdate = allContacts.firstWhere( +// (c) => c.id == contact.id, +// orElse: () => throw Exception("Contact not found"), +// ); + +// if (contactToUpdate != null) { +// contactToUpdate.isStarred = !contactToUpdate.isStarred; + +// // Update the contact +// await FlutterContacts.updateContact(contactToUpdate); + +// // Refresh the favorite contacts list +// setState(() { +// _favoriteContacts = allContacts.where((c) => c.isStarred).toList(); +// }); +// } +// } catch (e) { +// print("Error updating favorite status: $e"); +// } +// } +// } + +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.black, +// appBar: AppBar( +// title: const Text('Favorites'), +// backgroundColor: Colors.black, +// actions: [ +// IconButton( +// icon: const Icon(Icons.refresh), +// onPressed: _refreshContacts, +// ), +// ], +// ), +// body: _favoriteContacts.isEmpty +// ? Center( +// child: Text( +// 'No favorite contacts yet!', +// style: TextStyle(color: Colors.white), +// ), +// ) +// : ListView.builder( +// controller: _scrollController, +// itemCount: _favoriteContacts.length, +// itemBuilder: (context, index) { +// Contact contact = _favoriteContacts[index]; +// String phoneNumber = contact.phones.isNotEmpty +// ? contact.phones.first.number +// : 'No phone number'; +// Color avatarColor = generateColorFromName(contact.displayName); + +// return ListTile( +// leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) +// ? CircleAvatar( +// backgroundImage: MemoryImage(contact.thumbnail!), +// ) +// : CircleAvatar( +// backgroundColor: avatarColor, +// child: Text( +// contact.displayName.isNotEmpty +// ? contact.displayName[0].toUpperCase() +// : '?', +// style: TextStyle(color: darken(avatarColor, 0.4)), +// ), +// ), +// title: Text(contact.displayName, style: TextStyle(color: Colors.white)), +// subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)), +// onTap: () { +// showModalBottomSheet( +// context: context, +// isScrollControlled: true, +// backgroundColor: Colors.transparent, +// builder: (context) { +// return ContactModal( +// contact: contact, +// onEdit: () async { +// // Trigger edit logic and refresh contacts +// if (await FlutterContacts.requestPermission()) { +// final updatedContact = +// await FlutterContacts.openExternalEdit(contact.id); +// if (updatedContact != null) { +// await _refreshContacts(); +// Navigator.of(context).pop(); +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('${contact.displayName} updated successfully!')), +// ); +// } else { +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Edit canceled or failed.')), +// ); +// } +// } +// }, +// onToggleFavorite: () { +// _toggleFavorite(contact); +// }, +// isFavorite: contact.isStarred, +// ); +// }, +// ); +// }, +// ); +// }, +// ), +// ); +// } + +// @override +// void dispose() { +// _scrollController.dispose(); +// super.dispose(); +// } +// } + + import 'package:flutter/material.dart'; class FavoritePage extends StatefulWidget { -- 2.45.2 From 62cf3b6207f866626518a8bd2390e480700079b1 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Thu, 12 Dec 2024 17:55:32 +0100 Subject: [PATCH 3/6] revert: favorite page (wrong branch) --- .../features/favorites/favorites_page.dart | 180 ------------------ 1 file changed, 180 deletions(-) diff --git a/dialer/lib/features/favorites/favorites_page.dart b/dialer/lib/features/favorites/favorites_page.dart index 4cfda5f..48227c5 100644 --- a/dialer/lib/features/favorites/favorites_page.dart +++ b/dialer/lib/features/favorites/favorites_page.dart @@ -1,183 +1,3 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter_contacts/flutter_contacts.dart'; -// import '../../../widgets/color_darkener.dart'; -// import '../contacts/contact_state.dart'; -// import '../contacts/widgets/add_contact_button.dart'; -// import '../contacts/widgets/contact_modal.dart'; -// import '../contacts/widgets/share_own_qr.dart'; -// import 'package:dialer/widgets/username_color_generator.dart'; - -// class FavoritePage extends StatefulWidget { -// final List contacts; -// final double scrollOffset; - -// const FavoritePage({ -// super.key, -// required this.contacts, -// required this.scrollOffset, -// }); - -// @override -// _FavoritePageState createState() => _FavoritePageState(); -// } - -// class _FavoritePageState extends State { -// late ScrollController _scrollController; -// List _favoriteContacts = []; // Local list of favorite contacts - -// @override -// void initState() { -// super.initState(); -// _favoriteContacts = widget.contacts.where((contact) => contact.isStarred).toList(); // Filter only favorites -// _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); -// _scrollController.addListener(_onScroll); -// } - -// void _onScroll() { -// final contactState = ContactState.of(context); -// contactState.setScrollOffset(_scrollController.offset); -// } - -// Future _refreshContacts() async { -// if (await FlutterContacts.requestPermission()) { -// final updatedContacts = await FlutterContacts.getContacts( -// withProperties: true, -// withThumbnail: true, -// ); -// setState(() { -// _favoriteContacts = updatedContacts.where((contact) => contact.isStarred).toList(); -// }); -// } -// } - -// void _toggleFavorite(Contact contact) async { -// if (await FlutterContacts.requestPermission()) { -// try { -// // Fetch all contacts (this can be slow if there are many contacts) -// List allContacts = await FlutterContacts.getContacts( -// withProperties: true, -// withThumbnail: true, -// withAccounts: true, -// ); - -// // Find the specific contact by matching contact.id -// Contact? contactToUpdate = allContacts.firstWhere( -// (c) => c.id == contact.id, -// orElse: () => throw Exception("Contact not found"), -// ); - -// if (contactToUpdate != null) { -// contactToUpdate.isStarred = !contactToUpdate.isStarred; - -// // Update the contact -// await FlutterContacts.updateContact(contactToUpdate); - -// // Refresh the favorite contacts list -// setState(() { -// _favoriteContacts = allContacts.where((c) => c.isStarred).toList(); -// }); -// } -// } catch (e) { -// print("Error updating favorite status: $e"); -// } -// } -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: Colors.black, -// appBar: AppBar( -// title: const Text('Favorites'), -// backgroundColor: Colors.black, -// actions: [ -// IconButton( -// icon: const Icon(Icons.refresh), -// onPressed: _refreshContacts, -// ), -// ], -// ), -// body: _favoriteContacts.isEmpty -// ? Center( -// child: Text( -// 'No favorite contacts yet!', -// style: TextStyle(color: Colors.white), -// ), -// ) -// : ListView.builder( -// controller: _scrollController, -// itemCount: _favoriteContacts.length, -// itemBuilder: (context, index) { -// Contact contact = _favoriteContacts[index]; -// String phoneNumber = contact.phones.isNotEmpty -// ? contact.phones.first.number -// : 'No phone number'; -// Color avatarColor = generateColorFromName(contact.displayName); - -// return ListTile( -// leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) -// ? CircleAvatar( -// backgroundImage: MemoryImage(contact.thumbnail!), -// ) -// : CircleAvatar( -// backgroundColor: avatarColor, -// child: Text( -// contact.displayName.isNotEmpty -// ? contact.displayName[0].toUpperCase() -// : '?', -// style: TextStyle(color: darken(avatarColor, 0.4)), -// ), -// ), -// title: Text(contact.displayName, style: TextStyle(color: Colors.white)), -// subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)), -// onTap: () { -// showModalBottomSheet( -// context: context, -// isScrollControlled: true, -// backgroundColor: Colors.transparent, -// builder: (context) { -// return ContactModal( -// contact: contact, -// onEdit: () async { -// // Trigger edit logic and refresh contacts -// if (await FlutterContacts.requestPermission()) { -// final updatedContact = -// await FlutterContacts.openExternalEdit(contact.id); -// if (updatedContact != null) { -// await _refreshContacts(); -// Navigator.of(context).pop(); -// ScaffoldMessenger.of(context).showSnackBar( -// SnackBar(content: Text('${contact.displayName} updated successfully!')), -// ); -// } else { -// ScaffoldMessenger.of(context).showSnackBar( -// SnackBar(content: Text('Edit canceled or failed.')), -// ); -// } -// } -// }, -// onToggleFavorite: () { -// _toggleFavorite(contact); -// }, -// isFavorite: contact.isStarred, -// ); -// }, -// ); -// }, -// ); -// }, -// ), -// ); -// } - -// @override -// void dispose() { -// _scrollController.dispose(); -// super.dispose(); -// } -// } - - import 'package:flutter/material.dart'; class FavoritePage extends StatefulWidget { -- 2.45.2 From e6807b865b98a6481443294c23cabf0b44cccb59 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Fri, 13 Dec 2024 10:00:20 +0100 Subject: [PATCH 4/6] feat: fetch Favorite, fix fetch redundancy --- .../widgets/alphabet_scroll_page.dart | 53 +++++++------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index f5ddc0a..24794a8 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -2,9 +2,8 @@ import 'package:dialer/widgets/username_color_generator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../contact_state.dart'; -import '../../../widgets/color_darkener.dart'; import '../contact_state.dart'; -import '../../../widgets/contact_service.dart'; +import '../../../widgets/color_darkener.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; import 'contact_modal.dart'; @@ -13,8 +12,6 @@ import 'share_own_qr.dart'; class AlphabetScrollPage extends StatefulWidget { final double scrollOffset; - const AlphabetScrollPage( - {super.key, required this.contacts, required this.scrollOffset}); const AlphabetScrollPage({super.key, required this.scrollOffset}); @override @@ -23,15 +20,10 @@ class AlphabetScrollPage extends StatefulWidget { class _AlphabetScrollPageState extends State { late ScrollController _scrollController; - List _contacts = []; // Local copy of contacts for updating - final ContactService _contactService = ContactService(); @override void initState() { super.initState(); - _scrollController = - ScrollController(initialScrollOffset: widget.scrollOffset); - _contacts = widget.contacts; // Initialize with the provided contacts _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); _scrollController.addListener(_onScroll); @@ -43,28 +35,20 @@ class _AlphabetScrollPageState extends State { } Future _refreshContacts() async { + final contactState = ContactState.of(context); try { - // Use the fetchContacts method from ContactService - final updatedContacts = await _contactService.fetchContacts(); - - if (mounted) { - setState(() { - _contacts = updatedContacts; - }); - } + await contactState.fetchContacts(); } catch (e) { print('Error refreshing contacts: $e'); - // Optionally show a user-friendly error message - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text('Failed to refresh contacts'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to refresh contacts')), + ); } } void _toggleFavorite(Contact contact) async { try { - // Request permission first if (await FlutterContacts.requestPermission()) { - // Fetch the full contact details with all available properties Contact? fullContact = await FlutterContacts.getContact(contact.id, withProperties: true, withAccounts: true, @@ -72,7 +56,6 @@ class _AlphabetScrollPageState extends State { withThumbnail: true); if (fullContact != null) { - // Update the contact fullContact.isStarred = !fullContact.isStarred; await FlutterContacts.updateContact(fullContact); } @@ -82,9 +65,9 @@ class _AlphabetScrollPageState extends State { } } catch (e) { print("Error updating favorite status: $e"); - // Optional: Show a user-friendly error message ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to update contact favorite status'))); + SnackBar(content: Text('Failed to update contact favorite status')), + ); } } @@ -94,8 +77,12 @@ class _AlphabetScrollPageState extends State { final contacts = contactState.contacts; final selfContact = contactState.selfContact; + final contactState = ContactState.of(context); + final contacts = contactState.contacts; + final selfContact = contactState.selfContact; + Map> alphabetizedContacts = {}; - for (var contact in _contacts) { + for (var contact in contacts) { String firstLetter = contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '#'; @@ -116,15 +103,12 @@ class _AlphabetScrollPageState extends State { // Top buttons row Container( color: Colors.black, - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AddContactButton(), - QRCodeButton( - contacts: _contacts, - selfContact: ContactState.of(context).selfContact), + QRCodeButton(contacts: contacts, selfContact: selfContact), ], ), ), @@ -136,6 +120,7 @@ class _AlphabetScrollPageState extends State { itemBuilder: (context, index) { String letter = alphabetKeys[index]; List contactsForLetter = alphabetizedContacts[letter]!; + List contactsForLetter = alphabetizedContacts[letter]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -155,7 +140,7 @@ class _AlphabetScrollPageState extends State { ), ), // Contact Entries - ...contacts.map((contact) { + ...contactsForLetter.map((contact) { String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number'; @@ -209,9 +194,7 @@ class _AlphabetScrollPageState extends State { return ContactModal( contact: contact, onEdit: () async { - // Trigger edit logic and refresh contacts - if (await FlutterContacts - .requestPermission()) { + if (await FlutterContacts.requestPermission()) { final updatedContact = await FlutterContacts.openExternalEdit( contact.id); -- 2.45.2 From c46a43aa4e52f592b3589329d5f7686027997468 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Sun, 15 Dec 2024 03:25:44 +0100 Subject: [PATCH 5/6] feat: favorite page --- .../lib/features/contacts/contact_page.dart | 6 ++- .../lib/features/contacts/contact_state.dart | 46 +++++++++++++------ .../widgets/alphabet_scroll_page.dart | 15 +++--- .../features/favorites/favorites_page.dart | 31 ++++++++----- dialer/lib/features/home/home_page.dart | 2 +- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/dialer/lib/features/contacts/contact_page.dart b/dialer/lib/features/contacts/contact_page.dart index 93acc45..9f09490 100644 --- a/dialer/lib/features/contacts/contact_page.dart +++ b/dialer/lib/features/contacts/contact_page.dart @@ -17,8 +17,10 @@ class _ContactPageState extends State { return Scaffold( body: contactState.loading ? const LoadingIndicatorWidget() - // : ContactListWidget(contacts: contactState.contacts), - : AlphabetScrollPage(scrollOffset: contactState.scrollOffset), + : AlphabetScrollPage( + scrollOffset: contactState.scrollOffset, + contacts: contactState.contacts, // Use all contacts here + ), ); } } diff --git a/dialer/lib/features/contacts/contact_state.dart b/dialer/lib/features/contacts/contact_state.dart index 75e70d8..82e21c2 100644 --- a/dialer/lib/features/contacts/contact_state.dart +++ b/dialer/lib/features/contacts/contact_state.dart @@ -19,12 +19,15 @@ class ContactState extends StatefulWidget { class _ContactStateState extends State { final ContactService _contactService = ContactService(); - List _contacts = []; + List _allContacts = []; + List _favoriteContacts = []; bool _loading = true; double _scrollOffset = 0.0; Contact? _selfContact = Contact(); - List get contacts => _contacts; + // Getters for all contacts and favorites + List get contacts => _allContacts; + List get favoriteContacts => _favoriteContacts; bool get loading => _loading; double get scrollOffset => _scrollOffset; Contact? get selfContact => _selfContact; @@ -32,9 +35,7 @@ class _ContactStateState extends State { @override void initState() { super.initState(); - fetchContacts(); - - // Add listener for contact changes + fetchContacts(); // Fetch all contacts by default FlutterContacts.addListener(_onContactChange); } @@ -42,20 +43,33 @@ class _ContactStateState extends State { @override void dispose() { - // Remove listener FlutterContacts.removeListener(_onContactChange); super.dispose(); } - Future fetchContacts({bool onlyStarred = false}) async { - List contacts = onlyStarred - ? await _contactService.fetchFavoriteContacts() - : await _contactService.fetchContacts(); + // Fetch all contacts + Future fetchContacts() async { + setState(() => _loading = true); + try { + List contacts = await _contactService.fetchContacts(); + _processContacts(contacts); + } finally { + setState(() => _loading = false); + } + } - debugPrint( - "Fetched ${contacts.length} ${onlyStarred ? 'favorite' : ''} contacts"); + // Fetch only favorite contacts + Future fetchFavoriteContacts() async { + setState(() => _loading = true); + try { + List contacts = await _contactService.fetchFavoriteContacts(); + setState(() => _favoriteContacts = contacts); + } finally { + setState(() => _loading = false); + } + } - // Find selfContact before filtering + void _processContacts(List contacts) { _selfContact = contacts.firstWhere( (contact) => contact.displayName.toLowerCase() == "user", orElse: () => Contact(), @@ -70,8 +84,9 @@ class _ContactStateState extends State { contacts.sort((a, b) => a.displayName.compareTo(b.displayName)); setState(() { - _contacts = contacts; - _loading = false; + _allContacts = contacts; + _favoriteContacts = + contacts.where((contact) => contact.isStarred).toList(); _selfContact = _selfContact; }); } @@ -96,6 +111,7 @@ class _ContactStateState extends State { } } + class _InheritedContactState extends InheritedWidget { final _ContactStateState data; diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index 24794a8..3ed3fc4 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -11,8 +11,13 @@ import 'share_own_qr.dart'; class AlphabetScrollPage extends StatefulWidget { final double scrollOffset; + final List contacts; - const AlphabetScrollPage({super.key, required this.scrollOffset}); + const AlphabetScrollPage({ + super.key, + required this.scrollOffset, + required this.contacts, + }); @override _AlphabetScrollPageState createState() => _AlphabetScrollPageState(); @@ -24,8 +29,7 @@ class _AlphabetScrollPageState extends State { @override void initState() { super.initState(); - _scrollController = - ScrollController(initialScrollOffset: widget.scrollOffset); + _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); _scrollController.addListener(_onScroll); } @@ -73,9 +77,8 @@ class _AlphabetScrollPageState extends State { @override Widget build(BuildContext context) { - final contactState = ContactState.of(context); - final contacts = contactState.contacts; - final selfContact = contactState.selfContact; + final contacts = widget.contacts; + final selfContact = ContactState.of(context).selfContact; final contactState = ContactState.of(context); final contacts = contactState.contacts; diff --git a/dialer/lib/features/favorites/favorites_page.dart b/dialer/lib/features/favorites/favorites_page.dart index 48227c5..47f74ec 100644 --- a/dialer/lib/features/favorites/favorites_page.dart +++ b/dialer/lib/features/favorites/favorites_page.dart @@ -1,23 +1,32 @@ +import 'package:dialer/features/contacts/contact_state.dart'; +import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart'; import 'package:flutter/material.dart'; +import 'package:dialer/widgets/loading_indicator.dart'; -class FavoritePage extends StatefulWidget { - const FavoritePage({super.key}); +class FavoritesPage extends StatefulWidget { + const FavoritesPage({super.key}); @override - _FavoritePageState createState() => _FavoritePageState(); + _FavoritesPageState createState() => _FavoritesPageState(); } -class _FavoritePageState extends State { +class _FavoritesPageState extends State { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { + final contactState = ContactState.of(context); return Scaffold( - backgroundColor: Colors.black, - body: Center( // Center the text within the body - child: Text( - "Hello", - style: TextStyle(color: Colors.white), // Change text color for visibility - ), - ), + body: contactState.loading + ? const LoadingIndicatorWidget() + : AlphabetScrollPage( + scrollOffset: contactState.scrollOffset, + contacts: + contactState.favoriteContacts, // Use only favorites here + ), ); } } diff --git a/dialer/lib/features/home/home_page.dart b/dialer/lib/features/home/home_page.dart index 5552388..99328cb 100644 --- a/dialer/lib/features/home/home_page.dart +++ b/dialer/lib/features/home/home_page.dart @@ -140,7 +140,7 @@ class _MyHomePageState extends State TabBarView( controller: _tabController, children: const [ - FavoritePage(), + FavoritesPage(), HistoryPage(), ContactPage(), SettingsPage(), // Add your SettingsPage here -- 2.45.2 From 6ffd179f207b7999ef467932dfd560f45285551f Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Sun, 15 Dec 2024 19:38:35 +0100 Subject: [PATCH 6/6] Rebase --- .../lib/features/composition/composition.dart | 53 ++++++------------- .../widgets/alphabet_scroll_page.dart | 29 ---------- .../contacts/widgets/contact_modal.dart | 1 - 3 files changed, 16 insertions(+), 67 deletions(-) diff --git a/dialer/lib/features/composition/composition.dart b/dialer/lib/features/composition/composition.dart index b2dfb7d..783fcf9 100644 --- a/dialer/lib/features/composition/composition.dart +++ b/dialer/lib/features/composition/composition.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../../widgets/contact_service.dart'; -import '../../widgets/contact_service.dart'; class CompositionPage extends StatefulWidget { const CompositionPage({super.key}); @@ -15,7 +14,6 @@ class _CompositionPageState extends State { List _allContacts = []; List _filteredContacts = []; final ContactService _contactService = ContactService(); - final ContactService _contactService = ContactService(); @override void initState() { @@ -83,9 +81,9 @@ class _CompositionPageState extends State { // Top half: Display contacts matching dialed number Expanded( flex: 2, - child: Container( - padding: const EdgeInsets.only( - top: 42.0, left: 16.0, right: 16.0, bottom: 16.0), + child: + Container( + padding: const EdgeInsets.only(top: 42.0, left: 16.0, right: 16.0, bottom: 16.0), color: Colors.black, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -97,14 +95,12 @@ class _CompositionPageState extends State { return ListTile( title: Text( contact.displayName, - style: - const TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), subtitle: contact.phones.isNotEmpty ? Text( contact.phones.first.number, - style: const TextStyle( - color: Colors.grey), + style: const TextStyle(color: Colors.grey), ) : null, trailing: Row( @@ -112,22 +108,16 @@ class _CompositionPageState extends State { children: [ // Call button IconButton( - icon: Icon(Icons.phone, - color: Colors.green[300], - size: 20), + icon: Icon(Icons.phone, color: Colors.green[300], size: 20), onPressed: () { - print( - 'Calling ${contact.displayName}'); + print('Calling ${contact.displayName}'); }, ), // Text button IconButton( - icon: Icon(Icons.message, - color: Colors.blue[300], - size: 20), + icon: Icon(Icons.message, color: Colors.blue[300], size: 20), onPressed: () { - print( - 'Texting ${contact.displayName}'); + print('Texting ${contact.displayName}'); }, ), ], @@ -137,12 +127,7 @@ class _CompositionPageState extends State { }, ); }).toList() - : [ - Center( - child: Text('No contacts found', - style: - TextStyle(color: Colors.white))) - ], + : [Center(child: Text('No contacts found', style: TextStyle(color: Colors.white)))], ), ), ], @@ -167,16 +152,14 @@ class _CompositionPageState extends State { alignment: Alignment.center, child: Text( dialedNumber, - style: const TextStyle( - fontSize: 24, color: Colors.white), + style: const TextStyle(fontSize: 24, color: Colors.white), overflow: TextOverflow.ellipsis, ), ), ), IconButton( onPressed: _onClearPress, - icon: const Icon(Icons.backspace, - color: Colors.white), + icon: const Icon(Icons.backspace, color: Colors.white), ), ], ), @@ -189,8 +172,7 @@ class _CompositionPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildDialButton('1'), _buildDialButton('2'), @@ -198,8 +180,7 @@ class _CompositionPageState extends State { ], ), Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildDialButton('4'), _buildDialButton('5'), @@ -207,8 +188,7 @@ class _CompositionPageState extends State { ], ), Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildDialButton('7'), _buildDialButton('8'), @@ -216,8 +196,7 @@ class _CompositionPageState extends State { ], ), Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildDialButton('*'), _buildDialButton('0'), diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index 3ed3fc4..c144084 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -2,11 +2,9 @@ import 'package:dialer/widgets/username_color_generator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../contact_state.dart'; -import '../contact_state.dart'; import '../../../widgets/color_darkener.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; -import 'contact_modal.dart'; import 'share_own_qr.dart'; class AlphabetScrollPage extends StatefulWidget { @@ -80,10 +78,6 @@ class _AlphabetScrollPageState extends State { final contacts = widget.contacts; final selfContact = ContactState.of(context).selfContact; - final contactState = ContactState.of(context); - final contacts = contactState.contacts; - final selfContact = contactState.selfContact; - Map> alphabetizedContacts = {}; for (var contact in contacts) { String firstLetter = contact.displayName.isNotEmpty @@ -100,8 +94,6 @@ class _AlphabetScrollPageState extends State { return Scaffold( backgroundColor: Colors.black, body: Column( - children: [ - // Top buttons row children: [ // Top buttons row Container( @@ -123,14 +115,11 @@ class _AlphabetScrollPageState extends State { itemBuilder: (context, index) { String letter = alphabetKeys[index]; List contactsForLetter = alphabetizedContacts[letter]!; - List contactsForLetter = alphabetizedContacts[letter]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Alphabet Letter Header Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, horizontal: 16.0), padding: const EdgeInsets.symmetric( vertical: 8.0, horizontal: 16.0), child: Text( @@ -150,17 +139,12 @@ class _AlphabetScrollPageState extends State { Color avatarColor = generateColorFromName(contact.displayName); return ListTile( - leading: (contact.thumbnail != null && - contact.thumbnail!.isNotEmpty) leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) ? CircleAvatar( backgroundImage: MemoryImage(contact.thumbnail!), ) - backgroundImage: - MemoryImage(contact.thumbnail!), - ) : CircleAvatar( backgroundColor: avatarColor, child: Text( @@ -173,19 +157,6 @@ class _AlphabetScrollPageState extends State { ), title: Text(contact.displayName, style: TextStyle(color: Colors.white)), - subtitle: Text(phoneNumber, - style: TextStyle(color: Colors.white70)), - backgroundColor: avatarColor, - child: Text( - contact.displayName.isNotEmpty - ? contact.displayName[0].toUpperCase() - : '?', - style: TextStyle( - color: darken(avatarColor, 0.4)), - ), - ), - title: Text(contact.displayName, - style: TextStyle(color: Colors.white)), subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)), onTap: () { diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart index 6514500..2d7d395 100644 --- a/dialer/lib/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/features/contacts/widgets/contact_modal.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:dialer/widgets/username_color_generator.dart'; -import 'package:flutter/material.dart'; class ContactModal extends StatelessWidget { final Contact contact; -- 2.45.2