From e05880c9d8bd64cce35814f9432936acec93949d Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Fri, 20 Dec 2024 09:20:17 +0000 Subject: [PATCH] contact-modal-delete-contact (#19) Reviewed-on: https://git.gmoker.com/icing/G-EIP-700-TLS-7-1-eip-stephane.corbiere/pulls/19 Co-authored-by: Florian Griffon Co-committed-by: Florian Griffon --- .../contacts/widgets/contact_modal.dart | 230 +++++++++++++----- dialer/lib/widgets/block_service.dart | 52 ++++ 2 files changed, 224 insertions(+), 58 deletions(-) create mode 100644 dialer/lib/widgets/block_service.dart diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart index 2d7d395..66cfdda 100644 --- a/dialer/lib/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/features/contacts/widgets/contact_modal.dart @@ -2,8 +2,9 @@ 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 '../../../widgets/block_service.dart'; -class ContactModal extends StatelessWidget { +class ContactModal extends StatefulWidget { final Contact contact; final Function onEdit; final Function onToggleFavorite; @@ -17,6 +18,55 @@ class ContactModal extends StatelessWidget { required this.isFavorite, }); + @override + _ContactModalState createState() => _ContactModalState(); +} + +class _ContactModalState extends State { + late String phoneNumber; + bool isBlocked = false; + + @override + void initState() { + super.initState(); + phoneNumber = widget.contact.phones.isNotEmpty + ? widget.contact.phones.first.number + : 'No phone number'; + _checkIfBlocked(); + } + + Future _checkIfBlocked() async { + if (phoneNumber != 'No phone number') { + bool blocked = await BlockService().isNumberBlocked(phoneNumber); + setState(() { + isBlocked = blocked; + }); + } + } + + Future _toggleBlockState() async { + if (phoneNumber == 'No phone number') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No phone number to block or unblock')), + ); + } else if (isBlocked) { + await BlockService().unblockNumber(phoneNumber); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$phoneNumber has been unblocked')), + ); + } else { + await BlockService().blockNumber(phoneNumber); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$phoneNumber has been blocked')), + ); + } + + if (phoneNumber != 'No phone number') { + _checkIfBlocked(); + } + Navigator.of(context).pop(); + } + void _launchPhoneDialer(String phoneNumber) async { final uri = Uri(scheme: 'tel', path: phoneNumber); if (await canLaunchUrl(uri)) { @@ -44,13 +94,51 @@ class ContactModal extends StatelessWidget { } } +void _deleteContact() async { + final bool shouldDelete = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Delete Contact'), + content: Text('Are you sure you want to delete ${widget.contact.displayName}?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Delete'), + ), + ], + ), + ); + + if (shouldDelete) { + try { + // Delete the contact + await FlutterContacts.deleteContact(widget.contact); + + // Show success message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${widget.contact.displayName} deleted')), + ); + + // Close the modal + Navigator.of(context).pop(); + } catch (e) { + // Handle errors and show a failure message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to delete ${widget.contact.displayName}: $e')), + ); + } + } +} + @override Widget build(BuildContext context) { - String phoneNumber = contact.phones.isNotEmpty - ? contact.phones.first.number - : 'No phone number'; - String email = - contact.emails.isNotEmpty ? contact.emails.first.address : 'No email'; + String email = widget.contact.emails.isNotEmpty + ? widget.contact.emails.first.address + : 'No email'; return GestureDetector( onTap: () => Navigator.of(context).pop(), @@ -59,17 +147,16 @@ class ContactModal extends StatelessWidget { child: GestureDetector( onTap: () {}, child: FractionallySizedBox( - heightFactor: 0.7, child: Container( decoration: BoxDecoration( color: Colors.grey[900], - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + borderRadius: + const BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Modal Handle - // Top Bar with Handle and Three-Dot Menu + // Modal Handle and Three-Dot Menu Stack( children: [ Align( @@ -91,31 +178,33 @@ class ContactModal extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(top: 10, right: 10), child: PopupMenuButton( - icon: Icon(Icons.more_vert, color: Colors.white), + icon: const Icon(Icons.more_vert, + color: Colors.white), onSelected: (String choice) { - print( - 'Selected: $choice'); // Placeholder for menu actions + if (choice == 'delete') { + _deleteContact(); + } }, itemBuilder: (BuildContext context) { - return >[ - PopupMenuItem( + return [ + const PopupMenuItem( value: 'show_associated_contacts', child: Text('Show associated contacts'), ), - PopupMenuItem( + const PopupMenuItem( value: 'delete', child: Text('Delete'), ), - PopupMenuItem( + const PopupMenuItem( value: 'share', child: Text('Share (via QR code)'), ), - PopupMenuItem( + const PopupMenuItem( value: 'create_shortcut', child: Text('Create shortcut (to home screen)'), ), - PopupMenuItem( + const PopupMenuItem( value: 'set_ringtone', child: Text('Set ringtone'), ), @@ -126,7 +215,6 @@ class ContactModal extends StatelessWidget { ), ], ), - // Contact Profile Padding( padding: const EdgeInsets.all(16.0), @@ -134,88 +222,114 @@ class ContactModal extends StatelessWidget { children: [ CircleAvatar( radius: 50, - backgroundImage: (contact.thumbnail != null && - contact.thumbnail!.isNotEmpty) - ? MemoryImage(contact.thumbnail!) + backgroundImage: (widget.contact.thumbnail != null && + widget.contact.thumbnail!.isNotEmpty) + ? MemoryImage(widget.contact.thumbnail!) : null, backgroundColor: - generateColorFromName(contact.displayName), - child: (contact.thumbnail == null || - contact.thumbnail!.isEmpty) + generateColorFromName(widget.contact.displayName), + child: (widget.contact.thumbnail == null || + widget.contact.thumbnail!.isEmpty) ? Text( - contact.displayName.isNotEmpty - ? contact.displayName[0].toUpperCase() + widget.contact.displayName.isNotEmpty + ? widget.contact.displayName[0] + .toUpperCase() : '?', - style: TextStyle( + style: const TextStyle( fontSize: 40, color: Colors.white), ) : null, ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( - contact.displayName, - style: TextStyle( + widget.contact.displayName, + style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold), ), ], ), ), + const Divider(), // Contact Actions - Divider(), ListTile( - leading: Icon(Icons.phone, color: Colors.green), + leading: const Icon(Icons.phone, color: Colors.green), title: Text(phoneNumber), onTap: () { - if (contact.phones.isNotEmpty) { + if (widget.contact.phones.isNotEmpty) { _launchPhoneDialer(phoneNumber); } }, ), ListTile( - leading: Icon(Icons.message, color: Colors.blue), + leading: const Icon(Icons.message, color: Colors.blue), title: Text(phoneNumber), onTap: () { - if (contact.phones.isNotEmpty) { + if (widget.contact.phones.isNotEmpty) { _launchSms(phoneNumber); } }, ), ListTile( - leading: Icon(Icons.email, color: Colors.orange), + leading: const Icon(Icons.email, color: Colors.orange), title: Text(email), onTap: () { - if (contact.emails.isNotEmpty) { + if (widget.contact.emails.isNotEmpty) { _launchEmail(email); } }, ), - Divider(), - // Favorite and Edit Buttons + const Divider(), + // Favorite, Edit, and Block/Unblock Buttons Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( children: [ - ElevatedButton.icon( - onPressed: () { - Navigator.of(context).pop(); - onToggleFavorite(); - }, - icon: Icon(contact.isStarred - ? Icons.star - : Icons.star_border), - label: Text( - contact.isStarred ? 'Unfavorite' : 'Favorite'), + // Favorite button + SizedBox( + width: double + .infinity, // This makes the button take full width + child: ElevatedButton.icon( + onPressed: () { + Navigator.of(context).pop(); + widget.onToggleFavorite(); + }, + icon: Icon(widget.isFavorite + ? Icons.star + : Icons.star_border), + label: Text( + widget.isFavorite ? 'Unfavorite' : 'Favorite'), + ), ), - ElevatedButton.icon( - onPressed: () => onEdit(), - icon: Icon(Icons.edit), - label: Text('Edit Contact'), + const SizedBox(height: 10), // Space between buttons + + // Edit button + SizedBox( + width: double + .infinity, // This makes the button take full width + child: ElevatedButton.icon( + onPressed: () => widget.onEdit(), + icon: const Icon(Icons.edit), + label: const Text('Edit Contact'), + ), + ), + const SizedBox(height: 10), // Space between buttons + + // Block/Unblock button + SizedBox( + width: double + .infinity, // This makes the button take full width + child: ElevatedButton.icon( + onPressed: _toggleBlockState, + icon: Icon( + isBlocked ? Icons.block : Icons.block_flipped), + label: Text(isBlocked ? 'Unblock' : 'Block'), + ), ), ], ), ), - SizedBox(height: 16), + + const SizedBox(height: 16), ], ), ), diff --git a/dialer/lib/widgets/block_service.dart b/dialer/lib/widgets/block_service.dart new file mode 100644 index 0000000..d8aaaf0 --- /dev/null +++ b/dialer/lib/widgets/block_service.dart @@ -0,0 +1,52 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class BlockService { + static final BlockService _instance = BlockService._internal(); + + factory BlockService() { + return _instance; + } + + BlockService._internal(); + + // Function to add a number to the blocked list + Future blockNumber(String number) async { + if (number.isEmpty) return; + + final prefs = await SharedPreferences.getInstance(); + List blockedNumbers = prefs.getStringList('blockedNumbers') ?? []; + + if (!blockedNumbers.contains(number)) { + blockedNumbers.add(number); + await prefs.setStringList('blockedNumbers', blockedNumbers); + print('$number has been blocked'); + } else { + print('$number is already blocked'); + } + } + + // Function to remove a number from the blocked list + Future unblockNumber(String number) async { + if (number.isEmpty) return; + + final prefs = await SharedPreferences.getInstance(); + List blockedNumbers = prefs.getStringList('blockedNumbers') ?? []; + + if (blockedNumbers.contains(number)) { + blockedNumbers.remove(number); + await prefs.setStringList('blockedNumbers', blockedNumbers); + print('$number has been unblocked'); + } else { + print('$number is not blocked'); + } + } + + // Check if a number is blocked + Future isNumberBlocked(String number) async { + if (number.isEmpty) return false; + + final prefs = await SharedPreferences.getInstance(); + List blockedNumbers = prefs.getStringList('blockedNumbers') ?? []; + return blockedNumbers.contains(number); + } +}