feat(block2): persist chat history (messages survive leaving the chat)
Messages were in-memory only — gone on leaving the conversation. - MessageStore: keystore-backed (no plaintext), a capped per-peer JSON list (last 200). Simple; the encrypted Drift DB is the eventual home at scale. - MessagesCubit: loads saved history on start (subscribes FIRST, then loads, so a message arriving during the load isn't dropped) and persists every sent and received message. Wired via DI + TaneApp(messageStore). Tests (plain 'test', no hang risk): MessageStore round-trip/per-peer/cap, and a cubit test that reopens a fresh cubit and sees the saved conversation. Analyzer clean; run 'flutter test' locally to confirm.
This commit is contained in:
parent
847590ff11
commit
6d16656911
8 changed files with 186 additions and 22 deletions
|
|
@ -4,6 +4,7 @@ import 'package:commons_core/commons_core.dart';
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../services/message_store.dart';
|
||||
import '../services/social_service.dart';
|
||||
import '../services/social_settings.dart';
|
||||
|
||||
|
|
@ -46,11 +47,14 @@ class MessagesCubit extends Cubit<ChatState> {
|
|||
this._transport, {
|
||||
required this.peerPubkey,
|
||||
required this.selfPubkey,
|
||||
MessageStore? store,
|
||||
Future<void> Function()? onDispose,
|
||||
}) : _onDispose = onDispose,
|
||||
}) : _store = store,
|
||||
_onDispose = onDispose,
|
||||
super(const ChatState());
|
||||
|
||||
final MessageTransport? _transport;
|
||||
final MessageStore? _store;
|
||||
final String peerPubkey;
|
||||
final String selfPubkey;
|
||||
final Future<void> Function()? _onDispose;
|
||||
|
|
@ -58,17 +62,26 @@ class MessagesCubit extends Cubit<ChatState> {
|
|||
|
||||
bool get isOnline => _transport != null;
|
||||
|
||||
/// Subscribes to incoming messages from [peerPubkey].
|
||||
void start() {
|
||||
/// Subscribes to incoming messages, then loads any saved history. Subscribing
|
||||
/// first (before the async history load) avoids dropping an event that arrives
|
||||
/// during the load.
|
||||
Future<void> start() async {
|
||||
final transport = _transport;
|
||||
if (transport == null) return;
|
||||
_sub = transport.inbox().listen(
|
||||
(message) {
|
||||
if (message.fromPubkey != peerPubkey) return; // another conversation
|
||||
emit(state.copyWith(messages: [...state.messages, message]));
|
||||
},
|
||||
onError: (Object e) => emit(state.copyWith(error: () => '$e')),
|
||||
);
|
||||
if (transport != null) {
|
||||
_sub = transport.inbox().listen(
|
||||
(message) async {
|
||||
if (message.fromPubkey != peerPubkey) return; // another conversation
|
||||
await _store?.append(peerPubkey, message);
|
||||
emit(state.copyWith(messages: [...state.messages, message]));
|
||||
},
|
||||
onError: (Object e) => emit(state.copyWith(error: () => '$e')),
|
||||
);
|
||||
}
|
||||
// History is older than anything that arrives now, so prepend it.
|
||||
final history = await _store?.history(peerPubkey);
|
||||
if (history != null && history.isNotEmpty) {
|
||||
emit(state.copyWith(messages: [...history, ...state.messages]));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends [text] to the peer and appends it optimistically.
|
||||
|
|
@ -79,16 +92,15 @@ class MessagesCubit extends Cubit<ChatState> {
|
|||
emit(state.copyWith(sending: true, error: () => null));
|
||||
try {
|
||||
await transport.send(toPubkey: peerPubkey, text: trimmed);
|
||||
final mine = PrivateMessage(
|
||||
fromPubkey: selfPubkey,
|
||||
text: trimmed,
|
||||
at: DateTime.now(),
|
||||
);
|
||||
await _store?.append(peerPubkey, mine);
|
||||
emit(state.copyWith(
|
||||
sending: false,
|
||||
messages: [
|
||||
...state.messages,
|
||||
PrivateMessage(
|
||||
fromPubkey: selfPubkey,
|
||||
text: trimmed,
|
||||
at: DateTime.now(),
|
||||
),
|
||||
],
|
||||
messages: [...state.messages, mine],
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(sending: false, error: () => '$e'));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue