diff --git a/apps/app_seeds/assets/logo.png b/apps/app_seeds/assets/logo.png new file mode 100644 index 0000000..a2db482 Binary files /dev/null and b/apps/app_seeds/assets/logo.png differ diff --git a/apps/app_seeds/lib/app.dart b/apps/app_seeds/lib/app.dart index 665cfd7..c392f83 100644 --- a/apps/app_seeds/lib/app.dart +++ b/apps/app_seeds/lib/app.dart @@ -10,6 +10,7 @@ import 'state/inventory_cubit.dart'; import 'state/variety_detail_cubit.dart'; import 'ui/home_screen.dart'; import 'ui/inventory_list_screen.dart'; +import 'ui/settings_screen.dart'; import 'ui/theme.dart'; import 'ui/variety_detail_screen.dart'; @@ -27,6 +28,10 @@ class TaneApp extends StatelessWidget { return GoRouter( routes: [ GoRoute(path: '/', builder: (context, state) => const HomeScreen()), + GoRoute( + path: '/settings', + builder: (context, state) => const SettingsScreen(), + ), GoRoute( path: '/inventory', builder: (context, state) => BlocProvider( diff --git a/apps/app_seeds/lib/i18n/en.i18n.json b/apps/app_seeds/lib/i18n/en.i18n.json index 1e586b1..67f7e51 100644 --- a/apps/app_seeds/lib/i18n/en.i18n.json +++ b/apps/app_seeds/lib/i18n/en.i18n.json @@ -23,6 +23,14 @@ "following": "Following", "settings": "Settings" }, + "settings": { + "language": "Language", + "systemLanguage": "System language", + "langEs": "Español", + "langEn": "English", + "about": "About", + "aboutText": "Local-first, encrypted inventory for traditional seeds. AGPL-3.0." + }, "inventory": { "title": "Inventory", "searchHint": "Search seeds", diff --git a/apps/app_seeds/lib/i18n/es.i18n.json b/apps/app_seeds/lib/i18n/es.i18n.json index d11401c..87cc7dd 100644 --- a/apps/app_seeds/lib/i18n/es.i18n.json +++ b/apps/app_seeds/lib/i18n/es.i18n.json @@ -23,6 +23,14 @@ "following": "Siguiendo", "settings": "Ajustes" }, + "settings": { + "language": "Idioma", + "systemLanguage": "Idioma del sistema", + "langEs": "Español", + "langEn": "English", + "about": "Acerca de", + "aboutText": "Inventario local y cifrado para semillas tradicionales. AGPL-3.0." + }, "inventory": { "title": "Inventario", "searchHint": "Buscar semillas", diff --git a/apps/app_seeds/lib/i18n/strings.g.dart b/apps/app_seeds/lib/i18n/strings.g.dart index 31be84b..abe1c4b 100644 --- a/apps/app_seeds/lib/i18n/strings.g.dart +++ b/apps/app_seeds/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 244 (122 per locale) +/// Strings: 256 (128 per locale) /// -/// Built on 2026-07-08 at 10:27 UTC +/// Built on 2026-07-08 at 12:01 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/apps/app_seeds/lib/i18n/strings_en.g.dart b/apps/app_seeds/lib/i18n/strings_en.g.dart index 5376688..80e2540 100644 --- a/apps/app_seeds/lib/i18n/strings_en.g.dart +++ b/apps/app_seeds/lib/i18n/strings_en.g.dart @@ -44,6 +44,7 @@ class Translations with BaseTranslations { late final Translations$common$en common = Translations$common$en.internal(_root); late final Translations$home$en home = Translations$home$en.internal(_root); late final Translations$menu$en menu = Translations$menu$en.internal(_root); + late final Translations$settings$en settings = Translations$settings$en.internal(_root); late final Translations$inventory$en inventory = Translations$inventory$en.internal(_root); late final Translations$quickAdd$en quickAdd = Translations$quickAdd$en.internal(_root); late final Translations$detail$en detail = Translations$detail$en.internal(_root); @@ -139,6 +140,33 @@ class Translations$menu$en { String get settings => 'Settings'; } +// Path: settings +class Translations$settings$en { + Translations$settings$en.internal(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Language' + String get language => 'Language'; + + /// en: 'System language' + String get systemLanguage => 'System language'; + + /// en: 'Español' + String get langEs => 'Español'; + + /// en: 'English' + String get langEn => 'English'; + + /// en: 'About' + String get about => 'About'; + + /// en: 'Local-first, encrypted inventory for traditional seeds. AGPL-3.0.' + String get aboutText => 'Local-first, encrypted inventory for traditional seeds. AGPL-3.0.'; +} + // Path: inventory class Translations$inventory$en { Translations$inventory$en.internal(this._root); @@ -752,6 +780,12 @@ extension on Translations { 'menu.wishlist' => 'Wishlist', 'menu.following' => 'Following', 'menu.settings' => 'Settings', + 'settings.language' => 'Language', + 'settings.systemLanguage' => 'System language', + 'settings.langEs' => 'Español', + 'settings.langEn' => 'English', + 'settings.about' => 'About', + 'settings.aboutText' => 'Local-first, encrypted inventory for traditional seeds. AGPL-3.0.', 'inventory.title' => 'Inventory', 'inventory.searchHint' => 'Search seeds', 'inventory.empty' => 'No seeds yet. Tap + to add your first.', diff --git a/apps/app_seeds/lib/i18n/strings_es.g.dart b/apps/app_seeds/lib/i18n/strings_es.g.dart index a224e8b..1591189 100644 --- a/apps/app_seeds/lib/i18n/strings_es.g.dart +++ b/apps/app_seeds/lib/i18n/strings_es.g.dart @@ -43,6 +43,7 @@ class TranslationsEs extends Translations with BaseTranslations 'Ajustes'; } +// Path: settings +class _Translations$settings$es extends Translations$settings$en { + _Translations$settings$es._(TranslationsEs root) : this._root = root, super.internal(root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get language => 'Idioma'; + @override String get systemLanguage => 'Idioma del sistema'; + @override String get langEs => 'Español'; + @override String get langEn => 'English'; + @override String get about => 'Acerca de'; + @override String get aboutText => 'Inventario local y cifrado para semillas tradicionales. AGPL-3.0.'; +} + // Path: inventory class _Translations$inventory$es extends Translations$inventory$en { _Translations$inventory$es._(TranslationsEs root) : this._root = root, super.internal(root); @@ -529,6 +545,12 @@ extension on TranslationsEs { 'menu.wishlist' => 'Lista de deseos', 'menu.following' => 'Siguiendo', 'menu.settings' => 'Ajustes', + 'settings.language' => 'Idioma', + 'settings.systemLanguage' => 'Idioma del sistema', + 'settings.langEs' => 'Español', + 'settings.langEn' => 'English', + 'settings.about' => 'Acerca de', + 'settings.aboutText' => 'Inventario local y cifrado para semillas tradicionales. AGPL-3.0.', 'inventory.title' => 'Inventario', 'inventory.searchHint' => 'Buscar semillas', 'inventory.empty' => 'Aún no hay semillas. Toca + para añadir la primera.', diff --git a/apps/app_seeds/lib/ui/app_drawer.dart b/apps/app_seeds/lib/ui/app_drawer.dart index 72aaa2e..172cb46 100644 --- a/apps/app_seeds/lib/ui/app_drawer.dart +++ b/apps/app_seeds/lib/ui/app_drawer.dart @@ -38,7 +38,14 @@ class AppDrawer extends StatelessWidget { _SoonItem(icon: Icons.group_outlined, label: t.menu.following), const Spacer(), const Divider(), - _SoonItem(icon: Icons.settings_outlined, label: t.menu.settings), + _ActiveItem( + icon: const Icon(Icons.settings_outlined), + label: t.menu.settings, + onTap: () { + Navigator.of(context).pop(); + context.go('/settings'); + }, + ), ], ), ), diff --git a/apps/app_seeds/lib/ui/home_screen.dart b/apps/app_seeds/lib/ui/home_screen.dart index be2b9e7..97e51db 100644 --- a/apps/app_seeds/lib/ui/home_screen.dart +++ b/apps/app_seeds/lib/ui/home_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:material_symbols_icons/symbols.dart'; import '../i18n/strings.g.dart'; import 'app_drawer.dart'; @@ -31,11 +30,10 @@ class HomeScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Icon( - Symbols.eco, - size: 120, - color: seedGreen, - fill: 1, + Image.asset( + 'assets/logo.png', + height: 140, + fit: BoxFit.contain, ), const SizedBox(height: 40), _MenuButton( diff --git a/apps/app_seeds/lib/ui/settings_screen.dart b/apps/app_seeds/lib/ui/settings_screen.dart new file mode 100644 index 0000000..bba7b6a --- /dev/null +++ b/apps/app_seeds/lib/ui/settings_screen.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +import '../i18n/strings.g.dart'; +import 'theme.dart'; + +/// Basic settings: app language and an "about" section. +class SettingsScreen extends StatelessWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + final t = context.t; + final current = TranslationProvider.of(context).flutterLocale.languageCode; + return Scaffold( + appBar: AppBar(title: Text(t.menu.settings)), + body: ListView( + children: [ + _SectionHeader(t.settings.language), + _LanguageTile( + label: t.settings.langEs, + selected: current == 'es', + onTap: () => LocaleSettings.setLocale(AppLocale.es), + ), + _LanguageTile( + label: t.settings.langEn, + selected: current == 'en', + onTap: () => LocaleSettings.setLocale(AppLocale.en), + ), + ListTile( + leading: const Icon(Icons.smartphone_outlined), + title: Text(t.settings.systemLanguage), + onTap: () => LocaleSettings.useDeviceLocale(), + ), + const Divider(), + _SectionHeader(t.settings.about), + ListTile( + leading: Image.asset('assets/logo.png', width: 32), + title: Text(t.app.title), + subtitle: Text(t.settings.aboutText), + ), + ], + ), + ); + } +} + +class _SectionHeader extends StatelessWidget { + const _SectionHeader(this.text); + + final String text; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), + child: Text( + text, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: seedGreenDark, + fontWeight: FontWeight.w600, + ), + ), + ); + } +} + +class _LanguageTile extends StatelessWidget { + const _LanguageTile({ + required this.label, + required this.selected, + required this.onTap, + }); + + final String label; + final bool selected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(label), + trailing: selected ? const Icon(Icons.check, color: seedGreen) : null, + onTap: onTap, + ); + } +} diff --git a/apps/app_seeds/pubspec.yaml b/apps/app_seeds/pubspec.yaml index 17faf7c..4b6e7b1 100644 --- a/apps/app_seeds/pubspec.yaml +++ b/apps/app_seeds/pubspec.yaml @@ -68,6 +68,7 @@ flutter: # Seed strings for slang live under lib/i18n (not a Flutter asset; compiled). assets: - assets/catalog/species.json + - assets/logo.png fonts: # Custom seed-themed icon glyphs (chars a–k). See docs/mockups/seedks-glyphs.png. - family: seedks diff --git a/apps/app_seeds/test/ui/home_screen_test.dart b/apps/app_seeds/test/ui/home_screen_test.dart index f573cc3..5ad9805 100644 --- a/apps/app_seeds/test/ui/home_screen_test.dart +++ b/apps/app_seeds/test/ui/home_screen_test.dart @@ -52,4 +52,22 @@ void main() { expect(find.text('No seeds yet. Tap + to add your first.'), findsOneWidget); await disposeTree(tester); }); + + testWidgets('drawer Settings opens the settings screen', (tester) async { + LocaleSettings.setLocaleSync(AppLocale.en); + final db = newTestDatabase(); + addTearDown(db.close); + + await tester.pumpWidget(app(db)); + await tester.pumpAndSettle(); + + await tester.tap(find.byIcon(Icons.menu)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Settings')); + await tester.pumpAndSettle(); + + expect(find.text('Language'), findsOneWidget); + expect(find.text('Español'), findsOneWidget); + await disposeTree(tester); + }); }