From f092a77b0ff21ded9c223fc943e57f1fe98fd9a9 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sun, 14 Jun 2026 11:03:28 +0700 Subject: [PATCH] design: redesign app UI/UX to a premium traditional family restaurant style with rounded layouts and cozy ivory/terracotta palette --- lib/screens/branches_screen.dart | 27 +++-- lib/screens/loyalty_dashboard.dart | 106 ++++++++++++++---- lib/theme/app_theme.dart | 127 ++++++++++++---------- lib/widgets/carousel_widget.dart | 19 +++- lib/widgets/promo_card_widget.dart | 24 +++- lib/widgets/subscription_list_widget.dart | 14 ++- 6 files changed, 222 insertions(+), 95 deletions(-) diff --git a/lib/screens/branches_screen.dart b/lib/screens/branches_screen.dart index 6502ef1..7ce52bd 100644 --- a/lib/screens/branches_screen.dart +++ b/lib/screens/branches_screen.dart @@ -233,9 +233,16 @@ class _BranchesScreenState extends State { return Container( margin: const EdgeInsets.only(bottom: 12), - decoration: const BoxDecoration( + decoration: BoxDecoration( color: AppTheme.surfaceContainerLow, - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], ), child: ListTile( contentPadding: const EdgeInsets.all(16), @@ -244,9 +251,8 @@ class _BranchesScreenState extends State { leading: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppTheme.secondaryContainer - .withValues(alpha: 0.2), - shape: BoxShape.rectangle, + color: AppTheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), ), child: const Icon(Icons.storefront, color: AppTheme.secondary), @@ -258,7 +264,11 @@ class _BranchesScreenState extends State { branch['name'] ?? 'Mapan Branch', style: Theme.of(context) .textTheme - .titleMedium, + .titleMedium + ?.copyWith( + fontFamily: 'serif', + fontWeight: FontWeight.bold, + ), ), ), if (distanceLabel.isNotEmpty) @@ -266,8 +276,9 @@ class _BranchesScreenState extends State { margin: const EdgeInsets.only(left: 8), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3), - decoration: const BoxDecoration( + decoration: BoxDecoration( color: AppTheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), ), child: Text( distanceLabel, @@ -276,7 +287,7 @@ class _BranchesScreenState extends State { .labelLarge ?.copyWith( color: AppTheme.onSecondaryContainer, - fontSize: 11, + fontSize: 10, fontWeight: FontWeight.bold, ), ), diff --git a/lib/screens/loyalty_dashboard.dart b/lib/screens/loyalty_dashboard.dart index c539e1d..9f058ec 100644 --- a/lib/screens/loyalty_dashboard.dart +++ b/lib/screens/loyalty_dashboard.dart @@ -131,17 +131,28 @@ class _LoyaltyCardTile extends StatelessWidget { @override Widget build(BuildContext context) { final programName = (card['program_id']?[1] as String? ?? '').toLowerCase(); - String tier = 'Member'; - if (programName.contains('silver')) tier = 'Silver Member'; - if (programName.contains('gold')) tier = 'Gold Member'; - if (programName.contains('platinum')) tier = 'Platinum Member'; + String tier = 'MEMBER'; + if (programName.contains('silver')) tier = 'SILVER MEMBER'; + if (programName.contains('gold')) tier = 'GOLD MEMBER'; + if (programName.contains('platinum')) tier = 'PLATINUM MEMBER'; return Container( - margin: const EdgeInsets.fromLTRB(16, 16, 16, 0), + margin: const EdgeInsets.fromLTRB(16, 16, 16, 8), padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - color: AppTheme.surfaceContainerHighest, - borderRadius: BorderRadius.zero, + decoration: BoxDecoration( + color: AppTheme.primary, // Rich brick red background + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFF3DCA2), // Golden border + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.12), + blurRadius: 16, + offset: const Offset(0, 6), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -152,43 +163,94 @@ class _LoyaltyCardTile extends StatelessWidget { Expanded( child: Text( '${card['program_id']?[1] ?? 'Loyalty Program'}', - style: Theme.of(context).textTheme.titleLarge, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.white, + fontFamily: 'serif', + ), softWrap: true, ), ), const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: const BoxDecoration( - color: AppTheme.secondaryContainer, - borderRadius: BorderRadius.zero, + decoration: BoxDecoration( + color: const Color(0xFFB58428), // Golden honey badge background + borderRadius: BorderRadius.circular(20), ), child: Text( tier, style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: AppTheme.onSecondaryContainer, + color: Colors.white, fontWeight: FontWeight.bold, + fontSize: 9, + letterSpacing: 0.8, ), ), ), ], ), - const SizedBox(height: 20), - Text('Membership Code', style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 24), + Text( + 'MEMBERSHIP CODE', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.white.withValues(alpha: 0.7), + fontWeight: FontWeight.bold, + fontSize: 10, + letterSpacing: 1.0, + ), + ), const SizedBox(height: 4), - Text('${card['code'] ?? 'N/A'}', - style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 16), + Text( + '${card['code'] ?? 'N/A'}', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.white, + fontFamily: 'monospace', + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('Available Points', - style: Theme.of(context).textTheme.bodyMedium), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'AVAILABLE POINTS', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.white.withValues(alpha: 0.7), + fontWeight: FontWeight.bold, + fontSize: 10, + letterSpacing: 1.0, + ), + ), + const SizedBox(height: 4), + Row( + children: [ + const Icon( + Icons.restaurant_rounded, + color: Color(0xFFF3DCA2), + size: 16, + ), + const SizedBox(width: 6), + Text( + 'Dine & Save', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white.withValues(alpha: 0.9), + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + ), Text( '${card['points'] ?? 0}', - style: Theme.of(context).textTheme.displayMedium?.copyWith( - color: AppTheme.primary, + style: Theme.of(context).textTheme.displayLarge?.copyWith( + color: const Color(0xFFF3DCA2), // Bright golden amber accent + fontWeight: FontWeight.bold, ), ), ], diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 1e6514f..2efccb8 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -2,26 +2,26 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class AppTheme { - // Editorial Organicism Tokens - static const Color primary = Color(0xFFFFEF00); - static const Color primaryContainer = Color(0xFFFFEF00); - static const Color secondary = Color(0xFF705900); - static const Color secondaryContainer = Color(0xFFFACD34); - static const Color onPrimaryContainer = Color(0xFF5F5800); - static const Color onSecondaryContainer = Color(0xFF584500); + // Warm Traditional Family Restaurant Brand Colors + static const Color primary = Color(0xFF8A1C14); // Rich Brick/Crimson Red + static const Color primaryContainer = Color(0xFF8A1C14); + static const Color secondary = Color(0xFFB58428); // Warm Gold/Honey Amber + static const Color secondaryContainer = Color(0xFFF3DCA2); + static const Color onPrimaryContainer = Colors.white; + static const Color onSecondaryContainer = Color(0xFF5A3E00); - // Surface Hierarchy - static const Color surface = Color(0xFFF7F7F4); - static const Color surfaceContainer = Color(0xFFE8E8E5); - static const Color surfaceContainerLow = Color(0xFFF0F1EE); - static const Color surfaceContainerLowest = Colors.white; - static const Color surfaceContainerHighest = Color(0xFFDCDDDA); + // Warm Ivory & Earthy Surface Hierarchy + static const Color surface = Color(0xFFFAF6EE); // Warm paper ivory background + static const Color surfaceContainer = Color(0xFFF2EAD8); // Soft cream + static const Color surfaceContainerLow = Color(0xFFF7F1E3); // Milky cream + static const Color surfaceContainerLowest = Color(0xFFFCFAF6); // Softest ivory/white + static const Color surfaceContainerHighest = Color(0xFFE5D5BA); // Warm toasted sand // Text & On-Colors - static const Color onSurface = Color(0xFF2D2F2D); - static const Color onSurfaceVariant = Color(0xFF5A5C5A); - static const Color onPrimary = Color(0xFFFFF59B); - static const Color outlineVariant = Color(0xFFACADAB); + static const Color onSurface = Color(0xFF2E251B); // Earthy dark brown instead of charcoal + static const Color onSurfaceVariant = Color(0xFF635647); // Subdued warm wood tone + static const Color onPrimary = Colors.white; + static const Color outlineVariant = Color(0xFFD3C5B1); // Soft sandy divider static ThemeData get lightTheme => getTheme(); @@ -31,8 +31,8 @@ class AppTheme { final sColor = secondaryColor ?? secondary; // Dynamically compute readable contrast text colors - final onPrimaryColor = pColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; - final onSecondaryColor = sColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; + final onPrimaryColor = pColor.computeLuminance() > 0.5 ? Color(0xFF2E251B) : Colors.white; + final onSecondaryColor = sColor.computeLuminance() > 0.5 ? Color(0xFF2E251B) : Colors.white; return ThemeData( useMaterial3: true, @@ -41,7 +41,7 @@ class AppTheme { primary: pColor, primaryContainer: pColor, secondary: sColor, - secondaryContainer: sColor, + secondaryContainer: sColor.withValues(alpha: 0.15), onSecondaryContainer: onSecondaryColor, surface: surface, onSurface: onSurface, @@ -50,75 +50,91 @@ class AppTheme { error: const Color(0xFFB02500), ), textTheme: baseTheme.textTheme.copyWith( - displayLarge: GoogleFonts.epilogue( + displayLarge: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, - letterSpacing: -0.02, + letterSpacing: -0.01, ), - displayMedium: GoogleFonts.epilogue( + displayMedium: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, - letterSpacing: -0.02, + letterSpacing: -0.01, ), - displaySmall: GoogleFonts.epilogue( + displaySmall: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, - letterSpacing: -0.02, + letterSpacing: -0.01, ), - headlineMedium: GoogleFonts.epilogue( + headlineMedium: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, - letterSpacing: -0.02, + letterSpacing: -0.01, ), - titleLarge: GoogleFonts.epilogue( + titleLarge: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, ), - titleMedium: GoogleFonts.epilogue( + titleMedium: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.bold, ), - titleSmall: GoogleFonts.epilogue( + titleSmall: GoogleFonts.lora( color: onSurface, fontWeight: FontWeight.w600, ), - bodyLarge: GoogleFonts.manrope(color: onSurface), - bodyMedium: GoogleFonts.manrope(color: onSurfaceVariant), - bodySmall: GoogleFonts.manrope(color: onSurfaceVariant), - labelLarge: GoogleFonts.manrope(color: onSurfaceVariant), + bodyLarge: GoogleFonts.manrope( + color: onSurface, + letterSpacing: 0.1, + ), + bodyMedium: GoogleFonts.manrope( + color: onSurfaceVariant, + letterSpacing: 0.1, + ), + bodySmall: GoogleFonts.manrope( + color: onSurfaceVariant, + letterSpacing: 0.1, + ), + labelLarge: GoogleFonts.manrope( + color: onSurfaceVariant, + fontWeight: FontWeight.w600, + ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28), // Elegant pill buttons ), foregroundColor: onPrimaryColor, backgroundColor: pColor, - elevation: 0, - side: const BorderSide(color: Colors.red, width: 2), + elevation: 2, + shadowColor: pColor.withValues(alpha: 0.2), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), ), ), - cardTheme: const CardThemeData( + cardTheme: CardThemeData( color: surfaceContainerLow, - elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.zero), + elevation: 1, + shadowColor: Colors.black.withValues(alpha: 0.05), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), // Softer warm card corners + ), margin: EdgeInsets.zero, ), inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: surfaceContainer, // Spec: surfaceContainer with 0px radius - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - border: const OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.zero, - ), - enabledBorder: const OutlineInputBorder( + fillColor: surfaceContainer, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + border: OutlineInputBorder( borderSide: BorderSide.none, - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(12), ), focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: pColor.withValues(alpha: 0.5), width: 2), - borderRadius: BorderRadius.zero, + borderSide: BorderSide(color: pColor.withValues(alpha: 0.5), width: 1.5), + borderRadius: BorderRadius.circular(12), ), labelStyle: const TextStyle(color: onSurfaceVariant), hintStyle: GoogleFonts.manrope(color: onSurfaceVariant), @@ -128,11 +144,12 @@ class AppTheme { foregroundColor: onSurface, elevation: 0, surfaceTintColor: Colors.transparent, - titleTextStyle: GoogleFonts.epilogue( + centerTitle: true, + titleTextStyle: GoogleFonts.lora( color: onSurface, - fontSize: 20, + fontSize: 21, fontWeight: FontWeight.bold, - letterSpacing: -0.02, + letterSpacing: -0.01, ), ), ); diff --git a/lib/widgets/carousel_widget.dart b/lib/widgets/carousel_widget.dart index 1160364..b81114d 100644 --- a/lib/widgets/carousel_widget.dart +++ b/lib/widgets/carousel_widget.dart @@ -129,9 +129,22 @@ class _SlideImage extends StatelessWidget { } return Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - decoration: const BoxDecoration(color: AppTheme.surfaceContainerLow), - child: ClipRect(child: image), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: image, + ), ); } diff --git a/lib/widgets/promo_card_widget.dart b/lib/widgets/promo_card_widget.dart index fe79b0a..c8ccbde 100644 --- a/lib/widgets/promo_card_widget.dart +++ b/lib/widgets/promo_card_widget.dart @@ -73,9 +73,21 @@ class _PromoCard extends StatelessWidget { }, child: Container( width: 140, - margin: const EdgeInsets.only(right: 12), - decoration: const BoxDecoration( - color: AppTheme.surfaceContainerLow, + margin: const EdgeInsets.only(right: 12, bottom: 6), + decoration: BoxDecoration( + color: AppTheme.surfaceContainerLowest, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppTheme.outlineVariant.withValues(alpha: 0.3), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 6, + offset: const Offset(0, 3), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -83,7 +95,10 @@ class _PromoCard extends StatelessWidget { SizedBox( height: 110, width: double.infinity, - child: ClipRect(child: imageWidget), + child: ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), + child: imageWidget, + ), ), Padding( padding: const EdgeInsets.all(8.0), @@ -92,6 +107,7 @@ class _PromoCard extends StatelessWidget { style: Theme.of(context).textTheme.labelLarge?.copyWith( color: AppTheme.onSurface, fontWeight: FontWeight.bold, + fontSize: 11, ), maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/lib/widgets/subscription_list_widget.dart b/lib/widgets/subscription_list_widget.dart index 6cbe7f6..ebadfe7 100644 --- a/lib/widgets/subscription_list_widget.dart +++ b/lib/widgets/subscription_list_widget.dart @@ -88,10 +88,17 @@ class _SubscriptionCard extends StatelessWidget { decoration: BoxDecoration( color: AppTheme.surfaceContainerLowest, border: Border.all( - color: active ? AppTheme.secondaryContainer : AppTheme.surfaceContainerHighest, + color: active ? AppTheme.secondary.withValues(alpha: 0.4) : AppTheme.outlineVariant.withValues(alpha: 0.4), width: 1.5, ), - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.04), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -124,7 +131,7 @@ class _SubscriptionCard extends StatelessWidget { color: active ? const Color(0xFF1B5E20).withValues(alpha: 0.10) : const Color(0xFFB02500).withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(20), // Pill status badge ), child: Text( active ? 'ACTIVE' : 'EXPIRED', @@ -146,6 +153,7 @@ class _SubscriptionCard extends StatelessWidget { style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: AppTheme.onSurface, + fontFamily: 'serif', ), ), const SizedBox(height: 12),