From 41b68a00bbbec25b59944aa51beb59337ef364c7 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Fri, 28 Mar 2025 14:05:37 +0200 Subject: [PATCH] fix: search bar is non case sensitive and don't have delay --- dialer/lib/features/home/home_page.dart | 143 +++++++++++++----------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/dialer/lib/features/home/home_page.dart b/dialer/lib/features/home/home_page.dart index 65adaa8..ae333e6 100644 --- a/dialer/lib/features/home/home_page.dart +++ b/dialer/lib/features/home/home_page.dart @@ -10,53 +10,80 @@ import '../../services/contact_service.dart'; import 'package:dialer/features/voicemail/voicemail_page.dart'; import '../contacts/widgets/contact_modal.dart'; - -class _MyHomePageState extends State - with SingleTickerProviderStateMixin { +class _MyHomePageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; List _allContacts = []; List _contactSuggestions = []; final ContactService _contactService = ContactService(); final ObfuscateService _obfuscateService = ObfuscateService(); final TextEditingController _searchController = TextEditingController(); - + late SearchController _searchBarController; @override void initState() { super.initState(); - // Set the TabController length to 4 _tabController = TabController(length: 4, vsync: this, initialIndex: 1); _tabController.addListener(_handleTabIndex); + _searchBarController = SearchController(); + _searchBarController.addListener(() { + // Sync _searchController with SearchController in real-time + if (_searchController.text != _searchBarController.text) { + _searchController.text = _searchBarController.text; + _onSearchChanged(_searchBarController.text); + } + }); _fetchContacts(); } void _fetchContacts() async { _allContacts = await _contactService.fetchContacts(); - setState(() {}); + _contactSuggestions = List.from(_allContacts); + if (mounted) setState(() {}); } void _clearSearch() { _searchController.clear(); + _searchBarController.clear(); _onSearchChanged(''); } void _onSearchChanged(String query) { setState(() { if (query.isEmpty) { - _contactSuggestions = List.from(_allContacts); // Reset suggestions + _contactSuggestions = List.from(_allContacts); } else { + final normalizedQuery = _normalizeString(query.toLowerCase()); _contactSuggestions = _allContacts.where((contact) { - return contact.displayName - .toLowerCase() - .contains(query.toLowerCase()); + final normalizedName = _normalizeString(contact.displayName.toLowerCase()); + return normalizedName.contains(normalizedQuery); }).toList(); } }); } + String _normalizeString(String input) { + const accentMap = { + 'àáâãäå': 'a', + 'èéêë': 'e', + 'ìíîï': 'i', + 'òóôõö': 'o', + 'ùúûü': 'u', + 'ç': 'c', + 'ñ': 'n', + }; + String normalized = input; + accentMap.forEach((accents, base) { + for (var accent in accents.split('')) { + normalized = normalized.replaceAll(accent, base); + } + }); + return normalized; + } + @override void dispose() { _searchController.dispose(); + _searchBarController.dispose(); _tabController.removeListener(_handleTabIndex); _tabController.dispose(); super.dispose(); @@ -69,19 +96,18 @@ class _MyHomePageState extends State void _toggleFavorite(Contact contact) async { try { if (await FlutterContacts.requestPermission()) { - Contact? fullContact = await FlutterContacts.getContact(contact.id, - withProperties: true, - withAccounts: true, - withPhoto: true, - withThumbnail: true); + Contact? fullContact = await FlutterContacts.getContact( + contact.id, + withProperties: true, + withAccounts: true, + withPhoto: true, + withThumbnail: true, + ); if (fullContact != null) { fullContact.isStarred = !fullContact.isStarred; await FlutterContacts.updateContact(fullContact); - setState(() { - // Updating the contact list after toggling the favorite - _fetchContacts(); - }); + _fetchContacts(); } } else { print("Could not fetch contact details"); @@ -100,7 +126,6 @@ class _MyHomePageState extends State backgroundColor: Colors.black, body: Column( children: [ - // Persistent Search Bar Padding( padding: const EdgeInsets.only( top: 24.0, @@ -118,34 +143,32 @@ class _MyHomePageState extends State border: Border.all(color: Colors.grey.shade800, width: 1), ), child: SearchAnchor( - builder: - (BuildContext context, SearchController controller) { + searchController: _searchBarController, + builder: (BuildContext context, SearchController controller) { return GestureDetector( onTap: () { - controller.openView(); // Open the search view + controller.openView(); }, child: Container( decoration: BoxDecoration( color: const Color.fromARGB(255, 30, 30, 30), borderRadius: BorderRadius.circular(12.0), - border: Border.all( - color: Colors.grey.shade800, width: 1), + border: Border.all(color: Colors.grey.shade800, width: 1), ), - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), child: Row( children: [ - const Icon(Icons.search, - color: Colors.grey, size: 24.0), + const Icon(Icons.search, color: Colors.grey, size: 24.0), const SizedBox(width: 8.0), - Text( - _searchController.text.isEmpty - ? 'Search contacts' - : _searchController.text, - style: const TextStyle( - color: Colors.grey, fontSize: 16.0), + Expanded( + child: Text( + _searchController.text.isEmpty + ? 'Search contacts' + : _searchController.text, + style: const TextStyle(color: Colors.grey, fontSize: 16.0), + overflow: TextOverflow.ellipsis, + ), ), - const Spacer(), if (_searchController.text.isNotEmpty) GestureDetector( onTap: _clearSearch, @@ -161,23 +184,22 @@ class _MyHomePageState extends State ); }, viewOnChanged: (query) { - _onSearchChanged(query); // Update immediately + // Update immediately as you type + if (_searchBarController.text != query) { + _searchBarController.text = query; + } + _onSearchChanged(query); }, - suggestionsBuilder: - (BuildContext context, SearchController controller) { + suggestionsBuilder: (BuildContext context, SearchController controller) { return _contactSuggestions.map((contact) { return ListTile( key: ValueKey(contact.id), - title: Text(_obfuscateService.obfuscateData(contact.displayName), - style: const TextStyle(color: Colors.white)), + title: Text( + _obfuscateService.obfuscateData(contact.displayName), + style: const TextStyle(color: Colors.white), + ), onTap: () { - // Clear the search text input - controller.text = ''; - - // Close the search view controller.closeView(contact.displayName); - - // Show the ContactModal when a contact is tapped showModalBottomSheet( context: context, isScrollControlled: true, @@ -186,34 +208,28 @@ class _MyHomePageState extends State return ContactModal( contact: contact, onEdit: () async { - if (await FlutterContacts - .requestPermission()) { - final updatedContact = - await FlutterContacts - .openExternalEdit(contact.id); + if (await FlutterContacts.requestPermission()) { + final updatedContact = await FlutterContacts + .openExternalEdit(contact.id); if (updatedContact != null) { _fetchContacts(); Navigator.of(context).pop(); - ScaffoldMessenger.of(context) - .showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '${contact.displayName} updated successfully!'), ), ); } else { - ScaffoldMessenger.of(context) - .showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - 'Edit canceled or failed.'), + content: Text('Edit canceled or failed.'), ), ); } } }, - onToggleFavorite: () => - _toggleFavorite(contact), + onToggleFavorite: () => _toggleFavorite(contact), isFavorite: contact.isStarred, ); }, @@ -225,7 +241,6 @@ class _MyHomePageState extends State ), ), ), - // 3-dot menu PopupMenuButton( icon: const Icon(Icons.more_vert, color: Colors.white), itemBuilder: (BuildContext context) => [ @@ -238,8 +253,7 @@ class _MyHomePageState extends State if (value == 'settings') { Navigator.push( context, - MaterialPageRoute( - builder: (context) => const SettingsPage()), + MaterialPageRoute(builder: (context) => const SettingsPage()), ); } }, @@ -247,7 +261,6 @@ class _MyHomePageState extends State ], ), ), - // Main content with TabBarView Expanded( child: Stack( children: [ @@ -322,4 +335,4 @@ class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); -} +} \ No newline at end of file