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