import '../models/contact.dart'; class InferredContactGroup { final String key; final String label; final List contacts; const InferredContactGroup({ required this.key, required this.label, required this.contacts, }); DateTime get latestSeen => contacts.first.lastSeenTime; } class ContactListItem { final Contact? contact; final InferredContactGroup? group; const ContactListItem._({this.contact, this.group}); const ContactListItem.contact(Contact contact) : this._(contact: contact); const ContactListItem.group(InferredContactGroup group) : this._(group: group); bool get isGroup => group != null; DateTime get latestSeen => group?.latestSeen ?? contact!.lastSeenTime; } class ContactGrouping { static final RegExp _prefixedNamePattern = RegExp( r'^([A-Za-z0-9]{2,})([-_/:])', ); static List sortByLastSeen(List contacts) { return List.from(contacts) ..sort((a, b) => b.lastSeenTime.compareTo(a.lastSeenTime)); } static List buildItems( List contacts, { int minGroupSize = 4, }) { final sortedContacts = sortByLastSeen(contacts); return buildItemsFromSorted(sortedContacts, minGroupSize: minGroupSize); } static List buildItemsFromSorted( List sortedContacts, { int minGroupSize = 4, }) { final groupedContacts = >{}; final groupLabels = {}; for (final contact in sortedContacts) { final prefix = _extractPrefix(contact.displayName); if (prefix == null) continue; groupedContacts.putIfAbsent(prefix.key, () => []).add(contact); groupLabels.putIfAbsent(prefix.key, () => prefix.label); } final eligibleGroups = {}; for (final entry in groupedContacts.entries) { if (entry.value.length < minGroupSize) continue; eligibleGroups[entry.key] = InferredContactGroup( key: entry.key, label: groupLabels[entry.key] ?? entry.key, contacts: entry.value, ); } final emittedGroups = {}; final items = []; for (final contact in sortedContacts) { final prefix = _extractPrefix(contact.displayName); final group = prefix == null ? null : eligibleGroups[prefix.key]; if (group == null) { items.add(ContactListItem.contact(contact)); continue; } if (emittedGroups.add(group.key)) { items.add(ContactListItem.group(group)); } } return items; } static _GroupPrefix? _extractPrefix(String name) { final trimmed = name.trim(); if (trimmed.isEmpty) return null; final match = _prefixedNamePattern.firstMatch(trimmed); if (match == null) return null; final rawPrefix = match.group(1); final separator = match.group(2); if (rawPrefix == null || separator == null) return null; return _GroupPrefix( key: rawPrefix.toUpperCase(), label: '$rawPrefix$separator', ); } } class _GroupPrefix { final String key; final String label; const _GroupPrefix({required this.key, required this.label}); }