feat(social): local blocklist — hide blocked authors' offers, chats and incoming messages, with unblock management

This commit is contained in:
vjrj 2026-07-13 01:03:10 +02:00
parent 5d25f65f76
commit 11b242edbf
13 changed files with 399 additions and 4 deletions

View file

@ -16,6 +16,7 @@ import '../services/profile_cache.dart';
import '../services/profile_store.dart';
import '../services/social_connection.dart';
import '../services/social_service.dart';
import '../services/social_settings.dart';
import '../services/unread_service.dart';
import '../state/messages_cubit.dart';
import '../state/peer_rating_cubit.dart';
@ -36,6 +37,7 @@ class ChatScreen extends StatefulWidget {
this.messageStore,
this.profileCache,
this.onboarding,
this.settings,
super.key,
});
@ -57,6 +59,11 @@ class ChatScreen extends StatefulWidget {
/// entering the market). Null in tests no gate.
final OnboardingStore? onboarding;
/// Social settings holding the local blocklist; falls back to the app-wide
/// instance so the block action also works when not injected. Null in
/// tests the block menu is hidden.
final SocialSettings? settings;
@override
State<ChatScreen> createState() => _ChatScreenState();
}
@ -191,6 +198,42 @@ class _ChatScreenState extends State<ChatScreen> {
}
}
SocialSettings? get _settings =>
widget.settings ??
(getIt.isRegistered<SocialSettings>() ? getIt<SocialSettings>() : null);
/// Blocks this peer after confirmation and leaves the chat; their offers,
/// conversation and future messages disappear (all locally).
Future<void> _blockPeer() async {
final settings = _settings;
if (settings == null) return;
final t = context.t;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(t.block.confirmTitle),
content: Text(t.block.confirmBody),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(t.common.cancel),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(t.block.confirm),
),
],
),
);
if (confirmed != true || !mounted) return;
await settings.block(widget.peerPubkey);
if (!mounted) return;
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(t.block.blockedToast)));
Navigator.of(context).pop();
}
Future<void> _send() async {
final cubit = _messages;
if (cubit == null || _input.text.trim().isEmpty) return;
@ -244,6 +287,19 @@ class _ChatScreenState extends State<ChatScreen> {
tooltip: t.chat.payG1,
onPressed: _payG1,
),
if (_settings != null)
PopupMenuButton<String>(
key: const Key('chat.menu'),
onSelected: (value) {
if (value == 'block') _blockPeer();
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'block',
child: Text(t.block.action),
),
],
),
],
),
body: _loading || messages == null || trust == null || rating == null