feat(block2): publish my seeds to the network (completes publish->discover)

The other half of the market: share your marked lots as offers.
- VarietyRepository.shareableLots(): non-private lots WITH their id (stable,
  updatable offer id) + the variety label. Private lots never leave the device.
- OffersCubit.publishLots(): maps each via OfferMapper and publishes, tagged
  with the coarse area + signed by the derived key; counts acceptances; no-op
  offline or with no area.
- Market screen: a 'share my seeds' action (online only) — publishes, then
  re-discovers to show them; 'mark some seeds first' when nothing's shared.
- i18n en/es/pt.

14 tests green (repo query, cubit publish, market UI); analyzer clean for new code.
This commit is contained in:
vjrj 2026-07-10 10:18:30 +02:00
parent cb5f55e146
commit f8f73c4153
12 changed files with 284 additions and 5 deletions

View file

@ -2,6 +2,7 @@ import 'package:commons_core/commons_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../data/variety_repository.dart';
import '../i18n/strings.g.dart';
import '../services/social_service.dart';
import '../services/social_settings.dart';
@ -57,6 +58,36 @@ class _MarketScreenState extends State<MarketScreen> {
if (changed == true) await _init();
}
/// Publishes the user's shared lots as offers to the network, tagged with the
/// coarse area. Prompts for setup when the area isn't set.
Future<void> _shareMine() async {
final cubit = _cubit;
if (cubit == null || !cubit.isOnline) return;
final repo = context.read<VarietyRepository>();
final messenger = ScaffoldMessenger.of(context);
final t = context.t;
final area = await widget.settings.areaGeohash();
if (!mounted) return;
if (area.isEmpty) {
await _openConfig();
return;
}
final lots = await repo.shareableLots();
if (!mounted) return;
if (lots.isEmpty) {
messenger.showSnackBar(SnackBar(content: Text(t.market.nothingToShare)));
return;
}
final count = await cubit.publishLots(
lots,
authorPubkeyHex: widget.social.publicKeyHex,
areaGeohash: area,
);
if (!mounted) return;
messenger.showSnackBar(SnackBar(content: Text(t.market.sharedCount(n: count))));
await cubit.discover(area); // refresh now including the just-shared lots
}
@override
void dispose() {
_cubit?.close();
@ -71,6 +102,13 @@ class _MarketScreenState extends State<MarketScreen> {
appBar: AppBar(
title: Text(t.market.title),
actions: [
if (cubit != null && cubit.isOnline)
IconButton(
key: const Key('market.shareMine'),
icon: const Icon(Icons.ios_share_outlined),
tooltip: t.market.shareMine,
onPressed: _shareMine,
),
IconButton(
key: const Key('market.config'),
icon: const Icon(Icons.tune),