feat(plantare): UI — propose from chat, review & sign on the ledger

The viral loop, made usable end-to-end:

- Propose sheet (plantare_propose_sheet.dart): from a chat with a peer
  (their key already in hand), pick your side, name the seed, choose what
  comes back (similar · non-GMO organic / work hours / other) and an
  optional return-by, then sign and send. Reached from the chat overflow
  menu; confirms "waiting for them to sign".
- Plantares screen: incoming proposals show Accept & sign / Decline right
  on the tile; every bilateral row wears a handshake badge — awaiting
  signature, signed by both, or declined. Accept falls back to a gentle
  "offline" nudge if the proposal isn't in hand yet.
- i18n: full en + es for the bilateral flow; other locales fall back to
  base per slang config.

Wire-up only touches human copy (no jargon) and stays behind the
optional PlantareService, so inventory-only builds are unaffected.

Tests: propose sheet drives a real sign+send; the ledger renders the
signed/awaiting badges. Fake SocialSession in your_people test gains the
new transport getter. Widget tests targeted + timed. All green.
This commit is contained in:
vjrj 2026-07-14 11:19:04 +02:00
parent b607ddde41
commit e78656bc07
11 changed files with 2223 additions and 1459 deletions

View file

@ -63,6 +63,57 @@ void main() {
await disposeTree(tester);
});
testWidgets('a fully-signed bilateral pledge shows the "signed by both" badge',
(tester) async {
LocaleSettings.setLocaleSync(AppLocale.en);
final repo = newTestRepository(db);
await repo.createPlantare(
direction: PlantareDirection.owedToMe,
counterparty: 'Ana',
pledgeId: 'p-1',
debtorKey: 'dk',
creditorKey: 'ck',
debtorSignature: 'ds',
creditorSignature: 'cs',
remoteState: PlantareRemoteState.accepted,
);
await tester.pumpWidget(
wrapScreen(repository: repo, child: const PlantaresScreen()),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('Signed by both'), findsOneWidget);
// No accept/decline for a closed deal.
expect(find.byKey(const Key('plantare.accept.')), findsNothing);
await disposeTree(tester);
});
testWidgets('a pending proposal shows the "awaiting signature" badge',
(tester) async {
LocaleSettings.setLocaleSync(AppLocale.en);
final repo = newTestRepository(db);
await repo.createPlantare(
direction: PlantareDirection.iReturn,
counterparty: 'Ben',
pledgeId: 'p-2',
debtorKey: 'dk',
creditorKey: 'ck',
creditorSignature: 'cs', // proposer (creditor) signed; I (debtor) haven't
remoteState: PlantareRemoteState.proposed,
);
await tester.pumpWidget(
wrapScreen(repository: repo, child: const PlantaresScreen()),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('Awaiting signature'), findsOneWidget);
await disposeTree(tester);
});
testWidgets('tapping a linked commitment opens its variety', (tester) async {
final repo = newTestRepository(db);
final vid = await repo.addQuickVariety(label: 'Judía');