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:
parent
0dd7c28039
commit
efd64c4da4
13 changed files with 627 additions and 9 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../data/variety_repository.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
|
|
@ -166,17 +167,27 @@ class _MarketScreenState extends State<MarketScreen> {
|
|||
? const Center(child: CircularProgressIndicator())
|
||||
: BlocProvider.value(
|
||||
value: cubit,
|
||||
child: MarketBody(onConfigure: _openConfig),
|
||||
child: MarketBody(
|
||||
onConfigure: _openConfig,
|
||||
selfPubkey: widget.social.publicKeyHex,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MarketBody extends StatelessWidget {
|
||||
const MarketBody({required this.onConfigure, super.key});
|
||||
const MarketBody({
|
||||
required this.onConfigure,
|
||||
this.selfPubkey,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final VoidCallback onConfigure;
|
||||
|
||||
/// This user's own pubkey, so we don't offer to message our own listings.
|
||||
final String? selfPubkey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
|
|
@ -215,7 +226,15 @@ class MarketBody extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(16),
|
||||
itemCount: state.offers.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) => _OfferCard(offer: state.offers[i]),
|
||||
itemBuilder: (context, i) {
|
||||
final o = state.offers[i];
|
||||
return _OfferCard(
|
||||
offer: o,
|
||||
onContact: o.authorPubkeyHex == selfPubkey
|
||||
? null
|
||||
: () => context.go('/chat/${o.authorPubkeyHex}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -225,10 +244,13 @@ class MarketBody extends StatelessWidget {
|
|||
/// One discovered offer. Human words for the reciprocity mode; coarse "near you"
|
||||
/// instead of any precise location; price only when it's a sale.
|
||||
class _OfferCard extends StatelessWidget {
|
||||
const _OfferCard({required this.offer});
|
||||
const _OfferCard({required this.offer, this.onContact});
|
||||
|
||||
final Offer offer;
|
||||
|
||||
/// Opens a private chat with the author; null for our own listings.
|
||||
final VoidCallback? onContact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
|
|
@ -263,8 +285,8 @@ class _OfferCard extends StatelessWidget {
|
|||
const Icon(Icons.place_outlined, size: 15, color: seedMuted),
|
||||
const SizedBox(width: 4),
|
||||
Text(t.market.near, style: const TextStyle(color: seedMuted, fontSize: 13)),
|
||||
if (offer.type == OfferType.sale && offer.priceAmount != null) ...[
|
||||
const Spacer(),
|
||||
const Spacer(),
|
||||
if (offer.type == OfferType.sale && offer.priceAmount != null)
|
||||
Text(
|
||||
'${offer.priceAmount} ${offer.priceCurrency ?? ''}'.trim(),
|
||||
style: const TextStyle(
|
||||
|
|
@ -272,7 +294,12 @@ class _OfferCard extends StatelessWidget {
|
|||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (onContact != null)
|
||||
TextButton.icon(
|
||||
onPressed: onContact,
|
||||
icon: const Icon(Icons.chat_bubble_outline, size: 16),
|
||||
label: Text(t.market.contact),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue