feat(ui): Material 3 redesign, cover-photo viewer, About screen
Apply a Material 3 seed-green palette and rounded components across the home, inventory, quick-add, quantity picker and variety-detail screens. The full-screen photo viewer can now set any photo as the cover (via attachment sort order) and delete after confirmation. Lot forms and packaging surface as choice chips. Extract About out of Settings into its own screen (app version via package_info_plus, website link). Add es/en strings for the new lot types, presentation, home tagline and About. Update Seedees v2 mockups.
This commit is contained in:
parent
f5c36f2369
commit
42c16c0e3f
24 changed files with 1960 additions and 416 deletions
|
|
@ -6,9 +6,10 @@ import '../i18n/strings.g.dart';
|
|||
import 'seed_glyph.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// The app's navigation drawer (mockup 03). Inventory is live; the social items
|
||||
/// (market, profile, chat…) belong to Block 2 and are shown disabled with a
|
||||
/// "coming soon" tag so the roadmap is visible.
|
||||
/// The app's navigation drawer (redesign screen 05). A white sheet: Inventory is
|
||||
/// the live destination (green seed glyph), the social items (market, profile,
|
||||
/// chat…) belong to Block 2 and are greyed with a "soon" tag, and Settings sits
|
||||
/// pinned at the bottom.
|
||||
class AppDrawer extends StatelessWidget {
|
||||
const AppDrawer({super.key});
|
||||
|
||||
|
|
@ -16,34 +17,42 @@ class AppDrawer extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
return Drawer(
|
||||
backgroundColor: Colors.white,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const _DrawerHeader(),
|
||||
_ActiveItem(
|
||||
icon: const SeedGlyph(SeedGlyphs.jars, size: 22),
|
||||
_DrawerItem(
|
||||
icon: const SeedGlyph(SeedGlyphs.jars, size: 23),
|
||||
label: t.menu.inventory,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/inventory');
|
||||
},
|
||||
),
|
||||
_SoonItem(icon: Symbols.storefront, label: t.menu.market),
|
||||
const Divider(),
|
||||
_SoonItem(icon: Icons.person_outline, label: t.menu.profile),
|
||||
_SoonItem(icon: Icons.chat_bubble_outline, label: t.menu.chat),
|
||||
_SoonItem(icon: Icons.favorite_border, label: t.menu.wishlist),
|
||||
_SoonItem(icon: Icons.group_outlined, label: t.menu.following),
|
||||
_DrawerItem(
|
||||
icon: const Icon(Symbols.storefront),
|
||||
label: t.menu.market,
|
||||
divider: true,
|
||||
),
|
||||
_DrawerItem(icon: const Icon(Icons.person), label: t.menu.profile),
|
||||
_DrawerItem(
|
||||
icon: const Icon(Icons.chat_bubble),
|
||||
label: t.menu.chat,
|
||||
),
|
||||
_DrawerItem(
|
||||
icon: const Icon(Icons.favorite),
|
||||
label: t.menu.wishlist,
|
||||
),
|
||||
_DrawerItem(icon: const Icon(Icons.group), label: t.menu.following),
|
||||
const Spacer(),
|
||||
const Divider(),
|
||||
_ActiveItem(
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
const Divider(height: 1),
|
||||
_DrawerItem(
|
||||
icon: const Icon(Icons.settings),
|
||||
label: t.menu.settings,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/settings');
|
||||
context.push('/settings');
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
@ -53,70 +62,110 @@ class AppDrawer extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// The drawer's brand header. Tapping it closes the drawer and returns to the
|
||||
/// home screen.
|
||||
class _DrawerHeader extends StatelessWidget {
|
||||
const _DrawerHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.eco, color: seedGreen, size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.t.app.title,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: seedGreenDark,
|
||||
fontWeight: FontWeight.bold,
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/');
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(22, 18, 22, 16),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: seedDivider)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset('assets/logo.png', height: 34),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.t.app.title,
|
||||
style: const TextStyle(
|
||||
color: seedGreen,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActiveItem extends StatelessWidget {
|
||||
const _ActiveItem({
|
||||
/// A 52-tall drawer row. A live destination has an [onTap] (dark text, green
|
||||
/// icon); a disabled Block-2 destination is greyed with a "soon" tag. [divider]
|
||||
/// draws a hairline under the row (separating live from upcoming items).
|
||||
class _DrawerItem extends StatelessWidget {
|
||||
const _DrawerItem({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
this.onTap,
|
||||
this.divider = false,
|
||||
});
|
||||
|
||||
final Widget icon;
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onTap;
|
||||
final bool divider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: SizedBox(width: 24, child: Center(child: icon)),
|
||||
title: Text(label),
|
||||
final enabled = onTap != null;
|
||||
final fg = enabled ? seedOnSurface : const Color(0xFF9AA88F);
|
||||
final iconColor = enabled ? seedGreen : const Color(0xFF9AA88F);
|
||||
final row = InkWell(
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A disabled Block-2 destination with a subtle "coming soon" tag.
|
||||
class _SoonItem extends StatelessWidget {
|
||||
const _SoonItem({required this.icon, required this.label});
|
||||
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
enabled: false,
|
||||
leading: Icon(icon),
|
||||
title: Text(label),
|
||||
trailing: Text(
|
||||
context.t.common.comingSoon,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.copyWith(color: Colors.black38),
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22),
|
||||
decoration: divider
|
||||
? const BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: seedDivider)),
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24,
|
||||
child: Center(
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(size: 23, color: iconColor),
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: fg,
|
||||
fontSize: 15,
|
||||
fontWeight: enabled ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!enabled)
|
||||
Text(
|
||||
context.t.common.comingSoon.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFB3BDA8),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue