feat(block2): messages inbox + drawer 'Chat' link (was unreachable)
Chat was only reachable via an offer's 'Message' button, and the drawer 'Chat' sat on 'coming soon' — so messaging/trust were effectively hidden. - MessageStore keeps a conversation index (the keystore has no key enumeration) and exposes conversations() — peers newest-active-first with their last line. - ChatListScreen: the messages inbox (tap a conversation → /chat/:pubkey). - Drawer 'Chat' is now a live destination -> /messages (gated like Market). - i18n en/es/pt. conversations() covered by a plain test (no widget hang). Analyzer clean.
This commit is contained in:
parent
6d16656911
commit
7f1c520960
12 changed files with 211 additions and 3 deletions
|
|
@ -49,6 +49,12 @@ class AppDrawer extends StatelessWidget {
|
|||
_DrawerItem(
|
||||
icon: const Icon(Icons.chat_bubble),
|
||||
label: t.menu.chat,
|
||||
onTap: marketEnabled
|
||||
? () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/messages');
|
||||
}
|
||||
: null,
|
||||
),
|
||||
_DrawerItem(
|
||||
icon: const Icon(Icons.favorite),
|
||||
|
|
|
|||
79
apps/app_seeds/lib/ui/chat_list_screen.dart
Normal file
79
apps/app_seeds/lib/ui/chat_list_screen.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../services/message_store.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// The messages inbox: past conversations, newest first. Tapping one opens the
|
||||
/// chat. Reached from the drawer's "Chat" entry.
|
||||
class ChatListScreen extends StatefulWidget {
|
||||
const ChatListScreen({required this.store, super.key});
|
||||
|
||||
final MessageStore store;
|
||||
|
||||
@override
|
||||
State<ChatListScreen> createState() => _ChatListScreenState();
|
||||
}
|
||||
|
||||
class _ChatListScreenState extends State<ChatListScreen> {
|
||||
List<ChatSummary>? _items;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
final items = await widget.store.conversations();
|
||||
if (mounted) setState(() => _items = items);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
final items = _items;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(t.chatList.title)),
|
||||
body: items == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: items.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Text(
|
||||
t.chatList.empty,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: seedMuted, fontSize: 15),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, i) {
|
||||
final c = items[i];
|
||||
return ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: seedAvatar,
|
||||
child: Icon(Icons.person, color: seedOnAvatar),
|
||||
),
|
||||
title: Text(_shortId(c.peerPubkey)),
|
||||
subtitle: Text(
|
||||
c.lastText,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: () => context.go('/chat/${c.peerPubkey}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A compact, human-ish rendering of a public key until we resolve names.
|
||||
String _shortId(String pubkey) => pubkey.length <= 12
|
||||
? pubkey
|
||||
: '${pubkey.substring(0, 6)}…${pubkey.substring(pubkey.length - 4)}';
|
||||
Loading…
Add table
Add a link
Reference in a new issue