From 370099930d82ffe24414c0cb2741e6f361d0147c Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sun, 14 Jun 2026 11:09:57 +0700 Subject: [PATCH] refactor: resolve all hardcoded color references to support dynamic branding colors from backend via Theme.of(context) --- lib/screens/branches_screen.dart | 53 ++++++++++++----------- lib/screens/loyalty_dashboard.dart | 44 ++++++++++--------- lib/screens/main_shell.dart | 3 +- lib/widgets/carousel_widget.dart | 25 ++++++----- lib/widgets/promo_card_widget.dart | 23 +++++----- lib/widgets/subscription_list_widget.dart | 33 +++++++------- 6 files changed, 97 insertions(+), 84 deletions(-) diff --git a/lib/screens/branches_screen.dart b/lib/screens/branches_screen.dart index 7ce52bd..3ae4dbe 100644 --- a/lib/screens/branches_screen.dart +++ b/lib/screens/branches_screen.dart @@ -153,6 +153,9 @@ class _BranchesScreenState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + return _isLoading ? const Center(child: CircularProgressIndicator()) : RefreshIndicator( @@ -163,17 +166,17 @@ class _BranchesScreenState extends State { if (_locationDenied) Container( width: double.infinity, - color: AppTheme.surfaceContainerLow, + color: colorScheme.surfaceContainerLow, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ - const Icon(Icons.location_off, size: 16, color: AppTheme.onSurfaceVariant), + Icon(Icons.location_off, size: 16, color: colorScheme.onSurfaceVariant), const SizedBox(width: 8), Expanded( child: Text( 'Location not available. Showing branches alphabetically.', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.onSurfaceVariant, + style: theme.textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, ), ), ), @@ -183,16 +186,16 @@ class _BranchesScreenState extends State { else if (_userPosition != null) Container( width: double.infinity, - color: AppTheme.surfaceContainerLow, + color: colorScheme.surfaceContainerLow, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ - const Icon(Icons.my_location, size: 16, color: AppTheme.secondary), + Icon(Icons.my_location, size: 16, color: colorScheme.secondary), const SizedBox(width: 8), Text( 'Sorted by distance from your location', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.onSurfaceVariant, + style: theme.textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, ), ), ], @@ -234,7 +237,7 @@ class _BranchesScreenState extends State { return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( - color: AppTheme.surfaceContainerLow, + color: colorScheme.surfaceContainerLow, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -251,18 +254,18 @@ class _BranchesScreenState extends State { leading: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppTheme.secondaryContainer, + color: colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(12), ), - child: const Icon(Icons.storefront, - color: AppTheme.secondary), + child: Icon(Icons.storefront, + color: colorScheme.secondary), ), title: Row( children: [ Expanded( child: Text( branch['name'] ?? 'Mapan Branch', - style: Theme.of(context) + style: theme .textTheme .titleMedium ?.copyWith( @@ -277,16 +280,16 @@ class _BranchesScreenState extends State { padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: AppTheme.secondaryContainer, + color: colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(12), ), child: Text( distanceLabel, - style: Theme.of(context) + style: theme .textTheme .labelLarge ?.copyWith( - color: AppTheme.onSecondaryContainer, + color: colorScheme.onSecondaryContainer, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -303,22 +306,22 @@ class _BranchesScreenState extends State { addressParts.isEmpty ? 'No address specified' : addressParts, - style: Theme.of(context).textTheme.bodyMedium, + style: theme.textTheme.bodyMedium, ), if (phone.isNotEmpty) ...[ const SizedBox(height: 4), Row( children: [ - const Icon(Icons.phone, + Icon(Icons.phone, size: 14, - color: AppTheme.onSurfaceVariant), + color: colorScheme.onSurfaceVariant), const SizedBox(width: 4), Text(phone, - style: Theme.of(context) + style: theme .textTheme .bodySmall ?.copyWith( - color: AppTheme.onSurfaceVariant)), + color: colorScheme.onSurfaceVariant)), ], ), ] @@ -327,13 +330,13 @@ class _BranchesScreenState extends State { ), trailing: phone.isNotEmpty ? IconButton( - icon: const Icon(Icons.chat_bubble, - color: AppTheme.onSurface), + icon: Icon(Icons.chat_bubble, + color: colorScheme.onSurface), onPressed: () => _launchWhatsApp(phone), tooltip: 'Chat on WhatsApp', ) - : const Icon(Icons.chevron_right, - color: AppTheme.onSurfaceVariant), + : Icon(Icons.chevron_right, + color: colorScheme.onSurfaceVariant), ), ); }, diff --git a/lib/screens/loyalty_dashboard.dart b/lib/screens/loyalty_dashboard.dart index 9f058ec..1309288 100644 --- a/lib/screens/loyalty_dashboard.dart +++ b/lib/screens/loyalty_dashboard.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import '../services/odoo_service.dart'; -import '../theme/app_theme.dart'; import '../widgets/carousel_widget.dart'; import '../widgets/promo_card_widget.dart'; import '../widgets/subscription_list_widget.dart'; @@ -130,20 +129,25 @@ class _LoyaltyCardTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; 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'; + final onPrimary = colorScheme.onPrimary; + final accentColor = colorScheme.secondary; + return Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 8), padding: const EdgeInsets.all(24), decoration: BoxDecoration( - color: AppTheme.primary, // Rich brick red background + color: colorScheme.primary, borderRadius: BorderRadius.circular(16), border: Border.all( - color: const Color(0xFFF3DCA2), // Golden border + color: accentColor.withValues(alpha: 0.5), width: 1.5, ), boxShadow: [ @@ -163,8 +167,8 @@ class _LoyaltyCardTile extends StatelessWidget { Expanded( child: Text( '${card['program_id']?[1] ?? 'Loyalty Program'}', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Colors.white, + style: theme.textTheme.titleLarge?.copyWith( + color: onPrimary, fontFamily: 'serif', ), softWrap: true, @@ -174,13 +178,13 @@ class _LoyaltyCardTile extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: const Color(0xFFB58428), // Golden honey badge background + color: accentColor, borderRadius: BorderRadius.circular(20), ), child: Text( tier, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: Colors.white, + style: theme.textTheme.labelLarge?.copyWith( + color: colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white, fontWeight: FontWeight.bold, fontSize: 9, letterSpacing: 0.8, @@ -192,8 +196,8 @@ class _LoyaltyCardTile extends StatelessWidget { const SizedBox(height: 24), Text( 'MEMBERSHIP CODE', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.white.withValues(alpha: 0.7), + style: theme.textTheme.bodySmall?.copyWith( + color: onPrimary.withValues(alpha: 0.7), fontWeight: FontWeight.bold, fontSize: 10, letterSpacing: 1.0, @@ -202,8 +206,8 @@ class _LoyaltyCardTile extends StatelessWidget { const SizedBox(height: 4), Text( '${card['code'] ?? 'N/A'}', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Colors.white, + style: theme.textTheme.titleMedium?.copyWith( + color: onPrimary, fontFamily: 'monospace', fontSize: 16, fontWeight: FontWeight.bold, @@ -219,8 +223,8 @@ class _LoyaltyCardTile extends StatelessWidget { children: [ Text( 'AVAILABLE POINTS', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.white.withValues(alpha: 0.7), + style: theme.textTheme.bodySmall?.copyWith( + color: onPrimary.withValues(alpha: 0.7), fontWeight: FontWeight.bold, fontSize: 10, letterSpacing: 1.0, @@ -229,16 +233,16 @@ class _LoyaltyCardTile extends StatelessWidget { const SizedBox(height: 4), Row( children: [ - const Icon( + Icon( Icons.restaurant_rounded, - color: Color(0xFFF3DCA2), + color: accentColor, size: 16, ), const SizedBox(width: 6), Text( 'Dine & Save', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.white.withValues(alpha: 0.9), + style: theme.textTheme.bodyMedium?.copyWith( + color: onPrimary.withValues(alpha: 0.9), fontStyle: FontStyle.italic, ), ), @@ -248,8 +252,8 @@ class _LoyaltyCardTile extends StatelessWidget { ), Text( '${card['points'] ?? 0}', - style: Theme.of(context).textTheme.displayLarge?.copyWith( - color: const Color(0xFFF3DCA2), // Bright golden amber accent + style: theme.textTheme.displayLarge?.copyWith( + color: accentColor, fontWeight: FontWeight.bold, ), ), diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index 0ea4092..240347a 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -5,7 +5,6 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../services/odoo_service.dart'; import '../services/notification_service.dart'; import '../services/theme_manager.dart'; -import '../theme/app_theme.dart'; import 'notifications_screen.dart'; import 'loyalty_dashboard.dart'; import 'branches_screen.dart'; @@ -195,7 +194,7 @@ class _MainShellState extends State { onDestinationSelected: (index) { setState(() => _currentIndex = index); }, - backgroundColor: AppTheme.surfaceContainerLowest, + backgroundColor: colorScheme.surfaceContainerLowest, indicatorColor: colorScheme.primary, destinations: List.generate(4, (i) { return NavigationDestination( diff --git a/lib/widgets/carousel_widget.dart b/lib/widgets/carousel_widget.dart index b81114d..4be6729 100644 --- a/lib/widgets/carousel_widget.dart +++ b/lib/widgets/carousel_widget.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; -import '../theme/app_theme.dart'; import '../screens/carousel_detail_screen.dart'; /// Auto-scrolling carousel widget that shows slides from CMS. @@ -55,6 +54,8 @@ class _CarouselWidgetState extends State { @override Widget build(BuildContext context) { if (widget.slides.isEmpty) return const SizedBox.shrink(); + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; return Column( children: [ @@ -83,8 +84,8 @@ class _CarouselWidgetState extends State { dotHeight: 6, dotWidth: 6, expansionFactor: 3, - dotColor: AppTheme.surfaceContainer, - activeDotColor: AppTheme.secondary, + dotColor: colorScheme.surfaceContainer, + activeDotColor: colorScheme.secondary, ), ), ), @@ -99,6 +100,8 @@ class _SlideImage extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; final base64Img = slide['image'] as String?; final externalUrl = slide['image_url'] as String?; @@ -110,7 +113,7 @@ class _SlideImage extends StatelessWidget { final Uint8List bytes = base64Decode(base64Img); image = Image.memory(bytes, fit: BoxFit.cover, width: double.infinity); } catch (_) { - image = _placeholder(); + image = _placeholder(colorScheme); } } else if (externalUrl != null && externalUrl.isNotEmpty) { // External URL image @@ -118,20 +121,20 @@ class _SlideImage extends StatelessWidget { externalUrl, fit: BoxFit.cover, width: double.infinity, - errorBuilder: (_, __, ___) => _placeholder(), + errorBuilder: (_, __, ___) => _placeholder(colorScheme), loadingBuilder: (ctx, child, progress) { if (progress == null) return child; return const Center(child: CircularProgressIndicator()); }, ); } else { - image = _placeholder(); + image = _placeholder(colorScheme); } return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( - color: AppTheme.surfaceContainerLow, + color: colorScheme.surfaceContainerLow, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -148,11 +151,11 @@ class _SlideImage extends StatelessWidget { ); } - Widget _placeholder() { + Widget _placeholder(ColorScheme colorScheme) { return Container( - color: AppTheme.surfaceContainer, - child: const Center( - child: Icon(Icons.image_rounded, size: 48, color: AppTheme.outlineVariant), + color: colorScheme.surfaceContainer, + child: Center( + child: Icon(Icons.image_rounded, size: 48, color: colorScheme.outline), ), ); } diff --git a/lib/widgets/promo_card_widget.dart b/lib/widgets/promo_card_widget.dart index c8ccbde..d63c277 100644 --- a/lib/widgets/promo_card_widget.dart +++ b/lib/widgets/promo_card_widget.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import '../theme/app_theme.dart'; import '../screens/promo_detail_screen.dart'; /// Horizontal scrollable row of promo highlight cards. @@ -48,6 +47,8 @@ class _PromoCard extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; final base64Img = promo['image_128'] as String?; final title = promo['name'] as String? ?? ''; @@ -58,10 +59,10 @@ class _PromoCard extends StatelessWidget { imageWidget = Image.memory(bytes, fit: BoxFit.cover, width: double.infinity, height: 110); } catch (_) { - imageWidget = _imagePlaceholder(); + imageWidget = _imagePlaceholder(colorScheme); } } else { - imageWidget = _imagePlaceholder(); + imageWidget = _imagePlaceholder(colorScheme); } return GestureDetector( @@ -75,10 +76,10 @@ class _PromoCard extends StatelessWidget { width: 140, margin: const EdgeInsets.only(right: 12, bottom: 6), decoration: BoxDecoration( - color: AppTheme.surfaceContainerLowest, + color: colorScheme.surfaceContainerLowest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: AppTheme.outlineVariant.withValues(alpha: 0.3), + color: colorScheme.outline.withValues(alpha: 0.3), width: 1, ), boxShadow: [ @@ -104,8 +105,8 @@ class _PromoCard extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Text( title, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: AppTheme.onSurface, + style: theme.textTheme.labelLarge?.copyWith( + color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 11, ), @@ -119,11 +120,11 @@ class _PromoCard extends StatelessWidget { ); } - Widget _imagePlaceholder() { + Widget _imagePlaceholder(ColorScheme colorScheme) { return Container( - color: AppTheme.surfaceContainer, - child: const Center( - child: Icon(Icons.local_offer_rounded, size: 32, color: AppTheme.outlineVariant), + color: colorScheme.surfaceContainer, + child: Center( + child: Icon(Icons.local_offer_rounded, size: 32, color: colorScheme.outline), ), ); } diff --git a/lib/widgets/subscription_list_widget.dart b/lib/widgets/subscription_list_widget.dart index ebadfe7..1777cfa 100644 --- a/lib/widgets/subscription_list_widget.dart +++ b/lib/widgets/subscription_list_widget.dart @@ -74,6 +74,9 @@ class _SubscriptionCard extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final programName = sub['program_id'] is List ? (sub['program_id'][1] as String? ?? 'Subscription') : 'Subscription'; @@ -86,9 +89,9 @@ class _SubscriptionCard extends StatelessWidget { margin: const EdgeInsets.fromLTRB(16, 8, 16, 8), padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppTheme.surfaceContainerLowest, + color: colorScheme.surfaceContainerLowest, border: Border.all( - color: active ? AppTheme.secondary.withValues(alpha: 0.4) : AppTheme.outlineVariant.withValues(alpha: 0.4), + color: active ? colorScheme.secondary.withValues(alpha: 0.4) : colorScheme.outline.withValues(alpha: 0.4), width: 1.5, ), borderRadius: BorderRadius.circular(16), @@ -111,13 +114,13 @@ class _SubscriptionCard extends StatelessWidget { Icon( Icons.card_membership_rounded, size: 18, - color: active ? AppTheme.secondary : AppTheme.outlineVariant, + color: active ? colorScheme.secondary : colorScheme.outline, ), const SizedBox(width: 8), Text( 'SUBSCRIPTION CARD', - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: active ? AppTheme.secondary : AppTheme.outlineVariant, + style: theme.textTheme.labelLarge?.copyWith( + color: active ? colorScheme.secondary : colorScheme.outline, fontWeight: FontWeight.bold, fontSize: 10, letterSpacing: 1.0, @@ -150,9 +153,9 @@ class _SubscriptionCard extends StatelessWidget { const SizedBox(height: 14), Text( programName, - style: Theme.of(context).textTheme.titleMedium?.copyWith( + style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, - color: AppTheme.onSurface, + color: colorScheme.onSurface, fontFamily: 'serif', ), ), @@ -165,18 +168,18 @@ class _SubscriptionCard extends StatelessWidget { children: [ Text( 'Card Number', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.onSurfaceVariant, + style: theme.textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, fontSize: 10, ), ), const SizedBox(height: 2), Text( code.isNotEmpty ? code : 'N/A', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( + style: theme.textTheme.bodyMedium?.copyWith( fontFamily: 'monospace', fontWeight: FontWeight.bold, - color: AppTheme.onSurface, + color: colorScheme.onSurface, ), ), ], @@ -186,16 +189,16 @@ class _SubscriptionCard extends StatelessWidget { children: [ Text( 'Validity Period', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.onSurfaceVariant, + style: theme.textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, fontSize: 10, ), ), const SizedBox(height: 2), Text( '$startDate - $endDate', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: AppTheme.onSurface, + style: theme.textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface, fontWeight: FontWeight.w600, ), ),