contact-modal-delete-contact (#19)
All checks were successful
/ mirror (push) Successful in 4s
All checks were successful
/ mirror (push) Successful in 4s
Reviewed-on: #19 Co-authored-by: Florian Griffon <florian.griffon@epitech.eu> Co-committed-by: Florian Griffon <florian.griffon@epitech.eu>
This commit is contained in:
parent
e8aba933d0
commit
e05880c9d8
@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:dialer/widgets/username_color_generator.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 Contact contact;
|
||||||
final Function onEdit;
|
final Function onEdit;
|
||||||
final Function onToggleFavorite;
|
final Function onToggleFavorite;
|
||||||
@ -17,6 +18,55 @@ class ContactModal extends StatelessWidget {
|
|||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ContactModalState createState() => _ContactModalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactModalState extends State<ContactModal> {
|
||||||
|
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<void> _checkIfBlocked() async {
|
||||||
|
if (phoneNumber != 'No phone number') {
|
||||||
|
bool blocked = await BlockService().isNumberBlocked(phoneNumber);
|
||||||
|
setState(() {
|
||||||
|
isBlocked = blocked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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 {
|
void _launchPhoneDialer(String phoneNumber) async {
|
||||||
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
||||||
if (await canLaunchUrl(uri)) {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String phoneNumber = contact.phones.isNotEmpty
|
String email = widget.contact.emails.isNotEmpty
|
||||||
? contact.phones.first.number
|
? widget.contact.emails.first.address
|
||||||
: 'No phone number';
|
: 'No email';
|
||||||
String email =
|
|
||||||
contact.emails.isNotEmpty ? contact.emails.first.address : 'No email';
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
@ -59,17 +147,16 @@ class ContactModal extends StatelessWidget {
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
heightFactor: 0.7,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[900],
|
color: Colors.grey[900],
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
borderRadius:
|
||||||
|
const BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Modal Handle
|
// Modal Handle and Three-Dot Menu
|
||||||
// Top Bar with Handle and Three-Dot Menu
|
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
@ -91,31 +178,33 @@ class ContactModal extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 10, right: 10),
|
padding: const EdgeInsets.only(top: 10, right: 10),
|
||||||
child: PopupMenuButton<String>(
|
child: PopupMenuButton<String>(
|
||||||
icon: Icon(Icons.more_vert, color: Colors.white),
|
icon: const Icon(Icons.more_vert,
|
||||||
|
color: Colors.white),
|
||||||
onSelected: (String choice) {
|
onSelected: (String choice) {
|
||||||
print(
|
if (choice == 'delete') {
|
||||||
'Selected: $choice'); // Placeholder for menu actions
|
_deleteContact();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) {
|
itemBuilder: (BuildContext context) {
|
||||||
return <PopupMenuEntry<String>>[
|
return [
|
||||||
PopupMenuItem<String>(
|
const PopupMenuItem<String>(
|
||||||
value: 'show_associated_contacts',
|
value: 'show_associated_contacts',
|
||||||
child: Text('Show associated contacts'),
|
child: Text('Show associated contacts'),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
const PopupMenuItem<String>(
|
||||||
value: 'delete',
|
value: 'delete',
|
||||||
child: Text('Delete'),
|
child: Text('Delete'),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
const PopupMenuItem<String>(
|
||||||
value: 'share',
|
value: 'share',
|
||||||
child: Text('Share (via QR code)'),
|
child: Text('Share (via QR code)'),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
const PopupMenuItem<String>(
|
||||||
value: 'create_shortcut',
|
value: 'create_shortcut',
|
||||||
child:
|
child:
|
||||||
Text('Create shortcut (to home screen)'),
|
Text('Create shortcut (to home screen)'),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
const PopupMenuItem<String>(
|
||||||
value: 'set_ringtone',
|
value: 'set_ringtone',
|
||||||
child: Text('Set ringtone'),
|
child: Text('Set ringtone'),
|
||||||
),
|
),
|
||||||
@ -126,7 +215,6 @@ class ContactModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Contact Profile
|
// Contact Profile
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@ -134,88 +222,114 @@ class ContactModal extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 50,
|
radius: 50,
|
||||||
backgroundImage: (contact.thumbnail != null &&
|
backgroundImage: (widget.contact.thumbnail != null &&
|
||||||
contact.thumbnail!.isNotEmpty)
|
widget.contact.thumbnail!.isNotEmpty)
|
||||||
? MemoryImage(contact.thumbnail!)
|
? MemoryImage(widget.contact.thumbnail!)
|
||||||
: null,
|
: null,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
generateColorFromName(contact.displayName),
|
generateColorFromName(widget.contact.displayName),
|
||||||
child: (contact.thumbnail == null ||
|
child: (widget.contact.thumbnail == null ||
|
||||||
contact.thumbnail!.isEmpty)
|
widget.contact.thumbnail!.isEmpty)
|
||||||
? Text(
|
? Text(
|
||||||
contact.displayName.isNotEmpty
|
widget.contact.displayName.isNotEmpty
|
||||||
? contact.displayName[0].toUpperCase()
|
? widget.contact.displayName[0]
|
||||||
|
.toUpperCase()
|
||||||
: '?',
|
: '?',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 40, color: Colors.white),
|
fontSize: 40, color: Colors.white),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
contact.displayName,
|
widget.contact.displayName,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24, fontWeight: FontWeight.bold),
|
fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Divider(),
|
||||||
// Contact Actions
|
// Contact Actions
|
||||||
Divider(),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.phone, color: Colors.green),
|
leading: const Icon(Icons.phone, color: Colors.green),
|
||||||
title: Text(phoneNumber),
|
title: Text(phoneNumber),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (contact.phones.isNotEmpty) {
|
if (widget.contact.phones.isNotEmpty) {
|
||||||
_launchPhoneDialer(phoneNumber);
|
_launchPhoneDialer(phoneNumber);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.message, color: Colors.blue),
|
leading: const Icon(Icons.message, color: Colors.blue),
|
||||||
title: Text(phoneNumber),
|
title: Text(phoneNumber),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (contact.phones.isNotEmpty) {
|
if (widget.contact.phones.isNotEmpty) {
|
||||||
_launchSms(phoneNumber);
|
_launchSms(phoneNumber);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(Icons.email, color: Colors.orange),
|
leading: const Icon(Icons.email, color: Colors.orange),
|
||||||
title: Text(email),
|
title: Text(email),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (contact.emails.isNotEmpty) {
|
if (widget.contact.emails.isNotEmpty) {
|
||||||
_launchEmail(email);
|
_launchEmail(email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Divider(),
|
const Divider(),
|
||||||
// Favorite and Edit Buttons
|
// Favorite, Edit, and Block/Unblock Buttons
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
// Favorite button
|
||||||
onPressed: () {
|
SizedBox(
|
||||||
Navigator.of(context).pop();
|
width: double
|
||||||
onToggleFavorite();
|
.infinity, // This makes the button take full width
|
||||||
},
|
child: ElevatedButton.icon(
|
||||||
icon: Icon(contact.isStarred
|
onPressed: () {
|
||||||
? Icons.star
|
Navigator.of(context).pop();
|
||||||
: Icons.star_border),
|
widget.onToggleFavorite();
|
||||||
label: Text(
|
},
|
||||||
contact.isStarred ? 'Unfavorite' : 'Favorite'),
|
icon: Icon(widget.isFavorite
|
||||||
|
? Icons.star
|
||||||
|
: Icons.star_border),
|
||||||
|
label: Text(
|
||||||
|
widget.isFavorite ? 'Unfavorite' : 'Favorite'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
const SizedBox(height: 10), // Space between buttons
|
||||||
onPressed: () => onEdit(),
|
|
||||||
icon: Icon(Icons.edit),
|
// Edit button
|
||||||
label: Text('Edit Contact'),
|
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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
52
dialer/lib/widgets/block_service.dart
Normal file
52
dialer/lib/widgets/block_service.dart
Normal file
@ -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<void> blockNumber(String number) async {
|
||||||
|
if (number.isEmpty) return;
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> 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<void> unblockNumber(String number) async {
|
||||||
|
if (number.isEmpty) return;
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> 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<bool> isNumberBlocked(String number) async {
|
||||||
|
if (number.isEmpty) return false;
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
|
||||||
|
return blockedNumbers.contains(number);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user