feat(block2): messaging UI — private 1:1 chat (NIP-17)

- MessagesCubit: 1:1 conversation over MessageTransport. Filters the shared
  inbox to the peer; tags our own sends with our pubkey (NIP-17 wraps don't come
  back to the sender) so the UI can align bubbles; degrades gracefully offline.
- ChatScreen (mockup 10): message bubbles (mine right / peer left) + a composer;
  opens a session lazily, shows 'set up sharing' when there's no relay.
- Entry point: a 'Message' button on each offer card (hidden on your own
  listings) → /chat/:pubkey.
- Routing + i18n en/es/pt.

Tests: 5 cubit (send/receive/order/filter/offline) + chat offline + market, green.
This commit is contained in:
vjrj 2026-07-10 10:59:47 +02:00
parent 0dd7c28039
commit efd64c4da4
13 changed files with 627 additions and 9 deletions

View file

@ -70,6 +70,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
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$market$en market = Translations$market$en.internal(_root);
late final Translations$chat$en chat = Translations$chat$en.internal(_root);
}
// Path: app
@ -1085,6 +1086,9 @@ class Translations$market$en {
/// en: 'Near you'
String get near => 'Near you';
/// en: 'Message'
String get contact => 'Message';
/// en: 'Sharing setup'
String get configTitle => 'Sharing setup';
@ -1134,6 +1138,30 @@ class Translations$market$en {
String get queued => 'Saved — we\'ll share these when you\'re connected';
}
// Path: chat
class Translations$chat$en {
Translations$chat$en.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'Chat'
String get title => 'Chat';
/// en: 'Write a message…'
String get hint => 'Write a message…';
/// en: 'Send'
String get send => 'Send';
/// en: 'No messages yet — say hello'
String get empty => 'No messages yet — say hello';
/// en: 'Set up sharing to send messages'
String get offline => 'Set up sharing to send messages';
}
// Path: intro.slides
class Translations$intro$slides$en {
Translations$intro$slides$en.internal(this._root);
@ -1895,6 +1923,7 @@ extension on Translations {
'market.searching' => 'Looking around your area…',
'market.empty' => 'No seeds shared near you yet',
'market.near' => 'Near you',
'market.contact' => 'Message',
'market.configTitle' => 'Sharing setup',
'market.setupIntro' => 'Sharing with people nearby is optional. To turn it on, set your rough area and join a community you trust — a seed network or collective that runs a shared meeting point for passing messages. No company in the middle.',
'market.areaLabel' => 'Your area',
@ -1911,6 +1940,11 @@ extension on Translations {
'market.useLocation' => 'Use my approximate location',
'market.locationFailed' => 'Couldn\'t get your location',
'market.queued' => 'Saved — we\'ll share these when you\'re connected',
'chat.title' => 'Chat',
'chat.hint' => 'Write a message…',
'chat.send' => 'Send',
'chat.empty' => 'No messages yet — say hello',
'chat.offline' => 'Set up sharing to send messages',
_ => null,
};
}