diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 2ef42fb..bcbb60d 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4761,8 +4761,8 @@ class MeshCoreConnector extends ChangeNotifier { /// increase across [_coreScopeStableChecks] consecutive polls. There is no /// true "final" count (it only ever grows), so this is a pragmatic stop, the /// tappable badge covers re-checking later. - static const List _coreScopePollSecs = [10, 20, 30, 60, 60, 60, 60]; - static const int _coreScopeStableChecks = 2; + static const List _coreScopePollSecs = [10, 20, 30, 60, 60, 60, 90, 90]; + static const int _coreScopeStableChecks = 3; Future _fetchAndStoreCoreScopeCount( int channelIndex, @@ -4814,20 +4814,21 @@ class MeshCoreConnector extends ChangeNotifier { } /// Tap-to-refresh: re-query CoreScope for a message's stored on-air hash and - /// bump the observer count if it grew (counts only ever climb). No-op without - /// a stored hash. - Future refreshCoreScopeObserverCount( + /// bump the observer count if it grew (counts only ever climb). Returns the + /// fetched count (or null on failure / no stored hash) so the UI can confirm. + Future refreshCoreScopeObserverCount( int channelIndex, String messageId, String hashHex, ) async { final count = await _coreScopeService.fetchObserverCount(hashHex); - if (count == null) return; + if (count == null) return null; _updateChannelMessageById(channelIndex, messageId, (m) { final current = m.coreScopeObserverCount ?? 0; return count > current ? m.copyWith(coreScopeObserverCount: count) : m; }); notifyListeners(); + return count; } void _updateChannelMessageById( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cbdb224..75beaf5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -884,6 +884,8 @@ } } }, + "channel_coreScopeRefresh": "Refresh CoreScope observers", + "channel_coreScopeRefreshFailed": "CoreScope unreachable", "chat_successes": "successes", "chat_score": "Score", "chat_removePath": "Remove path", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1a50515..60ed869 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3088,6 +3088,18 @@ abstract class AppLocalizations { /// **'Seen by {count, plural, =1{1 CoreScope observer} other{{count} CoreScope observers}}'** String channel_coreScopeTooltip(int count); + /// No description provided for @channel_coreScopeRefresh. + /// + /// In en, this message translates to: + /// **'Refresh CoreScope observers'** + String get channel_coreScopeRefresh; + + /// No description provided for @channel_coreScopeRefreshFailed. + /// + /// In en, this message translates to: + /// **'CoreScope unreachable'** + String get channel_coreScopeRefreshFailed; + /// No description provided for @chat_successes. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index ae3110b..f224c2f 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1713,6 +1713,12 @@ class AppLocalizationsBg extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Успехи'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6e5a280..e8be34f 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1711,6 +1711,12 @@ class AppLocalizationsDe extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Erfolgreich'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8e0a6f5..879bfcd 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1681,6 +1681,12 @@ class AppLocalizationsEn extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'successes'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 8dab2d2..1fe615c 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1708,6 +1708,12 @@ class AppLocalizationsEs extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Éxitos'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 1c0a46a..b952743 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1715,6 +1715,12 @@ class AppLocalizationsFr extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Succès'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 50232fe..43b349f 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1720,6 +1720,12 @@ class AppLocalizationsHu extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'sikerek'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index fc416a4..2ec7193 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1710,6 +1710,12 @@ class AppLocalizationsIt extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'successi'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 4dfbae5..3fb763a 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1645,6 +1645,12 @@ class AppLocalizationsJa extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => '成功事例'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 72e02fc..b5c946f 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1641,6 +1641,12 @@ class AppLocalizationsKo extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => '성공 사례'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f77dbd5..2bf67f4 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1698,6 +1698,12 @@ class AppLocalizationsNl extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Succesvol'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 9149e2e..fbe9376 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1723,6 +1723,12 @@ class AppLocalizationsPl extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Sukcesy'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 4486ed5..95db95d 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1707,6 +1707,12 @@ class AppLocalizationsPt extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Sucessos'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 15c2402..d9964f4 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1711,6 +1711,12 @@ class AppLocalizationsRu extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'успешно'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 1cf8063..5771315 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1700,6 +1700,12 @@ class AppLocalizationsSk extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Úspechy'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index bb78aa1..8f9230a 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1696,6 +1696,12 @@ class AppLocalizationsSl extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Uspešni'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index ac202cc..8011941 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1691,6 +1691,12 @@ class AppLocalizationsSv extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'framgångar'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 4b0914a..da55a4a 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1706,6 +1706,12 @@ class AppLocalizationsUk extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => 'Успішно'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index e39bf18..8cbc2bc 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1620,6 +1620,12 @@ class AppLocalizationsZh extends AppLocalizations { return 'Seen by $_temp0'; } + @override + String get channel_coreScopeRefresh => 'Refresh CoreScope observers'; + + @override + String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override String get chat_successes => '成功'; diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index cd17a4c..347c498 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -1522,28 +1522,26 @@ class _ChannelChatScreenState extends State { // re-query on demand (the count only ever climbs). if (isOutgoing && (message.coreScopeObserverCount ?? 0) > 0) ...[ dot, - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: message.onAirHash == null - ? null - : () => context - .read() - .refreshCoreScopeObserverCount( - message.channelIndex ?? _currentChannel.index, - message.messageId, - message.onAirHash!, - ), - child: Tooltip( - message: context.l10n.channel_coreScopeTooltip( - message.coreScopeObserverCount!, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.cloud_outlined, size: 12, color: metaColor), - const SizedBox(width: 4), - Text('${message.coreScopeObserverCount}', style: metaStyle), - ], + Tooltip( + message: context.l10n.channel_coreScopeTooltip( + message.coreScopeObserverCount!, + ), + child: InkWell( + onTap: message.onAirHash == null + ? null + : () => _refreshCoreScope(message), + borderRadius: BorderRadius.circular(10), + child: Padding( + // Generous padding so the small badge is a reliable tap target. + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.cloud_outlined, size: 12, color: metaColor), + const SizedBox(width: 4), + Text('${message.coreScopeObserverCount}', style: metaStyle), + ], + ), ), ), ), @@ -1644,6 +1642,27 @@ class _ChannelChatScreenState extends State { ); } + Future _refreshCoreScope(ChannelMessage message) async { + final hash = message.onAirHash; + if (hash == null) return; + final connector = context.read(); + final l10n = context.l10n; + final count = await connector.refreshCoreScopeObserverCount( + message.channelIndex ?? _currentChannel.index, + message.messageId, + hash, + ); + if (!mounted) return; + showDismissibleSnackBar( + context, + content: Text( + count == null + ? l10n.channel_coreScopeRefreshFailed + : l10n.channel_coreScopeTooltip(count), + ), + ); + } + void _retryChannelMessage(ChannelMessage message) { context.read().sendChannelMessage( _currentChannel, @@ -1710,6 +1729,24 @@ class _ChannelChatScreenState extends State { _retryChannelMessage(message); }, ), + // CoreScope observer count refresh (#524), owner feature. A large, + // reliable tap target vs the small inline badge. + if (message.isOutgoing && message.onAirHash != null) + ListTile( + leading: const Icon(Icons.cloud_outlined), + title: Text(context.l10n.channel_coreScopeRefresh), + subtitle: message.coreScopeObserverCount != null + ? Text( + context.l10n.channel_coreScopeTooltip( + message.coreScopeObserverCount!, + ), + ) + : null, + onTap: () { + Navigator.pop(sheetContext); + _refreshCoreScope(message); + }, + ), // Can't react to your own messages if (!message.isOutgoing) ListTile( diff --git a/untranslated.json b/untranslated.json index 8141241..f15eaec 100644 --- a/untranslated.json +++ b/untranslated.json @@ -52,6 +52,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -152,6 +154,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -252,6 +256,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -352,6 +358,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -452,6 +460,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -552,6 +562,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -652,6 +664,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -752,6 +766,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -852,6 +868,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -952,6 +970,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1052,6 +1072,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1152,6 +1174,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1252,6 +1276,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1352,6 +1378,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1452,6 +1480,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1552,6 +1582,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", @@ -1652,6 +1684,8 @@ "debugLog_logUnavailable", "channel_heardTooltip", "channel_coreScopeTooltip", + "channel_coreScopeRefresh", + "channel_coreScopeRefreshFailed", "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters",