diff --git a/dialer/lib/presentation/common/widgets/loading_indicator.dart b/dialer/lib/presentation/common/widgets/loading_indicator.dart new file mode 100644 index 0000000..ecb22c1 --- /dev/null +++ b/dialer/lib/presentation/common/widgets/loading_indicator.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class LoadingIndicatorWidget extends StatelessWidget { + const LoadingIndicatorWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: CircularProgressIndicator()); + } +} diff --git a/dialer/lib/presentation/features/contacts/contact_page.dart b/dialer/lib/presentation/features/contacts/contact_page.dart index ad9b2b9..ead0797 100644 --- a/dialer/lib/presentation/features/contacts/contact_page.dart +++ b/dialer/lib/presentation/features/contacts/contact_page.dart @@ -1,14 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_contacts/flutter_contacts.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../contacts/contact_state.dart'; +import '../contacts/widgets/alphabet_scroll_page.dart'; +import '../../common/widgets/loading_indicator.dart'; import '../../../domain/services/obfuscate_service.dart'; -import '../../common/widgets/obfuscated_avatar.dart'; -import '../../../core/utils/color_utils.dart'; -import '../../../domain/services/call_service.dart'; -import '../../../domain/services/block_service.dart'; -import 'widgets/contact_modal.dart'; -import 'widgets/alphabet_scroll_page.dart'; class ContactPage extends StatefulWidget { const ContactPage({super.key}); @@ -19,198 +13,17 @@ class ContactPage extends StatefulWidget { class _ContactPageState extends State { final ObfuscateService _obfuscateService = ObfuscateService(); - final CallService _callService = CallService(); - int? _expandedIndex; - - Future _refreshContacts() async { - final contactState = ContactState.of(context); - try { - await contactState.fetchContacts(); - } catch (e) { - debugPrint('Error refreshing contacts: $e'); - if (mounted) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text('Failed to refresh contacts'))); - } - } - } - - void _toggleFavorite(Contact contact) async { - try { - if (await FlutterContacts.requestPermission()) { - // Avoid full contact fetch by using minimal properties - Contact? fullContact = await FlutterContacts.getContact(contact.id, - withProperties: true, - withAccounts: false, // Don't need accounts for favorite toggle - withPhoto: false, // Don't need photo for favorite toggle - withThumbnail: false // Don't need thumbnail for favorite toggle - ); - - if (fullContact != null) { - fullContact.isStarred = !fullContact.isStarred; - await FlutterContacts.updateContact(fullContact); - await _refreshContacts(); - } - } else { - debugPrint("Could not fetch contact details"); - } - } catch (e) { - debugPrint("Error updating favorite status: $e"); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to update favorite status'))); - } - } - } @override Widget build(BuildContext context) { final contactState = ContactState.of(context); - - if (contactState.loading) { - return const Center(child: CircularProgressIndicator()); - } - - final contacts = contactState.contacts; - - if (contacts.isEmpty) { - return const Center( - child: Text( - 'No contacts found.\nAdd contacts to get started.', - style: TextStyle(color: Colors.white60), - textAlign: TextAlign.center, - ), - ); - } - return Scaffold( - backgroundColor: Colors.black, - body: AlphabetScrollPage( - scrollOffset: contactState.scrollOffset, - contacts: contacts, - ), - ); - } - - Widget _getCallIcon(Contact contact) { - if (!contact.phones.isNotEmpty) return const SizedBox.shrink(); - return Icon( - contact.isStarred ? Icons.star : Icons.star_border, - color: contact.isStarred ? Colors.amber : Colors.grey, - ); - } - - Future _launchSms(Contact contact) async { - if (!contact.phones.isNotEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No phone number available')), - ); - return; - } - - final Uri smsUri = Uri( - scheme: 'sms', - path: contact.phones.first.number, - ); - - if (await canLaunchUrl(smsUri)) { - await launchUrl(smsUri); - } else { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Could not launch SMS')), - ); - } - } - - void _editContact(Contact contact) async { - if (await FlutterContacts.requestPermission()) { - final updatedContact = await FlutterContacts.openExternalEdit(contact.id); - if (updatedContact != null) { - await _refreshContacts(); - setState(() => _expandedIndex = null); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${contact.displayName} updated successfully!')), - ); - } - } - } - - void _toggleBlock(Contact contact, bool isBlocked) async { - if (!contact.phones.isNotEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No phone number to block')), - ); - return; - } - - final phoneNumber = contact.phones.first.number; - if (isBlocked) { - await BlockService().unblockNumber(phoneNumber); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('$phoneNumber unblocked')), - ); - } else { - await BlockService().blockNumber(phoneNumber); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('$phoneNumber blocked')), - ); - } - setState(() {}); - } - - void _showContactModal(Contact contact) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => ContactModal( - contact: contact, - onEdit: () async { - 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!')), - ); - } - } - }, - onToggleFavorite: () => _toggleFavorite(contact), - isFavorite: contact.isStarred, - ), - ); - } - - Widget _buildActionButton({ - required IconData icon, - required String label, - required VoidCallback onPressed, - }) { - return InkWell( - onTap: onPressed, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, color: Colors.white), - const SizedBox(height: 4), - Text( - label, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - ), + body: contactState.loading + ? const Center(child: CircularProgressIndicator()) + : AlphabetScrollPage( + scrollOffset: contactState.scrollOffset, + contacts: contactState.contacts, ), - ], - ), - ), ); } } diff --git a/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart index 822c36e..d34f3a7 100644 --- a/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart @@ -1,7 +1,8 @@ -import 'package:dialer/services/obfuscate_service.dart'; -import 'package:dialer/widgets/username_color_generator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; +import '../../../../domain/services/obfuscate_service.dart'; +import '../../../../core/utils/color_utils.dart'; +import '../../../common/widgets/obfuscated_avatar.dart'; import '../contact_state.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; @@ -23,7 +24,6 @@ class AlphabetScrollPage extends StatefulWidget { class _AlphabetScrollPageState extends State { late ScrollController _scrollController; - final ObfuscateService _obfuscateService = ObfuscateService(); @override @@ -43,7 +43,7 @@ class _AlphabetScrollPageState extends State { try { await contactState.fetchContacts(); } catch (e) { - print('Error refreshing contacts: $e'); + debugPrint('Error refreshing contacts: $e'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to refresh contacts')), ); @@ -55,9 +55,9 @@ class _AlphabetScrollPageState extends State { if (await FlutterContacts.requestPermission()) { Contact? fullContact = await FlutterContacts.getContact(contact.id, withProperties: true, - withAccounts: true, - withPhoto: true, - withThumbnail: true); + withAccounts: false, + withPhoto: false, + withThumbnail: false); if (fullContact != null) { fullContact.isStarred = !fullContact.isStarred; @@ -65,10 +65,10 @@ class _AlphabetScrollPageState extends State { } await _refreshContacts(); } else { - print("Could not fetch contact details"); + debugPrint("Could not fetch contact details"); } } catch (e) { - print("Error updating favorite status: $e"); + debugPrint("Error updating favorite status: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to update contact favorite status')), ); @@ -139,7 +139,7 @@ class _AlphabetScrollPageState extends State { ? _obfuscateService.obfuscateData(contact.phones.first.number) : 'No phone number'; Color avatarColor = - generateColorFromName(contact.displayName); + generateColorFromName(contact.displayName); return ListTile( leading: ObfuscatedAvatar( imageBytes: contact.thumbnail, @@ -166,8 +166,8 @@ class _AlphabetScrollPageState extends State { onEdit: () async { if (await FlutterContacts.requestPermission()) { final updatedContact = - await FlutterContacts.openExternalEdit( - contact.id); + await FlutterContacts.openExternalEdit( + contact.id); if (updatedContact != null) { await _refreshContacts(); Navigator.of(context).pop(); @@ -183,7 +183,7 @@ class _AlphabetScrollPageState extends State { .showSnackBar( SnackBar( content: - Text('Edit canceled or failed.'), + Text('Edit canceled or failed.'), ), ); }