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
|
|
@ -18,6 +18,7 @@ import 'state/inventory_cubit.dart';
|
||||||
import 'state/variety_detail_cubit.dart';
|
import 'state/variety_detail_cubit.dart';
|
||||||
import 'ui/about_screen.dart';
|
import 'ui/about_screen.dart';
|
||||||
import 'ui/auto_backup_gate.dart';
|
import 'ui/auto_backup_gate.dart';
|
||||||
|
import 'ui/chat_list_screen.dart';
|
||||||
import 'ui/chat_screen.dart';
|
import 'ui/chat_screen.dart';
|
||||||
import 'ui/home_screen.dart';
|
import 'ui/home_screen.dart';
|
||||||
import 'ui/intro_screen.dart';
|
import 'ui/intro_screen.dart';
|
||||||
|
|
@ -106,6 +107,11 @@ class TaneApp extends StatelessWidget {
|
||||||
outbox: outbox,
|
outbox: outbox,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (social != null && socialSettings != null && messageStore != null)
|
||||||
|
GoRoute(
|
||||||
|
path: '/messages',
|
||||||
|
builder: (context, state) => ChatListScreen(store: messageStore),
|
||||||
|
),
|
||||||
if (social != null && socialSettings != null)
|
if (social != null && socialSettings != null)
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat/:pubkey',
|
path: '/chat/:pubkey',
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,10 @@
|
||||||
"locationFailed": "Couldn't get your location — check that location is on and the permission is granted",
|
"locationFailed": "Couldn't get your location — check that location is on and the permission is granted",
|
||||||
"queued": "Saved — we'll share these when you're connected"
|
"queued": "Saved — we'll share these when you're connected"
|
||||||
},
|
},
|
||||||
|
"chatList": {
|
||||||
|
"title": "Messages",
|
||||||
|
"empty": "No conversations yet. Message someone from the market."
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat",
|
"title": "Chat",
|
||||||
"hint": "Write a message…",
|
"hint": "Write a message…",
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,10 @@
|
||||||
"locationFailed": "No se pudo obtener tu ubicación — comprueba que la ubicación está activada y el permiso concedido",
|
"locationFailed": "No se pudo obtener tu ubicación — comprueba que la ubicación está activada y el permiso concedido",
|
||||||
"queued": "Guardado — las compartiremos cuando tengas conexión"
|
"queued": "Guardado — las compartiremos cuando tengas conexión"
|
||||||
},
|
},
|
||||||
|
"chatList": {
|
||||||
|
"title": "Mensajes",
|
||||||
|
"empty": "Aún no hay conversaciones. Escribe a alguien desde el mercado."
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat",
|
"title": "Chat",
|
||||||
"hint": "Escribe un mensaje…",
|
"hint": "Escribe un mensaje…",
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,10 @@
|
||||||
"locationFailed": "Não foi possível obter a tua localização — verifica se a localização está ativa e a permissão concedida",
|
"locationFailed": "Não foi possível obter a tua localização — verifica se a localização está ativa e a permissão concedida",
|
||||||
"queued": "Guardado — vamos partilhá-las quando tiveres ligação"
|
"queued": "Guardado — vamos partilhá-las quando tiveres ligação"
|
||||||
},
|
},
|
||||||
|
"chatList": {
|
||||||
|
"title": "Mensagens",
|
||||||
|
"empty": "Ainda não há conversas. Escreve a alguém a partir do mercado."
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Conversa",
|
"title": "Conversa",
|
||||||
"hint": "Escreve uma mensagem…",
|
"hint": "Escreve uma mensagem…",
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 3
|
/// Locales: 3
|
||||||
/// Strings: 992 (330 per locale)
|
/// Strings: 998 (332 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2026-07-10 at 10:09 UTC
|
/// Built on 2026-07-10 at 10:26 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint, unused_import
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
|
||||||
late final Translations$desiccant$en desiccant = Translations$desiccant$en.internal(_root);
|
late final Translations$desiccant$en desiccant = Translations$desiccant$en.internal(_root);
|
||||||
late final Translations$unit$en unit = Translations$unit$en.internal(_root);
|
late final Translations$unit$en unit = Translations$unit$en.internal(_root);
|
||||||
late final Translations$market$en market = Translations$market$en.internal(_root);
|
late final Translations$market$en market = Translations$market$en.internal(_root);
|
||||||
|
late final Translations$chatList$en chatList = Translations$chatList$en.internal(_root);
|
||||||
late final Translations$chat$en chat = Translations$chat$en.internal(_root);
|
late final Translations$chat$en chat = Translations$chat$en.internal(_root);
|
||||||
late final Translations$trust$en trust = Translations$trust$en.internal(_root);
|
late final Translations$trust$en trust = Translations$trust$en.internal(_root);
|
||||||
}
|
}
|
||||||
|
|
@ -1142,6 +1143,21 @@ class Translations$market$en {
|
||||||
String get queued => 'Saved — we\'ll share these when you\'re connected';
|
String get queued => 'Saved — we\'ll share these when you\'re connected';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: chatList
|
||||||
|
class Translations$chatList$en {
|
||||||
|
Translations$chatList$en.internal(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Messages'
|
||||||
|
String get title => 'Messages';
|
||||||
|
|
||||||
|
/// en: 'No conversations yet. Message someone from the market.'
|
||||||
|
String get empty => 'No conversations yet. Message someone from the market.';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: chat
|
// Path: chat
|
||||||
class Translations$chat$en {
|
class Translations$chat$en {
|
||||||
Translations$chat$en.internal(this._root);
|
Translations$chat$en.internal(this._root);
|
||||||
|
|
@ -1969,6 +1985,8 @@ extension on Translations {
|
||||||
'market.useLocation' => 'Use my approximate location',
|
'market.useLocation' => 'Use my approximate location',
|
||||||
'market.locationFailed' => 'Couldn\'t get your location — check that location is on and the permission is granted',
|
'market.locationFailed' => 'Couldn\'t get your location — check that location is on and the permission is granted',
|
||||||
'market.queued' => 'Saved — we\'ll share these when you\'re connected',
|
'market.queued' => 'Saved — we\'ll share these when you\'re connected',
|
||||||
|
'chatList.title' => 'Messages',
|
||||||
|
'chatList.empty' => 'No conversations yet. Message someone from the market.',
|
||||||
'chat.title' => 'Chat',
|
'chat.title' => 'Chat',
|
||||||
'chat.hint' => 'Write a message…',
|
'chat.hint' => 'Write a message…',
|
||||||
'chat.send' => 'Send',
|
'chat.send' => 'Send',
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$desiccant$es desiccant = _Translations$desiccant$es._(_root);
|
@override late final _Translations$desiccant$es desiccant = _Translations$desiccant$es._(_root);
|
||||||
@override late final _Translations$unit$es unit = _Translations$unit$es._(_root);
|
@override late final _Translations$unit$es unit = _Translations$unit$es._(_root);
|
||||||
@override late final _Translations$market$es market = _Translations$market$es._(_root);
|
@override late final _Translations$market$es market = _Translations$market$es._(_root);
|
||||||
|
@override late final _Translations$chatList$es chatList = _Translations$chatList$es._(_root);
|
||||||
@override late final _Translations$chat$es chat = _Translations$chat$es._(_root);
|
@override late final _Translations$chat$es chat = _Translations$chat$es._(_root);
|
||||||
@override late final _Translations$trust$es trust = _Translations$trust$es._(_root);
|
@override late final _Translations$trust$es trust = _Translations$trust$es._(_root);
|
||||||
}
|
}
|
||||||
|
|
@ -634,6 +635,17 @@ class _Translations$market$es extends Translations$market$en {
|
||||||
@override String get queued => 'Guardado — las compartiremos cuando tengas conexión';
|
@override String get queued => 'Guardado — las compartiremos cuando tengas conexión';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: chatList
|
||||||
|
class _Translations$chatList$es extends Translations$chatList$en {
|
||||||
|
_Translations$chatList$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsEs _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Mensajes';
|
||||||
|
@override String get empty => 'Aún no hay conversaciones. Escribe a alguien desde el mercado.';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: chat
|
// Path: chat
|
||||||
class _Translations$chat$es extends Translations$chat$en {
|
class _Translations$chat$es extends Translations$chat$en {
|
||||||
_Translations$chat$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
_Translations$chat$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1325,6 +1337,8 @@ extension on TranslationsEs {
|
||||||
'market.useLocation' => 'Usar mi ubicación aproximada',
|
'market.useLocation' => 'Usar mi ubicación aproximada',
|
||||||
'market.locationFailed' => 'No se pudo obtener tu ubicación — comprueba que la ubicación está activada y el permiso concedido',
|
'market.locationFailed' => 'No se pudo obtener tu ubicación — comprueba que la ubicación está activada y el permiso concedido',
|
||||||
'market.queued' => 'Guardado — las compartiremos cuando tengas conexión',
|
'market.queued' => 'Guardado — las compartiremos cuando tengas conexión',
|
||||||
|
'chatList.title' => 'Mensajes',
|
||||||
|
'chatList.empty' => 'Aún no hay conversaciones. Escribe a alguien desde el mercado.',
|
||||||
'chat.title' => 'Chat',
|
'chat.title' => 'Chat',
|
||||||
'chat.hint' => 'Escribe un mensaje…',
|
'chat.hint' => 'Escribe un mensaje…',
|
||||||
'chat.send' => 'Enviar',
|
'chat.send' => 'Enviar',
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ class TranslationsPt extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$desiccant$pt desiccant = _Translations$desiccant$pt._(_root);
|
@override late final _Translations$desiccant$pt desiccant = _Translations$desiccant$pt._(_root);
|
||||||
@override late final _Translations$unit$pt unit = _Translations$unit$pt._(_root);
|
@override late final _Translations$unit$pt unit = _Translations$unit$pt._(_root);
|
||||||
@override late final _Translations$market$pt market = _Translations$market$pt._(_root);
|
@override late final _Translations$market$pt market = _Translations$market$pt._(_root);
|
||||||
|
@override late final _Translations$chatList$pt chatList = _Translations$chatList$pt._(_root);
|
||||||
@override late final _Translations$chat$pt chat = _Translations$chat$pt._(_root);
|
@override late final _Translations$chat$pt chat = _Translations$chat$pt._(_root);
|
||||||
@override late final _Translations$trust$pt trust = _Translations$trust$pt._(_root);
|
@override late final _Translations$trust$pt trust = _Translations$trust$pt._(_root);
|
||||||
}
|
}
|
||||||
|
|
@ -630,6 +631,17 @@ class _Translations$market$pt extends Translations$market$en {
|
||||||
@override String get queued => 'Guardado — vamos partilhá-las quando tiveres ligação';
|
@override String get queued => 'Guardado — vamos partilhá-las quando tiveres ligação';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: chatList
|
||||||
|
class _Translations$chatList$pt extends Translations$chatList$en {
|
||||||
|
_Translations$chatList$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsPt _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Mensagens';
|
||||||
|
@override String get empty => 'Ainda não há conversas. Escreve a alguém a partir do mercado.';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: chat
|
// Path: chat
|
||||||
class _Translations$chat$pt extends Translations$chat$en {
|
class _Translations$chat$pt extends Translations$chat$en {
|
||||||
_Translations$chat$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
_Translations$chat$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1317,6 +1329,8 @@ extension on TranslationsPt {
|
||||||
'market.useLocation' => 'Usar a minha localização aproximada',
|
'market.useLocation' => 'Usar a minha localização aproximada',
|
||||||
'market.locationFailed' => 'Não foi possível obter a tua localização — verifica se a localização está ativa e a permissão concedida',
|
'market.locationFailed' => 'Não foi possível obter a tua localização — verifica se a localização está ativa e a permissão concedida',
|
||||||
'market.queued' => 'Guardado — vamos partilhá-las quando tiveres ligação',
|
'market.queued' => 'Guardado — vamos partilhá-las quando tiveres ligação',
|
||||||
|
'chatList.title' => 'Mensagens',
|
||||||
|
'chatList.empty' => 'Ainda não há conversas. Escreve a alguém a partir do mercado.',
|
||||||
'chat.title' => 'Conversa',
|
'chat.title' => 'Conversa',
|
||||||
'chat.hint' => 'Escreve uma mensagem…',
|
'chat.hint' => 'Escreve uma mensagem…',
|
||||||
'chat.send' => 'Enviar',
|
'chat.send' => 'Enviar',
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,29 @@ import '../security/secret_store.dart';
|
||||||
/// capped, per-peer JSON list — recent history survives leaving the chat. Small
|
/// capped, per-peer JSON list — recent history survives leaving the chat. Small
|
||||||
/// and simple on purpose; the encrypted Drift DB is the eventual home if chats
|
/// and simple on purpose; the encrypted Drift DB is the eventual home if chats
|
||||||
/// grow large.
|
/// grow large.
|
||||||
|
/// A conversation preview for the messages inbox.
|
||||||
|
class ChatSummary {
|
||||||
|
const ChatSummary({
|
||||||
|
required this.peerPubkey,
|
||||||
|
required this.lastText,
|
||||||
|
required this.lastAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String peerPubkey;
|
||||||
|
final String lastText;
|
||||||
|
final DateTime lastAt;
|
||||||
|
}
|
||||||
|
|
||||||
class MessageStore {
|
class MessageStore {
|
||||||
MessageStore(this._store);
|
MessageStore(this._store);
|
||||||
|
|
||||||
final SecretStore _store;
|
final SecretStore _store;
|
||||||
static const _prefix = 'tane.social.chat.';
|
static const _prefix = 'tane.social.chat.';
|
||||||
|
|
||||||
|
/// Index of peers we have a conversation with (the keystore is key/value with
|
||||||
|
/// no key enumeration, so we track the list ourselves).
|
||||||
|
static const _indexKey = 'tane.social.chats';
|
||||||
|
|
||||||
/// Keep only the most recent [_cap] messages per conversation, to bound the
|
/// Keep only the most recent [_cap] messages per conversation, to bound the
|
||||||
/// keystore entry size.
|
/// keystore entry size.
|
||||||
static const _cap = 200;
|
static const _cap = 200;
|
||||||
|
|
@ -36,7 +53,7 @@ class MessageStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends [message] to the conversation with [peerPubkey] (trimming to the
|
/// Appends [message] to the conversation with [peerPubkey] (trimming to the
|
||||||
/// cap).
|
/// cap) and records the peer in the conversation index.
|
||||||
Future<void> append(String peerPubkey, PrivateMessage message) async {
|
Future<void> append(String peerPubkey, PrivateMessage message) async {
|
||||||
final next = [...await history(peerPubkey), message];
|
final next = [...await history(peerPubkey), message];
|
||||||
final capped =
|
final capped =
|
||||||
|
|
@ -52,5 +69,35 @@ class MessageStore {
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
await _rememberPeer(peerPubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversations, most-recently-active first (for the messages inbox).
|
||||||
|
Future<List<ChatSummary>> conversations() async {
|
||||||
|
final summaries = <ChatSummary>[];
|
||||||
|
for (final peer in await _peers()) {
|
||||||
|
final history = await this.history(peer);
|
||||||
|
if (history.isEmpty) continue;
|
||||||
|
final last = history.last;
|
||||||
|
summaries.add(ChatSummary(
|
||||||
|
peerPubkey: peer,
|
||||||
|
lastText: last.text,
|
||||||
|
lastAt: last.at,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
summaries.sort((a, b) => b.lastAt.compareTo(a.lastAt));
|
||||||
|
return summaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> _peers() async {
|
||||||
|
final raw = await _store.read(_indexKey);
|
||||||
|
if (raw == null || raw.isEmpty) return const [];
|
||||||
|
return raw.split('\n').where((s) => s.isNotEmpty).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _rememberPeer(String peer) async {
|
||||||
|
final peers = await _peers();
|
||||||
|
if (peers.contains(peer)) return;
|
||||||
|
await _store.write(_indexKey, [...peers, peer].join('\n'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@ class AppDrawer extends StatelessWidget {
|
||||||
_DrawerItem(
|
_DrawerItem(
|
||||||
icon: const Icon(Icons.chat_bubble),
|
icon: const Icon(Icons.chat_bubble),
|
||||||
label: t.menu.chat,
|
label: t.menu.chat,
|
||||||
|
onTap: marketEnabled
|
||||||
|
? () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
context.go('/messages');
|
||||||
|
}
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
_DrawerItem(
|
_DrawerItem(
|
||||||
icon: const Icon(Icons.favorite),
|
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)}';
|
||||||
|
|
@ -34,6 +34,18 @@ void main() {
|
||||||
expect((await store.history('b')).single.text, 'toB');
|
expect((await store.history('b')).single.text, 'toB');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('conversations lists peers newest-first with their last message',
|
||||||
|
() async {
|
||||||
|
await store.append('peerA', msg('me', 'hi A', 1000));
|
||||||
|
await store.append('peerB', msg('peerB', 'yo B', 3000));
|
||||||
|
await store.append('peerA', msg('peerA', 'back A', 2000));
|
||||||
|
|
||||||
|
final convos = await store.conversations();
|
||||||
|
expect(convos.map((c) => c.peerPubkey), ['peerB', 'peerA']); // 3000 > 2000
|
||||||
|
expect(convos.first.lastText, 'yo B');
|
||||||
|
expect(convos.last.lastText, 'back A');
|
||||||
|
});
|
||||||
|
|
||||||
test('history is capped to the most recent 200', () async {
|
test('history is capped to the most recent 200', () async {
|
||||||
for (var i = 0; i < 210; i++) {
|
for (var i = 0; i < 210; i++) {
|
||||||
await store.append('peer', msg('me', 'm$i', i));
|
await store.append('peer', msg('me', 'm$i', i));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue