feat(block2): market screen — discover seeds near you (offers UI)

Wires the 'coming soon' market card to a real discovery screen (mockups 04/05):
- MarketScreen: opens an OfferTransport lazily; local-first, so it degrades to a
  human 'set up sharing' prompt when no relay/area is set or the net is down.
- Discovers offers by the saved coarse area, lists them with human reciprocity
  labels (gift/swap/for sale), 'near you' (never exact location), price only for
  sales. Config sheet to set area + community servers (no bundled public relays).
- Threaded via TaneApp(social, socialSettings) — null keeps the card 'coming
  soon' (so Block 1 tests pass unchanged); main wires it live.
- i18n en/es/pt (market.*), reuses share.* for type labels.

3 market widget tests + home tests green; app_seeds analyzes clean.
This commit is contained in:
vjrj 2026-07-10 03:05:12 +02:00
parent 528b499209
commit c696956ee2
8 changed files with 637 additions and 16 deletions

View file

@ -8,12 +8,15 @@ import 'data/species_repository.dart';
import 'data/variety_repository.dart';
import 'i18n/strings.g.dart';
import 'services/onboarding_store.dart';
import 'services/social_service.dart';
import 'services/social_settings.dart';
import 'state/inventory_cubit.dart';
import 'state/variety_detail_cubit.dart';
import 'ui/about_screen.dart';
import 'ui/home_screen.dart';
import 'ui/intro_screen.dart';
import 'ui/inventory_list_screen.dart';
import 'ui/market_screen.dart';
import 'ui/settings_screen.dart';
import 'ui/theme.dart';
import 'ui/variety_detail_screen.dart';
@ -26,13 +29,26 @@ class TaneApp extends StatelessWidget {
required this.repository,
required this.species,
required this.onboarding,
this.social,
this.socialSettings,
this.showIntro = false,
super.key,
}) : _router = _buildRouter(repository, onboarding, showIntro);
}) : _router = _buildRouter(
repository,
onboarding,
showIntro,
social,
socialSettings,
);
final VarietyRepository repository;
final SpeciesRepository species;
final OnboardingStore onboarding;
/// Block 2 social layer. Null until the social round is wired in `main`; when
/// null the market stays a "coming soon" card and `/market` is not routed.
final SocialService? social;
final SocialSettings? socialSettings;
final bool showIntro;
final GoRouter _router;
@ -40,11 +56,23 @@ class TaneApp extends StatelessWidget {
VarietyRepository repository,
OnboardingStore onboarding,
bool showIntro,
SocialService? social,
SocialSettings? socialSettings,
) {
return GoRouter(
initialLocation: showIntro ? '/intro' : '/',
routes: [
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/',
builder: (context, state) =>
HomeScreen(marketEnabled: social != null),
),
if (social != null && socialSettings != null)
GoRoute(
path: '/market',
builder: (context, state) =>
MarketScreen(social: social, settings: socialSettings),
),
GoRoute(
path: '/intro',
builder: (context, state) => IntroScreen(