fix(block2): sharing config UX + discovery + labels (raw feedback batch)
From on-device feedback: - Area is no longer shown as a raw geohash 'name'. 'Use my location' is the primary action; a human status line says whether your area is set (coarse, never exact). The geohash code + the relay servers moved under 'Advanced' (code field labelled 'a code like sp3e9 — not a place name'), which also brings back node/relay selection that had been fully hidden. - Location denial no longer yanks you into system settings — returns quietly with an actionable inline message. - Discovery no longer spins forever when nobody's sharing nearby: a timeout resolves 'searching' to the empty state. - Home card 'Open market' -> 'Market' + 'Discover and share seeds nearby' (the word 'open' confused). i18n en/es/pt; analyzer clean; the use-my-location test asserts the status flip.
This commit is contained in:
parent
d7136ec2c2
commit
5f87ad2d56
11 changed files with 208 additions and 90 deletions
|
|
@ -431,6 +431,7 @@ class _ConfigSheet extends StatefulWidget {
|
|||
|
||||
class _ConfigSheetState extends State<_ConfigSheet> {
|
||||
final _area = TextEditingController();
|
||||
final _servers = TextEditingController();
|
||||
bool _loading = true;
|
||||
bool _locationBusy = false;
|
||||
String? _locationError;
|
||||
|
|
@ -443,13 +444,13 @@ class _ConfigSheetState extends State<_ConfigSheet> {
|
|||
|
||||
Future<void> _load() async {
|
||||
_area.text = await widget.settings.areaGeohash();
|
||||
_servers.text = (await widget.settings.relayUrls()).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(_servers.text.split('\n'));
|
||||
if (mounted) Navigator.of(context).pop(true);
|
||||
}
|
||||
|
||||
|
|
@ -477,12 +478,14 @@ class _ConfigSheetState extends State<_ConfigSheet> {
|
|||
@override
|
||||
void dispose() {
|
||||
_area.dispose();
|
||||
_servers.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
final hasArea = _area.text.trim().isNotEmpty;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
|
|
@ -491,40 +494,34 @@ class _ConfigSheetState extends State<_ConfigSheet> {
|
|||
bottom: MediaQuery.of(context).viewInsets.bottom + 20,
|
||||
),
|
||||
child: _loading
|
||||
? const SizedBox(height: 120, child: Center(child: CircularProgressIndicator()))
|
||||
? const SizedBox(
|
||||
height: 120, child: Center(child: CircularProgressIndicator()))
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
t.market.configTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: seedTitle,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
t.market.configTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: seedTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
t.market.setupIntro,
|
||||
style: const TextStyle(color: seedMuted, fontSize: 13, height: 1.4),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
key: const Key('market.area'),
|
||||
controller: _area,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.market.areaLabel,
|
||||
helperText: t.market.areaHelp,
|
||||
helperMaxLines: 3,
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
t.market.setupIntro,
|
||||
style: const TextStyle(
|
||||
color: seedMuted, fontSize: 13, height: 1.4),
|
||||
),
|
||||
),
|
||||
if (widget.location != null)
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: TextButton.icon(
|
||||
const SizedBox(height: 18),
|
||||
// Setting your area from device location is the human path;
|
||||
// typing a code is the advanced fallback.
|
||||
if (widget.location != null)
|
||||
FilledButton.tonalIcon(
|
||||
key: const Key('market.useLocation'),
|
||||
onPressed: _locationBusy ? null : _useLocation,
|
||||
icon: _locationBusy
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
|
|
@ -533,26 +530,85 @@ class _ConfigSheetState extends State<_ConfigSheet> {
|
|||
)
|
||||
: const Icon(Icons.my_location, size: 18),
|
||||
label: Text(t.market.useLocation),
|
||||
onPressed: _locationBusy ? null : _useLocation,
|
||||
),
|
||||
if (_locationError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
_locationError!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFB3261E), fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
hasArea
|
||||
? Icons.check_circle
|
||||
: Icons.pending_outlined,
|
||||
size: 18,
|
||||
color: hasArea ? seedGreen : seedMuted,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
hasArea ? t.market.areaSet : t.market.areaNotSet,
|
||||
style: TextStyle(
|
||||
color: hasArea ? seedOnSurface : seedMuted,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
key: const Key('market.advanced'),
|
||||
tilePadding: EdgeInsets.zero,
|
||||
childrenPadding: const EdgeInsets.only(bottom: 8),
|
||||
title: Text(
|
||||
t.market.advanced,
|
||||
style: const TextStyle(fontSize: 13, color: seedMuted),
|
||||
),
|
||||
children: [
|
||||
TextField(
|
||||
key: const Key('market.area'),
|
||||
controller: _area,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: InputDecoration(
|
||||
labelText: t.market.areaCodeLabel,
|
||||
helperText: t.market.areaCodeHint,
|
||||
helperMaxLines: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
key: const Key('market.servers'),
|
||||
controller: _servers,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.market.serversLabel,
|
||||
helperText: t.market.serversHelp,
|
||||
helperMaxLines: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_locationError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
_locationError!,
|
||||
style: const TextStyle(color: Color(0xFFB3261E), fontSize: 12),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton(
|
||||
key: const Key('market.save'),
|
||||
onPressed: _save,
|
||||
child: Text(t.market.save),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton(
|
||||
key: const Key('market.save'),
|
||||
onPressed: _save,
|
||||
child: Text(t.market.save),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue