feat(plantare): usable UI — commitments screen, add sheet, entry points
The reproduction-commitment (Plantare) UI, local-first and plain-spoken: - A Plantares screen (drawer entry, /plantares) listing your commitments, grouped Open / Done, with a big 'mark returned', a menu (let it go / reopen / remove) and an empty-state that explains what a Plantare is. - An add sheet (scrollable): a two-choice 'who reproduces & returns' (I return / owed to me), an optional 'with whom', an optional 'what comes back' in the grower's own words, and a note. No sale, no currency — a promise. - Reachable from a seed too: an 'Add a commitment' action on the variety detail, pre-attached to that variety. - i18n en/es/pt/ast (human words: 'Plantare' + a plain explanation). Added the Plantares screen to the small-phone overflow guard (18 cases green).
This commit is contained in:
parent
81094f25a8
commit
de6938d5d7
15 changed files with 730 additions and 2 deletions
|
|
@ -32,6 +32,7 @@ import 'ui/chat_screen.dart';
|
||||||
import 'ui/home_screen.dart';
|
import 'ui/home_screen.dart';
|
||||||
import 'ui/intro_screen.dart';
|
import 'ui/intro_screen.dart';
|
||||||
import 'ui/inventory_list_screen.dart';
|
import 'ui/inventory_list_screen.dart';
|
||||||
|
import 'ui/plantares_screen.dart';
|
||||||
import 'ui/market_offer_detail_screen.dart';
|
import 'ui/market_offer_detail_screen.dart';
|
||||||
import 'ui/market_screen.dart';
|
import 'ui/market_screen.dart';
|
||||||
import 'ui/offline_banner.dart';
|
import 'ui/offline_banner.dart';
|
||||||
|
|
@ -254,6 +255,11 @@ class TaneApp extends StatelessWidget {
|
||||||
child: const InventoryListScreen(),
|
child: const InventoryListScreen(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Local reproduction commitments — inventory-adjacent, no network.
|
||||||
|
GoRoute(
|
||||||
|
path: '/plantares',
|
||||||
|
builder: (context, state) => const PlantaresScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/variety/:id',
|
path: '/variety/:id',
|
||||||
builder: (context, state) => BlocProvider(
|
builder: (context, state) => BlocProvider(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
"chat": "Charra",
|
"chat": "Charra",
|
||||||
"wishlist": "Llista de deseos",
|
"wishlist": "Llista de deseos",
|
||||||
"following": "Siguiendo",
|
"following": "Siguiendo",
|
||||||
|
"plantares": "Plantares",
|
||||||
"settings": "Axustes"
|
"settings": "Axustes"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
@ -552,5 +553,29 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"newMessageFrom": "Mensaxe nuevu de {name}"
|
"newMessageFrom": "Mensaxe nuevu de {name}"
|
||||||
|
},
|
||||||
|
"plantare": {
|
||||||
|
"title": "Plantares",
|
||||||
|
"help": "Un Plantare ye'l compromisu de reproducir una semilla y devolver una parte — asina una variedá sigue viaxando de mano en mano. Nun ye una venta.",
|
||||||
|
"add": "Amestar compromisu",
|
||||||
|
"empty": "Entá nun hai compromisos. Cuando compartas o recibas semilla col compromisu de reproducila y devolver daqué, anótalo equí.",
|
||||||
|
"iReturn": "Reprodúzola y devuélvola yo",
|
||||||
|
"owedToMe": "Devuélvenmela a min",
|
||||||
|
"direction": "Quién reproduz y devuelve",
|
||||||
|
"counterparty": "¿Con quién?",
|
||||||
|
"counterpartyHint": "Una persona o un coleutivu (opcional)",
|
||||||
|
"owed": "¿Qué se devuelve?",
|
||||||
|
"owedHint": "p. ex. un puñáu la próxima temporada (opcional)",
|
||||||
|
"note": "Nota (opcional)",
|
||||||
|
"save": "Guardar",
|
||||||
|
"markReturned": "Marcar devueltu",
|
||||||
|
"markForgiven": "Dar por saldáu",
|
||||||
|
"reopen": "Reabrir",
|
||||||
|
"delete": "Quitar",
|
||||||
|
"statusReturned": "Devueltu",
|
||||||
|
"statusForgiven": "Saldáu",
|
||||||
|
"openSection": "Pendientes",
|
||||||
|
"settledSection": "Fechos",
|
||||||
|
"removeConfirm": "¿Quitar esti compromisu?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
"wishlist": "Wishlist",
|
"wishlist": "Wishlist",
|
||||||
"following": "Following",
|
"following": "Following",
|
||||||
|
"plantares": "Plantares",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
@ -555,5 +556,29 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"newMessageFrom": "New message from {name}"
|
"newMessageFrom": "New message from {name}"
|
||||||
|
},
|
||||||
|
"plantare": {
|
||||||
|
"title": "Plantares",
|
||||||
|
"help": "A Plantare is a promise to reproduce a seed and return some — how a variety keeps travelling from hand to hand. It's not a sale.",
|
||||||
|
"add": "Add a commitment",
|
||||||
|
"empty": "No commitments yet. When you share or receive seed with a promise to grow it out and return some, note it here.",
|
||||||
|
"iReturn": "I'll grow it out & return some",
|
||||||
|
"owedToMe": "They'll return some to me",
|
||||||
|
"direction": "Who reproduces & returns",
|
||||||
|
"counterparty": "With whom?",
|
||||||
|
"counterpartyHint": "A person or a collective (optional)",
|
||||||
|
"owed": "What comes back?",
|
||||||
|
"owedHint": "e.g. a handful next season (optional)",
|
||||||
|
"note": "Note (optional)",
|
||||||
|
"save": "Save",
|
||||||
|
"markReturned": "Mark returned",
|
||||||
|
"markForgiven": "Let it go",
|
||||||
|
"reopen": "Reopen",
|
||||||
|
"delete": "Remove",
|
||||||
|
"statusReturned": "Returned",
|
||||||
|
"statusForgiven": "Settled",
|
||||||
|
"openSection": "Open",
|
||||||
|
"settledSection": "Done",
|
||||||
|
"removeConfirm": "Remove this commitment?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
"wishlist": "Lista de deseos",
|
"wishlist": "Lista de deseos",
|
||||||
"following": "Siguiendo",
|
"following": "Siguiendo",
|
||||||
|
"plantares": "Plantares",
|
||||||
"settings": "Ajustes"
|
"settings": "Ajustes"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
@ -554,5 +555,29 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"newMessageFrom": "Nuevo mensaje de {name}"
|
"newMessageFrom": "Nuevo mensaje de {name}"
|
||||||
|
},
|
||||||
|
"plantare": {
|
||||||
|
"title": "Plantares",
|
||||||
|
"help": "Un Plantare es el compromiso de reproducir una semilla y devolver una parte — así una variedad sigue viajando de mano en mano. No es una venta.",
|
||||||
|
"add": "Añadir compromiso",
|
||||||
|
"empty": "Aún no hay compromisos. Cuando compartas o recibas semilla con el compromiso de reproducirla y devolver algo, anótalo aquí.",
|
||||||
|
"iReturn": "La reproduzco y devuelvo yo",
|
||||||
|
"owedToMe": "Me la devuelven a mí",
|
||||||
|
"direction": "Quién reproduce y devuelve",
|
||||||
|
"counterparty": "¿Con quién?",
|
||||||
|
"counterpartyHint": "Una persona o un colectivo (opcional)",
|
||||||
|
"owed": "¿Qué se devuelve?",
|
||||||
|
"owedHint": "p. ej. un puñado la próxima temporada (opcional)",
|
||||||
|
"note": "Nota (opcional)",
|
||||||
|
"save": "Guardar",
|
||||||
|
"markReturned": "Marcar devuelto",
|
||||||
|
"markForgiven": "Dar por saldado",
|
||||||
|
"reopen": "Reabrir",
|
||||||
|
"delete": "Quitar",
|
||||||
|
"statusReturned": "Devuelto",
|
||||||
|
"statusForgiven": "Saldado",
|
||||||
|
"openSection": "Pendientes",
|
||||||
|
"settledSection": "Hechos",
|
||||||
|
"removeConfirm": "¿Quitar este compromiso?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"chat": "Conversas",
|
"chat": "Conversas",
|
||||||
"wishlist": "Lista de desejos",
|
"wishlist": "Lista de desejos",
|
||||||
"following": "A seguir",
|
"following": "A seguir",
|
||||||
|
"plantares": "Plantares",
|
||||||
"settings": "Definições"
|
"settings": "Definições"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|
@ -551,5 +552,29 @@
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"newMessageFrom": "Nova mensagem de {name}"
|
"newMessageFrom": "Nova mensagem de {name}"
|
||||||
|
},
|
||||||
|
"plantare": {
|
||||||
|
"title": "Plantares",
|
||||||
|
"help": "Um Plantare é o compromisso de reproduzir uma semente e devolver uma parte — assim uma variedade continua a viajar de mão em mão. Não é uma venda.",
|
||||||
|
"add": "Adicionar compromisso",
|
||||||
|
"empty": "Ainda não há compromissos. Quando partilhares ou receberes semente com o compromisso de a reproduzir e devolver algo, anota-o aqui.",
|
||||||
|
"iReturn": "Reproduzo e devolvo eu",
|
||||||
|
"owedToMe": "Devolvem-me a mim",
|
||||||
|
"direction": "Quem reproduz e devolve",
|
||||||
|
"counterparty": "Com quem?",
|
||||||
|
"counterpartyHint": "Uma pessoa ou um coletivo (opcional)",
|
||||||
|
"owed": "O que é devolvido?",
|
||||||
|
"owedHint": "p. ex. um punhado na próxima temporada (opcional)",
|
||||||
|
"note": "Nota (opcional)",
|
||||||
|
"save": "Guardar",
|
||||||
|
"markReturned": "Marcar devolvido",
|
||||||
|
"markForgiven": "Dar por saldado",
|
||||||
|
"reopen": "Reabrir",
|
||||||
|
"delete": "Remover",
|
||||||
|
"statusReturned": "Devolvido",
|
||||||
|
"statusForgiven": "Saldado",
|
||||||
|
"openSection": "Pendentes",
|
||||||
|
"settledSection": "Feitos",
|
||||||
|
"removeConfirm": "Remover este compromisso?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 4
|
/// Locales: 4
|
||||||
/// Strings: 1668 (417 per locale)
|
/// Strings: 1760 (440 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2026-07-10 at 19:42 UTC
|
/// Built on 2026-07-11 at 00:04 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint, unused_import
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class TranslationsAst extends Translations with BaseTranslations<AppLocale, Tran
|
||||||
@override late final _Translations$trust$ast trust = _Translations$trust$ast._(_root);
|
@override late final _Translations$trust$ast trust = _Translations$trust$ast._(_root);
|
||||||
@override late final _Translations$wot$ast wot = _Translations$wot$ast._(_root);
|
@override late final _Translations$wot$ast wot = _Translations$wot$ast._(_root);
|
||||||
@override late final _Translations$notifications$ast notifications = _Translations$notifications$ast._(_root);
|
@override late final _Translations$notifications$ast notifications = _Translations$notifications$ast._(_root);
|
||||||
|
@override late final _Translations$plantare$ast plantare = _Translations$plantare$ast._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: app
|
// Path: app
|
||||||
|
|
@ -157,6 +158,7 @@ class _Translations$menu$ast extends Translations$menu$en {
|
||||||
@override String get chat => 'Charra';
|
@override String get chat => 'Charra';
|
||||||
@override String get wishlist => 'Llista de deseos';
|
@override String get wishlist => 'Llista de deseos';
|
||||||
@override String get following => 'Siguiendo';
|
@override String get following => 'Siguiendo';
|
||||||
|
@override String get plantares => 'Plantares';
|
||||||
@override String get settings => 'Axustes';
|
@override String get settings => 'Axustes';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -806,6 +808,37 @@ class _Translations$notifications$ast extends Translations$notifications$en {
|
||||||
@override String newMessageFrom({required Object name}) => 'Mensaxe nuevu de ${name}';
|
@override String newMessageFrom({required Object name}) => 'Mensaxe nuevu de ${name}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: plantare
|
||||||
|
class _Translations$plantare$ast extends Translations$plantare$en {
|
||||||
|
_Translations$plantare$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsAst _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Plantares';
|
||||||
|
@override String get help => 'Un Plantare ye\'l compromisu de reproducir una semilla y devolver una parte — asina una variedá sigue viaxando de mano en mano. Nun ye una venta.';
|
||||||
|
@override String get add => 'Amestar compromisu';
|
||||||
|
@override String get empty => 'Entá nun hai compromisos. Cuando compartas o recibas semilla col compromisu de reproducila y devolver daqué, anótalo equí.';
|
||||||
|
@override String get iReturn => 'Reprodúzola y devuélvola yo';
|
||||||
|
@override String get owedToMe => 'Devuélvenmela a min';
|
||||||
|
@override String get direction => 'Quién reproduz y devuelve';
|
||||||
|
@override String get counterparty => '¿Con quién?';
|
||||||
|
@override String get counterpartyHint => 'Una persona o un coleutivu (opcional)';
|
||||||
|
@override String get owed => '¿Qué se devuelve?';
|
||||||
|
@override String get owedHint => 'p. ex. un puñáu la próxima temporada (opcional)';
|
||||||
|
@override String get note => 'Nota (opcional)';
|
||||||
|
@override String get save => 'Guardar';
|
||||||
|
@override String get markReturned => 'Marcar devueltu';
|
||||||
|
@override String get markForgiven => 'Dar por saldáu';
|
||||||
|
@override String get reopen => 'Reabrir';
|
||||||
|
@override String get delete => 'Quitar';
|
||||||
|
@override String get statusReturned => 'Devueltu';
|
||||||
|
@override String get statusForgiven => 'Saldáu';
|
||||||
|
@override String get openSection => 'Pendientes';
|
||||||
|
@override String get settledSection => 'Fechos';
|
||||||
|
@override String get removeConfirm => '¿Quitar esti compromisu?';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: intro.slides
|
// Path: intro.slides
|
||||||
class _Translations$intro$slides$ast extends Translations$intro$slides$en {
|
class _Translations$intro$slides$ast extends Translations$intro$slides$en {
|
||||||
_Translations$intro$slides$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
_Translations$intro$slides$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1173,6 +1206,7 @@ extension on TranslationsAst {
|
||||||
'menu.chat' => 'Charra',
|
'menu.chat' => 'Charra',
|
||||||
'menu.wishlist' => 'Llista de deseos',
|
'menu.wishlist' => 'Llista de deseos',
|
||||||
'menu.following' => 'Siguiendo',
|
'menu.following' => 'Siguiendo',
|
||||||
|
'menu.plantares' => 'Plantares',
|
||||||
'menu.settings' => 'Axustes',
|
'menu.settings' => 'Axustes',
|
||||||
'settings.language' => 'Llingua',
|
'settings.language' => 'Llingua',
|
||||||
'settings.systemLanguage' => 'Llingua del sistema',
|
'settings.systemLanguage' => 'Llingua del sistema',
|
||||||
|
|
@ -1563,6 +1597,28 @@ extension on TranslationsAst {
|
||||||
'wot.reset' => 'Reafitar a valores Duniter',
|
'wot.reset' => 'Reafitar a valores Duniter',
|
||||||
'wot.saved' => 'Guardáu',
|
'wot.saved' => 'Guardáu',
|
||||||
'notifications.newMessageFrom' => ({required Object name}) => 'Mensaxe nuevu de ${name}',
|
'notifications.newMessageFrom' => ({required Object name}) => 'Mensaxe nuevu de ${name}',
|
||||||
|
'plantare.title' => 'Plantares',
|
||||||
|
'plantare.help' => 'Un Plantare ye\'l compromisu de reproducir una semilla y devolver una parte — asina una variedá sigue viaxando de mano en mano. Nun ye una venta.',
|
||||||
|
'plantare.add' => 'Amestar compromisu',
|
||||||
|
'plantare.empty' => 'Entá nun hai compromisos. Cuando compartas o recibas semilla col compromisu de reproducila y devolver daqué, anótalo equí.',
|
||||||
|
'plantare.iReturn' => 'Reprodúzola y devuélvola yo',
|
||||||
|
'plantare.owedToMe' => 'Devuélvenmela a min',
|
||||||
|
'plantare.direction' => 'Quién reproduz y devuelve',
|
||||||
|
'plantare.counterparty' => '¿Con quién?',
|
||||||
|
'plantare.counterpartyHint' => 'Una persona o un coleutivu (opcional)',
|
||||||
|
'plantare.owed' => '¿Qué se devuelve?',
|
||||||
|
'plantare.owedHint' => 'p. ex. un puñáu la próxima temporada (opcional)',
|
||||||
|
'plantare.note' => 'Nota (opcional)',
|
||||||
|
'plantare.save' => 'Guardar',
|
||||||
|
'plantare.markReturned' => 'Marcar devueltu',
|
||||||
|
'plantare.markForgiven' => 'Dar por saldáu',
|
||||||
|
'plantare.reopen' => 'Reabrir',
|
||||||
|
'plantare.delete' => 'Quitar',
|
||||||
|
'plantare.statusReturned' => 'Devueltu',
|
||||||
|
'plantare.statusForgiven' => 'Saldáu',
|
||||||
|
'plantare.openSection' => 'Pendientes',
|
||||||
|
'plantare.settledSection' => 'Fechos',
|
||||||
|
'plantare.removeConfirm' => '¿Quitar esti compromisu?',
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
|
||||||
late final Translations$trust$en trust = Translations$trust$en.internal(_root);
|
late final Translations$trust$en trust = Translations$trust$en.internal(_root);
|
||||||
late final Translations$wot$en wot = Translations$wot$en.internal(_root);
|
late final Translations$wot$en wot = Translations$wot$en.internal(_root);
|
||||||
late final Translations$notifications$en notifications = Translations$notifications$en.internal(_root);
|
late final Translations$notifications$en notifications = Translations$notifications$en.internal(_root);
|
||||||
|
late final Translations$plantare$en plantare = Translations$plantare$en.internal(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: app
|
// Path: app
|
||||||
|
|
@ -214,6 +215,9 @@ class Translations$menu$en {
|
||||||
/// en: 'Following'
|
/// en: 'Following'
|
||||||
String get following => 'Following';
|
String get following => 'Following';
|
||||||
|
|
||||||
|
/// en: 'Plantares'
|
||||||
|
String get plantares => 'Plantares';
|
||||||
|
|
||||||
/// en: 'Settings'
|
/// en: 'Settings'
|
||||||
String get settings => 'Settings';
|
String get settings => 'Settings';
|
||||||
}
|
}
|
||||||
|
|
@ -1511,6 +1515,81 @@ class Translations$notifications$en {
|
||||||
String newMessageFrom({required Object name}) => 'New message from ${name}';
|
String newMessageFrom({required Object name}) => 'New message from ${name}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: plantare
|
||||||
|
class Translations$plantare$en {
|
||||||
|
Translations$plantare$en.internal(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Plantares'
|
||||||
|
String get title => 'Plantares';
|
||||||
|
|
||||||
|
/// en: 'A Plantare is a promise to reproduce a seed and return some — how a variety keeps travelling from hand to hand. It's not a sale.'
|
||||||
|
String get help => 'A Plantare is a promise to reproduce a seed and return some — how a variety keeps travelling from hand to hand. It\'s not a sale.';
|
||||||
|
|
||||||
|
/// en: 'Add a commitment'
|
||||||
|
String get add => 'Add a commitment';
|
||||||
|
|
||||||
|
/// en: 'No commitments yet. When you share or receive seed with a promise to grow it out and return some, note it here.'
|
||||||
|
String get empty => 'No commitments yet. When you share or receive seed with a promise to grow it out and return some, note it here.';
|
||||||
|
|
||||||
|
/// en: 'I'll grow it out & return some'
|
||||||
|
String get iReturn => 'I\'ll grow it out & return some';
|
||||||
|
|
||||||
|
/// en: 'They'll return some to me'
|
||||||
|
String get owedToMe => 'They\'ll return some to me';
|
||||||
|
|
||||||
|
/// en: 'Who reproduces & returns'
|
||||||
|
String get direction => 'Who reproduces & returns';
|
||||||
|
|
||||||
|
/// en: 'With whom?'
|
||||||
|
String get counterparty => 'With whom?';
|
||||||
|
|
||||||
|
/// en: 'A person or a collective (optional)'
|
||||||
|
String get counterpartyHint => 'A person or a collective (optional)';
|
||||||
|
|
||||||
|
/// en: 'What comes back?'
|
||||||
|
String get owed => 'What comes back?';
|
||||||
|
|
||||||
|
/// en: 'e.g. a handful next season (optional)'
|
||||||
|
String get owedHint => 'e.g. a handful next season (optional)';
|
||||||
|
|
||||||
|
/// en: 'Note (optional)'
|
||||||
|
String get note => 'Note (optional)';
|
||||||
|
|
||||||
|
/// en: 'Save'
|
||||||
|
String get save => 'Save';
|
||||||
|
|
||||||
|
/// en: 'Mark returned'
|
||||||
|
String get markReturned => 'Mark returned';
|
||||||
|
|
||||||
|
/// en: 'Let it go'
|
||||||
|
String get markForgiven => 'Let it go';
|
||||||
|
|
||||||
|
/// en: 'Reopen'
|
||||||
|
String get reopen => 'Reopen';
|
||||||
|
|
||||||
|
/// en: 'Remove'
|
||||||
|
String get delete => 'Remove';
|
||||||
|
|
||||||
|
/// en: 'Returned'
|
||||||
|
String get statusReturned => 'Returned';
|
||||||
|
|
||||||
|
/// en: 'Settled'
|
||||||
|
String get statusForgiven => 'Settled';
|
||||||
|
|
||||||
|
/// en: 'Open'
|
||||||
|
String get openSection => 'Open';
|
||||||
|
|
||||||
|
/// en: 'Done'
|
||||||
|
String get settledSection => 'Done';
|
||||||
|
|
||||||
|
/// en: 'Remove this commitment?'
|
||||||
|
String get removeConfirm => 'Remove this commitment?';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: intro.slides
|
// Path: intro.slides
|
||||||
class Translations$intro$slides$en {
|
class Translations$intro$slides$en {
|
||||||
Translations$intro$slides$en.internal(this._root);
|
Translations$intro$slides$en.internal(this._root);
|
||||||
|
|
@ -1995,6 +2074,7 @@ extension on Translations {
|
||||||
'menu.chat' => 'Chat',
|
'menu.chat' => 'Chat',
|
||||||
'menu.wishlist' => 'Wishlist',
|
'menu.wishlist' => 'Wishlist',
|
||||||
'menu.following' => 'Following',
|
'menu.following' => 'Following',
|
||||||
|
'menu.plantares' => 'Plantares',
|
||||||
'menu.settings' => 'Settings',
|
'menu.settings' => 'Settings',
|
||||||
'settings.language' => 'Language',
|
'settings.language' => 'Language',
|
||||||
'settings.systemLanguage' => 'System language',
|
'settings.systemLanguage' => 'System language',
|
||||||
|
|
@ -2387,6 +2467,28 @@ extension on Translations {
|
||||||
'wot.reset' => 'Reset to Duniter defaults',
|
'wot.reset' => 'Reset to Duniter defaults',
|
||||||
'wot.saved' => 'Saved',
|
'wot.saved' => 'Saved',
|
||||||
'notifications.newMessageFrom' => ({required Object name}) => 'New message from ${name}',
|
'notifications.newMessageFrom' => ({required Object name}) => 'New message from ${name}',
|
||||||
|
'plantare.title' => 'Plantares',
|
||||||
|
'plantare.help' => 'A Plantare is a promise to reproduce a seed and return some — how a variety keeps travelling from hand to hand. It\'s not a sale.',
|
||||||
|
'plantare.add' => 'Add a commitment',
|
||||||
|
'plantare.empty' => 'No commitments yet. When you share or receive seed with a promise to grow it out and return some, note it here.',
|
||||||
|
'plantare.iReturn' => 'I\'ll grow it out & return some',
|
||||||
|
'plantare.owedToMe' => 'They\'ll return some to me',
|
||||||
|
'plantare.direction' => 'Who reproduces & returns',
|
||||||
|
'plantare.counterparty' => 'With whom?',
|
||||||
|
'plantare.counterpartyHint' => 'A person or a collective (optional)',
|
||||||
|
'plantare.owed' => 'What comes back?',
|
||||||
|
'plantare.owedHint' => 'e.g. a handful next season (optional)',
|
||||||
|
'plantare.note' => 'Note (optional)',
|
||||||
|
'plantare.save' => 'Save',
|
||||||
|
'plantare.markReturned' => 'Mark returned',
|
||||||
|
'plantare.markForgiven' => 'Let it go',
|
||||||
|
'plantare.reopen' => 'Reopen',
|
||||||
|
'plantare.delete' => 'Remove',
|
||||||
|
'plantare.statusReturned' => 'Returned',
|
||||||
|
'plantare.statusForgiven' => 'Settled',
|
||||||
|
'plantare.openSection' => 'Open',
|
||||||
|
'plantare.settledSection' => 'Done',
|
||||||
|
'plantare.removeConfirm' => 'Remove this commitment?',
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$trust$es trust = _Translations$trust$es._(_root);
|
@override late final _Translations$trust$es trust = _Translations$trust$es._(_root);
|
||||||
@override late final _Translations$wot$es wot = _Translations$wot$es._(_root);
|
@override late final _Translations$wot$es wot = _Translations$wot$es._(_root);
|
||||||
@override late final _Translations$notifications$es notifications = _Translations$notifications$es._(_root);
|
@override late final _Translations$notifications$es notifications = _Translations$notifications$es._(_root);
|
||||||
|
@override late final _Translations$plantare$es plantare = _Translations$plantare$es._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: app
|
// Path: app
|
||||||
|
|
@ -158,6 +159,7 @@ class _Translations$menu$es extends Translations$menu$en {
|
||||||
@override String get chat => 'Chat';
|
@override String get chat => 'Chat';
|
||||||
@override String get wishlist => 'Lista de deseos';
|
@override String get wishlist => 'Lista de deseos';
|
||||||
@override String get following => 'Siguiendo';
|
@override String get following => 'Siguiendo';
|
||||||
|
@override String get plantares => 'Plantares';
|
||||||
@override String get settings => 'Ajustes';
|
@override String get settings => 'Ajustes';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,6 +810,37 @@ class _Translations$notifications$es extends Translations$notifications$en {
|
||||||
@override String newMessageFrom({required Object name}) => 'Nuevo mensaje de ${name}';
|
@override String newMessageFrom({required Object name}) => 'Nuevo mensaje de ${name}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: plantare
|
||||||
|
class _Translations$plantare$es extends Translations$plantare$en {
|
||||||
|
_Translations$plantare$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsEs _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Plantares';
|
||||||
|
@override String get help => 'Un Plantare es el compromiso de reproducir una semilla y devolver una parte — así una variedad sigue viajando de mano en mano. No es una venta.';
|
||||||
|
@override String get add => 'Añadir compromiso';
|
||||||
|
@override String get empty => 'Aún no hay compromisos. Cuando compartas o recibas semilla con el compromiso de reproducirla y devolver algo, anótalo aquí.';
|
||||||
|
@override String get iReturn => 'La reproduzco y devuelvo yo';
|
||||||
|
@override String get owedToMe => 'Me la devuelven a mí';
|
||||||
|
@override String get direction => 'Quién reproduce y devuelve';
|
||||||
|
@override String get counterparty => '¿Con quién?';
|
||||||
|
@override String get counterpartyHint => 'Una persona o un colectivo (opcional)';
|
||||||
|
@override String get owed => '¿Qué se devuelve?';
|
||||||
|
@override String get owedHint => 'p. ej. un puñado la próxima temporada (opcional)';
|
||||||
|
@override String get note => 'Nota (opcional)';
|
||||||
|
@override String get save => 'Guardar';
|
||||||
|
@override String get markReturned => 'Marcar devuelto';
|
||||||
|
@override String get markForgiven => 'Dar por saldado';
|
||||||
|
@override String get reopen => 'Reabrir';
|
||||||
|
@override String get delete => 'Quitar';
|
||||||
|
@override String get statusReturned => 'Devuelto';
|
||||||
|
@override String get statusForgiven => 'Saldado';
|
||||||
|
@override String get openSection => 'Pendientes';
|
||||||
|
@override String get settledSection => 'Hechos';
|
||||||
|
@override String get removeConfirm => '¿Quitar este compromiso?';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: intro.slides
|
// Path: intro.slides
|
||||||
class _Translations$intro$slides$es extends Translations$intro$slides$en {
|
class _Translations$intro$slides$es extends Translations$intro$slides$en {
|
||||||
_Translations$intro$slides$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
_Translations$intro$slides$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1176,6 +1209,7 @@ extension on TranslationsEs {
|
||||||
'menu.chat' => 'Chat',
|
'menu.chat' => 'Chat',
|
||||||
'menu.wishlist' => 'Lista de deseos',
|
'menu.wishlist' => 'Lista de deseos',
|
||||||
'menu.following' => 'Siguiendo',
|
'menu.following' => 'Siguiendo',
|
||||||
|
'menu.plantares' => 'Plantares',
|
||||||
'menu.settings' => 'Ajustes',
|
'menu.settings' => 'Ajustes',
|
||||||
'settings.language' => 'Idioma',
|
'settings.language' => 'Idioma',
|
||||||
'settings.systemLanguage' => 'Idioma del sistema',
|
'settings.systemLanguage' => 'Idioma del sistema',
|
||||||
|
|
@ -1567,6 +1601,28 @@ extension on TranslationsEs {
|
||||||
'wot.reset' => 'Restablecer a valores Duniter',
|
'wot.reset' => 'Restablecer a valores Duniter',
|
||||||
'wot.saved' => 'Guardado',
|
'wot.saved' => 'Guardado',
|
||||||
'notifications.newMessageFrom' => ({required Object name}) => 'Nuevo mensaje de ${name}',
|
'notifications.newMessageFrom' => ({required Object name}) => 'Nuevo mensaje de ${name}',
|
||||||
|
'plantare.title' => 'Plantares',
|
||||||
|
'plantare.help' => 'Un Plantare es el compromiso de reproducir una semilla y devolver una parte — así una variedad sigue viajando de mano en mano. No es una venta.',
|
||||||
|
'plantare.add' => 'Añadir compromiso',
|
||||||
|
'plantare.empty' => 'Aún no hay compromisos. Cuando compartas o recibas semilla con el compromiso de reproducirla y devolver algo, anótalo aquí.',
|
||||||
|
'plantare.iReturn' => 'La reproduzco y devuelvo yo',
|
||||||
|
'plantare.owedToMe' => 'Me la devuelven a mí',
|
||||||
|
'plantare.direction' => 'Quién reproduce y devuelve',
|
||||||
|
'plantare.counterparty' => '¿Con quién?',
|
||||||
|
'plantare.counterpartyHint' => 'Una persona o un colectivo (opcional)',
|
||||||
|
'plantare.owed' => '¿Qué se devuelve?',
|
||||||
|
'plantare.owedHint' => 'p. ej. un puñado la próxima temporada (opcional)',
|
||||||
|
'plantare.note' => 'Nota (opcional)',
|
||||||
|
'plantare.save' => 'Guardar',
|
||||||
|
'plantare.markReturned' => 'Marcar devuelto',
|
||||||
|
'plantare.markForgiven' => 'Dar por saldado',
|
||||||
|
'plantare.reopen' => 'Reabrir',
|
||||||
|
'plantare.delete' => 'Quitar',
|
||||||
|
'plantare.statusReturned' => 'Devuelto',
|
||||||
|
'plantare.statusForgiven' => 'Saldado',
|
||||||
|
'plantare.openSection' => 'Pendientes',
|
||||||
|
'plantare.settledSection' => 'Hechos',
|
||||||
|
'plantare.removeConfirm' => '¿Quitar este compromiso?',
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class TranslationsPt extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$trust$pt trust = _Translations$trust$pt._(_root);
|
@override late final _Translations$trust$pt trust = _Translations$trust$pt._(_root);
|
||||||
@override late final _Translations$wot$pt wot = _Translations$wot$pt._(_root);
|
@override late final _Translations$wot$pt wot = _Translations$wot$pt._(_root);
|
||||||
@override late final _Translations$notifications$pt notifications = _Translations$notifications$pt._(_root);
|
@override late final _Translations$notifications$pt notifications = _Translations$notifications$pt._(_root);
|
||||||
|
@override late final _Translations$plantare$pt plantare = _Translations$plantare$pt._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: app
|
// Path: app
|
||||||
|
|
@ -158,6 +159,7 @@ class _Translations$menu$pt extends Translations$menu$en {
|
||||||
@override String get chat => 'Conversas';
|
@override String get chat => 'Conversas';
|
||||||
@override String get wishlist => 'Lista de desejos';
|
@override String get wishlist => 'Lista de desejos';
|
||||||
@override String get following => 'A seguir';
|
@override String get following => 'A seguir';
|
||||||
|
@override String get plantares => 'Plantares';
|
||||||
@override String get settings => 'Definições';
|
@override String get settings => 'Definições';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -805,6 +807,37 @@ class _Translations$notifications$pt extends Translations$notifications$en {
|
||||||
@override String newMessageFrom({required Object name}) => 'Nova mensagem de ${name}';
|
@override String newMessageFrom({required Object name}) => 'Nova mensagem de ${name}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: plantare
|
||||||
|
class _Translations$plantare$pt extends Translations$plantare$en {
|
||||||
|
_Translations$plantare$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsPt _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Plantares';
|
||||||
|
@override String get help => 'Um Plantare é o compromisso de reproduzir uma semente e devolver uma parte — assim uma variedade continua a viajar de mão em mão. Não é uma venda.';
|
||||||
|
@override String get add => 'Adicionar compromisso';
|
||||||
|
@override String get empty => 'Ainda não há compromissos. Quando partilhares ou receberes semente com o compromisso de a reproduzir e devolver algo, anota-o aqui.';
|
||||||
|
@override String get iReturn => 'Reproduzo e devolvo eu';
|
||||||
|
@override String get owedToMe => 'Devolvem-me a mim';
|
||||||
|
@override String get direction => 'Quem reproduz e devolve';
|
||||||
|
@override String get counterparty => 'Com quem?';
|
||||||
|
@override String get counterpartyHint => 'Uma pessoa ou um coletivo (opcional)';
|
||||||
|
@override String get owed => 'O que é devolvido?';
|
||||||
|
@override String get owedHint => 'p. ex. um punhado na próxima temporada (opcional)';
|
||||||
|
@override String get note => 'Nota (opcional)';
|
||||||
|
@override String get save => 'Guardar';
|
||||||
|
@override String get markReturned => 'Marcar devolvido';
|
||||||
|
@override String get markForgiven => 'Dar por saldado';
|
||||||
|
@override String get reopen => 'Reabrir';
|
||||||
|
@override String get delete => 'Remover';
|
||||||
|
@override String get statusReturned => 'Devolvido';
|
||||||
|
@override String get statusForgiven => 'Saldado';
|
||||||
|
@override String get openSection => 'Pendentes';
|
||||||
|
@override String get settledSection => 'Feitos';
|
||||||
|
@override String get removeConfirm => 'Remover este compromisso?';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: intro.slides
|
// Path: intro.slides
|
||||||
class _Translations$intro$slides$pt extends Translations$intro$slides$en {
|
class _Translations$intro$slides$pt extends Translations$intro$slides$en {
|
||||||
_Translations$intro$slides$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
_Translations$intro$slides$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1173,6 +1206,7 @@ extension on TranslationsPt {
|
||||||
'menu.chat' => 'Conversas',
|
'menu.chat' => 'Conversas',
|
||||||
'menu.wishlist' => 'Lista de desejos',
|
'menu.wishlist' => 'Lista de desejos',
|
||||||
'menu.following' => 'A seguir',
|
'menu.following' => 'A seguir',
|
||||||
|
'menu.plantares' => 'Plantares',
|
||||||
'menu.settings' => 'Definições',
|
'menu.settings' => 'Definições',
|
||||||
'settings.language' => 'Idioma',
|
'settings.language' => 'Idioma',
|
||||||
'settings.systemLanguage' => 'Idioma do sistema',
|
'settings.systemLanguage' => 'Idioma do sistema',
|
||||||
|
|
@ -1561,6 +1595,28 @@ extension on TranslationsPt {
|
||||||
'wot.reset' => 'Repor para valores Duniter',
|
'wot.reset' => 'Repor para valores Duniter',
|
||||||
'wot.saved' => 'Guardado',
|
'wot.saved' => 'Guardado',
|
||||||
'notifications.newMessageFrom' => ({required Object name}) => 'Nova mensagem de ${name}',
|
'notifications.newMessageFrom' => ({required Object name}) => 'Nova mensagem de ${name}',
|
||||||
|
'plantare.title' => 'Plantares',
|
||||||
|
'plantare.help' => 'Um Plantare é o compromisso de reproduzir uma semente e devolver uma parte — assim uma variedade continua a viajar de mão em mão. Não é uma venda.',
|
||||||
|
'plantare.add' => 'Adicionar compromisso',
|
||||||
|
'plantare.empty' => 'Ainda não há compromissos. Quando partilhares ou receberes semente com o compromisso de a reproduzir e devolver algo, anota-o aqui.',
|
||||||
|
'plantare.iReturn' => 'Reproduzo e devolvo eu',
|
||||||
|
'plantare.owedToMe' => 'Devolvem-me a mim',
|
||||||
|
'plantare.direction' => 'Quem reproduz e devolve',
|
||||||
|
'plantare.counterparty' => 'Com quem?',
|
||||||
|
'plantare.counterpartyHint' => 'Uma pessoa ou um coletivo (opcional)',
|
||||||
|
'plantare.owed' => 'O que é devolvido?',
|
||||||
|
'plantare.owedHint' => 'p. ex. um punhado na próxima temporada (opcional)',
|
||||||
|
'plantare.note' => 'Nota (opcional)',
|
||||||
|
'plantare.save' => 'Guardar',
|
||||||
|
'plantare.markReturned' => 'Marcar devolvido',
|
||||||
|
'plantare.markForgiven' => 'Dar por saldado',
|
||||||
|
'plantare.reopen' => 'Reabrir',
|
||||||
|
'plantare.delete' => 'Remover',
|
||||||
|
'plantare.statusReturned' => 'Devolvido',
|
||||||
|
'plantare.statusForgiven' => 'Saldado',
|
||||||
|
'plantare.openSection' => 'Pendentes',
|
||||||
|
'plantare.settledSection' => 'Feitos',
|
||||||
|
'plantare.removeConfirm' => 'Remover este compromisso?',
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,15 @@ class AppDrawer extends StatelessWidget {
|
||||||
context.push('/inventory');
|
context.push('/inventory');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
// Local, works offline — a reproduction-commitment ledger.
|
||||||
|
_DrawerItem(
|
||||||
|
icon: const Icon(Icons.volunteer_activism_outlined),
|
||||||
|
label: t.menu.plantares,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
context.push('/plantares');
|
||||||
|
},
|
||||||
|
),
|
||||||
_DrawerItem(
|
_DrawerItem(
|
||||||
icon: const Icon(Symbols.storefront),
|
icon: const Icon(Symbols.storefront),
|
||||||
label: t.menu.market,
|
label: t.menu.market,
|
||||||
|
|
|
||||||
145
apps/app_seeds/lib/ui/plantare_sheet.dart
Normal file
145
apps/app_seeds/lib/ui/plantare_sheet.dart
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../data/variety_repository.dart';
|
||||||
|
import '../db/enums.dart';
|
||||||
|
import '../i18n/strings.g.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// Opens the "add a Plantare" sheet — a reproduction commitment. Optionally
|
||||||
|
/// pre-attached to [varietyId] (when opened from a variety's detail).
|
||||||
|
Future<void> showPlantareSheet(
|
||||||
|
BuildContext context, {
|
||||||
|
required VarietyRepository repository,
|
||||||
|
String? varietyId,
|
||||||
|
}) {
|
||||||
|
return showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (_) => _PlantareSheet(repository: repository, varietyId: varietyId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlantareSheet extends StatefulWidget {
|
||||||
|
const _PlantareSheet({required this.repository, this.varietyId});
|
||||||
|
|
||||||
|
final VarietyRepository repository;
|
||||||
|
final String? varietyId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_PlantareSheet> createState() => _PlantareSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlantareSheetState extends State<_PlantareSheet> {
|
||||||
|
PlantareDirection _direction = PlantareDirection.iReturn;
|
||||||
|
final _counterparty = TextEditingController();
|
||||||
|
final _owed = TextEditingController();
|
||||||
|
final _note = TextEditingController();
|
||||||
|
bool _saving = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_counterparty.dispose();
|
||||||
|
_owed.dispose();
|
||||||
|
_note.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _nullIfBlank(String s) => s.trim().isEmpty ? null : s.trim();
|
||||||
|
|
||||||
|
Future<void> _save() async {
|
||||||
|
setState(() => _saving = true);
|
||||||
|
await widget.repository.createPlantare(
|
||||||
|
direction: _direction,
|
||||||
|
varietyId: widget.varietyId,
|
||||||
|
counterparty: _nullIfBlank(_counterparty.text),
|
||||||
|
owedDescription: _nullIfBlank(_owed.text),
|
||||||
|
note: _nullIfBlank(_note.text),
|
||||||
|
);
|
||||||
|
if (mounted) Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = context.t;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 16,
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom + 16,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(t.plantare.add,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(t.plantare.help,
|
||||||
|
style: const TextStyle(color: seedMuted, fontSize: 13)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(t.plantare.direction,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
// Two big, human choices — no jargon.
|
||||||
|
for (final d in PlantareDirection.values)
|
||||||
|
ListTile(
|
||||||
|
key: Key('plantare.direction.${d.name}'),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: Icon(
|
||||||
|
_direction == d
|
||||||
|
? Icons.radio_button_checked
|
||||||
|
: Icons.radio_button_unchecked,
|
||||||
|
color: _direction == d ? seedGreen : seedMuted,
|
||||||
|
),
|
||||||
|
title: Text(d == PlantareDirection.iReturn
|
||||||
|
? t.plantare.iReturn
|
||||||
|
: t.plantare.owedToMe),
|
||||||
|
onTap: () => setState(() => _direction = d),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextField(
|
||||||
|
key: const Key('plantare.counterparty'),
|
||||||
|
controller: _counterparty,
|
||||||
|
textCapitalization: TextCapitalization.words,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.plantare.counterparty,
|
||||||
|
helperText: t.plantare.counterpartyHint,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
key: const Key('plantare.owed'),
|
||||||
|
controller: _owed,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.plantare.owed,
|
||||||
|
helperText: t.plantare.owedHint,
|
||||||
|
helperMaxLines: 2,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
key: const Key('plantare.note'),
|
||||||
|
controller: _note,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.plantare.note,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
FilledButton(
|
||||||
|
key: const Key('plantare.save'),
|
||||||
|
onPressed: _saving ? null : _save,
|
||||||
|
child: Text(t.plantare.save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
apps/app_seeds/lib/ui/plantares_screen.dart
Normal file
160
apps/app_seeds/lib/ui/plantares_screen.dart
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../data/variety_repository.dart';
|
||||||
|
import '../db/database.dart';
|
||||||
|
import '../db/enums.dart';
|
||||||
|
import '../i18n/strings.g.dart';
|
||||||
|
import 'plantare_sheet.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// The Plantares screen — your reproduction commitments (data-model §2.7).
|
||||||
|
/// Lists what's open (still to grow out & return) and what's done, and lets you
|
||||||
|
/// add or settle one. Local-first; reads the encrypted inventory DB.
|
||||||
|
class PlantaresScreen extends StatelessWidget {
|
||||||
|
const PlantaresScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = context.t;
|
||||||
|
final repo = context.read<VarietyRepository>();
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(t.plantare.title)),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
key: const Key('plantares.add'),
|
||||||
|
onPressed: () => showPlantareSheet(context, repository: repo),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(t.plantare.add),
|
||||||
|
),
|
||||||
|
body: StreamBuilder<List<Plantare>>(
|
||||||
|
stream: repo.watchPlantares(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final all = snapshot.data;
|
||||||
|
if (all == null) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (all.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Text(
|
||||||
|
t.plantare.empty,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: seedMuted, fontSize: 15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final open = all.where((p) => p.status == PlantareStatus.open);
|
||||||
|
final settled = all.where((p) => p.status != PlantareStatus.open);
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.only(bottom: 88),
|
||||||
|
children: [
|
||||||
|
if (open.isNotEmpty) _SectionHeader(t.plantare.openSection),
|
||||||
|
for (final p in open) _PlantareTile(p, repo),
|
||||||
|
if (settled.isNotEmpty) _SectionHeader(t.plantare.settledSection),
|
||||||
|
for (final p in settled) _PlantareTile(p, repo),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionHeader extends StatelessWidget {
|
||||||
|
const _SectionHeader(this.label);
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||||
|
child: Text(
|
||||||
|
label.toUpperCase(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: seedMuted,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One commitment row. An open one shows a big "mark returned"; a settled one
|
||||||
|
/// reads muted. The overflow menu carries the rest (let go / reopen / remove).
|
||||||
|
class _PlantareTile extends StatelessWidget {
|
||||||
|
const _PlantareTile(this.p, this.repo);
|
||||||
|
|
||||||
|
final Plantare p;
|
||||||
|
final VarietyRepository repo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = context.t;
|
||||||
|
final done = p.status != PlantareStatus.open;
|
||||||
|
final iReturn = p.direction == PlantareDirection.iReturn;
|
||||||
|
final title = (p.owedDescription?.trim().isNotEmpty ?? false)
|
||||||
|
? p.owedDescription!.trim()
|
||||||
|
: (iReturn ? t.plantare.iReturn : t.plantare.owedToMe);
|
||||||
|
final subtitleParts = <String>[
|
||||||
|
iReturn ? t.plantare.iReturn : t.plantare.owedToMe,
|
||||||
|
if (p.counterparty?.trim().isNotEmpty ?? false) p.counterparty!.trim(),
|
||||||
|
if (done)
|
||||||
|
p.status == PlantareStatus.returned
|
||||||
|
? t.plantare.statusReturned
|
||||||
|
: t.plantare.statusForgiven,
|
||||||
|
];
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
iReturn ? Icons.volunteer_activism_outlined : Icons.eco_outlined,
|
||||||
|
color: done ? seedMuted : seedGreen,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
decoration: done ? TextDecoration.lineThrough : null,
|
||||||
|
color: done ? seedMuted : seedOnSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(subtitleParts.join(' · '),
|
||||||
|
maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (!done)
|
||||||
|
IconButton(
|
||||||
|
key: Key('plantare.return.${p.id}'),
|
||||||
|
icon: const Icon(Icons.check_circle_outline),
|
||||||
|
tooltip: t.plantare.markReturned,
|
||||||
|
color: seedGreen,
|
||||||
|
onPressed: () =>
|
||||||
|
repo.setPlantareStatus(p.id, PlantareStatus.returned),
|
||||||
|
),
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
key: Key('plantare.menu.${p.id}'),
|
||||||
|
onSelected: (v) async {
|
||||||
|
switch (v) {
|
||||||
|
case 'forgiven':
|
||||||
|
await repo.setPlantareStatus(p.id, PlantareStatus.forgiven);
|
||||||
|
case 'reopen':
|
||||||
|
await repo.setPlantareStatus(p.id, PlantareStatus.open);
|
||||||
|
case 'delete':
|
||||||
|
await repo.deletePlantare(p.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
if (!done)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'forgiven', child: Text(t.plantare.markForgiven)),
|
||||||
|
if (done)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'reopen', child: Text(t.plantare.reopen)),
|
||||||
|
PopupMenuItem(value: 'delete', child: Text(t.plantare.delete)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import '../i18n/strings.g.dart';
|
||||||
import '../state/variety_detail_cubit.dart';
|
import '../state/variety_detail_cubit.dart';
|
||||||
import 'harvest_date_picker.dart';
|
import 'harvest_date_picker.dart';
|
||||||
import 'photo_pick.dart';
|
import 'photo_pick.dart';
|
||||||
|
import 'plantare_sheet.dart';
|
||||||
import 'quantity_kind_l10n.dart';
|
import 'quantity_kind_l10n.dart';
|
||||||
import 'quantity_picker.dart';
|
import 'quantity_picker.dart';
|
||||||
import 'seed_glyph.dart';
|
import 'seed_glyph.dart';
|
||||||
|
|
@ -108,6 +109,21 @@ class _DetailView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_SectionTitle(t.plantare.title),
|
||||||
|
Align(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
key: const Key('detail.addPlantare'),
|
||||||
|
icon: const Icon(Icons.volunteer_activism_outlined, size: 18),
|
||||||
|
onPressed: () => showPlantareSheet(
|
||||||
|
context,
|
||||||
|
repository: context.read<VarietyRepository>(),
|
||||||
|
varietyId: detail.id,
|
||||||
|
),
|
||||||
|
label: Text(t.plantare.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (detail.notes != null && detail.notes!.trim().isNotEmpty) ...[
|
if (detail.notes != null && detail.notes!.trim().isNotEmpty) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_SectionTitle(t.detail.notes),
|
_SectionTitle(t.detail.notes),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import 'package:tane/ui/home_screen.dart';
|
||||||
import 'package:tane/ui/intro_screen.dart';
|
import 'package:tane/ui/intro_screen.dart';
|
||||||
import 'package:tane/ui/inventory_list_screen.dart';
|
import 'package:tane/ui/inventory_list_screen.dart';
|
||||||
import 'package:tane/ui/market_screen.dart';
|
import 'package:tane/ui/market_screen.dart';
|
||||||
|
import 'package:tane/ui/plantares_screen.dart';
|
||||||
import 'package:tane/ui/profile_screen.dart';
|
import 'package:tane/ui/profile_screen.dart';
|
||||||
import 'package:tane/ui/settings_screen.dart';
|
import 'package:tane/ui/settings_screen.dart';
|
||||||
|
|
||||||
|
|
@ -236,6 +237,27 @@ void main() {
|
||||||
await disposeTree(tester);
|
await disposeTree(tester);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('the Plantares screen (with a commitment) fits a small screen',
|
||||||
|
(tester) async {
|
||||||
|
final db = newTestDatabase();
|
||||||
|
addTearDown(db.close);
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
await repo.createPlantare(
|
||||||
|
direction: PlantareDirection.iReturn,
|
||||||
|
counterparty: 'Colectivo semillero de la comarca',
|
||||||
|
owedDescription: 'un puñado la próxima temporada, si germina bien',
|
||||||
|
);
|
||||||
|
await pumpSmall(
|
||||||
|
tester,
|
||||||
|
wrapScreen(
|
||||||
|
repository: repo,
|
||||||
|
locale: AppLocale.es,
|
||||||
|
child: const PlantaresScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await disposeTree(tester);
|
||||||
|
});
|
||||||
|
|
||||||
// NOTE: the variety-detail screen is deliberately NOT overflow-checked here.
|
// NOTE: the variety-detail screen is deliberately NOT overflow-checked here.
|
||||||
// It reports a ~2px overflow on a RenderFlex that is already DISPOSED/DEFUNCT
|
// It reports a ~2px overflow on a RenderFlex that is already DISPOSED/DEFUNCT
|
||||||
// — a transient stale frame while its cubit's Drift stream rebuilds during
|
// — a transient stale frame while its cubit's Drift stream rebuilds during
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue