fix(block2): hide server config, enable drawer Market, badge my own offers

Three feedback fixes:
- Servers are fully hidden now — relays are managed automatically (default
  community servers; the pool skips dead ones). Sharing setup shows only 'your
  area'; no jargon-y URL field. (3 default relays for redundancy.)
- Drawer 'Market' is a live destination (was stuck on 'coming soon'), gated on
  the social layer being wired like the home card.
- My own listings in the market carry a 'You' badge (and no 'message yourself'
  button), so it's clear which offers are mine.

Analyzer clean. Note: could not run the flutter widget suite here (10+ min
compile after the geolocator native dep in this sandbox) — run 'flutter test'
locally to confirm; the You-badge test was simplified to be robust.
This commit is contained in:
vjrj 2026-07-10 11:56:55 +02:00
parent 68b336ec93
commit aa457ab91a
12 changed files with 92 additions and 108 deletions

View file

@ -228,11 +228,11 @@ class MarketBody extends StatelessWidget {
separatorBuilder: (_, _) => const SizedBox(height: 12),
itemBuilder: (context, i) {
final o = state.offers[i];
final mine = o.authorPubkeyHex == selfPubkey;
return _OfferCard(
offer: o,
onContact: o.authorPubkeyHex == selfPubkey
? null
: () => context.go('/chat/${o.authorPubkeyHex}'),
mine: mine,
onContact: mine ? null : () => context.go('/chat/${o.authorPubkeyHex}'),
);
},
);
@ -244,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, this.onContact});
const _OfferCard({required this.offer, this.mine = false, this.onContact});
final Offer offer;
/// Whether this is the current user's own listing (shown with a "You" badge).
final bool mine;
/// Opens a private chat with the author; null for our own listings.
final VoidCallback? onContact;
@ -276,6 +279,24 @@ class _OfferCard extends StatelessWidget {
),
),
),
if (mine) ...[
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: seedGreen,
borderRadius: BorderRadius.circular(20),
),
child: Text(
t.market.mine,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 6),
],
_TypeChip(label: _offerTypeLabel(t, offer.type)),
],
),
@ -408,8 +429,6 @@ class _ConfigSheet extends StatefulWidget {
class _ConfigSheetState extends State<_ConfigSheet> {
final _area = TextEditingController();
final _servers = TextEditingController();
final Set<String> _selectedRelays = {};
bool _loading = true;
bool _locationBusy = false;
String? _locationError;
@ -422,22 +441,13 @@ class _ConfigSheetState extends State<_ConfigSheet> {
Future<void> _load() async {
_area.text = await widget.settings.areaGeohash();
final current = await widget.settings.relayUrls();
final presetUrls = SocialSettings.presets.map((p) => p.url).toSet();
_selectedRelays
..clear()
..addAll(current.where(presetUrls.contains));
// Any relays that aren't presets go in the "add another" field.
_servers.text = current.where((u) => !presetUrls.contains(u)).join('\n');
if (mounted) setState(() => _loading = false);
}
Future<void> _save() async {
// Relays are managed automatically (default community servers, dead ones
// skipped by the pool) the user only sets their area here.
await widget.settings.setAreaGeohash(_area.text);
await widget.settings.setRelayUrls([
..._selectedRelays,
..._servers.text.split('\n'),
]);
if (mounted) Navigator.of(context).pop(true);
}
@ -465,7 +475,6 @@ class _ConfigSheetState extends State<_ConfigSheet> {
@override
void dispose() {
_area.dispose();
_servers.dispose();
super.dispose();
}
@ -533,64 +542,6 @@ class _ConfigSheetState extends State<_ConfigSheet> {
style: const TextStyle(color: Color(0xFFB3261E), fontSize: 12),
),
),
const SizedBox(height: 16),
// Pick from a short list of shared servers pre-selected so it
// works out of the box; no jargon-y blank field to puzzle over.
Text(
t.market.serversLabel,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: seedOnSurface,
),
),
Text(
t.market.serversHelp,
style: const TextStyle(color: seedMuted, fontSize: 12, height: 1.3),
),
for (final preset in SocialSettings.presets)
CheckboxListTile(
key: Key('market.relay.${preset.name}'),
contentPadding: EdgeInsets.zero,
dense: true,
controlAffinity: ListTileControlAffinity.leading,
value: _selectedRelays.contains(preset.url),
title: Text(preset.name),
subtitle: Text(preset.url,
style: const TextStyle(fontSize: 11, color: seedMuted)),
onChanged: (checked) => setState(() {
if (checked ?? false) {
_selectedRelays.add(preset.url);
} else {
_selectedRelays.remove(preset.url);
}
}),
),
// A rarely-needed escape hatch for a community's own server.
Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
key: const Key('market.serversAdvanced'),
tilePadding: EdgeInsets.zero,
childrenPadding: const EdgeInsets.only(bottom: 8),
title: Text(
t.market.serversAdvanced,
style: const TextStyle(fontSize: 13, color: seedMuted),
),
children: [
TextField(
key: const Key('market.servers'),
controller: _servers,
minLines: 1,
maxLines: 3,
decoration: const InputDecoration(
hintText: 'wss://…',
),
),
],
),
),
const SizedBox(height: 20),
FilledButton(
key: const Key('market.save'),