diff --git a/README.md b/README.md
index bad9b6c..10fb0a5 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- ✅ **Android**: Full support (API 21+)
- ✅ **iOS**: Full support (iOS 12+)
- 🚧 **Desktop**: Limited support (macOS/Linux/Windows)
+- 🚧 **Web**: Under construction (Chrome)
### Dependencies
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index e7d2b42..e0a8029 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -19,13 +19,13 @@ android {
ndkVersion = flutter.ndkVersion
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_11.toString()
+ jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
diff --git a/assets/icons/done_all.svg b/assets/icons/done_all.svg
new file mode 100644
index 0000000..bfeeec0
--- /dev/null
+++ b/assets/icons/done_all.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb
index 8609023..e9f46c6 100644
--- a/lib/l10n/app_bg.arb
+++ b/lib/l10n/app_bg.arb
@@ -1554,6 +1554,8 @@
"contacts_clipboardEmpty": "Клипборда е празна.",
"contacts_invalidAdvertFormat": "Невалидни данни за контакт",
"appSettings_languageRu": "Руски",
+ "appSettings_enableMessageTracing": "Разрешаване на проследяване на съобщения",
+ "appSettings_enableMessageTracingSubtitle": "Показване на подробни метаданни за маршрутизация и синхронизация за съобщения",
"contacts_contactImported": "Контактът е импортиран.",
"contacts_zeroHopAdvert": "Реклама без скок",
"contacts_contactImportFailed": "Контактът не е успешно импортиран.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Показване на LOS панел",
"losHidePanelTooltip": "Скриване на LOS панела",
"losElevationAttribution": "Данни за надморска височина: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index e5c82f7..bdea574 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -1554,6 +1554,8 @@
"contacts_invalidAdvertFormat": "Ungültige Kontaktdaten",
"contacts_clipboardEmpty": "Die Zwischenablage ist leer.",
"appSettings_languageUk": "Ukrainisch",
+ "appSettings_enableMessageTracing": "Nachrichtenverfolgung aktivieren",
+ "appSettings_enableMessageTracingSubtitle": "Detaillierte Routing- und Timing-Metadaten für Nachrichten anzeigen",
"contacts_contactImported": "Kontakt wurde importiert.",
"contacts_contactImportFailed": "Kontakt konnte nicht importiert werden",
"contacts_zeroHopAdvert": "Zero-Hop-Ankündigung",
@@ -1745,4 +1747,4 @@
"losShowPanelTooltip": "LOS-Panel anzeigen",
"losHidePanelTooltip": "LOS-Panel ausblenden",
"losElevationAttribution": "Höhendaten: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 67ca72e..0e96e46 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -183,6 +183,8 @@
"appSettings_languageBg": "Български",
"appSettings_languageRu": "Русский",
"appSettings_languageUk": "Українська",
+ "appSettings_enableMessageTracing": "Enable Message Tracing",
+ "appSettings_enableMessageTracingSubtitle": "Show detailed routing and timing metadata for messages",
"appSettings_notifications": "Notifications",
"appSettings_enableNotifications": "Enable Notifications",
"appSettings_enableNotificationsSubtitle": "Receive notifications for messages and adverts",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 483b4d3..99db15d 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1553,6 +1553,8 @@
"appSettings_languageUk": "Ucraniano",
"contacts_clipboardEmpty": "El portapapeles está vacío.",
"appSettings_languageRu": "Ruso",
+ "appSettings_enableMessageTracing": "Habilitar seguimiento de mensajes",
+ "appSettings_enableMessageTracingSubtitle": "Mostrar metadatos detallados de enrutamiento y tiempo para los mensajes",
"contacts_invalidAdvertFormat": "Datos de contacto no válidos",
"contacts_floodAdvert": "Anuncio de inundación",
"contacts_contactImported": "El contacto ha sido importado.",
@@ -1745,4 +1747,4 @@
"losShowPanelTooltip": "Mostrar panel LOS",
"losHidePanelTooltip": "Ocultar panel LOS",
"losElevationAttribution": "Datos de elevación: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index e162cdb..bc82195 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -262,7 +262,7 @@
}
},
"contacts_manageRepeater": "Gérer le répéteur",
- "contacts_roomLogin": "Connexion Salle",
+ "contacts_roomLogin": "Connexion Room Server",
"contacts_openChat": "Ouverture du Chat",
"contacts_editGroup": "Modifier le groupe",
"contacts_deleteGroup": "Supprimer le groupe",
@@ -798,7 +798,7 @@
"dialog_disconnect": "Déconnecter",
"dialog_disconnectConfirm": "Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?",
"login_repeaterLogin": "Connexion au répéteur",
- "login_roomLogin": "Connexion Salle",
+ "login_roomLogin": "Connexion Room Server",
"login_password": "Mot de passe",
"login_enterPassword": "Entrez votre mot de passe",
"login_savePassword": "Sauvegarder le mot de passe",
@@ -1393,7 +1393,7 @@
"settings_locationIntervalSec": "Intervalle de mise-à-jour du GPS (Secondes)",
"settings_locationIntervalInvalid": "L'intervalle doit être compris entre 60 et 86400 secondes.",
"contacts_manageRoom": "Gérer le Room Server",
- "room_management": "Administración del Servidor de Habitación",
+ "room_management": "Administrattion Room Server",
"@community_joinConfirmation": {
"placeholders": {
"name": {
@@ -1553,6 +1553,8 @@
"contacts_invalidAdvertFormat": "Données de contact non valides",
"appSettings_languageUk": "Ukrainien",
"appSettings_languageRu": "Russe",
+ "appSettings_enableMessageTracing": "Activer le traçage des messages",
+ "appSettings_enableMessageTracingSubtitle": "Afficher les métadonnées détaillées de routage et de synchronisation des messages",
"contacts_clipboardEmpty": "Le presse-papiers est vide.",
"contacts_contactImported": "Le contact a été importé.",
"contacts_floodAdvert": "Annonce à tout le réseau",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Afficher le panneau LOS",
"losHidePanelTooltip": "Masquer le panneau LOS",
"losElevationAttribution": "Données d'altitude : Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb
index 2f8d186..fe4bffc 100644
--- a/lib/l10n/app_it.arb
+++ b/lib/l10n/app_it.arb
@@ -1553,6 +1553,8 @@
"appSettings_languageRu": "Russo",
"contacts_invalidAdvertFormat": "Dati di contatto non validi",
"appSettings_languageUk": "Ucraino",
+ "appSettings_enableMessageTracing": "Abilita tracciamento messaggi",
+ "appSettings_enableMessageTracingSubtitle": "Mostra metadati dettagliati su instradamento e tempi per i messaggi",
"contacts_zeroHopAdvert": "Annuncio Zero Hop",
"contacts_floodAdvert": "Annuncio alluvionale",
"contacts_copyAdvertToClipboard": "Copia Annuncio negli Appunti",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Mostra il pannello LOS",
"losHidePanelTooltip": "Nascondi il pannello LOS",
"losElevationAttribution": "Dati di elevazione: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index e9686ce..6097f86 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -970,6 +970,18 @@ abstract class AppLocalizations {
/// **'Українська'**
String get appSettings_languageUk;
+ /// No description provided for @appSettings_enableMessageTracing.
+ ///
+ /// In en, this message translates to:
+ /// **'Enable Message Tracing'**
+ String get appSettings_enableMessageTracing;
+
+ /// No description provided for @appSettings_enableMessageTracingSubtitle.
+ ///
+ /// In en, this message translates to:
+ /// **'Show detailed routing and timing metadata for messages'**
+ String get appSettings_enableMessageTracingSubtitle;
+
/// No description provided for @appSettings_notifications.
///
/// In en, this message translates to:
diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart
index cf4bf7b..94f9f7a 100644
--- a/lib/l10n/app_localizations_bg.dart
+++ b/lib/l10n/app_localizations_bg.dart
@@ -466,6 +466,14 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get appSettings_languageUk => 'Украински';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Разрешаване на проследяване на съобщения';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Показване на подробни метаданни за маршрутизация и синхронизация за съобщения';
+
@override
String get appSettings_notifications => 'Уведомления';
diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart
index c6a07a4..ba0f5da 100644
--- a/lib/l10n/app_localizations_de.dart
+++ b/lib/l10n/app_localizations_de.dart
@@ -460,6 +460,14 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukrainisch';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Nachrichtenverfolgung aktivieren';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Detaillierte Routing- und Timing-Metadaten für Nachrichten anzeigen';
+
@override
String get appSettings_notifications => 'Benachrichtigungen';
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 254b5f4..ce5b2f0 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -458,6 +458,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get appSettings_languageUk => 'Українська';
+ @override
+ String get appSettings_enableMessageTracing => 'Enable Message Tracing';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Show detailed routing and timing metadata for messages';
+
@override
String get appSettings_notifications => 'Notifications';
diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart
index dcde365..3c7838d 100644
--- a/lib/l10n/app_localizations_es.dart
+++ b/lib/l10n/app_localizations_es.dart
@@ -463,6 +463,14 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ucraniano';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Habilitar seguimiento de mensajes';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Mostrar metadatos detallados de enrutamiento y tiempo para los mensajes';
+
@override
String get appSettings_notifications => 'Notificaciones';
diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart
index d572b8f..ee58fb4 100644
--- a/lib/l10n/app_localizations_fr.dart
+++ b/lib/l10n/app_localizations_fr.dart
@@ -464,6 +464,14 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukrainien';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Activer le traçage des messages';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Afficher les métadonnées détaillées de routage et de synchronisation des messages';
+
@override
String get appSettings_notifications => 'Notifications';
@@ -696,7 +704,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get contacts_manageRoom => 'Gérer le Room Server';
@override
- String get contacts_roomLogin => 'Connexion Salle';
+ String get contacts_roomLogin => 'Connexion Room Server';
@override
String get contacts_openChat => 'Ouverture du Chat';
@@ -1559,7 +1567,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get login_repeaterLogin => 'Connexion au répéteur';
@override
- String get login_roomLogin => 'Connexion Salle';
+ String get login_roomLogin => 'Connexion Room Server';
@override
String get login_password => 'Mot de passe';
@@ -1684,7 +1692,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_management => 'Gestion des répéteurs';
@override
- String get room_management => 'Administración del Servidor de Habitación';
+ String get room_management => 'Administrattion Room Server';
@override
String get repeater_managementTools => 'Outils de Gestion';
diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart
index d8e27f8..b3c41e7 100644
--- a/lib/l10n/app_localizations_it.dart
+++ b/lib/l10n/app_localizations_it.dart
@@ -462,6 +462,14 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ucraino';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Abilita tracciamento messaggi';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Mostra metadati dettagliati su instradamento e tempi per i messaggi';
+
@override
String get appSettings_notifications => 'Notifiche';
diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart
index 0a50e8b..0ff00ad 100644
--- a/lib/l10n/app_localizations_nl.dart
+++ b/lib/l10n/app_localizations_nl.dart
@@ -460,6 +460,13 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get appSettings_languageUk => 'Oekraïens';
+ @override
+ String get appSettings_enableMessageTracing => 'Berichttracking inschakelen';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Gedetailleerde routerings- en timing-metadata voor berichten weergeven';
+
@override
String get appSettings_notifications => 'Notificaties';
diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart
index 31dd8b5..d6c1e15 100644
--- a/lib/l10n/app_localizations_pl.dart
+++ b/lib/l10n/app_localizations_pl.dart
@@ -464,6 +464,13 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukraińska';
+ @override
+ String get appSettings_enableMessageTracing => 'Włącz śledzenie wiadomości';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Pokaż szczegółowe metadane trasowania i czasu dla wiadomości';
+
@override
String get appSettings_notifications => 'Powiadomienia';
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 5092826..a300ba9 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -464,6 +464,14 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ucraniano';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Ativar rastreamento de mensagens';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Mostrar metadados detalhados de roteamento e tempo para as mensagens';
+
@override
String get appSettings_notifications => 'Notificações';
diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart
index 570b7c8..c4c1633 100644
--- a/lib/l10n/app_localizations_ru.dart
+++ b/lib/l10n/app_localizations_ru.dart
@@ -462,6 +462,14 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get appSettings_languageUk => 'Українська';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Включить трассировку сообщений';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Показывать подробные метаданные о маршрутизации и времени для сообщений';
+
@override
String get appSettings_notifications => 'Уведомления';
diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart
index 8bbb6de..0df70a6 100644
--- a/lib/l10n/app_localizations_sk.dart
+++ b/lib/l10n/app_localizations_sk.dart
@@ -460,6 +460,13 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukrajinská';
+ @override
+ String get appSettings_enableMessageTracing => 'Povoliť sledovanie správ';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Zobraziť podrobné metadáta o smerovaní a časovaní správ';
+
@override
String get appSettings_notifications => 'Upozornenia';
diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart
index 61e3058..4be105e 100644
--- a/lib/l10n/app_localizations_sl.dart
+++ b/lib/l10n/app_localizations_sl.dart
@@ -459,6 +459,13 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukrajinsko';
+ @override
+ String get appSettings_enableMessageTracing => 'Omogoči sledenje sporočilom';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Prikaži podrobne metapodatke o usmerjanju in časovnem usklajevanju sporočil';
+
@override
String get appSettings_notifications => 'Obvestila';
diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart
index 79b30b8..52fa531 100644
--- a/lib/l10n/app_localizations_sv.dart
+++ b/lib/l10n/app_localizations_sv.dart
@@ -457,6 +457,13 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get appSettings_languageUk => 'Ukrainska';
+ @override
+ String get appSettings_enableMessageTracing => 'Aktivera meddelandespårning';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Visa detaljerade metadata om dirigering och tidsinställningar för meddelanden';
+
@override
String get appSettings_notifications => 'Meddelanden';
diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart
index f367002..4847009 100644
--- a/lib/l10n/app_localizations_uk.dart
+++ b/lib/l10n/app_localizations_uk.dart
@@ -462,6 +462,14 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get appSettings_languageUk => 'Українська';
+ @override
+ String get appSettings_enableMessageTracing =>
+ 'Увімкнути відстеження повідомлень';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle =>
+ 'Показувати детальні метадані про маршрутизацію та час для повідомлень';
+
@override
String get appSettings_notifications => 'Сповіщення';
diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart
index 7641800..454f127 100644
--- a/lib/l10n/app_localizations_zh.dart
+++ b/lib/l10n/app_localizations_zh.dart
@@ -446,6 +446,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get appSettings_languageUk => '乌克兰';
+ @override
+ String get appSettings_enableMessageTracing => '启用消息追踪';
+
+ @override
+ String get appSettings_enableMessageTracingSubtitle => '显示消息的详细路由和时间元数据';
+
@override
String get appSettings_notifications => '通知';
diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb
index 57b2fdd..2f39fdf 100644
--- a/lib/l10n/app_nl.arb
+++ b/lib/l10n/app_nl.arb
@@ -1557,6 +1557,8 @@
"contacts_floodAdvert": "Overstromingsadvertentie",
"contacts_copyAdvertToClipboard": "Advert naar klembord kopiëren",
"appSettings_languageRu": "Russisch",
+ "appSettings_enableMessageTracing": "Berichttracking inschakelen",
+ "appSettings_enableMessageTracingSubtitle": "Gedetailleerde routerings- en timing-metadata voor berichten weergeven",
"contacts_clipboardEmpty": "Knipbord is leeg.",
"contacts_addContactFromClipboard": "Contact uit klembord toevoegen",
"contacts_contactImported": "Contact is geïmporteerd.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Toon LOS-paneel",
"losHidePanelTooltip": "LOS-paneel verbergen",
"losElevationAttribution": "Hoogtegegevens: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index 3787fa7..0432f8f 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -1552,6 +1552,8 @@
"contacts_chatTraceRoute": "Śledź trasę promienia",
"appSettings_languageRu": "Rosyjski",
"appSettings_languageUk": "Ukraińska",
+ "appSettings_enableMessageTracing": "Włącz śledzenie wiadomości",
+ "appSettings_enableMessageTracingSubtitle": "Pokaż szczegółowe metadane trasowania i czasu dla wiadomości",
"contacts_contactImportFailed": "Kontakt nie został zaimportowany.",
"contacts_zeroHopAdvert": "Reklama Zero Hop",
"contacts_floodAdvert": "Reklama powodziowa",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Pokaż panel LOS",
"losHidePanelTooltip": "Ukryj panel LOS",
"losElevationAttribution": "Dane dotyczące wysokości: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 7be6694..01c5a83 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -1558,6 +1558,8 @@
"contacts_copyAdvertToClipboard": "Copiar Anúncio para Área de Transferência",
"contacts_addContactFromClipboard": "Adicionar Contato da Área de Transferência",
"appSettings_languageRu": "Russo",
+ "appSettings_enableMessageTracing": "Ativar rastreamento de mensagens",
+ "appSettings_enableMessageTracingSubtitle": "Mostrar metadados detalhados de roteamento e tempo para as mensagens",
"contacts_ShareContact": "Copiar contato para Área de Transferência",
"contacts_contactImportFailed": "Contato falhou ao ser importado.",
"contacts_zeroHopContactAdvertSent": "Enviou contato por anúncio.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Mostrar painel LOS",
"losHidePanelTooltip": "Ocultar painel LOS",
"losElevationAttribution": "Dados de elevação: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 26cfce3..b8a20d9 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -796,6 +796,8 @@
"contacts_invalidAdvertFormat": "Недействительные контактные данные",
"contacts_zeroHopAdvert": "Реклама Zero Hop",
"appSettings_languageUk": "Українська",
+ "appSettings_enableMessageTracing": "Включить трассировку сообщений",
+ "appSettings_enableMessageTracingSubtitle": "Показывать подробные метаданные о маршрутизации и времени для сообщений",
"contacts_floodAdvert": "Рекламный поток",
"contacts_clipboardEmpty": "Буфер обмена пуст.",
"contacts_copyAdvertToClipboard": "Копировать рекламу в буфер обмена",
@@ -957,4 +959,4 @@
"losShowPanelTooltip": "Показать панель LOS",
"losHidePanelTooltip": "Скрыть панель LOS",
"losElevationAttribution": "Данные о высоте: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb
index 8b2cb0a..3245282 100644
--- a/lib/l10n/app_sk.arb
+++ b/lib/l10n/app_sk.arb
@@ -1558,6 +1558,8 @@
"contacts_copyAdvertToClipboard": "Kopírovať reklamu do schránky",
"contacts_invalidAdvertFormat": "Neplatné kontaktné údaje",
"appSettings_languageRu": "Ruština",
+ "appSettings_enableMessageTracing": "Povoliť sledovanie správ",
+ "appSettings_enableMessageTracingSubtitle": "Zobraziť podrobné metadáta o smerovaní a časovaní správ",
"contacts_addContactFromClipboard": "Pridať kontakt z schránky",
"contacts_contactImported": "Kontakt bol importovaný.",
"contacts_zeroHopContactAdvertSent": "Poslal kontakt cez inzerát.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Zobraziť panel LOS",
"losHidePanelTooltip": "Skryť panel LOS",
"losElevationAttribution": "Údaje o nadmorskej výške: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb
index 4d3415d..c560c31 100644
--- a/lib/l10n/app_sl.arb
+++ b/lib/l10n/app_sl.arb
@@ -1552,6 +1552,8 @@
"contacts_pathTraceTo": "Trace route to {name}",
"appSettings_languageRu": "Ruščina",
"appSettings_languageUk": "Ukrajinsko",
+ "appSettings_enableMessageTracing": "Omogoči sledenje sporočilom",
+ "appSettings_enableMessageTracingSubtitle": "Prikaži podrobne metapodatke o usmerjanju in časovnem usklajevanju sporočil",
"contacts_contactImported": "Kontakt je bil uvožen.",
"contacts_contactImportFailed": "Kontakt ni bil uspešno uvožen.",
"contacts_zeroHopAdvert": "Reklama brez posrednikov",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Pokaži ploščo LOS",
"losHidePanelTooltip": "Skrij ploščo LOS",
"losElevationAttribution": "Podatki o višini: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb
index 8c5e399..b93c5ca 100644
--- a/lib/l10n/app_sv.arb
+++ b/lib/l10n/app_sv.arb
@@ -1558,6 +1558,8 @@
"contacts_copyAdvertToClipboard": "Kopiera annons till urklipp",
"contacts_invalidAdvertFormat": "Ogiltiga kontaktuppgifter",
"appSettings_languageUk": "Ukrainska",
+ "appSettings_enableMessageTracing": "Aktivera meddelandespårning",
+ "appSettings_enableMessageTracingSubtitle": "Visa detaljerade metadata om dirigering och tidsinställningar för meddelanden",
"contacts_addContactFromClipboard": "Lägg till kontakt från urklipp",
"contacts_contactImported": "Kontakt har importerats.",
"contacts_zeroHopContactAdvertSent": "Skickat kontakt via annons.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Visa LOS-panelen",
"losHidePanelTooltip": "Dölj LOS-panelen",
"losElevationAttribution": "Höjddata: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index 910f8b0..235e4ed 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1559,6 +1559,8 @@
"contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну",
"contacts_clipboardEmpty": "Буфер обміну порожній",
"appSettings_languageRu": "Російська",
+ "appSettings_enableMessageTracing": "Увімкнути відстеження повідомлень",
+ "appSettings_enableMessageTracingSubtitle": "Показувати детальні метадані про маршрутизацію та час для повідомлень",
"contacts_ShareContact": "Копіювати контакт у буфер обміну",
"contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.",
"contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "Показати панель LOS",
"losHidePanelTooltip": "Приховати панель LOS",
"losElevationAttribution": "Дані про висоту: Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index d9efce7..72f48ad 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -176,6 +176,8 @@
"appSettings_languageBg": "保加利亚",
"appSettings_languageRu": "俄语",
"appSettings_languageUk": "乌克兰",
+ "appSettings_enableMessageTracing": "启用消息追踪",
+ "appSettings_enableMessageTracingSubtitle": "显示消息的详细路由和时间元数据",
"appSettings_notifications": "通知",
"appSettings_enableNotifications": "启用通知",
"appSettings_enableNotificationsSubtitle": "接收消息和广告的通知",
@@ -1717,4 +1719,4 @@
"losShowPanelTooltip": "显示 LOS 面板",
"losHidePanelTooltip": "隐藏 LOS 面板",
"losElevationAttribution": "高程数据:Open-Meteo (CC BY 4.0)"
-}
+}
\ No newline at end of file
diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart
index d9504b3..62ba9ca 100644
--- a/lib/models/app_settings.dart
+++ b/lib/models/app_settings.dart
@@ -22,6 +22,7 @@ class AppSettings {
final bool mapKeyPrefixEnabled;
final String mapKeyPrefix;
final bool mapShowMarkers;
+ final bool enableMessageTracing;
final Map? mapCacheBounds;
final int mapCacheMinZoom;
final int mapCacheMaxZoom;
@@ -47,6 +48,7 @@ class AppSettings {
this.mapKeyPrefixEnabled = false,
this.mapKeyPrefix = '',
this.mapShowMarkers = true,
+ this.enableMessageTracing = false,
this.mapCacheBounds,
this.mapCacheMinZoom = 10,
this.mapCacheMaxZoom = 15,
@@ -76,6 +78,7 @@ class AppSettings {
'map_key_prefix_enabled': mapKeyPrefixEnabled,
'map_key_prefix': mapKeyPrefix,
'map_show_markers': mapShowMarkers,
+ 'enable_message_tracing': enableMessageTracing,
'map_cache_bounds': mapCacheBounds,
'map_cache_min_zoom': mapCacheMinZoom,
'map_cache_max_zoom': mapCacheMaxZoom,
@@ -112,6 +115,7 @@ class AppSettings {
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
mapKeyPrefix: json['map_key_prefix'] as String? ?? '',
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
+ enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
),
@@ -155,6 +159,7 @@ class AppSettings {
bool? mapKeyPrefixEnabled,
String? mapKeyPrefix,
bool? mapShowMarkers,
+ bool? enableMessageTracing,
Object? mapCacheBounds = _unset,
int? mapCacheMinZoom,
int? mapCacheMaxZoom,
@@ -180,6 +185,7 @@ class AppSettings {
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
mapShowMarkers: mapShowMarkers ?? this.mapShowMarkers,
+ enableMessageTracing: enableMessageTracing ?? this.enableMessageTracing,
mapCacheBounds: mapCacheBounds == _unset
? this.mapCacheBounds
: mapCacheBounds as Map?,
diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart
index b309b4d..a2c920e 100644
--- a/lib/screens/app_settings_screen.dart
+++ b/lib/screens/app_settings_screen.dart
@@ -82,6 +82,18 @@ class AppSettingsScreen extends StatelessWidget {
trailing: const Icon(Icons.chevron_right),
onTap: () => _showLanguageDialog(context, settingsService),
),
+ const Divider(height: 1),
+ SwitchListTile(
+ secondary: const Icon(Icons.location_searching),
+ title: Text(context.l10n.appSettings_enableMessageTracing),
+ subtitle: Text(
+ context.l10n.appSettings_enableMessageTracingSubtitle,
+ ),
+ value: settingsService.settings.enableMessageTracing,
+ onChanged: (value) {
+ settingsService.setEnableMessageTracing(value);
+ },
+ ),
],
),
);
diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart
index bf05110..9df91c3 100644
--- a/lib/screens/channel_chat_screen.dart
+++ b/lib/screens/channel_chat_screen.dart
@@ -17,11 +17,13 @@ import '../helpers/utf8_length_limiter.dart';
import '../l10n/l10n.dart';
import '../models/channel.dart';
import '../models/channel_message.dart';
+import '../services/app_settings_service.dart';
import '../utils/emoji_utils.dart';
import '../widgets/emoji_picker.dart';
import '../widgets/gif_message.dart';
import '../widgets/jump_to_bottom_button.dart';
import '../widgets/gif_picker.dart';
+import '../widgets/message_status_icon.dart';
import 'channel_message_path_screen.dart';
import 'map_screen.dart';
@@ -263,6 +265,8 @@ class _ChannelChatScreenState extends State {
}
Widget _buildMessageBubble(ChannelMessage message) {
+ final settingsService = context.watch();
+ final enableTracing = settingsService.settings.enableMessageTracing;
final isOutgoing = message.isOutgoing;
final gifId = _parseGifId(message.text);
final poi = _parsePoiMessage(message.text);
@@ -334,110 +338,189 @@ class _ChannelChatScreenState extends State {
const SizedBox(height: 8),
],
if (poi != null)
- _buildPoiMessage(context, poi, isOutgoing)
+ _buildPoiMessage(
+ context,
+ poi,
+ isOutgoing,
+ trailing: (!enableTracing && isOutgoing)
+ ? Padding(
+ padding: const EdgeInsets.only(bottom: 2),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ ChannelMessageStatus.sent &&
+ displayPath.isNotEmpty,
+ isFailed:
+ message.status ==
+ ChannelMessageStatus.failed,
+ ),
+ )
+ : null,
+ )
else if (gifId != null)
- ClipRRect(
- borderRadius: BorderRadius.circular(8),
- child: GifMessage(
- url:
- 'https://media.giphy.com/media/$gifId/giphy.gif',
- backgroundColor: Colors.transparent,
- fallbackTextColor: isOutgoing
- ? Theme.of(context)
- .colorScheme
- .onPrimaryContainer
- .withValues(alpha: 0.7)
- : Theme.of(context).colorScheme.onSurface
- .withValues(alpha: 0.6),
- ),
+ Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: GifMessage(
+ url:
+ 'https://media.giphy.com/media/$gifId/giphy.gif',
+ backgroundColor: Colors.transparent,
+ fallbackTextColor: isOutgoing
+ ? Theme.of(context)
+ .colorScheme
+ .onPrimaryContainer
+ .withValues(alpha: 0.7)
+ : Theme.of(context).colorScheme.onSurface
+ .withValues(alpha: 0.6),
+ ),
+ ),
+ if (!enableTracing && isOutgoing)
+ Positioned(
+ top: 0,
+ right: 0,
+ child: Container(
+ padding: const EdgeInsets.all(3),
+ decoration: BoxDecoration(
+ color: isOutgoing
+ ? Theme.of(
+ context,
+ ).colorScheme.primaryContainer
+ : Theme.of(
+ context,
+ ).colorScheme.surfaceContainerHighest,
+ borderRadius: const BorderRadius.only(
+ bottomLeft: Radius.circular(10),
+ topRight: Radius.circular(8),
+ ),
+ ),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ ChannelMessageStatus.sent &&
+ displayPath.isNotEmpty,
+ isFailed:
+ message.status ==
+ ChannelMessageStatus.failed,
+ ),
+ ),
+ ),
+ ],
)
else
- Linkify(
- text: message.text,
- style: const TextStyle(fontSize: 14),
- linkStyle: const TextStyle(
- fontSize: 14,
- color: Colors.green,
- decoration: TextDecoration.underline,
- ),
- options: const LinkifyOptions(
- humanize: false,
- defaultToHttps: false,
- ),
- linkifiers: const [UrlLinkifier()],
- onOpen: (link) =>
- LinkHandler.handleLinkTap(context, link.url),
- ),
- if (displayPath.isNotEmpty) ...[
- const SizedBox(height: 4),
- Padding(
- padding: gifId != null
- ? const EdgeInsets.symmetric(horizontal: 8)
- : EdgeInsets.zero,
- child: Text(
- 'via ${_formatPathPrefixes(displayPath)}',
- style: TextStyle(
- fontSize: 11,
- color: Colors.grey[600],
- ),
- ),
- ),
- ],
- const SizedBox(height: 4),
- Padding(
- padding: gifId != null
- ? const EdgeInsets.only(
- left: 8,
- right: 8,
- bottom: 4,
- )
- : EdgeInsets.zero,
- child: Row(
+ Row(
mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.end,
children: [
- Text(
- _formatTime(message.timestamp),
+ Flexible(
+ child: Linkify(
+ text: message.text,
+ style: const TextStyle(fontSize: 14),
+ linkStyle: const TextStyle(
+ fontSize: 14,
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ options: const LinkifyOptions(
+ humanize: false,
+ defaultToHttps: false,
+ ),
+ linkifiers: const [UrlLinkifier()],
+ onOpen: (link) => LinkHandler.handleLinkTap(
+ context,
+ link.url,
+ ),
+ ),
+ ),
+ if (!enableTracing && isOutgoing) ...[
+ const SizedBox(width: 4),
+ Padding(
+ padding: const EdgeInsets.only(bottom: 2),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ ChannelMessageStatus.sent &&
+ displayPath.isNotEmpty,
+ isFailed:
+ message.status ==
+ ChannelMessageStatus.failed,
+ ),
+ ),
+ ],
+ ],
+ ),
+ if (enableTracing) ...[
+ if (displayPath.isNotEmpty) ...[
+ const SizedBox(height: 4),
+ Padding(
+ padding: gifId != null
+ ? const EdgeInsets.symmetric(horizontal: 8)
+ : EdgeInsets.zero,
+ child: Text(
+ 'via ${_formatPathPrefixes(displayPath)}',
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
),
),
- if (message.repeatCount > 0) ...[
- const SizedBox(width: 6),
- Icon(
- Icons.repeat,
- size: 12,
- color: Colors.grey[600],
- ),
- const SizedBox(width: 2),
+ ),
+ ],
+ const SizedBox(height: 4),
+ Padding(
+ padding: gifId != null
+ ? const EdgeInsets.only(
+ left: 8,
+ right: 8,
+ bottom: 4,
+ )
+ : EdgeInsets.zero,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
Text(
- '${message.repeatCount}',
+ _formatTime(message.timestamp),
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
),
),
+ if (message.repeatCount > 0) ...[
+ const SizedBox(width: 6),
+ Icon(
+ Icons.repeat,
+ size: 12,
+ color: Colors.grey[600],
+ ),
+ const SizedBox(width: 2),
+ Text(
+ '${message.repeatCount}',
+ style: TextStyle(
+ fontSize: 11,
+ color: Colors.grey[600],
+ ),
+ ),
+ ],
+ if (isOutgoing) ...[
+ const SizedBox(width: 4),
+ Icon(
+ message.status == ChannelMessageStatus.sent
+ ? Icons.check
+ : message.status ==
+ ChannelMessageStatus.pending
+ ? Icons.schedule
+ : Icons.error_outline,
+ size: 14,
+ color:
+ message.status ==
+ ChannelMessageStatus.failed
+ ? Colors.red
+ : Colors.grey[600],
+ ),
+ ],
],
- if (isOutgoing) ...[
- const SizedBox(width: 4),
- Icon(
- message.status == ChannelMessageStatus.sent
- ? Icons.check
- : message.status ==
- ChannelMessageStatus.pending
- ? Icons.schedule
- : Icons.error_outline,
- size: 14,
- color:
- message.status ==
- ChannelMessageStatus.failed
- ? Colors.red
- : Colors.grey[600],
- ),
- ],
- ],
+ ),
),
- ),
+ ],
],
),
),
@@ -650,7 +733,12 @@ class _ChannelChatScreenState extends State {
return _PoiInfo(lat: lat, lon: lon, label: label);
}
- Widget _buildPoiMessage(BuildContext context, _PoiInfo poi, bool isOutgoing) {
+ Widget _buildPoiMessage(
+ BuildContext context,
+ _PoiInfo poi,
+ bool isOutgoing, {
+ Widget? trailing,
+ }) {
final colorScheme = Theme.of(context).colorScheme;
final textColor = isOutgoing
? colorScheme.onPrimaryContainer
@@ -696,6 +784,7 @@ class _ChannelChatScreenState extends State {
],
),
),
+ if (trailing != null) ...[const SizedBox(width: 4), trailing],
],
);
}
diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart
index 994d3e7..12e2a31 100644
--- a/lib/screens/channels_screen.dart
+++ b/lib/screens/channels_screen.dart
@@ -3,6 +3,7 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
+import 'package:meshcore_open/storage/channel_message_store.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
@@ -105,6 +106,7 @@ class _ChannelsScreenState extends State
@override
Widget build(BuildContext context) {
final connector = context.watch();
+ final channelMessageStore = ChannelMessageStore();
// Auto-navigate back to scanner if disconnected
if (!checkConnectionAndNavigate(connector)) {
@@ -304,6 +306,7 @@ class _ChannelsScreenState extends State
return _buildChannelTile(
context,
connector,
+ channelMessageStore,
channel,
showDragHandle: true,
dragIndex: index,
@@ -323,6 +326,7 @@ class _ChannelsScreenState extends State
return _buildChannelTile(
context,
connector,
+ channelMessageStore,
channel,
);
},
@@ -352,6 +356,7 @@ class _ChannelsScreenState extends State
Widget _buildChannelTile(
BuildContext context,
MeshCoreConnector connector,
+ ChannelMessageStore channelMessageStore,
Channel channel, {
bool showDragHandle = false,
int? dragIndex,
@@ -468,7 +473,12 @@ class _ChannelsScreenState extends State
);
}
},
- onLongPress: () => _showChannelActions(context, connector, channel),
+ onLongPress: () => _showChannelActions(
+ context,
+ connector,
+ channelMessageStore,
+ channel,
+ ),
),
);
}
@@ -476,6 +486,7 @@ class _ChannelsScreenState extends State
void _showChannelActions(
BuildContext context,
MeshCoreConnector connector,
+ ChannelMessageStore channelMessageStore,
Channel channel,
) {
final settingsService = context.read();
@@ -528,7 +539,12 @@ class _ChannelsScreenState extends State
Navigator.pop(context);
await Future.delayed(const Duration(milliseconds: 100));
if (context.mounted) {
- _confirmDeleteChannel(context, connector, channel);
+ _confirmDeleteChannel(
+ context,
+ connector,
+ channelMessageStore,
+ channel,
+ );
}
},
),
@@ -1474,6 +1490,7 @@ class _ChannelsScreenState extends State
void _confirmDeleteChannel(
BuildContext context,
MeshCoreConnector connector,
+ ChannelMessageStore channelMessageStore,
Channel channel,
) {
showDialog(
@@ -1492,6 +1509,7 @@ class _ChannelsScreenState extends State
onPressed: () {
Navigator.pop(dialogContext);
connector.deleteChannel(channel.index);
+ channelMessageStore.clearChannelMessages(channel.index);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index ad897a0..3556d6d 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -13,6 +13,7 @@ import 'package:latlong2/latlong.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../helpers/reaction_helper.dart';
+import '../widgets/message_status_icon.dart';
import '../helpers/chat_scroll_controller.dart';
import '../helpers/link_handler.dart';
import '../helpers/utf8_length_limiter.dart';
@@ -20,6 +21,7 @@ import '../models/channel_message.dart';
import '../models/contact.dart';
import '../models/message.dart';
import '../models/path_history.dart';
+import '../services/app_settings_service.dart';
import '../services/path_history_service.dart';
import '../widgets/elements_ui.dart';
import 'channel_message_path_screen.dart';
@@ -1172,6 +1174,8 @@ class _MessageBubble extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final settingsService = context.watch();
+ final enableTracing = settingsService.settings.enableMessageTracing;
final isOutgoing = message.isOutgoing;
final colorScheme = Theme.of(context).colorScheme;
final gifId = _parseGifId(message.text);
@@ -1249,102 +1253,175 @@ class _MessageBubble extends StatelessWidget {
if (gifId == null) const SizedBox(height: 4),
],
if (poi != null)
- _buildPoiMessage(context, poi, textColor, metaColor)
+ _buildPoiMessage(
+ context,
+ poi,
+ textColor,
+ metaColor,
+ trailing: (!enableTracing && isOutgoing)
+ ? Padding(
+ padding: const EdgeInsets.only(bottom: 2),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ MessageStatus.delivered &&
+ message.pathBytes.isNotEmpty,
+ isFailed:
+ message.status ==
+ MessageStatus.failed,
+ ),
+ )
+ : null,
+ )
else if (gifId != null)
- ClipRRect(
- borderRadius: BorderRadius.circular(12),
- child: GifMessage(
- url:
- 'https://media.giphy.com/media/$gifId/giphy.gif',
- backgroundColor: Colors.transparent,
- fallbackTextColor: textColor.withValues(
- alpha: 0.7,
+ Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(12),
+ child: GifMessage(
+ url:
+ 'https://media.giphy.com/media/$gifId/giphy.gif',
+ backgroundColor: Colors.transparent,
+ fallbackTextColor: textColor.withValues(
+ alpha: 0.7,
+ ),
+ ),
),
- ),
+ if (!enableTracing && isOutgoing)
+ Positioned(
+ top: 0,
+ right: 0,
+ child: Container(
+ padding: const EdgeInsets.all(3),
+ decoration: BoxDecoration(
+ color: bubbleColor,
+ borderRadius: const BorderRadius.only(
+ bottomLeft: Radius.circular(10),
+ topRight: Radius.circular(12),
+ ),
+ ),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ MessageStatus.delivered &&
+ message.pathBytes.isNotEmpty,
+ isFailed:
+ message.status ==
+ MessageStatus.failed,
+ ),
+ ),
+ ),
+ ],
)
else
- Linkify(
- text: messageText,
- style: TextStyle(color: textColor),
- linkStyle: const TextStyle(
- color: Colors.green,
- decoration: TextDecoration.underline,
- ),
- options: const LinkifyOptions(
- humanize: false,
- defaultToHttps: false,
- ),
- linkifiers: const [UrlLinkifier()],
- onOpen: (link) =>
- LinkHandler.handleLinkTap(context, link.url),
- ),
- if (isOutgoing && message.retryCount > 0) ...[
- const SizedBox(height: 4),
- Padding(
- padding: gifId != null
- ? const EdgeInsets.symmetric(horizontal: 8)
- : EdgeInsets.zero,
- child: Text(
- context.l10n.chat_retryCount(
- message.retryCount,
- 4,
- ),
- style: TextStyle(
- fontSize: 10,
- color: metaColor,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
- const SizedBox(height: 4),
- Padding(
- padding: gifId != null
- ? const EdgeInsets.only(
- left: 8,
- right: 8,
- bottom: 4,
- )
- : EdgeInsets.zero,
- child: Wrap(
- spacing: 4,
- crossAxisAlignment: WrapCrossAlignment.center,
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.end,
children: [
- Text(
- _formatTime(message.timestamp),
- style: TextStyle(
- fontSize: 10,
- color: metaColor,
+ Flexible(
+ child: Linkify(
+ text: messageText,
+ style: TextStyle(color: textColor),
+ linkStyle: const TextStyle(
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ options: const LinkifyOptions(
+ humanize: false,
+ defaultToHttps: false,
+ ),
+ linkifiers: const [UrlLinkifier()],
+ onOpen: (link) => LinkHandler.handleLinkTap(
+ context,
+ link.url,
+ ),
),
),
- if (isOutgoing) ...[
+ if (!enableTracing && isOutgoing) ...[
const SizedBox(width: 4),
- _buildStatusIcon(metaColor),
- ],
- if (message.tripTimeMs != null &&
- message.status ==
- MessageStatus.delivered) ...[
- const SizedBox(width: 4),
- Icon(
- Icons.speed,
- size: 10,
- color: isOutgoing
- ? metaColor
- : Colors.green[700],
- ),
- Text(
- '${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
- style: TextStyle(
- fontSize: 9,
- color: isOutgoing
- ? metaColor
- : Colors.green[700],
+ Padding(
+ padding: const EdgeInsets.only(bottom: 2),
+ child: MessageStatusIcon(
+ isAcked:
+ message.status ==
+ MessageStatus.delivered &&
+ message.pathBytes.isNotEmpty,
+ isFailed:
+ message.status == MessageStatus.failed,
),
),
],
],
),
- ),
+ if (enableTracing) ...[
+ if (isOutgoing && message.retryCount > 0) ...[
+ const SizedBox(height: 4),
+ Padding(
+ padding: gifId != null
+ ? const EdgeInsets.symmetric(horizontal: 8)
+ : EdgeInsets.zero,
+ child: Text(
+ context.l10n.chat_retryCount(
+ message.retryCount,
+ 4,
+ ),
+ style: TextStyle(
+ fontSize: 10,
+ color: metaColor,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ const SizedBox(height: 4),
+ Padding(
+ padding: gifId != null
+ ? const EdgeInsets.only(
+ left: 8,
+ right: 8,
+ bottom: 4,
+ )
+ : EdgeInsets.zero,
+ child: Wrap(
+ spacing: 4,
+ crossAxisAlignment: WrapCrossAlignment.center,
+ children: [
+ Text(
+ _formatTime(message.timestamp),
+ style: TextStyle(
+ fontSize: 10,
+ color: metaColor,
+ ),
+ ),
+ if (isOutgoing) ...[
+ const SizedBox(width: 4),
+ _buildStatusIcon(metaColor),
+ ],
+ if (message.tripTimeMs != null &&
+ message.status ==
+ MessageStatus.delivered) ...[
+ const SizedBox(width: 4),
+ Icon(
+ Icons.speed,
+ size: 10,
+ color: isOutgoing
+ ? metaColor
+ : Colors.green[700],
+ ),
+ Text(
+ '${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
+ style: TextStyle(
+ fontSize: 9,
+ color: isOutgoing
+ ? metaColor
+ : Colors.green[700],
+ ),
+ ),
+ ],
+ ],
+ ),
+ ),
+ ],
],
),
),
@@ -1387,8 +1464,9 @@ class _MessageBubble extends StatelessWidget {
BuildContext context,
_PoiInfo poi,
Color textColor,
- Color metaColor,
- ) {
+ Color metaColor, {
+ Widget? trailing,
+ }) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -1425,6 +1503,7 @@ class _MessageBubble extends StatelessWidget {
],
),
),
+ if (trailing != null) ...[const SizedBox(width: 4), trailing],
],
);
}
diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart
index e80f903..eacf26f 100644
--- a/lib/services/app_settings_service.dart
+++ b/lib/services/app_settings_service.dart
@@ -80,6 +80,10 @@ class AppSettingsService extends ChangeNotifier {
await updateSettings(_settings.copyWith(mapShowMarkers: value));
}
+ Future setEnableMessageTracing(bool value) async {
+ await updateSettings(_settings.copyWith(enableMessageTracing: value));
+ }
+
Future setMapCacheBounds(Map? value) async {
await updateSettings(_settings.copyWith(mapCacheBounds: value));
}
diff --git a/lib/widgets/message_status_icon.dart b/lib/widgets/message_status_icon.dart
new file mode 100644
index 0000000..0689f0b
--- /dev/null
+++ b/lib/widgets/message_status_icon.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class MessageStatusIcon extends StatelessWidget {
+ final bool isAcked;
+ final bool isFailed;
+ final double size;
+
+ const MessageStatusIcon({
+ super.key,
+ required this.isAcked,
+ this.isFailed = false,
+ this.size = 14,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ if (isFailed) {
+ return Icon(Icons.cancel, size: size, color: Colors.red);
+ }
+
+ final Color color;
+ if (isAcked) {
+ color = Colors.green;
+ } else {
+ color = Colors.grey;
+ }
+
+ return SvgPicture.asset(
+ 'assets/icons/done_all.svg',
+ width: size,
+ height: size,
+ colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
+ );
+ }
+}
diff --git a/macos/Podfile.lock b/macos/Podfile.lock
index 65fed26..8224cfb 100644
--- a/macos/Podfile.lock
+++ b/macos/Podfile.lock
@@ -5,10 +5,13 @@ PODS:
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- - mobile_scanner (6.0.2):
+ - mobile_scanner (7.0.0):
+ - Flutter
- FlutterMacOS
- package_info_plus (0.0.1):
- FlutterMacOS
+ - share_plus (0.0.1):
+ - FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -24,8 +27,9 @@ DEPENDENCIES:
- flutter_blue_plus_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
+ - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
+ - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
@@ -39,9 +43,11 @@ EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
mobile_scanner:
- :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
+ :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
+ share_plus:
+ :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
@@ -53,10 +59,11 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
- flutter_local_notifications: 13862b132e32eb858dea558a86d45d08daeacfe7
+ flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
- mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3
+ mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
+ share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
diff --git a/pubspec.lock b/pubspec.lock
index f695838..ed84c40 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -513,10 +513,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
+ sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
- version: "1.17.0"
+ version: "1.18.0"
mgrs_dart:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index f5ceaaf..54aab31 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -50,7 +50,7 @@ dependencies:
cached_network_image: ^3.4.1
flutter_cache_manager: ^3.4.1
flutter_foreground_task: ^9.2.0
- wakelock_plus: ^1.2.8
+ wakelock_plus: ^1.4.0
characters: ^1.4.0
package_info_plus: ^9.0.0
mobile_scanner: ^7.1.4 # QR/barcode scanning
@@ -60,6 +60,8 @@ dependencies:
gpx: ^2.3.0
path_provider: ^2.1.5
share_plus: ^12.0.1
+ web: ^1.1.1
+ flutter_svg: ^2.0.10+1
dev_dependencies:
flutter_test:
@@ -87,6 +89,7 @@ flutter:
assets:
- assets/images/
+ - assets/icons/
flutter_launcher_icons:
android: true