feat(#172): block/unblock in contacts, DM, and discovery menus

Completes the A5 entry points: contact long-press sheet, DM app-bar overflow, and
discovery item sheet each get a Block/Unblock toggle (by pubkey, reflecting current
state). Pairs with the channel 'Block sender' from 8a2e764.

Epic A #165 / A5 #172. Design: docs/architecture/block-contract-as-built.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/209/head
Strycher 1 week ago
parent 41113cddde
commit b1757c6539

@ -27,6 +27,7 @@ import '../models/app_settings.dart';
import '../models/path_history.dart';
import '../models/translation_support.dart';
import '../services/app_settings_service.dart';
import '../services/block_service.dart';
import '../services/chat_text_scale_service.dart';
import '../services/path_history_service.dart';
import '../services/translation_service.dart';
@ -353,8 +354,22 @@ class _ChatScreenState extends State<ChatScreen> {
if (value == 'clearChat') {
connector.clearMessagesForContact(widget.contact);
}
if (value == 'block') {
context.read<BlockService>().block(
widget.contact.publicKeyHex,
);
}
if (value == 'unblock') {
context.read<BlockService>().unblock(
widget.contact.publicKeyHex,
);
}
},
itemBuilder: (context) => [
itemBuilder: (context) {
final isBlocked = context.read<BlockService>().isBlocked(
widget.contact.publicKeyHex,
);
return [
PopupMenuItem(
value: 'info',
child: Row(
@ -398,7 +413,28 @@ class _ChatScreenState extends State<ChatScreen> {
],
),
),
PopupMenuItem(
value: isBlocked ? 'unblock' : 'block',
child: Row(
children: [
Icon(
isBlocked
? Icons.check_circle_outline
: Icons.block,
size: 20,
color: isBlocked ? null : Colors.red.shade700,
),
const SizedBox(width: 12),
Text(
isBlocked
? context.l10n.block_unblock
: context.l10n.block_block,
),
],
),
),
];
},
);
},
),

@ -1259,6 +1259,8 @@ class _ContactsScreenState extends State<ContactsScreen>
final isRepeater = contact.type == advTypeRepeater;
final isRoom = contact.type == advTypeRoom;
final isFavorite = contact.isFavorite;
final blockService = context.read<BlockService>();
final isBlocked = blockService.isBlocked(contact.publicKeyHex);
showModalBottomSheet(
context: context,
@ -1266,6 +1268,25 @@ class _ContactsScreenState extends State<ContactsScreen>
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(
isBlocked ? Icons.check_circle_outline : Icons.block,
color: isBlocked ? null : Colors.red.shade700,
),
title: Text(
isBlocked
? context.l10n.block_unblock
: context.l10n.block_block,
),
onTap: () async {
Navigator.pop(sheetContext);
if (isBlocked) {
await blockService.unblock(contact.publicKeyHex);
} else {
await blockService.block(contact.publicKeyHex);
}
},
),
if (isRepeater) ...[
ListTile(
leading: const Icon(Icons.radar, color: Colors.green),

@ -218,6 +218,8 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
Contact contact,
MeshCoreConnector connector,
) async {
final blockService = context.read<BlockService>();
final isBlocked = blockService.isBlocked(contact.publicKeyHex);
final action = await showModalBottomSheet<String>(
context: context,
showDragHandle: true,
@ -237,6 +239,16 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
title: Text(l10n.discoveredContacts_copyContact),
onTap: () => Navigator.of(sheetContext).pop('copy_contact'),
),
ListTile(
leading: Icon(
isBlocked ? Icons.check_circle_outline : Icons.block,
color: isBlocked ? null : Colors.red.shade700,
),
title: Text(isBlocked ? l10n.block_unblock : l10n.block_block),
onTap: () => Navigator.of(
sheetContext,
).pop(isBlocked ? 'unblock' : 'block'),
),
ListTile(
leading: const Icon(Icons.delete),
title: Text(l10n.discoveredContacts_deleteContact),
@ -267,6 +279,12 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
case 'delete_contact':
connector.removeDiscoveredContact(contact);
break;
case 'block':
await blockService.block(contact.publicKeyHex);
break;
case 'unblock':
await blockService.unblock(contact.publicKeyHex);
break;
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.