diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 2c56c373..b96aef98 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -336,15 +336,26 @@ class MeshCoreConnector extends ChangeNotifier { } int getTotalUnreadCount() { + if (!_unreadStateLoaded) return 0; + return getTotalContactsUnreadCount() + getTotalChannelsUnreadCount(); + } + + int getTotalContactsUnreadCount() { if (!_unreadStateLoaded) return 0; var total = 0; - // Count unread contact messages for (final contact in _contacts) { total += getUnreadCountForContact(contact); } - // Count unread channel messages - for (final channelIndex in _channelMessages.keys) { - total += getUnreadCountForChannelIndex(channelIndex); + return total; + } + + int getTotalChannelsUnreadCount() { + if (!_unreadStateLoaded) return 0; + var total = 0; + // Check all channels (from _channels or _cachedChannels) + final allChannels = _channels.isNotEmpty ? _channels : _cachedChannels; + for (final channel in allChannels) { + total += channel.unreadCount; } return total; } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ee5cf7d6..c91345d7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1304,6 +1304,7 @@ "listFilter_latestMessages": "Latest messages", "listFilter_heardRecently": "Heard recently", "listFilter_az": "A-Z", + "listFilter_usersFirst": "Users first", "listFilter_filters": "Filters", "listFilter_all": "All", "listFilter_users": "Users", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 055667fa..33aa4032 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4658,6 +4658,12 @@ abstract class AppLocalizations { /// **'A-Z'** String get listFilter_az; + /// No description provided for @listFilter_usersFirst. + /// + /// In en, this message translates to: + /// **'Users first'** + String get listFilter_usersFirst; + /// No description provided for @listFilter_filters. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 701429e6..e373a156 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -2662,6 +2662,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Филтри'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 514a7a19..779a5344 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2666,6 +2666,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtere'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 040d8090..29f83919 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2622,6 +2622,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filters'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e65cbcd3..7f61ee25 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2661,6 +2661,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtros'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 4496fc84..d858934e 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2678,6 +2678,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get listFilter_az => 'A à Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtres'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 02345c4e..9786262d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2661,6 +2661,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtri'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 292181fc..74f2abae 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2652,6 +2652,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filters'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 08323295..13bb1bf4 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -2660,6 +2660,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtry'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index eadea3bc..c555be3b 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2663,6 +2663,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtros'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index ec0f1ba3..6e3a9d7c 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2665,6 +2665,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get listFilter_az => 'По алфавиту'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Фильтры'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 346047b0..65e41fcd 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -2648,6 +2648,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtre'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index ed711224..c3c46aed 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -2651,6 +2651,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filtri'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 97b849fa..f8652ae2 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -2636,6 +2636,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get listFilter_az => 'A-Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => 'Filteralternativ'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 899d540d..cfb88d58 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -2672,6 +2672,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get listFilter_az => 'А-Я'; + @override + String get listFilter_usersFirst => 'Спочатку користувачі'; + @override String get listFilter_filters => 'Фільтри'; @@ -2703,7 +2706,7 @@ class AppLocalizationsUk extends AppLocalizations { String get pathTrace_notAvailable => 'Трасування шляху недоступне.'; @override - String get pathTrace_refreshTooltip => 'Оновити Path Trace'; + String get pathTrace_refreshTooltip => 'Оновити трасування шляху'; @override String get contacts_pathTrace => 'Трасування шляхів'; @@ -2744,10 +2747,10 @@ class AppLocalizationsUk extends AppLocalizations { String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати'; @override - String get contacts_zeroHopAdvert => 'Реклама без перехоплення'; + String get contacts_zeroHopAdvert => 'Оголошення без ретрансляції'; @override - String get contacts_floodAdvert => 'Залив реклами'; + String get contacts_floodAdvert => 'Оголошення з ретрансляцією'; @override String get contacts_copyAdvertToClipboard => @@ -2774,7 +2777,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contacts_contactAdvertCopied => - 'Рекламу скопійовано до буфера обміну.'; + 'Оголошення скопійовано до буфера обміну.'; @override String get contacts_contactAdvertCopyFailed => diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 77467924..d6dc2ff4 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2521,6 +2521,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get listFilter_az => 'A 到 Z'; + @override + String get listFilter_usersFirst => 'Users first'; + @override String get listFilter_filters => '过滤器'; diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 3362d409..35edeee3 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1343,6 +1343,7 @@ "listFilter_latestMessages": "Останні повідомлення", "listFilter_heardRecently": "Нещодавно чули", "listFilter_az": "А-Я", + "listFilter_usersFirst": "Спочатку користувачі", "listFilter_filters": "Фільтри", "listFilter_all": "Все", "listFilter_users": "Користувачі", @@ -1545,7 +1546,7 @@ "pathTrace_you": "Ви", "pathTrace_failed": "Відстеження шляху не вдалося.", "pathTrace_notAvailable": "Трасування шляху недоступне.", - "pathTrace_refreshTooltip": "Оновити Path Trace", + "pathTrace_refreshTooltip": "Оновити трасування шляху", "contacts_pathTrace": "Трасування шляхів", "contacts_ping": "Пінгувати", "contacts_repeaterPathTrace": "Трасування шляху до повторювача", @@ -1557,14 +1558,14 @@ "contacts_invalidAdvertFormat": "Недійсні контактні дані", "contacts_contactImported": "Контакт було імпортовано.", "contacts_contactImportFailed": "Контакт не вдалося імпортувати", - "contacts_zeroHopAdvert": "Реклама без перехоплення", - "contacts_floodAdvert": "Залив реклами", + "contacts_zeroHopAdvert": "Оголошення без ретрансляції", + "contacts_floodAdvert": "Оголошення з ретрансляцією", "contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну", "contacts_clipboardEmpty": "Буфер обміну порожній", "appSettings_languageRu": "Російська", "contacts_ShareContact": "Копіювати контакт у буфер обміну", "contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.", - "contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.", + "contacts_contactAdvertCopied": "Оголошення скопійовано до буфера обміну.", "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало", "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням", "contacts_addContactFromClipboard": "Додати контакт з буфера обміну", diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 6b8b92d4..6d8ef038 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -343,6 +343,8 @@ class _ChannelsScreenState extends State selectedIndex: 1, onDestinationSelected: (index) => _handleQuickSwitch(index, context), + contactsUnreadCount: connector.getTotalContactsUnreadCount(), + channelsUnreadCount: connector.getTotalChannelsUnreadCount(), ), ), ), diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index f04bc502..ac157356 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -48,6 +48,7 @@ class _ContactsScreenState extends State String _searchQuery = ''; ContactSortOption _sortOption = ContactSortOption.lastSeen; bool _showUnreadOnly = false; + bool _prioritizePeople = true; ContactTypeFilter _typeFilter = ContactTypeFilter.all; final ContactGroupStore _groupStore = ContactGroupStore(); List _groups = []; @@ -332,6 +333,8 @@ class _ContactsScreenState extends State selectedIndex: 0, onDestinationSelected: (index) => _handleQuickSwitch(index, context), + contactsUnreadCount: connector.getTotalContactsUnreadCount(), + channelsUnreadCount: connector.getTotalChannelsUnreadCount(), ), ), ), @@ -350,6 +353,7 @@ class _ContactsScreenState extends State sortOption: _sortOption, typeFilter: _typeFilter, showUnreadOnly: _showUnreadOnly, + prioritizePeople: _prioritizePeople, onSortChanged: (value) { setState(() { _sortOption = value; @@ -365,6 +369,11 @@ class _ContactsScreenState extends State _showUnreadOnly = value; }); }, + onPrioritizePeopleChanged: (value) { + setState(() { + _prioritizePeople = value; + }); + }, onNewGroup: () => _showGroupEditor(context, connector.contacts), ); } @@ -545,14 +554,34 @@ class _ContactsScreenState extends State }).toList(); } + // Apply sorting within groups if prioritizing people + if (_prioritizePeople) { + // Separate people (advTypeChat) from others + final people = filtered.where((c) => c.type == advTypeChat).toList(); + final others = filtered.where((c) => c.type != advTypeChat).toList(); + + // Sort each group separately + _applySorting(people, connector); + _applySorting(others, connector); + + // Combine: people first, then others + filtered = [...people, ...others]; + } else { + _applySorting(filtered, connector); + } + + return filtered; + } + + void _applySorting(List contacts, MeshCoreConnector connector) { switch (_sortOption) { case ContactSortOption.lastSeen: - filtered.sort( + contacts.sort( (a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)), ); break; case ContactSortOption.recentMessages: - filtered.sort((a, b) { + contacts.sort((a, b) { final aMessages = connector.getMessages(a); final bMessages = connector.getMessages(b); final aLastMsg = aMessages.isEmpty @@ -565,13 +594,11 @@ class _ContactsScreenState extends State }); break; case ContactSortOption.name: - filtered.sort( + contacts.sort( (a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()), ); break; } - - return filtered; } bool _matchesTypeFilter(Contact contact) { diff --git a/lib/screens/device_screen.dart b/lib/screens/device_screen.dart index c5967cf4..c4a5310b 100644 --- a/lib/screens/device_screen.dart +++ b/lib/screens/device_screen.dart @@ -71,7 +71,7 @@ class _DeviceScreenState extends State const SizedBox(height: 16), _buildSectionLabel(theme, context.l10n.device_quickSwitch), const SizedBox(height: 12), - _buildQuickSwitchBar(context), + _buildQuickSwitchBar(context, connector), ], ), ), @@ -196,12 +196,14 @@ class _DeviceScreenState extends State ); } - Widget _buildQuickSwitchBar(BuildContext context) { + Widget _buildQuickSwitchBar(BuildContext context, MeshCoreConnector connector) { return QuickSwitchBar( selectedIndex: _quickIndex, onDestinationSelected: (index) { _openQuickDestination(index, context); }, + contactsUnreadCount: connector.getTotalContactsUnreadCount(), + channelsUnreadCount: connector.getTotalChannelsUnreadCount(), ); } diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 0da99601..4253391f 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -362,6 +362,8 @@ class _MapScreenState extends State { selectedIndex: 2, onDestinationSelected: (index) => _handleQuickSwitch(index, context), + contactsUnreadCount: connector.getTotalContactsUnreadCount(), + channelsUnreadCount: connector.getTotalChannelsUnreadCount(), ), ), floatingActionButton: FloatingActionButton( diff --git a/lib/widgets/list_filter_widget.dart b/lib/widgets/list_filter_widget.dart index e9c0d9e8..6075fecb 100644 --- a/lib/widgets/list_filter_widget.dart +++ b/lib/widgets/list_filter_widget.dart @@ -99,14 +99,17 @@ const int _actionFilterRepeaters = 6; const int _actionFilterRooms = 7; const int _actionToggleUnreadOnly = 8; const int _actionNewGroup = 9; +const int _actionTogglePrioritizePeople = 10; class ContactsFilterMenu extends StatelessWidget { final ContactSortOption sortOption; final ContactTypeFilter typeFilter; final bool showUnreadOnly; + final bool prioritizePeople; final ValueChanged onSortChanged; final ValueChanged onTypeFilterChanged; final ValueChanged onUnreadOnlyChanged; + final ValueChanged onPrioritizePeopleChanged; final VoidCallback onNewGroup; const ContactsFilterMenu({ @@ -114,9 +117,11 @@ class ContactsFilterMenu extends StatelessWidget { required this.sortOption, required this.typeFilter, required this.showUnreadOnly, + required this.prioritizePeople, required this.onSortChanged, required this.onTypeFilterChanged, required this.onUnreadOnlyChanged, + required this.onPrioritizePeopleChanged, required this.onNewGroup, }); @@ -144,6 +149,11 @@ class ContactsFilterMenu extends StatelessWidget { label: l10n.listFilter_az, checked: sortOption == ContactSortOption.name, ), + SortFilterMenuOption( + value: _actionTogglePrioritizePeople, + label: l10n.listFilter_usersFirst, + checked: prioritizePeople, + ), ], ), SortFilterMenuSection( @@ -192,6 +202,9 @@ class ContactsFilterMenu extends StatelessWidget { case _actionSortLastSeen: onSortChanged(ContactSortOption.lastSeen); break; + case _actionTogglePrioritizePeople: + onPrioritizePeopleChanged(!prioritizePeople); + break; case _actionFilterAll: onTypeFilterChanged(ContactTypeFilter.all); break; diff --git a/lib/widgets/quick_switch_bar.dart b/lib/widgets/quick_switch_bar.dart index 134091ff..78874fdf 100644 --- a/lib/widgets/quick_switch_bar.dart +++ b/lib/widgets/quick_switch_bar.dart @@ -6,11 +6,15 @@ import '../l10n/l10n.dart'; class QuickSwitchBar extends StatelessWidget { final int selectedIndex; final ValueChanged onDestinationSelected; + final int contactsUnreadCount; + final int channelsUnreadCount; const QuickSwitchBar({ super.key, required this.selectedIndex, required this.onDestinationSelected, + this.contactsUnreadCount = 0, + this.channelsUnreadCount = 0, }); @override @@ -62,15 +66,30 @@ class QuickSwitchBar extends StatelessWidget { onDestinationSelected: onDestinationSelected, destinations: [ NavigationDestination( - icon: const Icon(Icons.people_outline), + icon: _buildIconWithBadge( + const Icon(Icons.people_outline), + contactsUnreadCount, + ), + selectedIcon: _buildIconWithBadge( + const Icon(Icons.people), + contactsUnreadCount, + ), label: context.l10n.nav_contacts, ), NavigationDestination( - icon: const Icon(Icons.tag), + icon: _buildIconWithBadge( + const Icon(Icons.tag), + channelsUnreadCount, + ), + selectedIcon: _buildIconWithBadge( + const Icon(Icons.tag), + channelsUnreadCount, + ), label: context.l10n.nav_channels, ), NavigationDestination( icon: const Icon(Icons.map_outlined), + selectedIcon: const Icon(Icons.map), label: context.l10n.nav_map, ), ], @@ -81,4 +100,16 @@ class QuickSwitchBar extends StatelessWidget { ), ); } + + Widget _buildIconWithBadge(Icon icon, int count) { + if (count <= 0) return icon; + + return Badge( + label: Text( + count > 99 ? '99+' : count.toString(), + style: const TextStyle(fontSize: 10), + ), + child: icon, + ); + } } diff --git a/untranslated.json b/untranslated.json index 9e26dfee..29112d61 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1 +1,53 @@ -{} \ No newline at end of file +{ + "bg": [ + "listFilter_usersFirst" + ], + + "de": [ + "listFilter_usersFirst" + ], + + "es": [ + "listFilter_usersFirst" + ], + + "fr": [ + "listFilter_usersFirst" + ], + + "it": [ + "listFilter_usersFirst" + ], + + "nl": [ + "listFilter_usersFirst" + ], + + "pl": [ + "listFilter_usersFirst" + ], + + "pt": [ + "listFilter_usersFirst" + ], + + "ru": [ + "listFilter_usersFirst" + ], + + "sk": [ + "listFilter_usersFirst" + ], + + "sl": [ + "listFilter_usersFirst" + ], + + "sv": [ + "listFilter_usersFirst" + ], + + "zh": [ + "listFilter_usersFirst" + ] +}