mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-16 23:54:28 +10:00
Correct unread badges for tabs; people first contacts sort option
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 => 'Филтри';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 => 'Фильтры';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 => '过滤器';
|
||||
|
||||
|
||||
+5
-4
@@ -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": "Додати контакт з буфера обміну",
|
||||
|
||||
@@ -343,6 +343,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
selectedIndex: 1,
|
||||
onDestinationSelected: (index) =>
|
||||
_handleQuickSwitch(index, context),
|
||||
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -48,6 +48,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
String _searchQuery = '';
|
||||
ContactSortOption _sortOption = ContactSortOption.lastSeen;
|
||||
bool _showUnreadOnly = false;
|
||||
bool _prioritizePeople = true;
|
||||
ContactTypeFilter _typeFilter = ContactTypeFilter.all;
|
||||
final ContactGroupStore _groupStore = ContactGroupStore();
|
||||
List<ContactGroup> _groups = [];
|
||||
@@ -332,6 +333,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
selectedIndex: 0,
|
||||
onDestinationSelected: (index) =>
|
||||
_handleQuickSwitch(index, context),
|
||||
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -350,6 +353,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
sortOption: _sortOption,
|
||||
typeFilter: _typeFilter,
|
||||
showUnreadOnly: _showUnreadOnly,
|
||||
prioritizePeople: _prioritizePeople,
|
||||
onSortChanged: (value) {
|
||||
setState(() {
|
||||
_sortOption = value;
|
||||
@@ -365,6 +369,11 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
_showUnreadOnly = value;
|
||||
});
|
||||
},
|
||||
onPrioritizePeopleChanged: (value) {
|
||||
setState(() {
|
||||
_prioritizePeople = value;
|
||||
});
|
||||
},
|
||||
onNewGroup: () => _showGroupEditor(context, connector.contacts),
|
||||
);
|
||||
}
|
||||
@@ -545,14 +554,34 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
}).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) {
|
||||
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<ContactsScreen>
|
||||
});
|
||||
break;
|
||||
case ContactSortOption.name:
|
||||
filtered.sort(
|
||||
contacts.sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
bool _matchesTypeFilter(Contact contact) {
|
||||
|
||||
@@ -71,7 +71,7 @@ class _DeviceScreenState extends State<DeviceScreen>
|
||||
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<DeviceScreen>
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -362,6 +362,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
selectedIndex: 2,
|
||||
onDestinationSelected: (index) =>
|
||||
_handleQuickSwitch(index, context),
|
||||
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
|
||||
@@ -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<ContactSortOption> onSortChanged;
|
||||
final ValueChanged<ContactTypeFilter> onTypeFilterChanged;
|
||||
final ValueChanged<bool> onUnreadOnlyChanged;
|
||||
final ValueChanged<bool> 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;
|
||||
|
||||
@@ -6,11 +6,15 @@ import '../l10n/l10n.dart';
|
||||
class QuickSwitchBar extends StatelessWidget {
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+53
-1
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user