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:
parent
68b336ec93
commit
aa457ab91a
12 changed files with 92 additions and 108 deletions
|
|
@ -11,7 +11,11 @@ import 'theme.dart';
|
|||
/// chat…) belong to Block 2 and are greyed with a "soon" tag, and Settings sits
|
||||
/// pinned at the bottom.
|
||||
class AppDrawer extends StatelessWidget {
|
||||
const AppDrawer({super.key});
|
||||
const AppDrawer({this.marketEnabled = false, super.key});
|
||||
|
||||
/// When the Block 2 social layer is wired, the Market becomes a live drawer
|
||||
/// destination; other social items stay "soon" until they're built.
|
||||
final bool marketEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -34,6 +38,12 @@ class AppDrawer extends StatelessWidget {
|
|||
icon: const Icon(Symbols.storefront),
|
||||
label: t.menu.market,
|
||||
divider: true,
|
||||
onTap: marketEnabled
|
||||
? () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/market');
|
||||
}
|
||||
: null,
|
||||
),
|
||||
_DrawerItem(icon: const Icon(Icons.person), label: t.menu.profile),
|
||||
_DrawerItem(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class HomeScreen extends StatelessWidget {
|
|||
final t = context.t;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(t.app.title)),
|
||||
drawer: const AppDrawer(),
|
||||
drawer: AppDrawer(marketEnabled: marketEnabled),
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: _SeedWatermark()),
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue