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:
vjrj 2026-07-10 13:32:11 +02:00
parent d7136ec2c2
commit 5f87ad2d56
11 changed files with 208 additions and 90 deletions

View file

@ -76,6 +76,7 @@ class OffersCubit extends Cubit<OffersState> {
/// (null in tests, where the transport is a fake with nothing to close).
final Future<void> Function()? _onDispose;
StreamSubscription<Offer>? _sub;
Timer? _searchTimeout;
/// Whether a live transport is available (relay configured and reachable).
bool get isOnline => _transport != null;
@ -88,6 +89,7 @@ class OffersCubit extends Cubit<OffersState> {
return;
}
await _sub?.cancel();
_searchTimeout?.cancel();
emit(OffersState(
areaGeohash: geohashPrefix,
searching: true,
@ -99,6 +101,14 @@ class OffersCubit extends Cubit<OffersState> {
onError: (Object e) =>
emit(state.copyWith(searching: false, error: () => '$e')),
);
// The discover stream stays open for live offers and never signals "done",
// so stop the spinner after a beat: no results show the empty state, not
// an endless "searching".
_searchTimeout = Timer(const Duration(seconds: 6), () {
if (!isClosed && state.searching) {
emit(state.copyWith(searching: false));
}
});
}
/// Publishes [offer]; returns the transport's verdict. No-op result when
@ -157,6 +167,7 @@ class OffersCubit extends Cubit<OffersState> {
@override
Future<void> close() async {
_searchTimeout?.cancel();
await _sub?.cancel();
await _onDispose?.call();
return super.close();