feat(offers): asking price on lots, published with sale offers (schema v11)

The data model always meant sale offers to carry an informational price
(data-model §2.8, sharing-model §4.4); with offer state collapsed onto
the Lot, the price lives there. Null amount on a sell lot = 'to be
agreed' — the NIP-99 event simply omits the price tag.

- schema v11: Lots.price_amount/price_currency, guarded migration,
  schema dump + generated schema_v11 for the round-trip test
- ShareableLot/VarietyLot carry the price (sell-only in shareableLots);
  addLot/updateLot persist it and clear it when the lot leaves sale
- OffersCubit.publishLots forwards it (outbox flush picks it up free)
- lot sheet: price + currency inputs revealed only on 'For sale', with
  the €/Ğ1/hours quick-picks extracted to a shared CurrencyQuickPicks
  widget (reused by the sale sheet)
- backup JSON round-trips the new columns
- i18n share.price/priceHint in en/es/pt/ast
This commit is contained in:
vjrj 2026-07-11 07:24:23 +02:00
parent 89addb1ed7
commit cfa1053842
25 changed files with 4690 additions and 31 deletions

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import '../data/variety_repository.dart';
import '../db/enums.dart';
import '../i18n/strings.g.dart';
import 'currency_quick_picks.dart';
import 'theme.dart';
/// Opens the "record a sale" sheet. Optionally pre-attached to [varietyId]
@ -140,22 +141,10 @@ class _SaleSheetState extends State<_SaleSheet> {
],
),
const SizedBox(height: 8),
// Quick picks so a currency is one tap. Ğ1 sits quietly among the
// familiar ones offered, never imposed and free text still takes
// anything else.
Wrap(
spacing: 8,
children: [
for (final c in ['', 'Ğ1', t.sale.hours])
ChoiceChip(
key: Key('sale.currencyChip.$c'),
label: Text(c),
selected: _currency.text.trim() == c,
onSelected: (sel) => setState(() {
_currency.text = sel ? c : '';
}),
),
],
CurrencyQuickPicks(
controller: _currency,
keyPrefix: 'sale',
onChanged: () => setState(() {}),
),
const SizedBox(height: 12),
TextField(