Correct unread badges for tabs; people first contacts sort option

This commit is contained in:
Serge Tarkovski
2026-02-09 12:56:38 +02:00
parent c320378be1
commit 8668564464
26 changed files with 215 additions and 22 deletions
+15 -4
View File
@@ -336,15 +336,26 @@ class MeshCoreConnector extends ChangeNotifier {
} }
int getTotalUnreadCount() { int getTotalUnreadCount() {
if (!_unreadStateLoaded) return 0;
return getTotalContactsUnreadCount() + getTotalChannelsUnreadCount();
}
int getTotalContactsUnreadCount() {
if (!_unreadStateLoaded) return 0; if (!_unreadStateLoaded) return 0;
var total = 0; var total = 0;
// Count unread contact messages
for (final contact in _contacts) { for (final contact in _contacts) {
total += getUnreadCountForContact(contact); total += getUnreadCountForContact(contact);
} }
// Count unread channel messages return total;
for (final channelIndex in _channelMessages.keys) { }
total += getUnreadCountForChannelIndex(channelIndex);
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; return total;
} }
+1
View File
@@ -1304,6 +1304,7 @@
"listFilter_latestMessages": "Latest messages", "listFilter_latestMessages": "Latest messages",
"listFilter_heardRecently": "Heard recently", "listFilter_heardRecently": "Heard recently",
"listFilter_az": "A-Z", "listFilter_az": "A-Z",
"listFilter_usersFirst": "Users first",
"listFilter_filters": "Filters", "listFilter_filters": "Filters",
"listFilter_all": "All", "listFilter_all": "All",
"listFilter_users": "Users", "listFilter_users": "Users",
+6
View File
@@ -4658,6 +4658,12 @@ abstract class AppLocalizations {
/// **'A-Z'** /// **'A-Z'**
String get listFilter_az; 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. /// No description provided for @listFilter_filters.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
+3
View File
@@ -2662,6 +2662,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Филтри'; String get listFilter_filters => 'Филтри';
+3
View File
@@ -2666,6 +2666,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtere'; String get listFilter_filters => 'Filtere';
+3
View File
@@ -2622,6 +2622,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filters'; String get listFilter_filters => 'Filters';
+3
View File
@@ -2661,6 +2661,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtros'; String get listFilter_filters => 'Filtros';
+3
View File
@@ -2678,6 +2678,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get listFilter_az => 'A à Z'; String get listFilter_az => 'A à Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtres'; String get listFilter_filters => 'Filtres';
+3
View File
@@ -2661,6 +2661,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtri'; String get listFilter_filters => 'Filtri';
+3
View File
@@ -2652,6 +2652,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filters'; String get listFilter_filters => 'Filters';
+3
View File
@@ -2660,6 +2660,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtry'; String get listFilter_filters => 'Filtry';
+3
View File
@@ -2663,6 +2663,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtros'; String get listFilter_filters => 'Filtros';
+3
View File
@@ -2665,6 +2665,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get listFilter_az => 'По алфавиту'; String get listFilter_az => 'По алфавиту';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Фильтры'; String get listFilter_filters => 'Фильтры';
+3
View File
@@ -2648,6 +2648,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtre'; String get listFilter_filters => 'Filtre';
+3
View File
@@ -2651,6 +2651,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filtri'; String get listFilter_filters => 'Filtri';
+3
View File
@@ -2636,6 +2636,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get listFilter_az => 'A-Z'; String get listFilter_az => 'A-Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => 'Filteralternativ'; String get listFilter_filters => 'Filteralternativ';
+7 -4
View File
@@ -2672,6 +2672,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get listFilter_az => 'А'; String get listFilter_az => 'А';
@override
String get listFilter_usersFirst => 'Спочатку користувачі';
@override @override
String get listFilter_filters => 'Фільтри'; String get listFilter_filters => 'Фільтри';
@@ -2703,7 +2706,7 @@ class AppLocalizationsUk extends AppLocalizations {
String get pathTrace_notAvailable => 'Трасування шляху недоступне.'; String get pathTrace_notAvailable => 'Трасування шляху недоступне.';
@override @override
String get pathTrace_refreshTooltip => 'Оновити Path Trace'; String get pathTrace_refreshTooltip => 'Оновити трасування шляху';
@override @override
String get contacts_pathTrace => 'Трасування шляхів'; String get contacts_pathTrace => 'Трасування шляхів';
@@ -2744,10 +2747,10 @@ class AppLocalizationsUk extends AppLocalizations {
String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати'; String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати';
@override @override
String get contacts_zeroHopAdvert => 'Реклама без перехоплення'; String get contacts_zeroHopAdvert => 'Оголошення без ретрансляції';
@override @override
String get contacts_floodAdvert => 'Залив реклами'; String get contacts_floodAdvert => 'Оголошення з ретрансляцією';
@override @override
String get contacts_copyAdvertToClipboard => String get contacts_copyAdvertToClipboard =>
@@ -2774,7 +2777,7 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get contacts_contactAdvertCopied => String get contacts_contactAdvertCopied =>
'Рекламу скопійовано до буфера обміну.'; 'Оголошення скопійовано до буфера обміну.';
@override @override
String get contacts_contactAdvertCopyFailed => String get contacts_contactAdvertCopyFailed =>
+3
View File
@@ -2521,6 +2521,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get listFilter_az => 'A 到 Z'; String get listFilter_az => 'A 到 Z';
@override
String get listFilter_usersFirst => 'Users first';
@override @override
String get listFilter_filters => '过滤器'; String get listFilter_filters => '过滤器';
+5 -4
View File
@@ -1343,6 +1343,7 @@
"listFilter_latestMessages": "Останні повідомлення", "listFilter_latestMessages": "Останні повідомлення",
"listFilter_heardRecently": "Нещодавно чули", "listFilter_heardRecently": "Нещодавно чули",
"listFilter_az": "А-Я", "listFilter_az": "А-Я",
"listFilter_usersFirst": "Спочатку користувачі",
"listFilter_filters": "Фільтри", "listFilter_filters": "Фільтри",
"listFilter_all": "Все", "listFilter_all": "Все",
"listFilter_users": "Користувачі", "listFilter_users": "Користувачі",
@@ -1545,7 +1546,7 @@
"pathTrace_you": "Ви", "pathTrace_you": "Ви",
"pathTrace_failed": "Відстеження шляху не вдалося.", "pathTrace_failed": "Відстеження шляху не вдалося.",
"pathTrace_notAvailable": "Трасування шляху недоступне.", "pathTrace_notAvailable": "Трасування шляху недоступне.",
"pathTrace_refreshTooltip": "Оновити Path Trace", "pathTrace_refreshTooltip": "Оновити трасування шляху",
"contacts_pathTrace": "Трасування шляхів", "contacts_pathTrace": "Трасування шляхів",
"contacts_ping": "Пінгувати", "contacts_ping": "Пінгувати",
"contacts_repeaterPathTrace": "Трасування шляху до повторювача", "contacts_repeaterPathTrace": "Трасування шляху до повторювача",
@@ -1557,14 +1558,14 @@
"contacts_invalidAdvertFormat": "Недійсні контактні дані", "contacts_invalidAdvertFormat": "Недійсні контактні дані",
"contacts_contactImported": "Контакт було імпортовано.", "contacts_contactImported": "Контакт було імпортовано.",
"contacts_contactImportFailed": "Контакт не вдалося імпортувати", "contacts_contactImportFailed": "Контакт не вдалося імпортувати",
"contacts_zeroHopAdvert": "Реклама без перехоплення", "contacts_zeroHopAdvert": "Оголошення без ретрансляції",
"contacts_floodAdvert": "Залив реклами", "contacts_floodAdvert": "Оголошення з ретрансляцією",
"contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну", "contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну",
"contacts_clipboardEmpty": "Буфер обміну порожній", "contacts_clipboardEmpty": "Буфер обміну порожній",
"appSettings_languageRu": "Російська", "appSettings_languageRu": "Російська",
"contacts_ShareContact": "Копіювати контакт у буфер обміну", "contacts_ShareContact": "Копіювати контакт у буфер обміну",
"contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.", "contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.",
"contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.", "contacts_contactAdvertCopied": "Оголошення скопійовано до буфера обміну.",
"contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало", "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало",
"contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням", "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
"contacts_addContactFromClipboard": "Додати контакт з буфера обміну", "contacts_addContactFromClipboard": "Додати контакт з буфера обміну",
+2
View File
@@ -343,6 +343,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
selectedIndex: 1, selectedIndex: 1,
onDestinationSelected: (index) => onDestinationSelected: (index) =>
_handleQuickSwitch(index, context), _handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
), ),
), ),
), ),
+32 -5
View File
@@ -48,6 +48,7 @@ class _ContactsScreenState extends State<ContactsScreen>
String _searchQuery = ''; String _searchQuery = '';
ContactSortOption _sortOption = ContactSortOption.lastSeen; ContactSortOption _sortOption = ContactSortOption.lastSeen;
bool _showUnreadOnly = false; bool _showUnreadOnly = false;
bool _prioritizePeople = true;
ContactTypeFilter _typeFilter = ContactTypeFilter.all; ContactTypeFilter _typeFilter = ContactTypeFilter.all;
final ContactGroupStore _groupStore = ContactGroupStore(); final ContactGroupStore _groupStore = ContactGroupStore();
List<ContactGroup> _groups = []; List<ContactGroup> _groups = [];
@@ -332,6 +333,8 @@ class _ContactsScreenState extends State<ContactsScreen>
selectedIndex: 0, selectedIndex: 0,
onDestinationSelected: (index) => onDestinationSelected: (index) =>
_handleQuickSwitch(index, context), _handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
), ),
), ),
), ),
@@ -350,6 +353,7 @@ class _ContactsScreenState extends State<ContactsScreen>
sortOption: _sortOption, sortOption: _sortOption,
typeFilter: _typeFilter, typeFilter: _typeFilter,
showUnreadOnly: _showUnreadOnly, showUnreadOnly: _showUnreadOnly,
prioritizePeople: _prioritizePeople,
onSortChanged: (value) { onSortChanged: (value) {
setState(() { setState(() {
_sortOption = value; _sortOption = value;
@@ -365,6 +369,11 @@ class _ContactsScreenState extends State<ContactsScreen>
_showUnreadOnly = value; _showUnreadOnly = value;
}); });
}, },
onPrioritizePeopleChanged: (value) {
setState(() {
_prioritizePeople = value;
});
},
onNewGroup: () => _showGroupEditor(context, connector.contacts), onNewGroup: () => _showGroupEditor(context, connector.contacts),
); );
} }
@@ -545,14 +554,34 @@ class _ContactsScreenState extends State<ContactsScreen>
}).toList(); }).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<Contact> contacts, MeshCoreConnector connector) {
switch (_sortOption) { switch (_sortOption) {
case ContactSortOption.lastSeen: case ContactSortOption.lastSeen:
filtered.sort( contacts.sort(
(a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)), (a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)),
); );
break; break;
case ContactSortOption.recentMessages: case ContactSortOption.recentMessages:
filtered.sort((a, b) { contacts.sort((a, b) {
final aMessages = connector.getMessages(a); final aMessages = connector.getMessages(a);
final bMessages = connector.getMessages(b); final bMessages = connector.getMessages(b);
final aLastMsg = aMessages.isEmpty final aLastMsg = aMessages.isEmpty
@@ -565,13 +594,11 @@ class _ContactsScreenState extends State<ContactsScreen>
}); });
break; break;
case ContactSortOption.name: case ContactSortOption.name:
filtered.sort( contacts.sort(
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()), (a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
); );
break; break;
} }
return filtered;
} }
bool _matchesTypeFilter(Contact contact) { bool _matchesTypeFilter(Contact contact) {
+4 -2
View File
@@ -71,7 +71,7 @@ class _DeviceScreenState extends State<DeviceScreen>
const SizedBox(height: 16), const SizedBox(height: 16),
_buildSectionLabel(theme, context.l10n.device_quickSwitch), _buildSectionLabel(theme, context.l10n.device_quickSwitch),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildQuickSwitchBar(context), _buildQuickSwitchBar(context, connector),
], ],
), ),
), ),
@@ -196,12 +196,14 @@ class _DeviceScreenState extends State<DeviceScreen>
); );
} }
Widget _buildQuickSwitchBar(BuildContext context) { Widget _buildQuickSwitchBar(BuildContext context, MeshCoreConnector connector) {
return QuickSwitchBar( return QuickSwitchBar(
selectedIndex: _quickIndex, selectedIndex: _quickIndex,
onDestinationSelected: (index) { onDestinationSelected: (index) {
_openQuickDestination(index, context); _openQuickDestination(index, context);
}, },
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
); );
} }
+2
View File
@@ -362,6 +362,8 @@ class _MapScreenState extends State<MapScreen> {
selectedIndex: 2, selectedIndex: 2,
onDestinationSelected: (index) => onDestinationSelected: (index) =>
_handleQuickSwitch(index, context), _handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
), ),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
+13
View File
@@ -99,14 +99,17 @@ const int _actionFilterRepeaters = 6;
const int _actionFilterRooms = 7; const int _actionFilterRooms = 7;
const int _actionToggleUnreadOnly = 8; const int _actionToggleUnreadOnly = 8;
const int _actionNewGroup = 9; const int _actionNewGroup = 9;
const int _actionTogglePrioritizePeople = 10;
class ContactsFilterMenu extends StatelessWidget { class ContactsFilterMenu extends StatelessWidget {
final ContactSortOption sortOption; final ContactSortOption sortOption;
final ContactTypeFilter typeFilter; final ContactTypeFilter typeFilter;
final bool showUnreadOnly; final bool showUnreadOnly;
final bool prioritizePeople;
final ValueChanged<ContactSortOption> onSortChanged; final ValueChanged<ContactSortOption> onSortChanged;
final ValueChanged<ContactTypeFilter> onTypeFilterChanged; final ValueChanged<ContactTypeFilter> onTypeFilterChanged;
final ValueChanged<bool> onUnreadOnlyChanged; final ValueChanged<bool> onUnreadOnlyChanged;
final ValueChanged<bool> onPrioritizePeopleChanged;
final VoidCallback onNewGroup; final VoidCallback onNewGroup;
const ContactsFilterMenu({ const ContactsFilterMenu({
@@ -114,9 +117,11 @@ class ContactsFilterMenu extends StatelessWidget {
required this.sortOption, required this.sortOption,
required this.typeFilter, required this.typeFilter,
required this.showUnreadOnly, required this.showUnreadOnly,
required this.prioritizePeople,
required this.onSortChanged, required this.onSortChanged,
required this.onTypeFilterChanged, required this.onTypeFilterChanged,
required this.onUnreadOnlyChanged, required this.onUnreadOnlyChanged,
required this.onPrioritizePeopleChanged,
required this.onNewGroup, required this.onNewGroup,
}); });
@@ -144,6 +149,11 @@ class ContactsFilterMenu extends StatelessWidget {
label: l10n.listFilter_az, label: l10n.listFilter_az,
checked: sortOption == ContactSortOption.name, checked: sortOption == ContactSortOption.name,
), ),
SortFilterMenuOption(
value: _actionTogglePrioritizePeople,
label: l10n.listFilter_usersFirst,
checked: prioritizePeople,
),
], ],
), ),
SortFilterMenuSection( SortFilterMenuSection(
@@ -192,6 +202,9 @@ class ContactsFilterMenu extends StatelessWidget {
case _actionSortLastSeen: case _actionSortLastSeen:
onSortChanged(ContactSortOption.lastSeen); onSortChanged(ContactSortOption.lastSeen);
break; break;
case _actionTogglePrioritizePeople:
onPrioritizePeopleChanged(!prioritizePeople);
break;
case _actionFilterAll: case _actionFilterAll:
onTypeFilterChanged(ContactTypeFilter.all); onTypeFilterChanged(ContactTypeFilter.all);
break; break;
+33 -2
View File
@@ -6,11 +6,15 @@ import '../l10n/l10n.dart';
class QuickSwitchBar extends StatelessWidget { class QuickSwitchBar extends StatelessWidget {
final int selectedIndex; final int selectedIndex;
final ValueChanged<int> onDestinationSelected; final ValueChanged<int> onDestinationSelected;
final int contactsUnreadCount;
final int channelsUnreadCount;
const QuickSwitchBar({ const QuickSwitchBar({
super.key, super.key,
required this.selectedIndex, required this.selectedIndex,
required this.onDestinationSelected, required this.onDestinationSelected,
this.contactsUnreadCount = 0,
this.channelsUnreadCount = 0,
}); });
@override @override
@@ -62,15 +66,30 @@ class QuickSwitchBar extends StatelessWidget {
onDestinationSelected: onDestinationSelected, onDestinationSelected: onDestinationSelected,
destinations: [ destinations: [
NavigationDestination( 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, label: context.l10n.nav_contacts,
), ),
NavigationDestination( 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, label: context.l10n.nav_channels,
), ),
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.map_outlined), icon: const Icon(Icons.map_outlined),
selectedIcon: const Icon(Icons.map),
label: context.l10n.nav_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,
);
}
} }
+53 -1
View File
@@ -1 +1,53 @@
{} {
"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"
]
}