From 4bf2519559d29d9af85fc90afe69e858f488c045 Mon Sep 17 00:00:00 2001 From: 446564 Date: Thu, 19 Feb 2026 11:46:57 -0800 Subject: [PATCH 01/12] clear app db of channel messages on delete we were only deleting channels and messages on device and the in app db would persist this caused weird messages to later show up in other channels as they were deleted and added due to the fact we store messages by channel index(slot #) --- lib/screens/channels_screen.dart | 22 ++++++++++++++++++++-- pubspec.lock | 20 ++++++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 6b8b92d..12dc534 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:provider/provider.dart'; import 'package:uuid/uuid.dart'; @@ -104,6 +105,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, ) { showModalBottomSheet( @@ -505,7 +516,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, + ); } }, ), @@ -1451,6 +1467,7 @@ class _ChannelsScreenState extends State void _confirmDeleteChannel( BuildContext context, MeshCoreConnector connector, + ChannelMessageStore channelMessageStore, Channel channel, ) { showDialog( @@ -1469,6 +1486,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/pubspec.lock b/pubspec.lock index 09e9301..ed84c40 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -497,26 +497,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: 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: @@ -910,10 +910,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" timezone: dependency: transitive description: From 8fe412920453307572275c638c4020a152c874fc Mon Sep 17 00:00:00 2001 From: Specter242 Date: Sat, 21 Feb 2026 21:01:57 -0500 Subject: [PATCH 02/12] Align Android app module to Java 17 and bump wakelock_plus --- android/app/build.gradle.kts | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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/pubspec.yaml b/pubspec.yaml index f5ceaaf..3624b93 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 From 230626938448e6e950312161226d10e65fb1131a Mon Sep 17 00:00:00 2001 From: spfmoby <40357319+spfmoby@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:20:55 +0100 Subject: [PATCH 03/12] Better french translations --- lib/l10n/app_fr.arb | 6 +++--- lib/l10n/app_localizations_fr.dart | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e162cdb..2d4846c 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": { diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d572b8f..c4e1e27 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -696,7 +696,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 +1559,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 +1684,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'; From 7288f11c88299234c394ca490d6fbc1f40ab96b0 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sun, 22 Feb 2026 06:49:14 -0800 Subject: [PATCH 04/12] add chrome in planning --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bad9b6c..da92d47 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**: Limited support (Chrome) ### Dependencies From c7b33f1d1b1db1e1babf13192edc72671ae12b61 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sun, 22 Feb 2026 06:51:40 -0800 Subject: [PATCH 05/12] readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da92d47..10fb0a5 100644 --- a/README.md +++ b/README.md @@ -78,7 +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**: Limited support (Chrome) +- 🚧 **Web**: Under construction (Chrome) ### Dependencies From bf4f52a4e30d2c585361c2865bdecc4c82e5b4d0 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sun, 22 Feb 2026 11:27:06 -0800 Subject: [PATCH 06/12] hide message tracing --- lib/l10n/app_bg.arb | 4 +- lib/l10n/app_de.arb | 4 +- lib/l10n/app_en.arb | 2 + lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/l10n/app_it.arb | 4 +- lib/l10n/app_localizations.dart | 12 ++ lib/l10n/app_localizations_bg.dart | 8 + lib/l10n/app_localizations_de.dart | 8 + lib/l10n/app_localizations_en.dart | 7 + lib/l10n/app_localizations_es.dart | 8 + lib/l10n/app_localizations_fr.dart | 8 + lib/l10n/app_localizations_it.dart | 8 + lib/l10n/app_localizations_nl.dart | 7 + lib/l10n/app_localizations_pl.dart | 7 + lib/l10n/app_localizations_pt.dart | 8 + lib/l10n/app_localizations_ru.dart | 8 + lib/l10n/app_localizations_sk.dart | 7 + lib/l10n/app_localizations_sl.dart | 7 + lib/l10n/app_localizations_sv.dart | 7 + lib/l10n/app_localizations_uk.dart | 8 + lib/l10n/app_localizations_zh.dart | 6 + lib/l10n/app_nl.arb | 4 +- lib/l10n/app_pl.arb | 4 +- lib/l10n/app_pt.arb | 4 +- lib/l10n/app_ru.arb | 4 +- lib/l10n/app_sk.arb | 4 +- lib/l10n/app_sl.arb | 4 +- lib/l10n/app_sv.arb | 4 +- lib/l10n/app_uk.arb | 4 +- lib/l10n/app_zh.arb | 4 +- lib/models/app_settings.dart | 6 + lib/screens/app_settings_screen.dart | 12 ++ lib/screens/channel_chat_screen.dart | 231 ++++++++++++++++--------- lib/screens/chat_screen.dart | 225 +++++++++++++++--------- lib/services/app_settings_service.dart | 4 + macos/Podfile.lock | 17 +- 37 files changed, 491 insertions(+), 186 deletions(-) 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..8f5a40b 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -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..a8851a2 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'; 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..8bb004e 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -17,6 +17,7 @@ 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'; @@ -263,6 +264,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); @@ -336,108 +339,166 @@ class _ChannelChatScreenState extends State { if (poi != null) _buildPoiMessage(context, poi, isOutgoing) 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: 4, + right: 4, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + child: Icon( + (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + ? Icons.check_circle + : message.status == ChannelMessageStatus.failed + ? Icons.cancel + : Icons.cloud, + size: 14, + color: (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + ? Colors.green + : message.status == ChannelMessageStatus.failed + ? Colors.red + : Colors.white70, + ), + ), + ), + ], ) 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: Icon( + (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + ? Icons.check_circle + : message.status == ChannelMessageStatus.failed + ? Icons.cancel + : Icons.cloud, + size: 14, + color: (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + ? Colors.green + : message.status == ChannelMessageStatus.failed + ? Colors.red + : Colors.grey, + ), + ), + ], + ], + ), + 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], - ), - ], - ], + ), ), - ), + ], ], ), ), diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ad897a0..f97d4bb 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -20,6 +20,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 +1173,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); @@ -1251,100 +1254,158 @@ class _MessageBubble extends StatelessWidget { if (poi != null) _buildPoiMessage(context, poi, textColor, metaColor) 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: 4, + right: 4, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + child: Icon( + (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + ? Icons.check_circle + : message.status == MessageStatus.failed + ? Icons.cancel + : Icons.cloud, + size: 14, + color: (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + ? Colors.green + : message.status == MessageStatus.failed + ? Colors.red + : Colors.white70, + ), + ), + ), + ], ) 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: Icon( + (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + ? Icons.check_circle + : message.status == MessageStatus.failed + ? Icons.cancel + : Icons.cloud, + size: 14, + color: (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + ? Colors.green + : message.status == MessageStatus.failed + ? Colors.red + : Colors.grey, ), ), ], ], ), - ), + 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], + ), + ), + ], + ], + ), + ), + ], ], ), ), 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/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 From 35498c1b9046a958206c174e582760fa31bd67cf Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sun, 22 Feb 2026 11:31:56 -0800 Subject: [PATCH 07/12] formatting fix --- lib/screens/channel_chat_screen.dart | 52 ++++++++++++++++++---------- lib/screens/chat_screen.dart | 50 +++++++++++++++++--------- 2 files changed, 67 insertions(+), 35 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 8bb004e..9b9de35 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -367,17 +367,24 @@ class _ChannelChatScreenState extends State { shape: BoxShape.circle, ), child: Icon( - (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + (message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty) ? Icons.check_circle - : message.status == ChannelMessageStatus.failed - ? Icons.cancel - : Icons.cloud, + : message.status == + ChannelMessageStatus.failed + ? Icons.cancel + : Icons.cloud, size: 14, - color: (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + color: + (message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty) ? Colors.green - : message.status == ChannelMessageStatus.failed - ? Colors.red - : Colors.white70, + : message.status == + ChannelMessageStatus.failed + ? Colors.red + : Colors.white70, ), ), ), @@ -402,8 +409,10 @@ class _ChannelChatScreenState extends State { defaultToHttps: false, ), linkifiers: const [UrlLinkifier()], - onOpen: (link) => - LinkHandler.handleLinkTap(context, link.url), + onOpen: (link) => LinkHandler.handleLinkTap( + context, + link.url, + ), ), ), if (!enableTracing && isOutgoing) ...[ @@ -411,17 +420,24 @@ class _ChannelChatScreenState extends State { Padding( padding: const EdgeInsets.only(bottom: 2), child: Icon( - (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + (message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty) ? Icons.check_circle - : message.status == ChannelMessageStatus.failed - ? Icons.cancel - : Icons.cloud, + : message.status == + ChannelMessageStatus.failed + ? Icons.cancel + : Icons.cloud, size: 14, - color: (message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty) + color: + (message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty) ? Colors.green - : message.status == ChannelMessageStatus.failed - ? Colors.red - : Colors.grey, + : message.status == + ChannelMessageStatus.failed + ? Colors.red + : Colors.grey, ), ), ], diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index f97d4bb..32a7882 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1274,21 +1274,30 @@ class _MessageBubble extends StatelessWidget { child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.3), + color: Colors.black.withValues( + alpha: 0.3, + ), shape: BoxShape.circle, ), child: Icon( - (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + (message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty) ? Icons.check_circle - : message.status == MessageStatus.failed - ? Icons.cancel - : Icons.cloud, + : message.status == + MessageStatus.failed + ? Icons.cancel + : Icons.cloud, size: 14, - color: (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + color: + (message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty) ? Colors.green - : message.status == MessageStatus.failed - ? Colors.red - : Colors.white70, + : message.status == + MessageStatus.failed + ? Colors.red + : Colors.white70, ), ), ), @@ -1312,8 +1321,10 @@ class _MessageBubble extends StatelessWidget { defaultToHttps: false, ), linkifiers: const [UrlLinkifier()], - onOpen: (link) => - LinkHandler.handleLinkTap(context, link.url), + onOpen: (link) => LinkHandler.handleLinkTap( + context, + link.url, + ), ), ), if (!enableTracing && isOutgoing) ...[ @@ -1321,17 +1332,22 @@ class _MessageBubble extends StatelessWidget { Padding( padding: const EdgeInsets.only(bottom: 2), child: Icon( - (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + (message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty) ? Icons.check_circle : message.status == MessageStatus.failed - ? Icons.cancel - : Icons.cloud, + ? Icons.cancel + : Icons.cloud, size: 14, - color: (message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty) + color: + (message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty) ? Colors.green : message.status == MessageStatus.failed - ? Colors.red - : Colors.grey, + ? Colors.red + : Colors.grey, ), ), ], From 7465e81996271830ba460b77cd4cfdf4ce6b47bb Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 23 Feb 2026 03:31:01 -0800 Subject: [PATCH 08/12] add done_all icon --- assets/icons/done_all.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/icons/done_all.svg 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 From 173fdf7168e8cdd1ccf06cfe08bb4d8abbb71bd0 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 23 Feb 2026 04:09:27 -0800 Subject: [PATCH 09/12] chat fixes --- lib/screens/channel_chat_screen.dart | 104 +++++++++++++++------------ lib/screens/chat_screen.dart | 94 ++++++++++++------------ lib/widgets/message_status_icon.dart | 36 ++++++++++ pubspec.lock | 40 +++++++++++ pubspec.yaml | 3 + 5 files changed, 187 insertions(+), 90 deletions(-) create mode 100644 lib/widgets/message_status_icon.dart diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 9b9de35..a6354c3 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; @@ -23,6 +24,7 @@ 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'; @@ -337,7 +339,23 @@ 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) Stack( children: [ @@ -358,33 +376,31 @@ class _ChannelChatScreenState extends State { ), if (!enableTracing && isOutgoing) Positioned( - top: 4, - right: 4, + top: 0, + right: 0, child: Container( - padding: const EdgeInsets.all(2), + padding: const EdgeInsets.all(3), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.3), - shape: BoxShape.circle, + 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: Icon( - (message.status == - ChannelMessageStatus.sent && - displayPath.isNotEmpty) - ? Icons.check_circle - : message.status == - ChannelMessageStatus.failed - ? Icons.cancel - : Icons.cloud, - size: 14, - color: - (message.status == - ChannelMessageStatus.sent && - displayPath.isNotEmpty) - ? Colors.green - : message.status == - ChannelMessageStatus.failed - ? Colors.red - : Colors.white70, + child: MessageStatusIcon( + isAcked: + message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty, + isFailed: + message.status == + ChannelMessageStatus.failed, ), ), ), @@ -419,25 +435,14 @@ class _ChannelChatScreenState extends State { const SizedBox(width: 4), Padding( padding: const EdgeInsets.only(bottom: 2), - child: Icon( - (message.status == - ChannelMessageStatus.sent && - displayPath.isNotEmpty) - ? Icons.check_circle - : message.status == - ChannelMessageStatus.failed - ? Icons.cancel - : Icons.cloud, - size: 14, - color: - (message.status == - ChannelMessageStatus.sent && - displayPath.isNotEmpty) - ? Colors.green - : message.status == - ChannelMessageStatus.failed - ? Colors.red - : Colors.grey, + child: MessageStatusIcon( + isAcked: + message.status == + ChannelMessageStatus.sent && + displayPath.isNotEmpty, + isFailed: + message.status == + ChannelMessageStatus.failed, ), ), ], @@ -727,7 +732,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 @@ -773,6 +783,10 @@ class _ChannelChatScreenState extends State { ], ), ), + if (trailing != null) ...[ + const SizedBox(width: 4), + trailing, + ], ], ); } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 32a7882..bfbd88f 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; @@ -13,6 +14,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'; @@ -1252,7 +1254,24 @@ 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) Stack( children: [ @@ -1269,35 +1288,25 @@ class _MessageBubble extends StatelessWidget { ), if (!enableTracing && isOutgoing) Positioned( - top: 4, - right: 4, + top: 0, + right: 0, child: Container( - padding: const EdgeInsets.all(2), + padding: const EdgeInsets.all(3), decoration: BoxDecoration( - color: Colors.black.withValues( - alpha: 0.3, + color: bubbleColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + topRight: Radius.circular(12), ), - shape: BoxShape.circle, ), - child: Icon( - (message.status == - MessageStatus.delivered && - message.pathBytes.isNotEmpty) - ? Icons.check_circle - : message.status == - MessageStatus.failed - ? Icons.cancel - : Icons.cloud, - size: 14, - color: - (message.status == - MessageStatus.delivered && - message.pathBytes.isNotEmpty) - ? Colors.green - : message.status == - MessageStatus.failed - ? Colors.red - : Colors.white70, + child: MessageStatusIcon( + isAcked: + message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty, + isFailed: + message.status == + MessageStatus.failed, ), ), ), @@ -1331,23 +1340,13 @@ class _MessageBubble extends StatelessWidget { const SizedBox(width: 4), Padding( padding: const EdgeInsets.only(bottom: 2), - child: Icon( - (message.status == - MessageStatus.delivered && - message.pathBytes.isNotEmpty) - ? Icons.check_circle - : message.status == MessageStatus.failed - ? Icons.cancel - : Icons.cloud, - size: 14, - color: - (message.status == - MessageStatus.delivered && - message.pathBytes.isNotEmpty) - ? Colors.green - : message.status == MessageStatus.failed - ? Colors.red - : Colors.grey, + child: MessageStatusIcon( + isAcked: + message.status == + MessageStatus.delivered && + message.pathBytes.isNotEmpty, + isFailed: + message.status == MessageStatus.failed, ), ), ], @@ -1464,8 +1463,9 @@ class _MessageBubble extends StatelessWidget { BuildContext context, _PoiInfo poi, Color textColor, - Color metaColor, - ) { + Color metaColor, { + Widget? trailing, + }) { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -1502,6 +1502,10 @@ class _MessageBubble extends StatelessWidget { ], ), ), + if (trailing != null) ...[ + const SizedBox(width: 4), + trailing, + ], ], ); } 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/pubspec.lock b/pubspec.lock index f695838..9830433 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -347,6 +347,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -597,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1010,6 +1026,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + url: "https://pub.dev" + source: hosted + version: "1.2.0" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f5ceaaf..dcca7f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 From 3730b2a6c2e0f8890ca58c13c6cbbe3988fc316d Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 23 Feb 2026 04:13:38 -0800 Subject: [PATCH 10/12] formatting --- lib/screens/channel_chat_screen.dart | 11 +++++------ lib/screens/chat_screen.dart | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index a6354c3..937fa87 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -347,10 +347,12 @@ class _ChannelChatScreenState extends State { ? Padding( padding: const EdgeInsets.only(bottom: 2), child: MessageStatusIcon( - isAcked: message.status == + isAcked: + message.status == ChannelMessageStatus.sent && displayPath.isNotEmpty, - isFailed: message.status == + isFailed: + message.status == ChannelMessageStatus.failed, ), ) @@ -783,10 +785,7 @@ class _ChannelChatScreenState extends State { ], ), ), - if (trailing != null) ...[ - const SizedBox(width: 4), - trailing, - ], + if (trailing != null) ...[const SizedBox(width: 4), trailing], ], ); } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index bfbd88f..180f813 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1263,10 +1263,12 @@ class _MessageBubble extends StatelessWidget { ? Padding( padding: const EdgeInsets.only(bottom: 2), child: MessageStatusIcon( - isAcked: message.status == + isAcked: + message.status == MessageStatus.delivered && message.pathBytes.isNotEmpty, - isFailed: message.status == + isFailed: + message.status == MessageStatus.failed, ), ) @@ -1502,10 +1504,7 @@ class _MessageBubble extends StatelessWidget { ], ), ), - if (trailing != null) ...[ - const SizedBox(width: 4), - trailing, - ], + if (trailing != null) ...[const SizedBox(width: 4), trailing], ], ); } From bf5fadd15eea2f101c52fe4a55946dca6748a6a4 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 23 Feb 2026 04:13:52 -0800 Subject: [PATCH 11/12] revert lockfile --- pubspec.lock | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9830433..f695838 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -347,14 +347,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.2" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" - url: "https://pub.dev" - source: hosted - version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -605,14 +597,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" - source: hosted - version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1026,30 +1010,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.2" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" - url: "https://pub.dev" - source: hosted - version: "1.2.0" vector_math: dependency: transitive description: From 88f8066ed3fe4e904f4fefd2ad5f529e8673dae3 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 23 Feb 2026 04:53:01 -0800 Subject: [PATCH 12/12] code formatting --- lib/screens/channel_chat_screen.dart | 1 - lib/screens/chat_screen.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 937fa87..9df91c3 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 180f813..3556d6d 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart';