mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
favorites handling only
This commit is contained in:
parent
a42cf77a70
commit
5a70ed48cf
27 changed files with 233 additions and 8 deletions
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
|
|
@ -30,6 +30,11 @@ jobs:
|
|||
${{ runner.os }}-gradle-
|
||||
- run: flutter pub get
|
||||
- run: flutter build apk --release --no-pub
|
||||
- name: Upload Debug APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-debug
|
||||
path: build/app/outputs/flutter-apk/app-release.apk
|
||||
|
||||
ios:
|
||||
runs-on: macos-latest
|
||||
|
|
|
|||
|
|
@ -669,6 +669,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
publicKey: contact.publicKey,
|
||||
name: contact.name,
|
||||
type: contact.type,
|
||||
flags: contact.flags,
|
||||
pathLength: selection.hopCount >= 0
|
||||
? selection.hopCount
|
||||
: contact.pathLength,
|
||||
|
|
@ -1185,11 +1186,78 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
customPath,
|
||||
pathLen,
|
||||
type: contact.type,
|
||||
flags: contact.flags,
|
||||
name: contact.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setContactFavorite(Contact contact, bool isFavorite) async {
|
||||
if (!isConnected) return;
|
||||
final latestContact =
|
||||
await _fetchContactSnapshotFromDevice(contact.publicKey) ?? contact;
|
||||
final updatedFlags = isFavorite
|
||||
? (latestContact.flags | contactFlagFavorite)
|
||||
: (latestContact.flags & ~contactFlagFavorite);
|
||||
|
||||
await sendFrame(
|
||||
buildUpdateContactPathFrame(
|
||||
latestContact.publicKey,
|
||||
latestContact.path,
|
||||
latestContact.pathLength,
|
||||
type: latestContact.type,
|
||||
flags: updatedFlags,
|
||||
name: latestContact.name,
|
||||
),
|
||||
);
|
||||
|
||||
final index = _contacts.indexWhere(
|
||||
(c) => c.publicKeyHex == contact.publicKeyHex,
|
||||
);
|
||||
if (index >= 0) {
|
||||
_contacts[index] = _contacts[index].copyWith(
|
||||
type: latestContact.type,
|
||||
name: latestContact.name,
|
||||
pathLength: latestContact.pathLength,
|
||||
path: latestContact.path,
|
||||
flags: updatedFlags,
|
||||
);
|
||||
notifyListeners();
|
||||
unawaited(_persistContacts());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Contact?> _fetchContactSnapshotFromDevice(
|
||||
Uint8List pubKey, {
|
||||
Duration timeout = const Duration(seconds: 3),
|
||||
}) async {
|
||||
if (!isConnected) return null;
|
||||
final expectedKeyHex = pubKeyToHex(pubKey);
|
||||
final completer = Completer<Contact?>();
|
||||
|
||||
void finish(Contact? result) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(result);
|
||||
}
|
||||
}
|
||||
|
||||
final subscription = receivedFrames.listen((frame) {
|
||||
if (frame.isEmpty || frame[0] != respCodeContact) return;
|
||||
final parsed = Contact.fromFrame(frame);
|
||||
if (parsed == null || parsed.publicKeyHex != expectedKeyHex) return;
|
||||
finish(parsed);
|
||||
});
|
||||
|
||||
final timer = Timer(timeout, () => finish(null));
|
||||
try {
|
||||
await getContactByKey(pubKey);
|
||||
return await completer.future;
|
||||
} finally {
|
||||
timer.cancel();
|
||||
await subscription.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set path override for a contact (persists across contact refreshes)
|
||||
/// pathLen: -1 = force flood, null = auto (use device path), >= 0 = specific path
|
||||
Future<void> setPathOverride(
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ int _minPositive(int a, int b) {
|
|||
const int contactPubKeyOffset = 1;
|
||||
const int contactTypeOffset = 33;
|
||||
const int contactFlagsOffset = 34;
|
||||
const int contactFlagFavorite = 0x01;
|
||||
const int contactPathLenOffset = 35;
|
||||
const int contactPathOffset = 36;
|
||||
const int contactNameOffset = 100;
|
||||
|
|
|
|||
|
|
@ -1343,6 +1343,7 @@
|
|||
"listFilter_az": "A-Z",
|
||||
"listFilter_filters": "Filtere",
|
||||
"listFilter_all": "Alle",
|
||||
"listFilter_favorites": "Favoriten",
|
||||
"listFilter_users": "Benutzer",
|
||||
"listFilter_repeaters": "Repeater",
|
||||
"listFilter_roomServers": "Raumserver",
|
||||
|
|
|
|||
|
|
@ -1555,6 +1555,7 @@
|
|||
"listFilter_az": "A-Z",
|
||||
"listFilter_filters": "Filters",
|
||||
"listFilter_all": "All",
|
||||
"listFilter_favorites": "Favorites",
|
||||
"listFilter_users": "Users",
|
||||
"listFilter_repeaters": "Repeaters",
|
||||
"listFilter_roomServers": "Room servers",
|
||||
|
|
@ -1779,4 +1780,4 @@
|
|||
"settings_gpxExportShareSubject": "meshcore-open GPX map data export",
|
||||
"snrIndicator_nearByRepeaters": "Nearby Repeaters",
|
||||
"snrIndicator_lastSeen": "Last seen"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4772,6 +4772,12 @@ abstract class AppLocalizations {
|
|||
/// **'All'**
|
||||
String get listFilter_all;
|
||||
|
||||
/// No description provided for @listFilter_favorites.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Favorites'**
|
||||
String get listFilter_favorites;
|
||||
|
||||
/// No description provided for @listFilter_users.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -2728,6 +2728,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Всички';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Потребители';
|
||||
|
||||
|
|
|
|||
|
|
@ -2733,6 +2733,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Alle';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favoriten';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Benutzer';
|
||||
|
||||
|
|
|
|||
|
|
@ -2686,6 +2686,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'All';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Users';
|
||||
|
||||
|
|
|
|||
|
|
@ -2726,6 +2726,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Todas';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Usuarios';
|
||||
|
||||
|
|
|
|||
|
|
@ -2742,6 +2742,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Tout';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Utilisateurs';
|
||||
|
||||
|
|
|
|||
|
|
@ -2726,6 +2726,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Tutti';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Utenti';
|
||||
|
||||
|
|
|
|||
|
|
@ -2717,6 +2717,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Alles';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Gebruikers';
|
||||
|
||||
|
|
|
|||
|
|
@ -2724,6 +2724,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Wszystko';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Użytkownicy';
|
||||
|
||||
|
|
|
|||
|
|
@ -2727,6 +2727,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Tudo';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Usuários';
|
||||
|
||||
|
|
|
|||
|
|
@ -2730,6 +2730,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Все';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Пользователи';
|
||||
|
||||
|
|
|
|||
|
|
@ -2712,6 +2712,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Všetko';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Používatelia';
|
||||
|
||||
|
|
|
|||
|
|
@ -2715,6 +2715,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Vse';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Uporabniki';
|
||||
|
||||
|
|
|
|||
|
|
@ -2700,6 +2700,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Alla';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Användare';
|
||||
|
||||
|
|
|
|||
|
|
@ -2737,6 +2737,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => 'Все';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => 'Користувачі';
|
||||
|
||||
|
|
|
|||
|
|
@ -2582,6 +2582,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get listFilter_all => '全部';
|
||||
|
||||
@override
|
||||
String get listFilter_favorites => 'Favorites';
|
||||
|
||||
@override
|
||||
String get listFilter_users => '用户';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ class Contact {
|
|||
final Uint8List publicKey;
|
||||
final String name;
|
||||
final int type;
|
||||
final int flags;
|
||||
final int pathLength; // -1 = flood, 0+ = direct hops (from device)
|
||||
final Uint8List path; // Path bytes from device
|
||||
final int?
|
||||
|
|
@ -19,6 +20,7 @@ class Contact {
|
|||
required this.publicKey,
|
||||
required this.name,
|
||||
required this.type,
|
||||
this.flags = 0,
|
||||
required this.pathLength,
|
||||
required this.path,
|
||||
this.pathOverride,
|
||||
|
|
@ -58,11 +60,13 @@ class Contact {
|
|||
}
|
||||
|
||||
bool get hasLocation => latitude != null && longitude != null;
|
||||
bool get isFavorite => (flags & contactFlagFavorite) != 0;
|
||||
|
||||
Contact copyWith({
|
||||
Uint8List? publicKey,
|
||||
String? name,
|
||||
int? type,
|
||||
int? flags,
|
||||
int? pathLength,
|
||||
Uint8List? path,
|
||||
int? pathOverride,
|
||||
|
|
@ -77,6 +81,7 @@ class Contact {
|
|||
publicKey: publicKey ?? this.publicKey,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
flags: flags ?? this.flags,
|
||||
pathLength: pathLength ?? this.pathLength,
|
||||
path: path ?? this.path,
|
||||
pathOverride: clearPathOverride
|
||||
|
|
@ -167,6 +172,7 @@ class Contact {
|
|||
data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize),
|
||||
);
|
||||
final type = data[contactTypeOffset];
|
||||
final flags = data[contactFlagsOffset];
|
||||
final pathLen = data[contactPathLenOffset].toSigned(8);
|
||||
final safePathLen = pathLen > 0
|
||||
? (pathLen > maxPathSize ? maxPathSize : pathLen)
|
||||
|
|
@ -191,6 +197,7 @@ class Contact {
|
|||
publicKey: pubKey,
|
||||
name: name.isEmpty ? 'Unknown' : name,
|
||||
type: type,
|
||||
flags: flags,
|
||||
pathLength: pathLen,
|
||||
path: pathBytes,
|
||||
latitude: lat,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import '../connector/meshcore_protocol.dart';
|
|||
import '../models/contact.dart';
|
||||
import '../models/contact_group.dart';
|
||||
import '../storage/contact_group_store.dart';
|
||||
import '../storage/contact_settings_store.dart';
|
||||
import '../utils/contact_search.dart';
|
||||
import '../utils/dialog_utils.dart';
|
||||
import '../utils/disconnect_navigation_mixin.dart';
|
||||
|
|
@ -481,6 +482,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
contact: contact,
|
||||
lastSeen: _resolveLastSeen(contact),
|
||||
unreadCount: unreadCount,
|
||||
isFavorite: contact.isFavorite,
|
||||
onTap: () => _openChat(context, contact),
|
||||
onLongPress: () =>
|
||||
_showContactOptions(context, connector, contact),
|
||||
|
|
@ -517,6 +519,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
})
|
||||
.where((group) {
|
||||
if (_typeFilter == ContactTypeFilter.all) return true;
|
||||
if (_typeFilter == ContactTypeFilter.favorites) return false;
|
||||
for (final key in group.memberKeys) {
|
||||
final contact = contactsByKey[key];
|
||||
if (contact != null && _matchesTypeFilter(contact)) return true;
|
||||
|
|
@ -591,6 +594,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
switch (_typeFilter) {
|
||||
case ContactTypeFilter.all:
|
||||
return true;
|
||||
case ContactTypeFilter.favorites:
|
||||
return contact.isFavorite;
|
||||
case ContactTypeFilter.users:
|
||||
return contact.type == advTypeChat;
|
||||
case ContactTypeFilter.repeaters:
|
||||
|
|
@ -981,6 +986,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
) {
|
||||
final isRepeater = contact.type == advTypeRepeater;
|
||||
final isRoom = contact.type == advTypeRoom;
|
||||
final isFavorite = contact.isFavorite;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
|
@ -1087,6 +1093,21 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
},
|
||||
),
|
||||
],
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
isFavorite ? Icons.star : Icons.star_border,
|
||||
color: Colors.amber[700],
|
||||
),
|
||||
title: Text(
|
||||
isFavorite
|
||||
? '${context.l10n.common_remove} ${context.l10n.listFilter_favorites}'
|
||||
: '${context.l10n.common_add} ${context.l10n.listFilter_favorites}',
|
||||
),
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
await connector.setContactFavorite(contact, !isFavorite);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: Text(context.l10n.contacts_ShareContact),
|
||||
|
|
@ -1155,6 +1176,7 @@ class _ContactTile extends StatelessWidget {
|
|||
final Contact contact;
|
||||
final DateTime lastSeen;
|
||||
final int unreadCount;
|
||||
final bool isFavorite;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onLongPress;
|
||||
|
||||
|
|
@ -1162,6 +1184,7 @@ class _ContactTile extends StatelessWidget {
|
|||
required this.contact,
|
||||
required this.lastSeen,
|
||||
required this.unreadCount,
|
||||
required this.isFavorite,
|
||||
required this.onTap,
|
||||
required this.onLongPress,
|
||||
});
|
||||
|
|
@ -1214,6 +1237,9 @@ class _ContactTile extends StatelessWidget {
|
|||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isFavorite)
|
||||
Icon(Icons.star, size: 14, color: Colors.amber[700]),
|
||||
if (isFavorite && contact.hasLocation) const SizedBox(width: 2),
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class ContactStore {
|
|||
'publicKey': base64Encode(contact.publicKey),
|
||||
'name': contact.name,
|
||||
'type': contact.type,
|
||||
'flags': contact.flags,
|
||||
'pathLength': contact.pathLength,
|
||||
'path': base64Encode(contact.path),
|
||||
'pathOverride': contact.pathOverride,
|
||||
|
|
@ -53,6 +54,7 @@ class ContactStore {
|
|||
publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)),
|
||||
name: json['name'] as String? ?? 'Unknown',
|
||||
type: json['type'] as int? ?? 0,
|
||||
flags: json['flags'] as int? ?? 0,
|
||||
pathLength: json['pathLength'] as int? ?? -1,
|
||||
path: json['path'] != null
|
||||
? Uint8List.fromList(base64Decode(json['path'] as String))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import '../l10n/l10n.dart';
|
|||
|
||||
enum ContactSortOption { lastSeen, recentMessages, name }
|
||||
|
||||
enum ContactTypeFilter { all, users, repeaters, rooms }
|
||||
enum ContactTypeFilter { all, favorites, users, repeaters, rooms }
|
||||
|
||||
class SortFilterMenuOption {
|
||||
final int value;
|
||||
|
|
@ -94,11 +94,12 @@ const int _actionSortRecentMessages = 1;
|
|||
const int _actionSortName = 2;
|
||||
const int _actionSortLastSeen = 3;
|
||||
const int _actionFilterAll = 4;
|
||||
const int _actionFilterUsers = 5;
|
||||
const int _actionFilterRepeaters = 6;
|
||||
const int _actionFilterRooms = 7;
|
||||
const int _actionToggleUnreadOnly = 8;
|
||||
const int _actionNewGroup = 9;
|
||||
const int _actionFilterFavorites = 5;
|
||||
const int _actionFilterUsers = 6;
|
||||
const int _actionFilterRepeaters = 7;
|
||||
const int _actionFilterRooms = 8;
|
||||
const int _actionToggleUnreadOnly = 9;
|
||||
const int _actionNewGroup = 10;
|
||||
|
||||
class ContactsFilterMenu extends StatelessWidget {
|
||||
final ContactSortOption sortOption;
|
||||
|
|
@ -154,6 +155,11 @@ class ContactsFilterMenu extends StatelessWidget {
|
|||
label: l10n.listFilter_all,
|
||||
checked: typeFilter == ContactTypeFilter.all,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterFavorites,
|
||||
label: l10n.listFilter_favorites,
|
||||
checked: typeFilter == ContactTypeFilter.favorites,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterUsers,
|
||||
label: l10n.listFilter_users,
|
||||
|
|
@ -198,6 +204,9 @@ class ContactsFilterMenu extends StatelessWidget {
|
|||
case _actionFilterUsers:
|
||||
onTypeFilterChanged(ContactTypeFilter.users);
|
||||
break;
|
||||
case _actionFilterFavorites:
|
||||
onTypeFilterChanged(ContactTypeFilter.favorites);
|
||||
break;
|
||||
case _actionFilterRepeaters:
|
||||
onTypeFilterChanged(ContactTypeFilter.repeaters);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import flutter_blue_plus_darwin
|
|||
import flutter_local_notifications
|
||||
import mobile_scanner
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
|
|
@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
|
|
|||
|
|
@ -1 +1,53 @@
|
|||
{}
|
||||
{
|
||||
"bg": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"sl": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"sv": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"listFilter_favorites"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"listFilter_favorites"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue