feat(#275): per-channel notify mode from the channel ellipsis menu

Extracts the #262 notify-mode selector out of channels_screen into a shared
widgets/channel_notify_mode.dart (key, icon, label, dialog) and points both
entry points at it, so the Channels list and the in-channel menu cannot drift.
Same PSK-keyed storage; no new settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/419/head
Strycher 2 months ago
parent 8b7f0a3d2c
commit f63f796940

@ -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<ChannelChatScreen> {
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
onSelected: (value) {
if (value == 'clearChat') {
if (value == 'notifications') {
showChannelNotifyModeDialog(context, widget.channel);
} else if (value == 'clearChat') {
context.read<MeshCoreConnector>().clearMessagesForChannel(
_currentChannel.index,
);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'notifications',
child: Row(
children: [
Icon(
channelNotifyModeIcon(
context.read<AppSettingsService>().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(

@ -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<ChannelsScreen>
);
}
/// 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<AppSettingsService>();
final identityKey = _notifyKeyFor(channel);
final current = settingsService.channelNotifyMode(
identityKey: identityKey,
channelName: channel.name,
);
showDialog<void>(
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<ChannelsScreen>
final parentContext = context;
final settingsService = context.read<AppSettingsService>();
final notifyMode = settingsService.channelNotifyMode(
identityKey: _notifyKeyFor(channel),
identityKey: channelNotifyKeyFor(channel),
channelName: channel.name,
);
@ -575,14 +510,14 @@ class _ChannelsScreenState extends State<ChannelsScreen>
},
),
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);
}
},
),

@ -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<AppSettingsService>();
final identityKey = channelNotifyKeyFor(channel);
final current = settingsService.channelNotifyMode(
identityKey: identityKey,
channelName: channel.name,
);
showDialog<void>(
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,
);
},
),
],
),
),
);
}
Loading…
Cancel
Save

Powered by TurnKey Linux.