diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index dfa8959..1cac581 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -24,6 +24,7 @@ import '../models/channel_message.dart'; import '../models/translation_support.dart'; import '../models/app_settings.dart'; import '../services/app_settings_service.dart'; +import '../widgets/channel_notify_mode.dart'; import '../services/block_service.dart'; import '../services/chat_text_scale_service.dart'; import '../services/translation_service.dart'; @@ -424,13 +425,33 @@ class _ChannelChatScreenState extends State { PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) { - if (value == 'clearChat') { + if (value == 'notifications') { + showChannelNotifyModeDialog(context, widget.channel); + } else if (value == 'clearChat') { context.read().clearMessagesForChannel( _currentChannel.index, ); } }, itemBuilder: (context) => [ + PopupMenuItem( + value: 'notifications', + child: Row( + children: [ + Icon( + channelNotifyModeIcon( + context.read().channelNotifyMode( + identityKey: channelNotifyKeyFor(widget.channel), + channelName: widget.channel.name, + ), + ), + size: 20, + ), + const SizedBox(width: 12), + Text(context.l10n.channels_notifications), + ], + ), + ), PopupMenuItem( value: 'clearChat', child: Row( diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index eedfd71..5d91ffd 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -13,7 +13,6 @@ import '../connector/meshcore_connector.dart'; import '../l10n/l10n.dart'; import '../services/app_settings_service.dart'; import '../services/ui_view_state_service.dart'; -import '../models/app_settings.dart'; import '../models/channel.dart'; import '../models/community.dart'; import '../storage/community_store.dart'; @@ -27,6 +26,7 @@ import '../widgets/app_shell.dart'; import '../widgets/channel_drawer_list.dart'; import '../widgets/sync_progress_overlay.dart'; import '../widgets/unread_badge.dart'; +import '../widgets/channel_notify_mode.dart'; import '../helpers/snack_bar_builder.dart'; import 'channel_chat_screen.dart'; import 'community_qr_scanner_screen.dart'; @@ -468,71 +468,6 @@ class _ChannelsScreenState extends State ); } - /// Notify-mode storage key for [channel]: its PSK identity, so the setting - /// survives a rename and does not follow a reused slot (#259). - String _notifyKeyFor(Channel channel) => AppSettings.channelNotifyKey( - channelIndex: channel.index, - pskHex: channel.pskHex, - ); - - IconData _notifyModeIcon(ChannelNotifyMode mode) { - switch (mode) { - case ChannelNotifyMode.all: - return Icons.notifications_outlined; - case ChannelNotifyMode.mentionsOnly: - return Icons.alternate_email; - case ChannelNotifyMode.off: - return Icons.notifications_off_outlined; - } - } - - String _notifyModeLabel(BuildContext context, ChannelNotifyMode mode) { - switch (mode) { - case ChannelNotifyMode.all: - return context.l10n.channels_notifyAll; - case ChannelNotifyMode.mentionsOnly: - return context.l10n.channels_notifyMentionsOnly; - case ChannelNotifyMode.off: - return context.l10n.channels_notifyOff; - } - } - - void _showNotifyModeDialog(BuildContext context, Channel channel) { - final settingsService = context.read(); - final identityKey = _notifyKeyFor(channel); - final current = settingsService.channelNotifyMode( - identityKey: identityKey, - channelName: channel.name, - ); - - showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: Text(dialogContext.l10n.channels_notifications), - contentPadding: const EdgeInsets.symmetric(vertical: 8), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final mode in ChannelNotifyMode.values) - ListTile( - leading: Icon(_notifyModeIcon(mode)), - title: Text(_notifyModeLabel(dialogContext, mode)), - trailing: mode == current ? const Icon(Icons.check) : null, - onTap: () async { - Navigator.pop(dialogContext); - await settingsService.setChannelNotifyMode( - identityKey: identityKey, - channelName: channel.name, - mode: mode, - ); - }, - ), - ], - ), - ), - ); - } - void _showChannelActions( BuildContext context, MeshCoreConnector connector, @@ -542,7 +477,7 @@ class _ChannelsScreenState extends State final parentContext = context; final settingsService = context.read(); final notifyMode = settingsService.channelNotifyMode( - identityKey: _notifyKeyFor(channel), + identityKey: channelNotifyKeyFor(channel), channelName: channel.name, ); @@ -575,14 +510,14 @@ class _ChannelsScreenState extends State }, ), ListTile( - leading: Icon(_notifyModeIcon(notifyMode)), + leading: Icon(channelNotifyModeIcon(notifyMode)), title: Text(context.l10n.channels_notifications), - subtitle: Text(_notifyModeLabel(context, notifyMode)), + subtitle: Text(channelNotifyModeLabel(context, notifyMode)), onTap: () async { Navigator.pop(sheetContext); await Future.delayed(const Duration(milliseconds: 100)); if (parentContext.mounted) { - _showNotifyModeDialog(parentContext, channel); + showChannelNotifyModeDialog(parentContext, channel); } }, ), diff --git a/lib/widgets/channel_notify_mode.dart b/lib/widgets/channel_notify_mode.dart new file mode 100644 index 0000000..c87486b --- /dev/null +++ b/lib/widgets/channel_notify_mode.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../l10n/l10n.dart'; +import '../models/app_settings.dart'; +import '../models/channel.dart'; +import '../services/app_settings_service.dart'; + +/// Shared per-channel notify-mode affordance (#262), reachable both from the +/// Channels list and from inside a channel's chat (#275). Single implementation +/// so the two entry points cannot drift. + +/// Notify-mode storage key for [channel]: its PSK identity, so the setting +/// survives a rename and does not follow a reused slot (#259). +String channelNotifyKeyFor(Channel channel) => AppSettings.channelNotifyKey( + channelIndex: channel.index, + pskHex: channel.pskHex, +); + +IconData channelNotifyModeIcon(ChannelNotifyMode mode) { + switch (mode) { + case ChannelNotifyMode.all: + return Icons.notifications_outlined; + case ChannelNotifyMode.mentionsOnly: + return Icons.alternate_email; + case ChannelNotifyMode.off: + return Icons.notifications_off_outlined; + } +} + +String channelNotifyModeLabel(BuildContext context, ChannelNotifyMode mode) { + switch (mode) { + case ChannelNotifyMode.all: + return context.l10n.channels_notifyAll; + case ChannelNotifyMode.mentionsOnly: + return context.l10n.channels_notifyMentionsOnly; + case ChannelNotifyMode.off: + return context.l10n.channels_notifyOff; + } +} + +void showChannelNotifyModeDialog(BuildContext context, Channel channel) { + final settingsService = context.read(); + final identityKey = channelNotifyKeyFor(channel); + final current = settingsService.channelNotifyMode( + identityKey: identityKey, + channelName: channel.name, + ); + + showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text(dialogContext.l10n.channels_notifications), + contentPadding: const EdgeInsets.symmetric(vertical: 8), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final mode in ChannelNotifyMode.values) + ListTile( + leading: Icon(channelNotifyModeIcon(mode)), + title: Text(channelNotifyModeLabel(dialogContext, mode)), + trailing: mode == current ? const Icon(Icons.check) : null, + onTap: () async { + Navigator.pop(dialogContext); + await settingsService.setChannelNotifyMode( + identityKey: identityKey, + channelName: channel.name, + mode: mode, + ); + }, + ), + ], + ), + ), + ); +}