From ceed58373f14b040ce6d7055a7d9224e6367ae43 Mon Sep 17 00:00:00 2001 From: Strycher Date: Fri, 17 Jul 2026 02:16:22 -0400 Subject: [PATCH] feat(#261): apply per-channel notify mode at the notification gate _maybeNotifyChannelMessage now switches on ChannelNotifyMode instead of the binary isChannelMuted check. mentionsOnly notifies only when the message carries a canonical @[selfName] mention. The mode is keyed by the channel's PSK identity, reusing the same _findChannelByIndex(index)?.pskHex resolver the message store already uses for history (#194), so the setting survives a rename and does not follow a reused slot. The mention is matched against resolvedText (post-translation) so a translated mention still fires, and case-insensitively since a hand-typed mention need not match the advert casing. Bare @Name is deliberately not matched: it false-positives on ordinary text and cannot be delimited for names containing spaces. Co-Authored-By: Claude Opus 4.8 --- lib/connector/meshcore_connector.dart | 35 ++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index fed5f57..c06460e 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus_platform_interface/flutter_blue_plus_platform_interface.dart'; +import '../models/app_settings.dart'; import '../models/channel.dart'; import '../models/channel_message.dart'; import '../models/companion_radio_stats.dart'; @@ -5584,6 +5585,19 @@ class MeshCoreConnector extends ChangeNotifier { return 'Channel $channelIndex'; } + /// True when [text] carries a canonical mention of this node: the `@[Name]` + /// form the composer inserts and the chat renders as a chip (#235). + /// + /// Matched case-insensitively, since a hand-typed mention need not match the + /// advert's casing. Bare `@Name` is deliberately NOT matched: it false- + /// positives on ordinary text and cannot be delimited for names containing + /// spaces. + bool _mentionsSelf(String text) { + final name = _selfName?.trim(); + if (name == null || name.isEmpty) return false; + return text.toLowerCase().contains('@[${name.toLowerCase()}]'); + } + void _maybeNotifyChannelMessage( ChannelMessage message, { String? channelName, @@ -5599,7 +5613,6 @@ class MeshCoreConnector extends ChangeNotifier { } final label = channelName ?? _channelDisplayName(channelIndex); - if (_appSettingsService!.isChannelMuted(label)) return; // Reuse translation result only if completed and non-empty; else use original text final resolvedText = @@ -5608,6 +5621,26 @@ class MeshCoreConnector extends ChangeNotifier { translationResult.translatedText.trim().isNotEmpty) ? translationResult.translatedText.trim() : message.text.trim(); + + // Per-channel notification level, keyed by the channel's PSK identity so + // the setting survives a rename and does not follow a reused slot (#259). + // Evaluated against resolvedText so a translated mention still matches. + final mode = _appSettingsService!.channelNotifyMode( + identityKey: AppSettings.channelNotifyKey( + channelIndex: channelIndex, + pskHex: _findChannelByIndex(channelIndex)?.pskHex, + ), + channelName: label, + ); + switch (mode) { + case ChannelNotifyMode.off: + return; + case ChannelNotifyMode.mentionsOnly: + if (!_mentionsSelf(resolvedText)) return; + case ChannelNotifyMode.all: + break; + } + unawaited( () async { await _notificationService.showChannelMessageNotification(