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:
vjrj 2026-07-10 12:20:25 +02:00
parent 847590ff11
commit 6d16656911
8 changed files with 186 additions and 22 deletions

View file

@ -9,6 +9,7 @@ import 'data/variety_repository.dart';
import 'i18n/strings.g.dart';
import 'services/auto_backup_service.dart';
import 'services/coarse_location.dart';
import 'services/message_store.dart';
import 'services/offer_outbox.dart';
import 'services/onboarding_store.dart';
import 'services/social_service.dart';
@ -38,6 +39,7 @@ class TaneApp extends StatelessWidget {
this.socialSettings,
this.location,
this.outbox,
this.messageStore,
this.showIntro = false,
this.autoBackup,
super.key,
@ -49,6 +51,7 @@ class TaneApp extends StatelessWidget {
socialSettings,
location,
outbox,
messageStore,
);
final VarietyRepository repository;
@ -65,6 +68,9 @@ class TaneApp extends StatelessWidget {
/// Optional offline outbox for the market's "share my seeds".
final OfferOutbox? outbox;
/// Optional persistence for chat history.
final MessageStore? messageStore;
final bool showIntro;
/// Drives silent periodic backups off the app lifecycle. Null in widget tests
@ -80,6 +86,7 @@ class TaneApp extends StatelessWidget {
SocialSettings? socialSettings,
CoarseLocationProvider? location,
OfferOutbox? outbox,
MessageStore? messageStore,
) {
return GoRouter(
initialLocation: showIntro ? '/intro' : '/',
@ -106,6 +113,7 @@ class TaneApp extends StatelessWidget {
social: social,
settings: socialSettings,
peerPubkey: state.pathParameters['pubkey']!,
messageStore: messageStore,
),
),
GoRoute(