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

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import '../di/injector.dart';
import '../i18n/strings.g.dart';
import '../services/profile_cache.dart';
import '../services/social_settings.dart';
import 'peer_avatar.dart';
import 'theme.dart';
/// Management list for the local blocklist: who is blocked, with one-tap
/// unblock. Opened from the market's sharing setup.
class BlockedPeopleSheet extends StatefulWidget {
const BlockedPeopleSheet({required this.settings, this.profileCache, super.key});
final SocialSettings settings;
/// Resolves peers' display names; falls back to the app-wide cache when not
/// injected (tests pass one explicitly or leave names as short keys).
final ProfileCache? profileCache;
@override
State<BlockedPeopleSheet> createState() => _BlockedPeopleSheetState();
}
class _BlockedPeopleSheetState extends State<BlockedPeopleSheet> {
List<String>? _blocked;
final Map<String, String> _names = {};
ProfileCache? get _cache =>
widget.profileCache ??
(getIt.isRegistered<ProfileCache>() ? getIt<ProfileCache>() : null);
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
final blocked = (await widget.settings.blockedPubkeys()).toList()..sort();
final cache = _cache;
if (cache != null) {
for (final p in blocked) {
final name = await cache.name(p);
if (name != null) _names[p] = name;
}
}
if (mounted) setState(() => _blocked = blocked);
}
Future<void> _unblock(String pubkey) async {
await widget.settings.unblock(pubkey);
await _load();
}
@override
Widget build(BuildContext context) {
final t = context.t;
final blocked = _blocked;
return SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t.block.manageTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: seedOnSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
if (blocked == null)
const Center(child: CircularProgressIndicator())
else if (blocked.isEmpty)
Text(
t.block.manageEmpty,
style: const TextStyle(color: seedMuted),
)
else
Flexible(
child: ListView.builder(
shrinkWrap: true,
itemCount: blocked.length,
itemBuilder: (context, i) {
final pubkey = blocked[i];
return ListTile(
contentPadding: EdgeInsets.zero,
leading: PeerAvatar(
pubkey: pubkey,
name: _names[pubkey],
),
title: Text(_names[pubkey] ?? shortPubkey(pubkey)),
trailing: TextButton(
onPressed: () => _unblock(pubkey),
child: Text(t.block.unblock),
),
);
},
),
),
],
),
),
);
}
}