240 lines
8.7 KiB
Dart
240 lines
8.7 KiB
Dart
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 '../contact_state.dart';
|
|
import 'add_contact_button.dart';
|
|
import 'contact_modal.dart';
|
|
import 'share_own_qr.dart';
|
|
|
|
class AlphabetScrollPage extends StatefulWidget {
|
|
final double scrollOffset;
|
|
final List<Contact> contacts;
|
|
|
|
const AlphabetScrollPage({
|
|
super.key,
|
|
required this.scrollOffset,
|
|
required this.contacts,
|
|
});
|
|
|
|
@override
|
|
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
|
|
}
|
|
|
|
class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|
late ScrollController _scrollController;
|
|
|
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_scrollController = ScrollController(initialScrollOffset: widget.scrollOffset);
|
|
_scrollController.addListener(_onScroll);
|
|
}
|
|
|
|
void _onScroll() {
|
|
final contactState = ContactState.of(context);
|
|
contactState.setScrollOffset(_scrollController.offset);
|
|
}
|
|
|
|
Future<void> _refreshContacts() async {
|
|
final contactState = ContactState.of(context);
|
|
try {
|
|
await contactState.fetchContacts();
|
|
} catch (e) {
|
|
print('Error refreshing contacts: $e');
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Failed to refresh contacts')),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _toggleFavorite(Contact contact) async {
|
|
try {
|
|
// Check permission only once
|
|
if (!await FlutterContacts.requestPermission()) {
|
|
print("Could not get contact permission");
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Contact permission not granted')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
Contact? fullContact = await FlutterContacts.getContact(contact.id,
|
|
withProperties: true,
|
|
withAccounts: true,
|
|
withPhoto: true,
|
|
withThumbnail: true);
|
|
|
|
if (fullContact != null) {
|
|
// Toggle the favorite status
|
|
fullContact.isStarred = !fullContact.isStarred;
|
|
// Update in database
|
|
await FlutterContacts.updateContact(fullContact);
|
|
|
|
// Update the UI immediately - we need to update the ContactState
|
|
final contactState = ContactState.of(context);
|
|
await contactState.updateContactInState(fullContact);
|
|
|
|
// Show feedback to the user
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
fullContact.isStarred
|
|
? '${fullContact.displayName} added to favorites'
|
|
: '${fullContact.displayName} removed from favorites'
|
|
),
|
|
duration: Duration(seconds: 1),
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
print("Error updating favorite status: $e");
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Failed to update contact favorite status')),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final contacts = widget.contacts;
|
|
final selfContact = ContactState.of(context).selfContact;
|
|
|
|
Map<String, List<Contact>> alphabetizedContacts = {};
|
|
for (var contact in contacts) {
|
|
String firstLetter = contact.displayName.isNotEmpty
|
|
? contact.displayName[0].toUpperCase()
|
|
: '#';
|
|
if (!alphabetizedContacts.containsKey(firstLetter)) {
|
|
alphabetizedContacts[firstLetter] = [];
|
|
}
|
|
alphabetizedContacts[firstLetter]!.add(contact);
|
|
}
|
|
|
|
List<String> alphabetKeys = alphabetizedContacts.keys.toList()..sort();
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.black,
|
|
body: Column(
|
|
children: [
|
|
// Top buttons row
|
|
Container(
|
|
color: Colors.black,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
AddContactButton(),
|
|
QRCodeButton(contacts: contacts, selfContact: selfContact),
|
|
],
|
|
),
|
|
),
|
|
// Contact List
|
|
Expanded(
|
|
child: ListView.builder(
|
|
controller: _scrollController,
|
|
itemCount: alphabetKeys.length,
|
|
itemBuilder: (context, index) {
|
|
String letter = alphabetKeys[index];
|
|
List<Contact> contactsForLetter = alphabetizedContacts[letter]!;
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Alphabet Letter Header
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 8.0, horizontal: 16.0),
|
|
child: Text(
|
|
letter,
|
|
style: const TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
// Contact Entries
|
|
...contactsForLetter.map((contact) {
|
|
String phoneNumber = contact.phones.isNotEmpty
|
|
? _obfuscateService.obfuscateData(contact.phones.first.number)
|
|
: 'No phone number';
|
|
Color avatarColor =
|
|
generateColorFromName(contact.displayName);
|
|
return ListTile(
|
|
leading: ObfuscatedAvatar(
|
|
imageBytes: contact.thumbnail,
|
|
radius: 25,
|
|
backgroundColor: avatarColor,
|
|
fallbackInitial: contact.displayName,
|
|
),
|
|
title: Text(
|
|
_obfuscateService.obfuscateData(contact.displayName),
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
subtitle: Text(
|
|
phoneNumber,
|
|
style: const TextStyle(color: Colors.white70),
|
|
),
|
|
onTap: () {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (context) {
|
|
return 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!'),
|
|
),
|
|
);
|
|
} 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();
|
|
}
|
|
}
|