update theme

This commit is contained in:
Phuoc Nguyen
2025-12-02 15:20:54 +07:00
parent 12bd70479c
commit 49a41d24eb
78 changed files with 3263 additions and 2756 deletions

View File

@@ -36,8 +36,10 @@ class AccountPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
body: SafeArea( body: SafeArea(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@@ -48,7 +50,7 @@ class AccountPage extends ConsumerWidget {
child: Column( child: Column(
children: [ children: [
// Simple Header // Simple Header
_buildHeader(), _buildHeader(context),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// User Profile Card - only this depends on provider // User Profile Card - only this depends on provider
@@ -76,26 +78,28 @@ class AccountPage extends ConsumerWidget {
} }
/// Build simple header with title /// Build simple header with title
Widget _buildHeader() { Widget _buildHeader(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
], ],
), ),
child: const Text( child: Text(
'Tài khoản', 'Tài khoản',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
); );
@@ -103,14 +107,16 @@ class AccountPage extends ConsumerWidget {
/// Build account menu section /// Build account menu section
Widget _buildAccountMenu(BuildContext context) { Widget _buildAccountMenu(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -158,14 +164,14 @@ class AccountPage extends ConsumerWidget {
context.push(RouteNames.changePassword); context.push(RouteNames.changePassword);
}, },
), ),
AccountMenuItem( // AccountMenuItem(
icon: FontAwesomeIcons.language, // icon: FontAwesomeIcons.language,
title: 'Ngôn ngữ', // title: 'Ngôn ngữ',
subtitle: 'Tiếng Việt', // subtitle: 'Tiếng Việt',
onTap: () { // onTap: () {
_showComingSoon(context); // _showComingSoon(context);
}, // },
), // ),
AccountMenuItem( AccountMenuItem(
icon: FontAwesomeIcons.palette, icon: FontAwesomeIcons.palette,
title: 'Giao diện', title: 'Giao diện',
@@ -181,14 +187,16 @@ class AccountPage extends ConsumerWidget {
/// Build support section /// Build support section
Widget _buildSupportSection(BuildContext context) { Widget _buildSupportSection(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -198,8 +206,8 @@ class AccountPage extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Section title // Section title
const Padding( Padding(
padding: EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
AppSpacing.md, AppSpacing.md,
AppSpacing.md, AppSpacing.md,
AppSpacing.md, AppSpacing.md,
@@ -210,7 +218,7 @@ class AccountPage extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -220,10 +228,10 @@ class AccountPage extends ConsumerWidget {
icon: FontAwesomeIcons.headset, icon: FontAwesomeIcons.headset,
title: 'Liên hệ hỗ trợ', title: 'Liên hệ hỗ trợ',
subtitle: 'Hotline: 1900 1234', subtitle: 'Hotline: 1900 1234',
trailing: const FaIcon( trailing: FaIcon(
FontAwesomeIcons.phone, FontAwesomeIcons.phone,
size: 18, size: 18,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -283,7 +291,7 @@ class AccountPage extends ConsumerWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Ứng dụng dành cho thầu thợ, kiến trúc sư, đại lý và môi giới trong ngành gạch ốp lát và nội thất.', 'Ứng dụng dành cho thầu thợ, kiến trúc sư, đại lý và môi giới trong ngành gạch ốp lát và nội thất.',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -307,27 +315,28 @@ class _ProfileCardSection extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final userInfoAsync = ref.watch(userInfoProvider); final userInfoAsync = ref.watch(userInfoProvider);
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: userInfoAsync.when( child: userInfoAsync.when(
loading: () => _buildLoadingCard(), loading: () => _buildLoadingCard(colorScheme),
error: (error, stack) => _buildErrorCard(context, ref, error), error: (error, stack) => _buildErrorCard(context, ref, error, colorScheme),
data: (userInfo) => _buildProfileCard(context, userInfo), data: (userInfo) => _buildProfileCard(context, userInfo, colorScheme),
), ),
); );
} }
Widget _buildLoadingCard() { Widget _buildLoadingCard(ColorScheme colorScheme) {
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -339,13 +348,13 @@ class _ProfileCardSection extends ConsumerWidget {
Container( Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
), ),
child: const Center( child: Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: AppColors.primaryBlue, color: colorScheme.primary,
strokeWidth: 2, strokeWidth: 2,
), ),
), ),
@@ -359,7 +368,7 @@ class _ProfileCardSection extends ConsumerWidget {
height: 20, height: 20,
width: 150, width: 150,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -368,7 +377,7 @@ class _ProfileCardSection extends ConsumerWidget {
height: 14, height: 14,
width: 100, width: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -380,15 +389,15 @@ class _ProfileCardSection extends ConsumerWidget {
); );
} }
Widget _buildErrorCard(BuildContext context, WidgetRef ref, Object error) { Widget _buildErrorCard(BuildContext context, WidgetRef ref, Object error, ColorScheme colorScheme) {
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -399,9 +408,9 @@ class _ProfileCardSection extends ConsumerWidget {
Container( Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
), ),
child: const Center( child: const Center(
child: FaIcon( child: FaIcon(
@@ -416,22 +425,22 @@ class _ProfileCardSection extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Không thể tải thông tin', 'Không thể tải thông tin',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
GestureDetector( GestureDetector(
onTap: () => ref.read(userInfoProvider.notifier).refresh(), onTap: () => ref.read(userInfoProvider.notifier).refresh(),
child: const Text( child: Text(
'Nhấn để thử lại', 'Nhấn để thử lại',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -444,15 +453,15 @@ class _ProfileCardSection extends ConsumerWidget {
); );
} }
Widget _buildProfileCard(BuildContext context, domain.UserInfo userInfo) { Widget _buildProfileCard(BuildContext context, domain.UserInfo userInfo, ColorScheme colorScheme) {
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -471,17 +480,13 @@ class _ProfileCardSection extends ConsumerWidget {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( color: colorScheme.primaryContainer,
colors: [Color(0xFF005B9A), Color(0xFF38B6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
), ),
child: const Center( child: Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: colorScheme.onPrimaryContainer,
strokeWidth: 2, strokeWidth: 2,
), ),
), ),
@@ -489,19 +494,15 @@ class _ProfileCardSection extends ConsumerWidget {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( color: colorScheme.primaryContainer,
colors: [Color(0xFF005B9A), Color(0xFF38B6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
), ),
child: Center( child: Center(
child: Text( child: Text(
userInfo.initials, userInfo.initials,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: colorScheme.onPrimaryContainer,
fontSize: 32, fontSize: 32,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -513,19 +514,15 @@ class _ProfileCardSection extends ConsumerWidget {
: Container( : Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( color: colorScheme.primaryContainer,
colors: [Color(0xFF005B9A), Color(0xFF38B6FF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
), ),
child: Center( child: Center(
child: Text( child: Text(
userInfo.initials, userInfo.initials,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: colorScheme.onPrimaryContainer,
fontSize: 32, fontSize: 32,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -541,27 +538,27 @@ class _ProfileCardSection extends ConsumerWidget {
children: [ children: [
Text( Text(
userInfo.fullName, userInfo.fullName,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
Text( Text(
'${_getRoleDisplayName(userInfo.role)} · Hạng ${userInfo.tierDisplayName}', '${_getRoleDisplayName(userInfo.role)} · Hạng ${userInfo.tierDisplayName}',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
if (userInfo.phoneNumber != null) ...[ if (userInfo.phoneNumber != null) ...[
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
Text( Text(
userInfo.phoneNumber!, userInfo.phoneNumber!,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -594,12 +591,14 @@ class _ProfileCardSection extends ConsumerWidget {
class _LogoutButton extends ConsumerWidget { class _LogoutButton extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
width: double.infinity, width: double.infinity,
child: OutlinedButton.icon( child: OutlinedButton.icon(
onPressed: () { onPressed: () {
_showLogoutConfirmation(context, ref); _showLogoutConfirmation(context, ref, colorScheme);
}, },
icon: const FaIcon(FontAwesomeIcons.arrowRightFromBracket, size: 18), icon: const FaIcon(FontAwesomeIcons.arrowRightFromBracket, size: 18),
label: const Text('Đăng xuất'), label: const Text('Đăng xuất'),
@@ -616,12 +615,19 @@ class _LogoutButton extends ConsumerWidget {
} }
/// Show logout confirmation dialog /// Show logout confirmation dialog
void _showLogoutConfirmation(BuildContext context, WidgetRef ref) { void _showLogoutConfirmation(BuildContext context, WidgetRef ref, ColorScheme colorScheme) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Đăng xuất'), backgroundColor: colorScheme.surface,
content: const Text('Bạn có chắc chắn muốn đăng xuất?'), title: Text(
'Đăng xuất',
style: TextStyle(color: colorScheme.onSurface),
),
content: Text(
'Bạn có chắc chắn muốn đăng xuất?',
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),

View File

@@ -32,6 +32,8 @@ class AddressFormPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Form key for validation // Form key for validation
final formKey = useMemoized(() => GlobalKey<FormState>()); final formKey = useMemoized(() => GlobalKey<FormState>());
@@ -89,32 +91,32 @@ class AddressFormPage extends HookConsumerWidget {
); );
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: Text( title: Text(
address == null ? 'Thêm địa chỉ mới' : 'Chỉnh sửa địa chỉ', address == null ? 'Thêm địa chỉ mới' : 'Chỉnh sửa địa chỉ',
style: const TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.circleInfo, FontAwesomeIcons.circleInfo,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => _showInfoDialog(context), onPressed: () => _showInfoDialog(context, colorScheme),
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
@@ -136,10 +138,12 @@ class AddressFormPage extends HookConsumerWidget {
children: [ children: [
// Contact Information Section // Contact Information Section
_buildSection( _buildSection(
colorScheme: colorScheme,
icon: FontAwesomeIcons.user, icon: FontAwesomeIcons.user,
title: 'Thông tin liên hệ', title: 'Thông tin liên hệ',
children: [ children: [
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: nameController, controller: nameController,
label: 'Họ và tên', label: 'Họ và tên',
icon: FontAwesomeIcons.user, icon: FontAwesomeIcons.user,
@@ -154,6 +158,7 @@ class AddressFormPage extends HookConsumerWidget {
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: phoneController, controller: phoneController,
label: 'Số điện thoại', label: 'Số điện thoại',
icon: FontAwesomeIcons.phone, icon: FontAwesomeIcons.phone,
@@ -173,6 +178,7 @@ class AddressFormPage extends HookConsumerWidget {
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: emailController, controller: emailController,
label: 'Email', label: 'Email',
icon: FontAwesomeIcons.envelope, icon: FontAwesomeIcons.envelope,
@@ -190,6 +196,7 @@ class AddressFormPage extends HookConsumerWidget {
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: taxIdController, controller: taxIdController,
label: 'Mã số thuế', label: 'Mã số thuế',
icon: FontAwesomeIcons.fileInvoice, icon: FontAwesomeIcons.fileInvoice,
@@ -203,10 +210,12 @@ class AddressFormPage extends HookConsumerWidget {
// Address Information Section // Address Information Section
_buildSection( _buildSection(
colorScheme: colorScheme,
icon: FontAwesomeIcons.locationDot, icon: FontAwesomeIcons.locationDot,
title: 'Địa chỉ giao hàng', title: 'Địa chỉ giao hàng',
children: [ children: [
_buildDropdownWithLoading( _buildDropdownWithLoading(
colorScheme: colorScheme,
label: 'Tỉnh/Thành phố', label: 'Tỉnh/Thành phố',
value: selectedCityCode.value, value: selectedCityCode.value,
items: citiesMap, items: citiesMap,
@@ -226,6 +235,7 @@ class AddressFormPage extends HookConsumerWidget {
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
_buildDropdownWithLoading( _buildDropdownWithLoading(
colorScheme: colorScheme,
label: 'Quận/Huyện', label: 'Quận/Huyện',
value: selectedWardCode.value, value: selectedWardCode.value,
items: wardsMap, items: wardsMap,
@@ -246,6 +256,7 @@ class AddressFormPage extends HookConsumerWidget {
if (citiesAsync.hasError) ...[ if (citiesAsync.hasError) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
_buildErrorBanner( _buildErrorBanner(
colorScheme,
'Không thể tải danh sách tỉnh/thành phố. Vui lòng thử lại.', 'Không thể tải danh sách tỉnh/thành phố. Vui lòng thử lại.',
), ),
], ],
@@ -253,11 +264,13 @@ class AddressFormPage extends HookConsumerWidget {
if (wardsAsync.hasError && selectedCityCode.value != null) ...[ if (wardsAsync.hasError && selectedCityCode.value != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
_buildErrorBanner( _buildErrorBanner(
colorScheme,
'Không thể tải danh sách quận/huyện. Vui lòng thử lại.', 'Không thể tải danh sách quận/huyện. Vui lòng thử lại.',
), ),
], ],
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
_buildTextArea( _buildTextArea(
colorScheme: colorScheme,
controller: addressDetailController, controller: addressDetailController,
label: 'Địa chỉ cụ thể', label: 'Địa chỉ cụ thể',
placeholder: 'Số nhà, tên đường, khu vực...', placeholder: 'Số nhà, tên đường, khu vực...',
@@ -279,11 +292,11 @@ class AddressFormPage extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -303,31 +316,31 @@ class AddressFormPage extends HookConsumerWidget {
value: isDefault.value, value: isDefault.value,
onChanged: (value) => onChanged: (value) =>
isDefault.value = value ?? false, isDefault.value = value ?? false,
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
materialTapTargetSize: materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap, MaterialTapTargetSize.shrinkWrap,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
const Text( Text(
'Đặt làm địa chỉ mặc định', 'Đặt làm địa chỉ mặc định',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Padding( Padding(
padding: EdgeInsets.only(left: 32), padding: const EdgeInsets.only(left: 32),
child: Text( child: Text(
'Địa chỉ này sẽ được sử dụng làm mặc định khi đặt hàng', 'Địa chỉ này sẽ được sử dụng làm mặc định khi đặt hàng',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -401,15 +414,15 @@ class AddressFormPage extends HookConsumerWidget {
vertical: AppSpacing.md, vertical: AppSpacing.md,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
border: Border( border: Border(
top: BorderSide( top: BorderSide(
color: Colors.grey.withValues(alpha: 0.15), color: colorScheme.outlineVariant,
), ),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.08), color: colorScheme.shadow.withValues(alpha: 0.08),
blurRadius: 12, blurRadius: 12,
offset: const Offset(0, -4), offset: const Offset(0, -4),
), ),
@@ -437,12 +450,12 @@ class AddressFormPage extends HookConsumerWidget {
isSaving, isSaving,
), ),
icon: isSaving.value icon: isSaving.value
? const SizedBox( ? SizedBox(
width: 18, width: 18,
height: 18, height: 18,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: Colors.white, color: colorScheme.onPrimary,
), ),
) )
: const FaIcon(FontAwesomeIcons.floppyDisk, size: 18), : const FaIcon(FontAwesomeIcons.floppyDisk, size: 18),
@@ -454,9 +467,9 @@ class AddressFormPage extends HookConsumerWidget {
), ),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor: AppColors.grey500, disabledBackgroundColor: colorScheme.onSurfaceVariant,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -475,6 +488,7 @@ class AddressFormPage extends HookConsumerWidget {
/// Build a section with icon and title /// Build a section with icon and title
Widget _buildSection({ Widget _buildSection({
required ColorScheme colorScheme,
required IconData icon, required IconData icon,
required String title, required String title,
required List<Widget> children, required List<Widget> children,
@@ -482,11 +496,11 @@ class AddressFormPage extends HookConsumerWidget {
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -500,15 +514,15 @@ class AddressFormPage extends HookConsumerWidget {
FaIcon( FaIcon(
icon, icon,
size: 16, size: 16,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -522,6 +536,7 @@ class AddressFormPage extends HookConsumerWidget {
/// Build a text field with label and icon /// Build a text field with label and icon
Widget _buildTextField({ Widget _buildTextField({
required ColorScheme colorScheme,
required TextEditingController controller, required TextEditingController controller,
required String label, required String label,
required IconData icon, required IconData icon,
@@ -538,10 +553,10 @@ class AddressFormPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (isRequired) if (isRequired)
@@ -561,13 +576,13 @@ class AddressFormPage extends HookConsumerWidget {
validator: validator, validator: validator,
decoration: InputDecoration( decoration: InputDecoration(
hintText: placeholder, hintText: placeholder,
hintStyle: const TextStyle(color: AppColors.grey500), hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
prefixIcon: Padding( prefixIcon: Padding(
padding: const EdgeInsets.only(left: 16, right: 12), padding: const EdgeInsets.only(left: 16, right: 12),
child: FaIcon( child: FaIcon(
icon, icon,
size: 16, size: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
prefixIconConstraints: const BoxConstraints( prefixIconConstraints: const BoxConstraints(
@@ -575,19 +590,19 @@ class AddressFormPage extends HookConsumerWidget {
minHeight: 0, minHeight: 0,
), ),
filled: true, filled: true,
fillColor: Colors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -612,9 +627,9 @@ class AddressFormPage extends HookConsumerWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
helperText, helperText,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -624,6 +639,7 @@ class AddressFormPage extends HookConsumerWidget {
/// Build a dropdown field /// Build a dropdown field
Widget _buildDropdown({ Widget _buildDropdown({
required ColorScheme colorScheme,
required String label, required String label,
required String? value, required String? value,
required Map<String, String> items, required Map<String, String> items,
@@ -639,10 +655,10 @@ class AddressFormPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (isRequired) if (isRequired)
@@ -662,23 +678,23 @@ class AddressFormPage extends HookConsumerWidget {
validator: validator, validator: validator,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: enabled ? Colors.white : const Color(0xFFF3F4F6), fillColor: enabled ? colorScheme.surface : colorScheme.surfaceContainerHighest,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
disabledBorder: OutlineInputBorder( disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -700,7 +716,7 @@ class AddressFormPage extends HookConsumerWidget {
), ),
hint: Text( hint: Text(
'-- Chọn $label --', '-- Chọn $label --',
style: const TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
items: enabled items: enabled
? () { ? () {
@@ -709,9 +725,9 @@ class AddressFormPage extends HookConsumerWidget {
value: entry.key, value: entry.key,
child: Text( child: Text(
entry.value, entry.value,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
); );
@@ -725,9 +741,9 @@ class AddressFormPage extends HookConsumerWidget {
value: value, value: value,
child: Text( child: Text(
'$value (đã lưu)', '$value (đã lưu)',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),
@@ -742,7 +758,7 @@ class AddressFormPage extends HookConsumerWidget {
icon: FaIcon( icon: FaIcon(
FontAwesomeIcons.chevronDown, FontAwesomeIcons.chevronDown,
size: 14, size: 14,
color: enabled ? AppColors.grey500 : AppColors.grey500.withValues(alpha: 0.5), color: enabled ? colorScheme.onSurfaceVariant : colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
), ),
], ],
@@ -751,6 +767,7 @@ class AddressFormPage extends HookConsumerWidget {
/// Build a dropdown field with loading indicator /// Build a dropdown field with loading indicator
Widget _buildDropdownWithLoading({ Widget _buildDropdownWithLoading({
required ColorScheme colorScheme,
required String label, required String label,
required String? value, required String? value,
required Map<String, String> items, required Map<String, String> items,
@@ -767,10 +784,10 @@ class AddressFormPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (isRequired) if (isRequired)
@@ -783,12 +800,12 @@ class AddressFormPage extends HookConsumerWidget {
), ),
if (isLoading) ...[ if (isLoading) ...[
const SizedBox(width: 8), const SizedBox(width: 8),
const SizedBox( SizedBox(
width: 12, width: 12,
height: 12, height: 12,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -801,23 +818,23 @@ class AddressFormPage extends HookConsumerWidget {
validator: validator, validator: validator,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: enabled && !isLoading ? Colors.white : const Color(0xFFF3F4F6), fillColor: enabled && !isLoading ? colorScheme.surface : colorScheme.surfaceContainerHighest,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
disabledBorder: OutlineInputBorder( disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -837,14 +854,14 @@ class AddressFormPage extends HookConsumerWidget {
vertical: 14, vertical: 14,
), ),
suffixIcon: isLoading suffixIcon: isLoading
? const Padding( ? Padding(
padding: EdgeInsets.only(right: 12), padding: const EdgeInsets.only(right: 12),
child: SizedBox( child: SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
) )
@@ -857,7 +874,7 @@ class AddressFormPage extends HookConsumerWidget {
? 'Vui lòng chọn Tỉnh/Thành phố trước' ? 'Vui lòng chọn Tỉnh/Thành phố trước'
: '-- Chọn $label --', : '-- Chọn $label --',
style: TextStyle( style: TextStyle(
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontSize: 14, fontSize: 14,
fontStyle: !enabled || isLoading ? FontStyle.italic : FontStyle.normal, fontStyle: !enabled || isLoading ? FontStyle.italic : FontStyle.normal,
), ),
@@ -869,9 +886,9 @@ class AddressFormPage extends HookConsumerWidget {
value: entry.key, value: entry.key,
child: Text( child: Text(
entry.value, entry.value,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
); );
@@ -885,9 +902,9 @@ class AddressFormPage extends HookConsumerWidget {
value: value, value: value,
child: Text( child: Text(
'$value (đã lưu)', '$value (đã lưu)',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),
@@ -903,8 +920,8 @@ class AddressFormPage extends HookConsumerWidget {
FontAwesomeIcons.chevronDown, FontAwesomeIcons.chevronDown,
size: 14, size: 14,
color: enabled && !isLoading color: enabled && !isLoading
? AppColors.grey500 ? colorScheme.onSurfaceVariant
: AppColors.grey500.withValues(alpha: 0.5), : colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
), ),
], ],
@@ -913,6 +930,7 @@ class AddressFormPage extends HookConsumerWidget {
/// Build a text area field /// Build a text area field
Widget _buildTextArea({ Widget _buildTextArea({
required ColorScheme colorScheme,
required TextEditingController controller, required TextEditingController controller,
required String label, required String label,
required String placeholder, required String placeholder,
@@ -927,10 +945,10 @@ class AddressFormPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (isRequired) if (isRequired)
@@ -950,21 +968,21 @@ class AddressFormPage extends HookConsumerWidget {
validator: validator, validator: validator,
decoration: InputDecoration( decoration: InputDecoration(
hintText: placeholder, hintText: placeholder,
hintStyle: const TextStyle(color: AppColors.grey500), hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
filled: true, filled: true,
fillColor: Colors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -986,9 +1004,9 @@ class AddressFormPage extends HookConsumerWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
helperText, helperText,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -997,7 +1015,7 @@ class AddressFormPage extends HookConsumerWidget {
} }
/// Build error banner for API failures /// Build error banner for API failures
Widget _buildErrorBanner(String message) { Widget _buildErrorBanner(ColorScheme colorScheme, String message) {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -1032,7 +1050,7 @@ class AddressFormPage extends HookConsumerWidget {
} }
/// Show info dialog /// Show info dialog
void _showInfoDialog(BuildContext context) { void _showInfoDialog(BuildContext context, ColorScheme colorScheme) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@@ -1132,7 +1150,7 @@ class AddressFormPage extends HookConsumerWidget {
Text(address == null ? 'Đã thêm địa chỉ thành công!' : 'Đã cập nhật địa chỉ thành công!'), Text(address == null ? 'Đã thêm địa chỉ thành công!' : 'Đã cập nhật địa chỉ thành công!'),
], ],
), ),
backgroundColor: const Color(0xFF10B981), backgroundColor: AppColors.success,
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );

View File

@@ -42,30 +42,32 @@ class AddressesPage extends HookConsumerWidget {
// Watch addresses from API // Watch addresses from API
final addressesAsync = ref.watch(addressesProvider); final addressesAsync = ref.watch(addressesProvider);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: Text( title: Text(
selectMode ? 'Chọn địa chỉ' : 'Địa chỉ của bạn', selectMode ? 'Chọn địa chỉ' : 'Địa chỉ của bạn',
style: const TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.circleInfo, FontAwesomeIcons.circleInfo,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () { onPressed: () {
@@ -85,7 +87,7 @@ class AddressesPage extends HookConsumerWidget {
await ref.read(addressesProvider.notifier).refresh(); await ref.read(addressesProvider.notifier).refresh();
}, },
child: addresses.isEmpty child: addresses.isEmpty
? _buildEmptyState(context) ? _buildEmptyState(context, colorScheme)
: ListView.separated( : ListView.separated(
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
itemCount: addresses.length, itemCount: addresses.length,
@@ -168,9 +170,9 @@ class AddressesPage extends HookConsumerWidget {
), ),
), ),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue, foregroundColor: colorScheme.primary,
side: const BorderSide( side: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 1.5, width: 1.5,
), ),
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
@@ -205,10 +207,10 @@ class AddressesPage extends HookConsumerWidget {
), ),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.surfaceContainerHighest,
disabledForegroundColor: AppColors.grey500, disabledForegroundColor: colorScheme.onSurfaceVariant,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -236,8 +238,8 @@ class AddressesPage extends HookConsumerWidget {
), ),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -262,18 +264,18 @@ class AddressesPage extends HookConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không thể tải danh sách địa chỉ', 'Không thể tải danh sách địa chỉ',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -284,8 +286,8 @@ class AddressesPage extends HookConsumerWidget {
icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18), icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18),
label: const Text('Thử lại'), label: const Text('Thử lại'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
), ),
), ),
], ],
@@ -296,7 +298,7 @@ class AddressesPage extends HookConsumerWidget {
} }
/// Build empty state /// Build empty state
Widget _buildEmptyState(BuildContext context) { Widget _buildEmptyState(BuildContext context, ColorScheme colorScheme) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -304,15 +306,15 @@ class AddressesPage extends HookConsumerWidget {
FaIcon( FaIcon(
FontAwesomeIcons.locationDot, FontAwesomeIcons.locationDot,
size: 64, size: 64,
color: AppColors.grey500.withValues(alpha: 0.4), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Chưa có địa chỉ nào', 'Chưa có địa chỉ nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -320,7 +322,7 @@ class AddressesPage extends HookConsumerWidget {
'Thêm địa chỉ để nhận hàng nhanh hơn', 'Thêm địa chỉ để nhận hàng nhanh hơn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500.withValues(alpha: 0.8), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.8),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -334,8 +336,8 @@ class AddressesPage extends HookConsumerWidget {
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -357,20 +359,20 @@ class AddressesPage extends HookConsumerWidget {
ref.read(addressesProvider.notifier).setDefaultAddress(address.name); ref.read(addressesProvider.notifier).setDefaultAddress(address.name);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( const SnackBar(
content: Row( content: Row(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.circleCheck, FontAwesomeIcons.circleCheck,
color: Colors.white, color: Colors.white,
size: 18, size: 18,
), ),
const SizedBox(width: 12), SizedBox(width: 12),
const Text('Đã đặt làm địa chỉ mặc định'), Text('Đã đặt làm địa chỉ mặc định'),
], ],
), ),
backgroundColor: const Color(0xFF10B981), backgroundColor: AppColors.success,
duration: const Duration(seconds: 2), duration: Duration(seconds: 2),
), ),
); );
} }
@@ -460,7 +462,7 @@ class AddressesPage extends HookConsumerWidget {
Text('Đã xóa địa chỉ'), Text('Đã xóa địa chỉ'),
], ],
), ),
backgroundColor: Color(0xFF10B981), backgroundColor: AppColors.success,
duration: Duration(seconds: 2), duration: Duration(seconds: 2),
), ),
); );

View File

@@ -63,19 +63,21 @@ class ChangePasswordPage extends HookConsumerWidget {
}; };
}, []); }, []);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Thay đổi mật khẩu', 'Thay đổi mật khẩu',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -95,11 +97,11 @@ class ChangePasswordPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -109,12 +111,12 @@ class ChangePasswordPage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Title // Title
const Text( Text(
'Cập nhật mật khẩu', 'Cập nhật mật khẩu',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
@@ -122,6 +124,7 @@ class ChangePasswordPage extends HookConsumerWidget {
// Current Password // Current Password
_buildPasswordField( _buildPasswordField(
colorScheme: colorScheme,
label: 'Mật khẩu hiện tại', label: 'Mật khẩu hiện tại',
controller: currentPasswordController, controller: currentPasswordController,
isVisible: currentPasswordVisible, isVisible: currentPasswordVisible,
@@ -138,6 +141,7 @@ class ChangePasswordPage extends HookConsumerWidget {
// New Password // New Password
_buildPasswordField( _buildPasswordField(
colorScheme: colorScheme,
label: 'Mật khẩu mới', label: 'Mật khẩu mới',
controller: newPasswordController, controller: newPasswordController,
isVisible: newPasswordVisible, isVisible: newPasswordVisible,
@@ -158,6 +162,7 @@ class ChangePasswordPage extends HookConsumerWidget {
// Confirm Password // Confirm Password
_buildPasswordField( _buildPasswordField(
colorScheme: colorScheme,
label: 'Nhập lại mật khẩu mới', label: 'Nhập lại mật khẩu mới',
controller: confirmPasswordController, controller: confirmPasswordController,
isVisible: confirmPasswordVisible, isVisible: confirmPasswordVisible,
@@ -206,7 +211,7 @@ class ChangePasswordPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Security Tips // Security Tips
_buildSecurityTips(), _buildSecurityTips(colorScheme),
], ],
), ),
), ),
@@ -216,6 +221,7 @@ class ChangePasswordPage extends HookConsumerWidget {
// Action Buttons // Action Buttons
_buildActionButtons( _buildActionButtons(
context: context, context: context,
colorScheme: colorScheme,
formKey: formKey, formKey: formKey,
currentPasswordController: currentPasswordController, currentPasswordController: currentPasswordController,
newPasswordController: newPasswordController, newPasswordController: newPasswordController,
@@ -232,6 +238,7 @@ class ChangePasswordPage extends HookConsumerWidget {
/// Build password field with show/hide toggle /// Build password field with show/hide toggle
Widget _buildPasswordField({ Widget _buildPasswordField({
required ColorScheme colorScheme,
required String label, required String label,
required TextEditingController controller, required TextEditingController controller,
required ValueNotifier<bool> isVisible, required ValueNotifier<bool> isVisible,
@@ -245,10 +252,10 @@ class ChangePasswordPage extends HookConsumerWidget {
RichText( RichText(
text: TextSpan( text: TextSpan(
text: label, text: label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
children: [ children: [
if (required) if (required)
@@ -267,11 +274,11 @@ class ChangePasswordPage extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Nhập $label', hintText: 'Nhập $label',
hintStyle: TextStyle( hintStyle: TextStyle(
color: AppColors.grey500.withValues(alpha: 0.6), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
fontSize: 14, fontSize: 14,
), ),
filled: true, filled: true,
fillColor: const Color(0xFFF8FAFC), fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
@@ -280,7 +287,7 @@ class ChangePasswordPage extends HookConsumerWidget {
icon: FaIcon( icon: FaIcon(
isVisible.value ? FontAwesomeIcons.eyeSlash : FontAwesomeIcons.eye, isVisible.value ? FontAwesomeIcons.eyeSlash : FontAwesomeIcons.eye,
size: 18, size: 18,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
onPressed: () { onPressed: () {
isVisible.value = !isVisible.value; isVisible.value = !isVisible.value;
@@ -288,16 +295,16 @@ class ChangePasswordPage extends HookConsumerWidget {
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -315,7 +322,7 @@ class ChangePasswordPage extends HookConsumerWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
helpText, helpText,
style: const TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
], ],
@@ -323,38 +330,38 @@ class ChangePasswordPage extends HookConsumerWidget {
} }
/// Build security tips section /// Build security tips section
Widget _buildSecurityTips() { Widget _buildSecurityTips(ColorScheme colorScheme) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Gợi ý bảo mật:', 'Gợi ý bảo mật:',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSecurityTip('Sử dụng ít nhất 8 ký tự'), _buildSecurityTip('Sử dụng ít nhất 8 ký tự', colorScheme),
_buildSecurityTip('Kết hợp chữ hoa, chữ thường và số'), _buildSecurityTip('Kết hợp chữ hoa, chữ thường và số', colorScheme),
_buildSecurityTip('Bao gồm ký tự đặc biệt (!@#\$%^&*)'), _buildSecurityTip('Bao gồm ký tự đặc biệt (!@#\$%^&*)', colorScheme),
_buildSecurityTip('Không sử dụng thông tin cá nhân'), _buildSecurityTip('Không sử dụng thông tin cá nhân', colorScheme),
_buildSecurityTip('Thường xuyên thay đổi mật khẩu'), _buildSecurityTip('Thường xuyên thay đổi mật khẩu', colorScheme),
], ],
), ),
); );
} }
/// Build individual security tip /// Build individual security tip
Widget _buildSecurityTip(String text) { Widget _buildSecurityTip(String text, ColorScheme colorScheme) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: Row( child: Row(
@@ -369,9 +376,9 @@ class ChangePasswordPage extends HookConsumerWidget {
Expanded( Expanded(
child: Text( child: Text(
text, text,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Color(0xFF475569), color: colorScheme.onSurfaceVariant,
height: 1.4, height: 1.4,
), ),
), ),
@@ -384,6 +391,7 @@ class ChangePasswordPage extends HookConsumerWidget {
/// Build action buttons /// Build action buttons
Widget _buildActionButtons({ Widget _buildActionButtons({
required BuildContext context, required BuildContext context,
required ColorScheme colorScheme,
required GlobalKey<FormState> formKey, required GlobalKey<FormState> formKey,
required TextEditingController currentPasswordController, required TextEditingController currentPasswordController,
required TextEditingController newPasswordController, required TextEditingController newPasswordController,
@@ -401,17 +409,17 @@ class ChangePasswordPage extends HookConsumerWidget {
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
side: const BorderSide(color: AppColors.grey100), side: BorderSide(color: colorScheme.outlineVariant),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
), ),
), ),
child: const Text( child: Text(
'Hủy bỏ', 'Hủy bỏ',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -438,8 +446,8 @@ class ChangePasswordPage extends HookConsumerWidget {
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@@ -31,6 +31,8 @@ class ProfileEditPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Watch user info from API // Watch user info from API
final userInfoAsync = ref.watch(userInfoProvider); final userInfoAsync = ref.watch(userInfoProvider);
@@ -45,31 +47,31 @@ class ProfileEditPage extends HookConsumerWidget {
return userInfoAsync.when( return userInfoAsync.when(
loading: () => Scaffold( loading: () => Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
title: const Text( title: Text(
'Thông tin cá nhân', 'Thông tin cá nhân',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
centerTitle: false, centerTitle: false,
), ),
body: const Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(color: AppColors.primaryBlue), CircularProgressIndicator(color: colorScheme.primary),
SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
Text( Text(
'Đang tải thông tin...', 'Đang tải thông tin...',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -77,18 +79,18 @@ class ProfileEditPage extends HookConsumerWidget {
), ),
), ),
error: (error, stack) => Scaffold( error: (error, stack) => Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Thông tin cá nhân', 'Thông tin cá nhân',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -105,11 +107,12 @@ class ProfileEditPage extends HookConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
const Text( Text(
'Không thể tải thông tin người dùng', 'Không thể tải thông tin người dùng',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
@@ -118,8 +121,8 @@ class ProfileEditPage extends HookConsumerWidget {
icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 16), icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 16),
label: const Text('Thử lại'), label: const Text('Thử lại'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
), ),
), ),
], ],
@@ -183,12 +186,12 @@ class ProfileEditPage extends HookConsumerWidget {
} }
}, },
child: Scaffold( child: Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () async { onPressed: () async {
if (hasChanges.value) { if (hasChanges.value) {
final shouldPop = await _showUnsavedChangesDialog(context); final shouldPop = await _showUnsavedChangesDialog(context);
@@ -200,10 +203,10 @@ class ProfileEditPage extends HookConsumerWidget {
} }
}, },
), ),
title: const Text( title: Text(
'Thông tin cá nhân', 'Thông tin cá nhân',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -224,6 +227,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Profile Avatar Section with Name and Status // Profile Avatar Section with Name and Status
_buildAvatarAndStatusSection( _buildAvatarAndStatusSection(
context, context,
colorScheme,
selectedImage, selectedImage,
userInfo.initials, userInfo.initials,
userInfo.avatarUrl, userInfo.avatarUrl,
@@ -240,11 +244,11 @@ class ProfileEditPage extends HookConsumerWidget {
Container( Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -253,13 +257,13 @@ class ProfileEditPage extends HookConsumerWidget {
child: TabBar( child: TabBar(
controller: tabController, controller: tabController,
indicator: BoxDecoration( indicator: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
), ),
indicatorSize: TabBarIndicatorSize.tab, indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
labelColor: Colors.white, labelColor: colorScheme.onPrimary,
unselectedLabelColor: AppColors.grey500, unselectedLabelColor: colorScheme.onSurfaceVariant,
labelStyle: const TextStyle( labelStyle: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -278,6 +282,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Tab 1: Personal Information (always show if no tabs, or when selected) // Tab 1: Personal Information (always show if no tabs, or when selected)
_buildPersonalInformationTab( _buildPersonalInformationTab(
ref: ref, ref: ref,
colorScheme: colorScheme,
nameController: nameController, nameController: nameController,
phoneController: phoneController, phoneController: phoneController,
emailController: emailController, emailController: emailController,
@@ -298,6 +303,7 @@ class ProfileEditPage extends HookConsumerWidget {
_buildVerificationTab( _buildVerificationTab(
ref: ref, ref: ref,
context: context, context: context,
colorScheme: colorScheme,
idCardFrontImage: idCardFrontImage, idCardFrontImage: idCardFrontImage,
idCardBackImage: idCardBackImage, idCardBackImage: idCardBackImage,
certificateImages: certificateImages, certificateImages: certificateImages,
@@ -329,6 +335,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build Personal Information Tab /// Build Personal Information Tab
Widget _buildPersonalInformationTab({ Widget _buildPersonalInformationTab({
required WidgetRef ref, required WidgetRef ref,
required ColorScheme colorScheme,
required TextEditingController nameController, required TextEditingController nameController,
required TextEditingController phoneController, required TextEditingController phoneController,
required TextEditingController emailController, required TextEditingController emailController,
@@ -351,11 +358,11 @@ class ProfileEditPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -365,20 +372,20 @@ class ProfileEditPage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Section Header // Section Header
const Row( Row(
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.circleUser, FontAwesomeIcons.circleUser,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(
'Thông tin cá nhân', 'Thông tin cá nhân',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -388,6 +395,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Full Name // Full Name
_buildTextField( _buildTextField(
colorScheme: colorScheme,
label: 'Họ và tên', label: 'Họ và tên',
controller: nameController, controller: nameController,
required: true, required: true,
@@ -403,6 +411,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Phone (Read-only) // Phone (Read-only)
_buildTextField( _buildTextField(
colorScheme: colorScheme,
label: 'Số điện thoại', label: 'Số điện thoại',
controller: phoneController, controller: phoneController,
readOnly: true, readOnly: true,
@@ -412,6 +421,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Email (Read-only) // Email (Read-only)
_buildTextField( _buildTextField(
colorScheme: colorScheme,
label: 'Email', label: 'Email',
controller: emailController, controller: emailController,
readOnly: true, readOnly: true,
@@ -422,6 +432,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Birth Date // Birth Date
_buildDateField( _buildDateField(
context: context, context: context,
colorScheme: colorScheme,
label: 'Ngày sinh', label: 'Ngày sinh',
controller: birthDateController, controller: birthDateController,
hasChanges: hasChanges, hasChanges: hasChanges,
@@ -431,6 +442,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Gender // Gender
_buildDropdownField( _buildDropdownField(
colorScheme: colorScheme,
label: 'Giới tính', label: 'Giới tính',
value: selectedGender.value, value: selectedGender.value,
items: const [ items: const [
@@ -450,6 +462,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Company Name // Company Name
_buildTextField( _buildTextField(
colorScheme: colorScheme,
label: 'Tên công ty/Cửa hàng', label: 'Tên công ty/Cửa hàng',
controller: companyController, controller: companyController,
), ),
@@ -458,6 +471,7 @@ class ProfileEditPage extends HookConsumerWidget {
// Tax ID // Tax ID
_buildTextField( _buildTextField(
colorScheme: colorScheme,
label: 'Mã số thuế', label: 'Mã số thuế',
controller: taxIdController, controller: taxIdController,
), ),
@@ -472,15 +486,15 @@ class ProfileEditPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEFF6FF), color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: Colors.blue), border: Border.all(color: colorScheme.primary),
), ),
child: Row( child: Row(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.circleInfo, FontAwesomeIcons.circleInfo,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -489,7 +503,7 @@ class ProfileEditPage extends HookConsumerWidget {
'Để thay đổi số điện thoại, email hoặc vai trò, vui lòng liên hệ bộ phận hỗ trợ.', 'Để thay đổi số điện thoại, email hoặc vai trò, vui lòng liên hệ bộ phận hỗ trợ.',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.primaryBlue.withValues(alpha: 0.9), color: colorScheme.onPrimaryContainer,
), ),
), ),
), ),
@@ -521,8 +535,8 @@ class ProfileEditPage extends HookConsumerWidget {
certificateImages: certificateImages.value, certificateImages: certificateImages.value,
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -549,6 +563,7 @@ class ProfileEditPage extends HookConsumerWidget {
Widget _buildVerificationTab({ Widget _buildVerificationTab({
required WidgetRef ref, required WidgetRef ref,
required BuildContext context, required BuildContext context,
required ColorScheme colorScheme,
required ValueNotifier<File?> idCardFrontImage, required ValueNotifier<File?> idCardFrontImage,
required ValueNotifier<File?> idCardBackImage, required ValueNotifier<File?> idCardBackImage,
required ValueNotifier<List<File>> certificateImages, required ValueNotifier<List<File>> certificateImages,
@@ -574,10 +589,10 @@ class ProfileEditPage extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isVerified color: isVerified
? const Color(0xFFF0FDF4) // Green for verified ? const Color(0xFFF0FDF4) // Green for verified
: const Color(0xFFEFF6FF), // Blue for not verified : colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all( border: Border.all(
color: isVerified ? const Color(0xFFBBF7D0) : Colors.blue, color: isVerified ? const Color(0xFFBBF7D0) : colorScheme.primary,
), ),
), ),
child: Row( child: Row(
@@ -586,7 +601,7 @@ class ProfileEditPage extends HookConsumerWidget {
isVerified isVerified
? FontAwesomeIcons.circleCheck ? FontAwesomeIcons.circleCheck
: FontAwesomeIcons.circleInfo, : FontAwesomeIcons.circleInfo,
color: isVerified ? AppColors.success : AppColors.primaryBlue, color: isVerified ? AppColors.success : colorScheme.primary,
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -599,7 +614,7 @@ class ProfileEditPage extends HookConsumerWidget {
fontSize: 12, fontSize: 12,
color: isVerified color: isVerified
? const Color(0xFF166534) ? const Color(0xFF166534)
: AppColors.primaryBlue.withValues(alpha: 0.9), : colorScheme.onPrimaryContainer,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -615,11 +630,11 @@ class ProfileEditPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -629,20 +644,20 @@ class ProfileEditPage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Section Header // Section Header
const Row( Row(
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.fileCircleCheck, FontAwesomeIcons.fileCircleCheck,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(
'Thông tin xác thực', 'Thông tin xác thực',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -651,17 +666,18 @@ class ProfileEditPage extends HookConsumerWidget {
const Divider(height: 32), const Divider(height: 32),
// ID Card Front Upload // ID Card Front Upload
const Text( Text(
'Ảnh mặt trước CCCD/CMND', 'Ảnh mặt trước CCCD/CMND',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildUploadCard( _buildUploadCard(
context: context, context: context,
colorScheme: colorScheme,
icon: FontAwesomeIcons.camera, icon: FontAwesomeIcons.camera,
title: 'Chụp ảnh hoặc chọn file', title: 'Chụp ảnh hoặc chọn file',
subtitle: 'JPG, PNG tối đa 5MB', subtitle: 'JPG, PNG tối đa 5MB',
@@ -675,17 +691,18 @@ class ProfileEditPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// ID Card Back Upload // ID Card Back Upload
const Text( Text(
'Ảnh mặt sau CCCD/CMND', 'Ảnh mặt sau CCCD/CMND',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildUploadCard( _buildUploadCard(
context: context, context: context,
colorScheme: colorScheme,
icon: FontAwesomeIcons.camera, icon: FontAwesomeIcons.camera,
title: 'Chụp ảnh hoặc chọn file', title: 'Chụp ảnh hoặc chọn file',
subtitle: 'JPG, PNG tối đa 5MB', subtitle: 'JPG, PNG tối đa 5MB',
@@ -699,17 +716,18 @@ class ProfileEditPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Certificates Upload (Multiple) // Certificates Upload (Multiple)
const Text( Text(
'Chứng chỉ hành nghề', 'Chứng chỉ hành nghề',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildMultipleUploadCard( _buildMultipleUploadCard(
context: context, context: context,
colorScheme: colorScheme,
selectedImages: certificateImages, selectedImages: certificateImages,
existingImageUrls: existingCertificateUrls, existingImageUrls: existingCertificateUrls,
isVerified: isVerified, isVerified: isVerified,
@@ -743,8 +761,8 @@ class ProfileEditPage extends HookConsumerWidget {
certificateImages: certificateImages.value, certificateImages: certificateImages.value,
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -770,6 +788,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build upload card for verification files /// Build upload card for verification files
Widget _buildUploadCard({ Widget _buildUploadCard({
required BuildContext context, required BuildContext context,
required ColorScheme colorScheme,
required IconData icon, required IconData icon,
required String title, required String title,
required String subtitle, required String subtitle,
@@ -790,14 +809,14 @@ class ProfileEditPage extends HookConsumerWidget {
color: hasAnyImage color: hasAnyImage
? const Color(0xFFF0FDF4) ? const Color(0xFFF0FDF4)
: isDisabled : isDisabled
? const Color(0xFFF1F5F9) // Gray for disabled ? colorScheme.surfaceContainerHighest
: const Color(0xFFF8FAFC), : colorScheme.surfaceContainerLowest,
border: Border.all( border: Border.all(
color: hasAnyImage color: hasAnyImage
? const Color(0xFFBBF7D0) ? const Color(0xFFBBF7D0)
: isDisabled : isDisabled
? const Color(0xFFCBD5E1) ? colorScheme.outlineVariant
: const Color(0xFFE2E8F0), : colorScheme.outlineVariant,
width: 2, width: 2,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
@@ -877,7 +896,7 @@ class ProfileEditPage extends HookConsumerWidget {
children: [ children: [
FaIcon( FaIcon(
icon, icon,
color: isDisabled ? AppColors.grey500 : AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 32, size: 32,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -887,8 +906,8 @@ class ProfileEditPage extends HookConsumerWidget {
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isDisabled color: isDisabled
? AppColors.grey500 ? colorScheme.onSurfaceVariant
: const Color(0xFF1E293B), : colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -896,7 +915,7 @@ class ProfileEditPage extends HookConsumerWidget {
subtitle, subtitle,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: isDisabled ? AppColors.grey500 : AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -908,6 +927,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build multiple upload card for certificates (supports multiple images) /// Build multiple upload card for certificates (supports multiple images)
Widget _buildMultipleUploadCard({ Widget _buildMultipleUploadCard({
required BuildContext context, required BuildContext context,
required ColorScheme colorScheme,
required ValueNotifier<List<File>> selectedImages, required ValueNotifier<List<File>> selectedImages,
List<String>? existingImageUrls, List<String>? existingImageUrls,
required bool isVerified, required bool isVerified,
@@ -972,35 +992,35 @@ class ProfileEditPage extends HookConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
border: Border.all( border: Border.all(
color: const Color(0xFFE2E8F0), color: colorScheme.outlineVariant,
width: 2, width: 2,
), ),
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
), ),
child: Column( child: Column(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.folderPlus, FontAwesomeIcons.folderPlus,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 32, size: 32,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
allImages.isEmpty ? 'Chọn ảnh chứng chỉ' : 'Thêm ảnh chứng chỉ', allImages.isEmpty ? 'Chọn ảnh chứng chỉ' : 'Thêm ảnh chứng chỉ',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Có thể chọn nhiều ảnh - JPG, PNG tối đa 5MB mỗi ảnh', 'Có thể chọn nhiều ảnh - JPG, PNG tối đa 5MB mỗi ảnh',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -1014,27 +1034,27 @@ class ProfileEditPage extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), color: colorScheme.surfaceContainerHighest,
border: Border.all( border: Border.all(
color: const Color(0xFFCBD5E1), color: colorScheme.outlineVariant,
width: 2, width: 2,
), ),
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
), ),
child: const Column( child: Column(
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.certificate, FontAwesomeIcons.certificate,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 32, size: 32,
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Chưa có chứng chỉ', 'Chưa có chứng chỉ',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -1124,6 +1144,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build avatar section with name, position, and status /// Build avatar section with name, position, and status
Widget _buildAvatarAndStatusSection( Widget _buildAvatarAndStatusSection(
BuildContext context, BuildContext context,
ColorScheme colorScheme,
ValueNotifier<File?> selectedImage, ValueNotifier<File?> selectedImage,
String initials, String initials,
String? avatarUrl, String? avatarUrl,
@@ -1168,11 +1189,11 @@ class ProfileEditPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -1190,11 +1211,11 @@ class ProfileEditPage extends HookConsumerWidget {
height: 96, height: 96,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.primaryBlue, color: colorScheme.primary,
border: Border.all(color: Colors.white, width: 4), border: Border.all(color: colorScheme.surface, width: 4),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.1), color: colorScheme.shadow.withValues(alpha: 0.1),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -1237,22 +1258,22 @@ class ProfileEditPage extends HookConsumerWidget {
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 3), border: Border.all(color: colorScheme.surface, width: 3),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.15), color: colorScheme.shadow.withValues(alpha: 0.15),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
], ],
), ),
child: const Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.camera, FontAwesomeIcons.camera,
size: 14, size: 14,
color: Colors.white, color: colorScheme.onPrimary,
), ),
), ),
), ),
@@ -1266,10 +1287,10 @@ class ProfileEditPage extends HookConsumerWidget {
// Name // Name
Text( Text(
fullName, fullName,
style: const TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
@@ -1278,9 +1299,9 @@ class ProfileEditPage extends HookConsumerWidget {
// Position // Position
Text( Text(
positionLabels[position] ?? position, positionLabels[position] ?? position,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
@@ -1325,6 +1346,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build text field /// Build text field
Widget _buildTextField({ Widget _buildTextField({
required ColorScheme colorScheme,
required String label, required String label,
required TextEditingController controller, required TextEditingController controller,
bool required = false, bool required = false,
@@ -1339,10 +1361,10 @@ class ProfileEditPage extends HookConsumerWidget {
RichText( RichText(
text: TextSpan( text: TextSpan(
text: label, text: label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
children: [ children: [
if (required) if (required)
@@ -1363,27 +1385,27 @@ class ProfileEditPage extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Nhập $label', hintText: 'Nhập $label',
hintStyle: TextStyle( hintStyle: TextStyle(
color: AppColors.grey500.withValues(alpha: 0.6), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
fontSize: 14, fontSize: 14,
), ),
filled: true, filled: true,
fillColor: readOnly ? const Color(0xFFF1F5F9) : const Color(0xFFF8FAFC), fillColor: readOnly ? colorScheme.surfaceContainerHighest : colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -1404,6 +1426,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build date field /// Build date field
Widget _buildDateField({ Widget _buildDateField({
required BuildContext context, required BuildContext context,
required ColorScheme colorScheme,
required String label, required String label,
required TextEditingController controller, required TextEditingController controller,
ValueNotifier<bool>? hasChanges, ValueNotifier<bool>? hasChanges,
@@ -1413,19 +1436,19 @@ class ProfileEditPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: controller, controller: controller,
readOnly: true, readOnly: true,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
onTap: () async { onTap: () async {
@@ -1447,32 +1470,32 @@ class ProfileEditPage extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Chọn ngày sinh', hintText: 'Chọn ngày sinh',
hintStyle: TextStyle( hintStyle: TextStyle(
color: AppColors.grey500.withValues(alpha: 0.6), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
fontSize: 14, fontSize: 14,
), ),
filled: true, filled: true,
fillColor: const Color(0xFFF8FAFC), fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 16, vertical: 16,
), ),
suffixIcon: const Icon( suffixIcon: Icon(
Icons.calendar_today, Icons.calendar_today,
size: 20, size: 20,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -1484,6 +1507,7 @@ class ProfileEditPage extends HookConsumerWidget {
/// Build dropdown field /// Build dropdown field
Widget _buildDropdownField({ Widget _buildDropdownField({
required ColorScheme colorScheme,
required String label, required String label,
required String value, required String value,
required List<Map<String, String>> items, required List<Map<String, String>> items,
@@ -1494,43 +1518,43 @@ class ProfileEditPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
initialValue: value, initialValue: value,
onChanged: onChanged, onChanged: onChanged,
icon: const Padding( icon: Padding(
padding: EdgeInsets.only(right: 12), padding: const EdgeInsets.only(right: 12),
child: FaIcon( child: FaIcon(
FontAwesomeIcons.chevronDown, FontAwesomeIcons.chevronDown,
size: 16, size: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: const Color(0xFFF8FAFC), fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),

View File

@@ -7,7 +7,6 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Account Menu Item Widget /// Account Menu Item Widget
/// ///
@@ -51,6 +50,8 @@ class AccountMenuItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
@@ -58,9 +59,9 @@ class AccountMenuItem extends StatelessWidget {
horizontal: AppSpacing.md, horizontal: AppSpacing.md,
vertical: AppSpacing.md, vertical: AppSpacing.md,
), ),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: AppColors.grey100, width: 1.0), bottom: BorderSide(color: colorScheme.outlineVariant, width: 1.0),
), ),
), ),
child: Row( child: Row(
@@ -70,16 +71,14 @@ class AccountMenuItem extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: iconBackgroundColor ?? colorScheme.primaryContainer,
iconBackgroundColor ??
AppColors.lightBlue.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center( child: Center(
child: FaIcon( child: FaIcon(
icon, icon,
size: 18, size: 18,
color: iconColor ?? AppColors.primaryBlue, color: iconColor ?? colorScheme.primary,
), ),
), ),
), ),
@@ -92,19 +91,19 @@ class AccountMenuItem extends StatelessWidget {
children: [ children: [
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (subtitle != null) ...[ if (subtitle != null) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
subtitle!, subtitle!,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -114,10 +113,10 @@ class AccountMenuItem extends StatelessWidget {
// Trailing widget (default: chevron) // Trailing widget (default: chevron)
trailing ?? trailing ??
const FaIcon( FaIcon(
FontAwesomeIcons.chevronRight, FontAwesomeIcons.chevronRight,
size: 18, size: 18,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),

View File

@@ -41,25 +41,27 @@ class AddressCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: isDefault border: isDefault
? Border.all(color: AppColors.primaryBlue, width: 2) ? Border.all(color: colorScheme.primary, width: 2)
: null, : null,
boxShadow: isDefault boxShadow: isDefault
? [ ? [
BoxShadow( BoxShadow(
color: AppColors.primaryBlue.withValues(alpha: 0.15), color: colorScheme.primary.withValues(alpha: 0.15),
blurRadius: 12, blurRadius: 12,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
] ]
: [ : [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -78,7 +80,7 @@ class AddressCard extends StatelessWidget {
value: true, value: true,
groupValue: isSelected, groupValue: isSelected,
onChanged: (_) => onRadioTap?.call(), onChanged: (_) => onRadioTap?.call(),
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
), ),
), ),
), ),
@@ -94,10 +96,10 @@ class AddressCard extends StatelessWidget {
Flexible( Flexible(
child: Text( child: Text(
name, name,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -110,15 +112,15 @@ class AddressCard extends StatelessWidget {
vertical: 2, vertical: 2,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: const Text( child: Text(
'Mặc định', 'Mặc định',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.white, color: colorScheme.onPrimary,
), ),
), ),
) )
@@ -133,18 +135,18 @@ class AddressCard extends StatelessWidget {
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: AppColors.primaryBlue.withValues( color: colorScheme.primary.withValues(
alpha: 0.3, alpha: 0.3,
), ),
), ),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: const Text( child: Text(
'Đặt mặc định', 'Đặt mặc định',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
), ),
@@ -157,9 +159,9 @@ class AddressCard extends StatelessWidget {
// Phone // Phone
Text( Text(
phone, phone,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
@@ -168,9 +170,9 @@ class AddressCard extends StatelessWidget {
// Address Text // Address Text
Text( Text(
address, address,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF212121), color: colorScheme.onSurface,
height: 1.4, height: 1.4,
), ),
), ),
@@ -194,14 +196,14 @@ class AddressCard extends StatelessWidget {
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.penToSquare, FontAwesomeIcons.penToSquare,
size: 16, size: 16,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
), ),
@@ -221,7 +223,7 @@ class AddressCard extends StatelessWidget {
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center( child: const Center(

View File

@@ -121,19 +121,21 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Đơn vị kinh doanh', 'Đơn vị kinh doanh',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -141,7 +143,7 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.circleInfo, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.circleInfo, color: colorScheme.onSurface, size: 20),
onPressed: _showInfoDialog, onPressed: _showInfoDialog,
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
@@ -164,20 +166,20 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
), ),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: const Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
'DBIZ', 'DBIZ',
style: TextStyle( style: TextStyle(
color: Colors.white, color: colorScheme.onPrimary,
fontSize: 32, fontSize: 32,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
Text( Text(
'Worker App', 'Worker App',
style: TextStyle(color: Colors.white, fontSize: 12), style: TextStyle(color: colorScheme.onPrimary, fontSize: 12),
), ),
], ],
), ),
@@ -187,22 +189,22 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Welcome Message // Welcome Message
const Text( Text(
'Chọn đơn vị kinh doanh để tiếp tục', 'Chọn đơn vị kinh doanh để tiếp tục',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: AppColors.grey500, fontSize: 14), style: TextStyle(color: colorScheme.onSurfaceVariant, fontSize: 14),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
const Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
child: Text( child: Text(
'Đơn vị kinh doanh', 'Đơn vị kinh doanh',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -227,11 +229,11 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
bottom: isLast ? 0 : AppSpacing.xs, bottom: isLast ? 0 : AppSpacing.xs,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? AppColors.primaryBlue ? colorScheme.primary
: AppColors.grey100, : colorScheme.surfaceContainerHighest,
width: isSelected ? 2 : 1, width: isSelected ? 2 : 1,
), ),
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
@@ -249,7 +251,7 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
boxShadow: isSelected boxShadow: isSelected
? [ ? [
BoxShadow( BoxShadow(
color: AppColors.primaryBlue.withValues( color: colorScheme.primary.withValues(
alpha: 0.1, alpha: 0.1,
), ),
blurRadius: 8, blurRadius: 8,
@@ -289,17 +291,17 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? AppColors.primaryBlue.withValues( ? colorScheme.primary.withValues(
alpha: 0.1, alpha: 0.1,
) )
: AppColors.grey50, : colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Icon( child: Icon(
FontAwesomeIcons.building, FontAwesomeIcons.building,
color: isSelected color: isSelected
? AppColors.primaryBlue ? colorScheme.primary
: AppColors.grey500, : colorScheme.onSurfaceVariant,
size: 20, size: 20,
), ),
), ),
@@ -317,17 +319,17 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
? FontWeight.w600 ? FontWeight.w600
: FontWeight.w500, : FontWeight.w500,
color: isSelected color: isSelected
? AppColors.primaryBlue ? colorScheme.primary
: AppColors.grey900, : colorScheme.onSurface,
), ),
), ),
if (unit.description != null) ...[ if (unit.description != null) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
unit.description!, unit.description!,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -342,19 +344,19 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? AppColors.primaryBlue ? colorScheme.primary
: AppColors.grey500, : colorScheme.onSurfaceVariant,
width: 2, width: 2,
), ),
color: isSelected color: isSelected
? AppColors.primaryBlue ? colorScheme.primary
: Colors.transparent, : Colors.transparent,
), ),
child: isSelected child: isSelected
? const Icon( ? Icon(
FontAwesomeIcons.solidCircle, FontAwesomeIcons.solidCircle,
size: 10, size: 10,
color: AppColors.white, color: colorScheme.onPrimary,
) )
: null, : null,
), ),
@@ -376,8 +378,8 @@ class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _handleContinue, onPressed: _handleContinue,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(

View File

@@ -137,10 +137,12 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
title: const Text( title: const Text(
'Quên mật khẩu', 'Quên mật khẩu',
@@ -166,27 +168,27 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Icon // Icon
_buildIcon(), _buildIcon(colorScheme),
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Title & Instructions // Title & Instructions
_buildInstructions(), _buildInstructions(colorScheme),
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Form Card // Form Card
_buildFormCard(), _buildFormCard(colorScheme),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Back to Login Link // Back to Login Link
_buildBackToLoginLink(), _buildBackToLoginLink(colorScheme),
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Support Link // Support Link
_buildSupportLink(), _buildSupportLink(colorScheme),
], ],
), ),
), ),
@@ -196,45 +198,45 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
} }
/// Build icon /// Build icon
Widget _buildIcon() { Widget _buildIcon(ColorScheme colorScheme) {
return Center( return Center(
child: Container( child: Container(
width: 100, width: 100,
height: 100, height: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.1), color: colorScheme.primary.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: Icon(
FontAwesomeIcons.key, FontAwesomeIcons.key,
size: 50, size: 50,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
); );
} }
/// Build instructions /// Build instructions
Widget _buildInstructions() { Widget _buildInstructions(ColorScheme colorScheme) {
return const Column( return Column(
children: [ children: [
Text( Text(
'Đặt lại mật khẩu', 'Đặt lại mật khẩu',
style: TextStyle( style: TextStyle(
fontSize: 28.0, fontSize: 28.0,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Text( child: Text(
'Nhập số điện thoại đã đăng ký. Chúng tôi sẽ gửi mã OTP để xác nhận và đặt lại mật khẩu của bạn.', 'Nhập số điện thoại đã đăng ký. Chúng tôi sẽ gửi mã OTP để xác nhận và đặt lại mật khẩu của bạn.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 15.0, fontSize: 15.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
height: 1.5, height: 1.5,
), ),
), ),
@@ -244,11 +246,11 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
} }
/// Build form card /// Build form card
Widget _buildFormCard() { Widget _buildFormCard(ColorScheme colorScheme) {
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -282,23 +284,23 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _isLoading ? null : _handleSubmit, onPressed: _isLoading ? null : _handleSubmit,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.surfaceContainerHighest,
disabledForegroundColor: AppColors.grey500, disabledForegroundColor: colorScheme.onSurfaceVariant,
elevation: ButtonSpecs.elevation, elevation: ButtonSpecs.elevation,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ButtonSpecs.borderRadius), borderRadius: BorderRadius.circular(ButtonSpecs.borderRadius),
), ),
), ),
child: _isLoading child: _isLoading
? const SizedBox( ? SizedBox(
height: 20.0, height: 20.0,
width: 20.0, width: 20.0,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2.0, strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white, colorScheme.onPrimary,
), ),
), ),
) )
@@ -317,21 +319,21 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
} }
/// Build back to login link /// Build back to login link
Widget _buildBackToLoginLink() { Widget _buildBackToLoginLink(ColorScheme colorScheme) {
return Center( return Center(
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: 'Nhớ mật khẩu? ', text: 'Nhớ mật khẩu? ',
style: const TextStyle(fontSize: 14.0, color: AppColors.grey500), style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant),
children: [ children: [
WidgetSpan( WidgetSpan(
child: GestureDetector( child: GestureDetector(
onTap: () => context.pop(), onTap: () => context.pop(),
child: const Text( child: Text(
'Đăng nhập', 'Đăng nhập',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
@@ -345,20 +347,20 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
} }
/// Build support link /// Build support link
Widget _buildSupportLink() { Widget _buildSupportLink(ColorScheme colorScheme) {
return Center( return Center(
child: TextButton.icon( child: TextButton.icon(
onPressed: _showSupport, onPressed: _showSupport,
icon: const Icon( icon: Icon(
FontAwesomeIcons.headset, FontAwesomeIcons.headset,
size: AppIconSize.sm, size: AppIconSize.sm,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
label: const Text( label: Text(
'Hỗ trợ khách hàng', 'Hỗ trợ khách hàng',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),

View File

@@ -166,9 +166,10 @@ class _LoginPageState extends ConsumerState<LoginPage> {
// Watch auth state for loading indicator // Watch auth state for loading indicator
final authState = ref.watch(authProvider); final authState = ref.watch(authProvider);
final isPasswordVisible = ref.watch(passwordVisibilityProvider); final isPasswordVisible = ref.watch(passwordVisibilityProvider);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
@@ -185,22 +186,22 @@ class _LoginPageState extends ConsumerState<LoginPage> {
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Welcome Message // Welcome Message
_buildWelcomeMessage(), _buildWelcomeMessage(colorScheme),
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Login Form Card // Login Form Card
_buildLoginForm(authState, isPasswordVisible), _buildLoginForm(authState, isPasswordVisible, colorScheme),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Register Link // Register Link
_buildRegisterLink(), _buildRegisterLink(colorScheme),
const SizedBox(height: AppSpacing.xl), const SizedBox(height: AppSpacing.xl),
// Support Link // Support Link
_buildSupportLink(), _buildSupportLink(colorScheme),
], ],
), ),
@@ -228,7 +229,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Text( Text(
'EUROTILE', 'EUROTILE',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: Colors.white,
fontSize: 32.0, fontSize: 32.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
letterSpacing: 1.5, letterSpacing: 1.5,
@@ -238,7 +239,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Text( Text(
'Worker App', 'Worker App',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: Colors.white,
fontSize: 12.0, fontSize: 12.0,
letterSpacing: 0.5, letterSpacing: 0.5,
), ),
@@ -250,21 +251,21 @@ class _LoginPageState extends ConsumerState<LoginPage> {
} }
/// Build welcome message /// Build welcome message
Widget _buildWelcomeMessage() { Widget _buildWelcomeMessage(ColorScheme colorScheme) {
return const Column( return Column(
children: [ children: [
Text( Text(
'Xin chào!', 'Xin chào!',
style: TextStyle( style: TextStyle(
fontSize: 32.0, fontSize: 32.0,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
Text( Text(
'Đăng nhập để tiếp tục', 'Đăng nhập để tiếp tục',
style: TextStyle(fontSize: 16.0, color: AppColors.grey500), style: TextStyle(fontSize: 16.0, color: colorScheme.onSurfaceVariant),
), ),
], ],
); );
@@ -274,13 +275,14 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Widget _buildLoginForm( Widget _buildLoginForm(
AsyncValue<dynamic> authState, AsyncValue<dynamic> authState,
bool isPasswordVisible, bool isPasswordVisible,
ColorScheme colorScheme,
) { ) {
final isLoading = authState.isLoading; final isLoading = authState.isLoading;
return Container( return Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -314,30 +316,30 @@ class _LoginPageState extends ConsumerState<LoginPage> {
enabled: !isLoading, enabled: !isLoading,
obscureText: !isPasswordVisible, obscureText: !isPasswordVisible,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
style: const TextStyle( style: TextStyle(
fontSize: InputFieldSpecs.fontSize, fontSize: InputFieldSpecs.fontSize,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Mật khẩu', labelText: 'Mật khẩu',
labelStyle: const TextStyle( labelStyle: TextStyle(
fontSize: InputFieldSpecs.labelFontSize, fontSize: InputFieldSpecs.labelFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
hintText: 'Nhập mật khẩu', hintText: 'Nhập mật khẩu',
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize, fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
prefixIcon: const Icon( prefixIcon: Icon(
FontAwesomeIcons.lock, FontAwesomeIcons.lock,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: AppIconSize.md, size: AppIconSize.md,
), ),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
isPasswordVisible ? FontAwesomeIcons.eye : FontAwesomeIcons.eyeSlash, isPasswordVisible ? FontAwesomeIcons.eye : FontAwesomeIcons.eyeSlash,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: AppIconSize.md, size: AppIconSize.md,
), ),
onPressed: () { onPressed: () {
@@ -345,14 +347,14 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}, },
), ),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding, contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius, InputFieldSpecs.borderRadius,
), ),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 1.0, width: 1.0,
), ),
), ),
@@ -360,8 +362,8 @@ class _LoginPageState extends ConsumerState<LoginPage> {
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius, InputFieldSpecs.borderRadius,
), ),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 1.0, width: 1.0,
), ),
), ),
@@ -369,8 +371,8 @@ class _LoginPageState extends ConsumerState<LoginPage> {
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius, InputFieldSpecs.borderRadius,
), ),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2.0, width: 2.0,
), ),
), ),
@@ -424,7 +426,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
_rememberMe = value ?? false; _rememberMe = value ?? false;
}); });
}, },
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0), borderRadius: BorderRadius.circular(4.0),
), ),
@@ -437,11 +439,11 @@ class _LoginPageState extends ConsumerState<LoginPage> {
_rememberMe = !_rememberMe; _rememberMe = !_rememberMe;
}); });
}, },
child: const Text( child: Text(
'Ghi nhớ đăng nhập', 'Ghi nhớ đăng nhập',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -455,7 +457,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
'Quên mật khẩu?', 'Quên mật khẩu?',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: isLoading ? AppColors.grey500 : AppColors.primaryBlue, color: isLoading ? colorScheme.onSurfaceVariant : colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -471,23 +473,23 @@ class _LoginPageState extends ConsumerState<LoginPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: isLoading ? null : _handleLogin, onPressed: isLoading ? null : _handleLogin,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.surfaceContainerHighest,
disabledForegroundColor: AppColors.grey500, disabledForegroundColor: colorScheme.onSurfaceVariant,
elevation: ButtonSpecs.elevation, elevation: ButtonSpecs.elevation,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ButtonSpecs.borderRadius), borderRadius: BorderRadius.circular(ButtonSpecs.borderRadius),
), ),
), ),
child: isLoading child: isLoading
? const SizedBox( ? SizedBox(
height: 20.0, height: 20.0,
width: 20.0, width: 20.0,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2.0, strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white, colorScheme.onPrimary,
), ),
), ),
) )
@@ -506,21 +508,21 @@ class _LoginPageState extends ConsumerState<LoginPage> {
} }
/// Build register link /// Build register link
Widget _buildRegisterLink() { Widget _buildRegisterLink(ColorScheme colorScheme) {
return Center( return Center(
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: 'Chưa có tài khoản? ', text: 'Chưa có tài khoản? ',
style: const TextStyle(fontSize: 14.0, color: AppColors.grey500), style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant),
children: [ children: [
WidgetSpan( WidgetSpan(
child: GestureDetector( child: GestureDetector(
onTap: _navigateToRegister, onTap: _navigateToRegister,
child: const Text( child: Text(
'Đăng ký ngay', 'Đăng ký ngay',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
@@ -534,20 +536,20 @@ class _LoginPageState extends ConsumerState<LoginPage> {
} }
/// Build support link /// Build support link
Widget _buildSupportLink() { Widget _buildSupportLink(ColorScheme colorScheme) {
return Center( return Center(
child: TextButton.icon( child: TextButton.icon(
onPressed: _showSupport, onPressed: _showSupport,
icon: const Icon( icon: Icon(
FontAwesomeIcons.headset, FontAwesomeIcons.headset,
size: AppIconSize.sm, size: AppIconSize.sm,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
label: const Text( label: Text(
'Hỗ trợ khách hàng', 'Hỗ trợ khách hàng',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),

View File

@@ -14,7 +14,7 @@ import 'package:go_router/go_router.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/router/app_router.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart'; // Keep for status colors and brand gradients
/// OTP Verification Page /// OTP Verification Page
/// ///
@@ -237,19 +237,21 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.grey50, backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Xác thực OTP', 'Xác thực OTP',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -276,14 +278,14 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [AppColors.primaryBlue, AppColors.lightBlue], colors: [AppColors.primaryBlue, AppColors.lightBlue], // Keep brand colors
), ),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: Icon(
FontAwesomeIcons.shieldHalved, FontAwesomeIcons.shieldHalved,
size: 36, size: 36,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
), ),
), ),
@@ -291,24 +293,24 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Instructions // Instructions
const Text( Text(
'Nhập mã xác thực', 'Nhập mã xác thực',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'Mã OTP đã được gửi đến số điện thoại', 'Mã OTP đã được gửi đến số điện thoại',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
@@ -317,10 +319,10 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
Text( Text(
_formatPhoneNumber(widget.phoneNumber), _formatPhoneNumber(widget.phoneNumber),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
@@ -329,7 +331,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
// OTP Input Card // OTP Input Card
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -351,7 +353,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: index > 0 ? 8 : 0, left: index > 0 ? 8 : 0,
), ),
child: _buildOtpInput(index), child: _buildOtpInput(index, colorScheme),
), ),
), ),
), ),
@@ -365,8 +367,8 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _isLoading ? null : _handleVerifyOtp, onPressed: _isLoading ? null : _handleVerifyOtp,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@@ -375,13 +377,13 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
), ),
), ),
child: _isLoading child: _isLoading
? const SizedBox( ? SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white, colorScheme.onPrimary,
), ),
), ),
) )
@@ -405,9 +407,9 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
text: 'Không nhận được mã? ', text: 'Không nhận được mã? ',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
children: [ children: [
WidgetSpan( WidgetSpan(
@@ -421,8 +423,8 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: _countdown > 0 color: _countdown > 0
? AppColors.grey500 ? colorScheme.onSurfaceVariant
: AppColors.primaryBlue, : colorScheme.primary,
decoration: _countdown == 0 decoration: _countdown == 0
? TextDecoration.none ? TextDecoration.none
: TextDecoration.none, : TextDecoration.none,
@@ -445,7 +447,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
} }
/// Build single OTP input box /// Build single OTP input box
Widget _buildOtpInput(int index) { Widget _buildOtpInput(int index, ColorScheme colorScheme) {
return SizedBox( return SizedBox(
width: 48, width: 48,
height: 48, height: 48,
@@ -455,10 +457,10 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
maxLength: 1, maxLength: 1,
style: const TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
@@ -468,20 +470,20 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
filled: false, filled: false,
fillColor: AppColors.white, fillColor: colorScheme.surface,
), ),
onChanged: (value) => _onOtpChanged(index, value), onChanged: (value) => _onOtpChanged(index, value),
onTap: () { onTap: () {

View File

@@ -379,6 +379,9 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Get color scheme at the start of build method
final colorScheme = Theme.of(context).colorScheme;
// Initialize data on first build // Initialize data on first build
if (!_hasInitialized) { if (!_hasInitialized) {
// Use addPostFrameCallback to avoid calling setState during build // Use addPostFrameCallback to avoid calling setState during build
@@ -388,18 +391,18 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
} }
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Đăng ký tài khoản', 'Đăng ký tài khoản',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -407,15 +410,15 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
centerTitle: false, centerTitle: false,
), ),
body: _isLoadingData body: _isLoadingData
? const Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(), const CircularProgressIndicator(),
SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
Text( Text(
'Đang tải dữ liệu...', 'Đang tải dữ liệu...',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -429,19 +432,19 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Welcome section // Welcome section
const Text( Text(
'Tạo tài khoản mới', 'Tạo tài khoản mới',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
const Text( Text(
'Điền thông tin để bắt đầu', 'Điền thông tin để bắt đầu',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
@@ -449,7 +452,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
// Form card // Form card
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -464,7 +467,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Full Name // Full Name
_buildLabel('Họ và tên *'), _buildLabel('Họ và tên *', colorScheme),
TextFormField( TextFormField(
controller: _fullNameController, controller: _fullNameController,
focusNode: _fullNameFocus, focusNode: _fullNameFocus,
@@ -472,6 +475,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Nhập họ và tên', hintText: 'Nhập họ và tên',
prefixIcon: FontAwesomeIcons.user, prefixIcon: FontAwesomeIcons.user,
colorScheme: colorScheme,
), ),
validator: (value) => Validators.minLength( validator: (value) => Validators.minLength(
value, value,
@@ -482,7 +486,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Phone Number // Phone Number
_buildLabel('Số điện thoại *'), _buildLabel('Số điện thoại *', colorScheme),
PhoneInputField( PhoneInputField(
controller: _phoneController, controller: _phoneController,
focusNode: _phoneFocus, focusNode: _phoneFocus,
@@ -491,7 +495,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Email // Email
_buildLabel('Email *'), _buildLabel('Email *', colorScheme),
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
focusNode: _emailFocus, focusNode: _emailFocus,
@@ -500,13 +504,14 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Nhập email', hintText: 'Nhập email',
prefixIcon: FontAwesomeIcons.envelope, prefixIcon: FontAwesomeIcons.envelope,
colorScheme: colorScheme,
), ),
validator: Validators.email, validator: Validators.email,
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Password // Password
_buildLabel('Mật khẩu *'), _buildLabel('Mật khẩu *', colorScheme),
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
focusNode: _passwordFocus, focusNode: _passwordFocus,
@@ -515,12 +520,13 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Tạo mật khẩu mới', hintText: 'Tạo mật khẩu mới',
prefixIcon: FontAwesomeIcons.lock, prefixIcon: FontAwesomeIcons.lock,
colorScheme: colorScheme,
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_passwordVisible _passwordVisible
? FontAwesomeIcons.eye ? FontAwesomeIcons.eye
: FontAwesomeIcons.eyeSlash, : FontAwesomeIcons.eyeSlash,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
onPressed: () { onPressed: () {
setState(() { setState(() {
@@ -533,28 +539,28 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
Validators.passwordSimple(value, minLength: 6), Validators.passwordSimple(value, minLength: 6),
), ),
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
const Text( Text(
'Mật khẩu tối thiểu 6 ký tự', 'Mật khẩu tối thiểu 6 ký tự',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Role Selection (Customer Groups) // Role Selection (Customer Groups)
_buildLabel('Vai trò *'), _buildLabel('Vai trò *', colorScheme),
_buildCustomerGroupDropdown(), _buildCustomerGroupDropdown(colorScheme),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Verification Section (conditional) // Verification Section (conditional)
if (_shouldShowVerification) ...[ if (_shouldShowVerification) ...[
_buildVerificationSection(), _buildVerificationSection(colorScheme),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
], ],
// Company Name (optional) // Company Name (optional)
_buildLabel('Tên công ty/Cửa hàng'), _buildLabel('Tên công ty/Cửa hàng', colorScheme),
TextFormField( TextFormField(
controller: _companyController, controller: _companyController,
focusNode: _companyFocus, focusNode: _companyFocus,
@@ -562,13 +568,14 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Nhập tên công ty (không bắt buộc)', hintText: 'Nhập tên công ty (không bắt buộc)',
prefixIcon: FontAwesomeIcons.building, prefixIcon: FontAwesomeIcons.building,
colorScheme: colorScheme,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// City/Province // City/Province
_buildLabel('Tỉnh/Thành phố *'), _buildLabel('Tỉnh/Thành phố *', colorScheme),
_buildCityDropdown(), _buildCityDropdown(colorScheme),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Terms and Conditions // Terms and Conditions
@@ -582,7 +589,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
_termsAccepted = value ?? false; _termsAccepted = value ?? false;
}); });
}, },
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
), ),
Expanded( Expanded(
child: Padding( child: Padding(
@@ -593,23 +600,23 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
_termsAccepted = !_termsAccepted; _termsAccepted = !_termsAccepted;
}); });
}, },
child: const Text.rich( child: Text.rich(
TextSpan( TextSpan(
text: 'Tôi đồng ý với ', text: 'Tôi đồng ý với ',
style: TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
children: [ children: [
TextSpan( TextSpan(
text: 'Điều khoản sử dụng', text: 'Điều khoản sử dụng',
style: TextStyle( style: TextStyle(
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
TextSpan(text: ''), const TextSpan(text: ''),
TextSpan( TextSpan(
text: 'Chính sách bảo mật', text: 'Chính sách bảo mật',
style: TextStyle( style: TextStyle(
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -629,8 +636,8 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _isLoading ? null : _handleRegister, onPressed: _isLoading ? null : _handleRegister,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@@ -639,13 +646,13 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
), ),
), ),
child: _isLoading child: _isLoading
? const SizedBox( ? SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white, colorScheme.onPrimary,
), ),
), ),
) )
@@ -667,17 +674,17 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( Text(
'Đã có tài khoản? ', 'Đã có tài khoản? ',
style: TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
), ),
GestureDetector( GestureDetector(
onTap: () => context.pop(), onTap: () => context.pop(),
child: const Text( child: Text(
'Đăng nhập', 'Đăng nhập',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -694,15 +701,15 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
} }
/// Build label widget /// Build label widget
Widget _buildLabel(String text) { Widget _buildLabel(String text, ColorScheme colorScheme) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.xs), padding: const EdgeInsets.only(bottom: AppSpacing.xs),
child: Text( child: Text(
text, text,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
); );
@@ -712,34 +719,35 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
InputDecoration _buildInputDecoration({ InputDecoration _buildInputDecoration({
required String hintText, required String hintText,
required IconData prefixIcon, required IconData prefixIcon,
required ColorScheme colorScheme,
Widget? suffixIcon, Widget? suffixIcon,
}) { }) {
return InputDecoration( return InputDecoration(
hintText: hintText, hintText: hintText,
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize, fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
prefixIcon: Icon( prefixIcon: Icon(
prefixIcon, prefixIcon,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: AppIconSize.md, size: AppIconSize.md,
), ),
suffixIcon: suffixIcon, suffixIcon: suffixIcon,
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding, contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2.0), borderSide: BorderSide(color: colorScheme.primary, width: 2.0),
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
@@ -753,7 +761,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
} }
/// Build customer group dropdown /// Build customer group dropdown
Widget _buildCustomerGroupDropdown() { Widget _buildCustomerGroupDropdown(ColorScheme colorScheme) {
final customerGroupsAsync = ref.watch(customerGroupsProvider); final customerGroupsAsync = ref.watch(customerGroupsProvider);
return customerGroupsAsync.when( return customerGroupsAsync.when(
@@ -763,6 +771,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Chọn vai trò', hintText: 'Chọn vai trò',
prefixIcon: FontAwesomeIcons.briefcase, prefixIcon: FontAwesomeIcons.briefcase,
colorScheme: colorScheme,
), ),
items: groups items: groups
.map( .map(
@@ -825,7 +834,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
} }
/// Build city dropdown /// Build city dropdown
Widget _buildCityDropdown() { Widget _buildCityDropdown(ColorScheme colorScheme) {
final citiesAsync = ref.watch(citiesProvider); final citiesAsync = ref.watch(citiesProvider);
return citiesAsync.when( return citiesAsync.when(
@@ -835,6 +844,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Chọn tỉnh/thành phố', hintText: 'Chọn tỉnh/thành phố',
prefixIcon: Icons.location_city, prefixIcon: Icons.location_city,
colorScheme: colorScheme,
), ),
items: cities items: cities
.map( .map(
@@ -890,11 +900,11 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
} }
/// Build verification section /// Build verification section
Widget _buildVerificationSection() { Widget _buildVerificationSection(ColorScheme colorScheme) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
border: Border.all(color: const Color(0xFFE2E8F0), width: 2), border: Border.all(color: colorScheme.outlineVariant, width: 2),
borderRadius: BorderRadius.circular(AppRadius.lg), borderRadius: BorderRadius.circular(AppRadius.lg),
), ),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
@@ -905,28 +915,28 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.shield, color: AppColors.primaryBlue, size: 20), Icon(Icons.shield, color: colorScheme.primary, size: 20),
const SizedBox(width: AppSpacing.xs), const SizedBox(width: AppSpacing.xs),
const Text( Text(
'Thông tin xác thực', 'Thông tin xác thực',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
), ),
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
const Text( Text(
'Thông tin này sẽ được dùng để xác minh tư cách chuyên môn của bạn', 'Thông tin này sẽ được dùng để xác minh tư cách chuyên môn của bạn',
style: TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// ID Number // ID Number
_buildLabel('Số CCCD/CMND'), _buildLabel('Số CCCD/CMND', colorScheme),
TextFormField( TextFormField(
controller: _idNumberController, controller: _idNumberController,
focusNode: _idNumberFocus, focusNode: _idNumberFocus,
@@ -935,12 +945,13 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Nhập số CCCD/CMND', hintText: 'Nhập số CCCD/CMND',
prefixIcon: Icons.badge, prefixIcon: Icons.badge,
colorScheme: colorScheme,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Tax Code // Tax Code
_buildLabel('Mã số thuế cá nhân/Công ty'), _buildLabel('Mã số thuế cá nhân/Công ty', colorScheme),
TextFormField( TextFormField(
controller: _taxCodeController, controller: _taxCodeController,
focusNode: _taxCodeFocus, focusNode: _taxCodeFocus,
@@ -949,13 +960,14 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
decoration: _buildInputDecoration( decoration: _buildInputDecoration(
hintText: 'Nhập mã số thuế (không bắt buộc)', hintText: 'Nhập mã số thuế (không bắt buộc)',
prefixIcon: Icons.receipt_long, prefixIcon: Icons.receipt_long,
colorScheme: colorScheme,
), ),
validator: Validators.taxIdOptional, validator: Validators.taxIdOptional,
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// ID Card Upload // ID Card Upload
_buildLabel('Ảnh mặt trước CCCD/CMND'), _buildLabel('Ảnh mặt trước CCCD/CMND', colorScheme),
FileUploadCard( FileUploadCard(
file: _idCardFile, file: _idCardFile,
onTap: () => _pickImage(true), onTap: () => _pickImage(true),
@@ -967,7 +979,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Certificate Upload // Certificate Upload
_buildLabel('Ảnh chứng chỉ hành nghề hoặc GPKD'), _buildLabel('Ảnh chứng chỉ hành nghề hoặc GPKD', colorScheme),
FileUploadCard( FileUploadCard(
file: _certificateFile, file: _certificateFile,
onTap: () => _pickImage(false), onTap: () => _pickImage(false),

View File

@@ -15,8 +15,10 @@ class SplashPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -37,7 +39,7 @@ class SplashPage extends StatelessWidget {
Text( Text(
'EUROTILE', 'EUROTILE',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: Colors.white,
fontSize: 32.0, fontSize: 32.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
letterSpacing: 1.5, letterSpacing: 1.5,
@@ -47,7 +49,7 @@ class SplashPage extends StatelessWidget {
Text( Text(
'Worker App', 'Worker App',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: Colors.white,
fontSize: 12.0, fontSize: 12.0,
letterSpacing: 0.5, letterSpacing: 0.5,
), ),
@@ -59,19 +61,19 @@ class SplashPage extends StatelessWidget {
const SizedBox(height: 48.0), const SizedBox(height: 48.0),
// Loading Indicator // Loading Indicator
const CircularProgressIndicator( CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryBlue), valueColor: AlwaysStoppedAnimation<Color>(colorScheme.primary),
strokeWidth: 3.0, strokeWidth: 3.0,
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
// Loading Text // Loading Text
const Text( Text(
'Đang tải...', 'Đang tải...',
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],

View File

@@ -77,25 +77,27 @@ class FileUploadCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
if (file != null) { if (file != null) {
// Show preview with remove option // Show preview with remove option
return _buildPreview(context); return _buildPreview(context, colorScheme);
} else { } else {
// Show upload area // Show upload area
return _buildUploadArea(context); return _buildUploadArea(context, colorScheme);
} }
} }
/// Build upload area /// Build upload area
Widget _buildUploadArea(BuildContext context) { Widget _buildUploadArea(BuildContext context, ColorScheme colorScheme) {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.lg), borderRadius: BorderRadius.circular(AppRadius.lg),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all( border: Border.all(
color: const Color(0xFFCBD5E1), color: colorScheme.outlineVariant,
width: 2, width: 2,
strokeAlign: BorderSide.strokeAlignInside, strokeAlign: BorderSide.strokeAlignInside,
), ),
@@ -105,16 +107,16 @@ class FileUploadCard extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
// Icon // Icon
Icon(icon, size: 32, color: AppColors.grey500), Icon(icon, size: 32, color: colorScheme.onSurfaceVariant),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
// Title // Title
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.xs), const SizedBox(height: AppSpacing.xs),
@@ -122,7 +124,7 @@ class FileUploadCard extends StatelessWidget {
// Subtitle // Subtitle
Text( Text(
subtitle, subtitle,
style: const TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -131,11 +133,11 @@ class FileUploadCard extends StatelessWidget {
} }
/// Build preview with remove button /// Build preview with remove button
Widget _buildPreview(BuildContext context) { Widget _buildPreview(BuildContext context, ColorScheme colorScheme) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all(color: AppColors.grey100, width: 1), border: Border.all(color: colorScheme.surfaceContainerHighest, width: 1),
borderRadius: BorderRadius.circular(AppRadius.md), borderRadius: BorderRadius.circular(AppRadius.md),
), ),
padding: const EdgeInsets.all(AppSpacing.sm), padding: const EdgeInsets.all(AppSpacing.sm),
@@ -153,10 +155,10 @@ class FileUploadCard extends StatelessWidget {
return Container( return Container(
width: 50, width: 50,
height: 50, height: 50,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 24, size: 24,
), ),
); );
@@ -173,10 +175,10 @@ class FileUploadCard extends StatelessWidget {
children: [ children: [
Text( Text(
_getFileName(file!.path), _getFileName(file!.path),
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -188,9 +190,9 @@ class FileUploadCard extends StatelessWidget {
if (snapshot.hasData) { if (snapshot.hasData) {
return Text( return Text(
_formatFileSize(snapshot.data!), _formatFileSize(snapshot.data!),
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
); );
} }

View File

@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart'; // For AppColors.danger
/// Phone Input Field /// Phone Input Field
/// ///
@@ -65,6 +65,8 @@ class PhoneInputField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return TextFormField( return TextFormField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
@@ -78,41 +80,41 @@ class PhoneInputField extends StatelessWidget {
// Limit to reasonable phone length // Limit to reasonable phone length
LengthLimitingTextInputFormatter(15), LengthLimitingTextInputFormatter(15),
], ],
style: const TextStyle( style: TextStyle(
fontSize: InputFieldSpecs.fontSize, fontSize: InputFieldSpecs.fontSize,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Số điện thoại', labelText: 'Số điện thoại',
labelStyle: const TextStyle( labelStyle: TextStyle(
fontSize: InputFieldSpecs.labelFontSize, fontSize: InputFieldSpecs.labelFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
hintText: 'Nhập số điện thoại', hintText: 'Nhập số điện thoại',
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize, fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
prefixIcon: const Icon( prefixIcon: Icon(
FontAwesomeIcons.phone, FontAwesomeIcons.phone,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: AppIconSize.md, size: AppIconSize.md,
), ),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding, contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2.0, width: 2.0,
), ),
), ),

View File

@@ -54,34 +54,36 @@ class RoleDropdown extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return DropdownButtonFormField<String>( return DropdownButtonFormField<String>(
initialValue: value, initialValue: value,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Chọn vai trò của bạn', hintText: 'Chọn vai trò của bạn',
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize, fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
prefixIcon: const Icon( prefixIcon: Icon(
FontAwesomeIcons.briefcase, FontAwesomeIcons.briefcase,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: AppIconSize.md, size: AppIconSize.md,
), ),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding, contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2.0, width: 2.0,
), ),
), ),
@@ -105,11 +107,11 @@ class RoleDropdown extends StatelessWidget {
], ],
onChanged: onChanged, onChanged: onChanged,
validator: validator, validator: validator,
icon: const FaIcon(FontAwesomeIcons.chevronDown, color: AppColors.grey500, size: 16), icon: FaIcon(FontAwesomeIcons.chevronDown, color: colorScheme.onSurfaceVariant, size: 16),
dropdownColor: AppColors.white, dropdownColor: colorScheme.surface,
style: const TextStyle( style: TextStyle(
fontSize: InputFieldSpecs.fontSize, fontSize: InputFieldSpecs.fontSize,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
); );
} }

View File

@@ -49,6 +49,7 @@ class _CartPageState extends ConsumerState<CartPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final cartState = ref.watch(cartProvider); final cartState = ref.watch(cartProvider);
final currencyFormatter = NumberFormat.currency( final currencyFormatter = NumberFormat.currency(
@@ -69,26 +70,26 @@ class _CartPageState extends ConsumerState<CartPage> {
} }
}, },
child: Scaffold( child: Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: Text( title: Text(
'Giỏ hàng ($itemCount)', 'Giỏ hàng ($itemCount)',
style: const TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
if (cartState.isNotEmpty) if (cartState.isNotEmpty)
IconButton( IconButton(
icon: Icon( icon: Icon(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
color: hasSelection ? AppColors.danger : AppColors.grey500, color: hasSelection ? AppColors.danger : colorScheme.onSurfaceVariant,
), ),
onPressed: hasSelection onPressed: hasSelection
? () { ? () {
@@ -130,7 +131,7 @@ class _CartPageState extends ConsumerState<CartPage> {
// Loading overlay // Loading overlay
if (cartState.isLoading) if (cartState.isLoading)
Container( Container(
color: Colors.black.withValues(alpha: 0.1), color: colorScheme.onSurface.withValues(alpha: 0.1),
child: const Center( child: const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
@@ -155,15 +156,17 @@ class _CartPageState extends ConsumerState<CartPage> {
/// Build select all section /// Build select all section
Widget _buildSelectAllSection(CartState cartState, WidgetRef ref) { Widget _buildSelectAllSection(CartState cartState, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 16), margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -200,7 +203,7 @@ class _CartPageState extends ConsumerState<CartPage> {
Text( Text(
'Đã chọn: ${cartState.selectedCount}/${cartState.itemCount}', 'Đã chọn: ${cartState.selectedCount}/${cartState.itemCount}',
style: AppTypography.bodyMedium.copyWith( style: AppTypography.bodyMedium.copyWith(
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 14, fontSize: 14,
), ),
@@ -218,15 +221,17 @@ class _CartPageState extends ConsumerState<CartPage> {
NumberFormat currencyFormatter, NumberFormat currencyFormatter,
bool hasSelection, bool hasSelection,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: const Border( border: Border(
top: BorderSide(color: Color(0xFFF0F0F0), width: 2), top: BorderSide(color: colorScheme.outlineVariant, width: 2),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.08), color: colorScheme.onSurface.withValues(alpha: 0.08),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, -2), offset: const Offset(0, -2),
), ),
@@ -245,14 +250,14 @@ class _CartPageState extends ConsumerState<CartPage> {
Text( Text(
'Tổng tạm tính (${cartState.selectedCount} sản phẩm)', 'Tổng tạm tính (${cartState.selectedCount} sản phẩm)',
style: AppTypography.bodyMedium.copyWith( style: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontSize: 14, fontSize: 14,
), ),
), ),
Text( Text(
currencyFormatter.format(cartState.selectedTotal), currencyFormatter.format(cartState.selectedTotal),
style: AppTypography.headlineSmall.copyWith( style: AppTypography.headlineSmall.copyWith(
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 20, fontSize: 20,
), ),
@@ -302,27 +307,27 @@ class _CartPageState extends ConsumerState<CartPage> {
} }
: null, : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.inverseSurface.withValues(alpha: 0.6),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
elevation: 0, elevation: 0,
), ),
child: _isSyncing child: _isSyncing
? const SizedBox( ? SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: valueColor:
AlwaysStoppedAnimation<Color>(AppColors.white), AlwaysStoppedAnimation<Color>(colorScheme.surface),
), ),
) )
: Text( : Text(
'Tiến hành đặt hàng', 'Tiến hành đặt hàng',
style: AppTypography.labelLarge.copyWith( style: AppTypography.labelLarge.copyWith(
color: AppColors.white, color: colorScheme.surface,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16, fontSize: 16,
), ),
@@ -359,6 +364,8 @@ class _CartPageState extends ConsumerState<CartPage> {
/// Build error state (shown when cart fails to load and is empty) /// Build error state (shown when cart fails to load and is empty)
Widget _buildErrorState(BuildContext context, String errorMessage) { Widget _buildErrorState(BuildContext context, String errorMessage) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -374,7 +381,7 @@ class _CartPageState extends ConsumerState<CartPage> {
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text( child: Text(
errorMessage, errorMessage,
style: AppTypography.bodyMedium.copyWith(color: AppColors.grey500), style: AppTypography.bodyMedium.copyWith(color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
@@ -393,6 +400,8 @@ class _CartPageState extends ConsumerState<CartPage> {
/// Build empty cart state /// Build empty cart state
Widget _buildEmptyCart(BuildContext context) { Widget _buildEmptyCart(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -400,19 +409,19 @@ class _CartPageState extends ConsumerState<CartPage> {
Icon( Icon(
FontAwesomeIcons.cartShopping, FontAwesomeIcons.cartShopping,
size: 80, size: 80,
color: AppColors.grey500.withValues(alpha: 0.5), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Giỏ hàng trống', 'Giỏ hàng trống',
style: AppTypography.headlineMedium.copyWith( style: AppTypography.headlineMedium.copyWith(
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Hãy thêm sản phẩm vào giỏ hàng', 'Hãy thêm sản phẩm vào giỏ hàng',
style: AppTypography.bodyMedium.copyWith(color: AppColors.grey500), style: AppTypography.bodyMedium.copyWith(color: colorScheme.onSurfaceVariant),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
ElevatedButton.icon( ElevatedButton.icon(
@@ -475,24 +484,26 @@ class _CustomCheckbox extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: () => onChanged?.call(!value), onTap: () => onChanged?.call(!value),
child: Container( child: Container(
width: 22, width: 22,
height: 22, height: 22,
decoration: BoxDecoration( decoration: BoxDecoration(
color: value ? AppColors.primaryBlue : AppColors.white, color: value ? colorScheme.primary : colorScheme.surface,
border: Border.all( border: Border.all(
color: value ? AppColors.primaryBlue : const Color(0xFFCBD5E1), color: value ? colorScheme.primary : colorScheme.outlineVariant,
width: 2, width: 2,
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: value child: value
? const Icon( ? Icon(
FontAwesomeIcons.check, FontAwesomeIcons.check,
size: 16, size: 16,
color: AppColors.white, color: colorScheme.surface,
) )
: null, : null,
), ),

View File

@@ -42,6 +42,8 @@ class CheckoutPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Form key for validation // Form key for validation
final formKey = useMemoized(() => GlobalKey<FormState>()); final formKey = useMemoized(() => GlobalKey<FormState>());
@@ -102,22 +104,22 @@ class CheckoutPage extends HookConsumerWidget {
final total = subtotal - memberDiscount + shipping; final total = subtotal - memberDiscount + shipping;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Thanh toán', 'Thanh toán',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -165,11 +167,11 @@ class CheckoutPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -183,11 +185,11 @@ class CheckoutPage extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -203,9 +205,9 @@ class CheckoutPage extends HookConsumerWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'Không thể tải phương thức thanh toán', 'Không thể tải phương thức thanh toán',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -225,7 +227,7 @@ class CheckoutPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Discount Code Section // Discount Code Section
_buildDiscountCodeSection(), _buildDiscountCodeSection(context),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
@@ -263,13 +265,13 @@ class CheckoutPage extends HookConsumerWidget {
}, },
activeColor: AppColors.warning, activeColor: AppColors.warning,
), ),
const Expanded( Expanded(
child: Text( child: Text(
'Yêu cầu hợp đồng', 'Yêu cầu hợp đồng',
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -281,20 +283,20 @@ class CheckoutPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Terms and Conditions // Terms and Conditions
const Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
text: 'Bằng cách đặt hàng, bạn đồng ý với ', text: 'Bằng cách đặt hàng, bạn đồng ý với ',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF6B7280), color: colorScheme.onSurfaceVariant,
), ),
children: [ children: [
TextSpan( TextSpan(
text: 'Điều khoản & Điều kiện', text: 'Điều khoản & Điều kiện',
style: TextStyle( style: TextStyle(
color: AppColors.primaryBlue, color: colorScheme.primary,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
), ),
@@ -328,16 +330,18 @@ class CheckoutPage extends HookConsumerWidget {
} }
/// Build Discount Code Section (Card 4 from HTML) /// Build Discount Code Section (Card 4 from HTML)
Widget _buildDiscountCodeSection() { Widget _buildDiscountCodeSection(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -351,16 +355,16 @@ class CheckoutPage extends HookConsumerWidget {
children: [ children: [
Icon( Icon(
FontAwesomeIcons.ticket, FontAwesomeIcons.ticket,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Mã giảm giá', 'Mã giảm giá',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -377,16 +381,16 @@ class CheckoutPage extends HookConsumerWidget {
hintText: 'Nhập mã giảm giá', hintText: 'Nhập mã giảm giá',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFFD1D5DB)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFFD1D5DB)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -403,7 +407,7 @@ class CheckoutPage extends HookConsumerWidget {
// TODO: Apply discount code // TODO: Apply discount code
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 24,
vertical: 12, vertical: 12,
@@ -413,10 +417,10 @@ class CheckoutPage extends HookConsumerWidget {
), ),
elevation: 0, elevation: 0,
), ),
child: const Text( child: Text(
'Áp dụng', 'Áp dụng',
style: TextStyle( style: TextStyle(
color: Colors.white, color: colorScheme.onPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
@@ -436,18 +440,18 @@ class CheckoutPage extends HookConsumerWidget {
), ),
child: Row( child: Row(
children: [ children: [
Icon( const Icon(
FontAwesomeIcons.circleCheck, FontAwesomeIcons.circleCheck,
color: AppColors.success, color: AppColors.success,
size: 18, size: 18,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Expanded( Expanded(
child: Text( child: Text(
'Bạn được giảm 15% (hạng Diamond)', 'Bạn được giảm 15% (hạng Diamond)',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF166534), color: const Color(0xFF166534),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/core/theme/typography.dart'; import 'package:worker/core/theme/typography.dart';
import 'package:worker/features/cart/presentation/providers/cart_provider.dart'; import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
import 'package:worker/features/cart/presentation/providers/cart_state.dart'; import 'package:worker/features/cart/presentation/providers/cart_state.dart';
@@ -74,6 +73,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final cartState = ref.watch(cartProvider); final cartState = ref.watch(cartProvider);
final isSelected = final isSelected =
cartState.selectedItems[widget.item.product.productId] ?? false; cartState.selectedItems[widget.item.product.productId] ?? false;
@@ -88,7 +88,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -127,7 +127,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 100, width: 100,
height: 100, height: 100,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
@@ -135,10 +135,10 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 100, width: 100,
height: 100, height: 100,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 32, size: 32,
), ),
), ),
@@ -169,7 +169,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
Text( Text(
'${currencyFormatter.format(widget.item.product.basePrice)}/m²', '${currencyFormatter.format(widget.item.product.basePrice)}/m²',
style: AppTypography.titleMedium.copyWith( style: AppTypography.titleMedium.copyWith(
color: AppColors.primaryBlue, color: colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
), ),
@@ -209,22 +209,22 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide( borderSide: BorderSide(
color: Color(0xFFE0E0E0), color: colorScheme.outlineVariant,
width: 1, width: 1,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide( borderSide: BorderSide(
color: Color(0xFFE0E0E0), color: colorScheme.outlineVariant,
width: 1, width: 1,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -254,7 +254,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
Text( Text(
'', '',
style: AppTypography.bodySmall.copyWith( style: AppTypography.bodySmall.copyWith(
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -266,7 +266,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
RichText( RichText(
text: TextSpan( text: TextSpan(
style: AppTypography.bodySmall.copyWith( style: AppTypography.bodySmall.copyWith(
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontSize: 13, fontSize: 13,
), ),
children: [ children: [
@@ -305,24 +305,25 @@ class _CustomCheckbox extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: () => onChanged?.call(!value), onTap: () => onChanged?.call(!value),
child: Container( child: Container(
width: 20, width: 20,
height: 20, height: 20,
decoration: BoxDecoration( decoration: BoxDecoration(
color: value ? AppColors.primaryBlue : AppColors.white, color: value ? colorScheme.primary : colorScheme.surface,
border: Border.all( border: Border.all(
color: value ? AppColors.primaryBlue : const Color(0xFFCBD5E1), color: value ? colorScheme.primary : colorScheme.outlineVariant,
width: 2, width: 2,
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: value child: value
? const Icon( ? Icon(
FontAwesomeIcons.check, FontAwesomeIcons.check,
size: 14, size: 14,
color: AppColors.white, color: colorScheme.surface,
) )
: null, : null,
), ),
@@ -341,6 +342,7 @@ class _QuantityButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell( return InkWell(
onTap: onPressed, onTap: onPressed,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
@@ -348,11 +350,11 @@ class _QuantityButton extends StatelessWidget {
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0), width: 2), border: Border.all(color: colorScheme.outlineVariant, width: 2),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
color: AppColors.white, color: colorScheme.surface,
), ),
child: Icon(icon, size: 16, color: AppColors.grey900), child: Icon(icon, size: 16, color: colorScheme.onSurface),
), ),
); );
} }

View File

@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Checkout Date Picker Field /// Checkout Date Picker Field
/// ///
@@ -24,15 +23,17 @@ class CheckoutDatePickerField extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ngày nhận hàng mong muốn', 'Ngày nhận hàng mong muốn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -51,9 +52,9 @@ class CheckoutDatePickerField extends HookWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -65,14 +66,14 @@ class CheckoutDatePickerField extends HookWidget {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: selectedDate.value != null color: selectedDate.value != null
? const Color(0xFF212121) ? colorScheme.onSurface
: AppColors.grey500.withValues(alpha: 0.6), : colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
), ),
), ),
const Icon( Icon(
FontAwesomeIcons.calendar, FontAwesomeIcons.calendar,
size: 20, size: 20,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),

View File

@@ -28,16 +28,18 @@ class CheckoutDropdownField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
text: label, text: label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
children: [ children: [
if (required) if (required)
@@ -53,23 +55,23 @@ class CheckoutDropdownField extends StatelessWidget {
initialValue: value, initialValue: value,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: const Color(0xFFF8FAFC), fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),

View File

@@ -42,6 +42,8 @@ class CheckoutSubmitButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -66,8 +68,8 @@ class CheckoutSubmitButton extends HookConsumerWidget {
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: ignorePricingRule backgroundColor: ignorePricingRule
? AppColors.warning ? AppColors.warning
: AppColors.primaryBlue, : colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.surface,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@@ -32,16 +32,18 @@ class CheckoutTextField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
text: label, text: label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
children: [ children: [
if (required) if (required)
@@ -61,27 +63,27 @@ class CheckoutTextField extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText ?? 'Nhập $label', hintText: hintText ?? 'Nhập $label',
hintStyle: TextStyle( hintStyle: TextStyle(
color: AppColors.grey500.withValues(alpha: 0.6), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
fontSize: 14, fontSize: 14,
), ),
filled: true, filled: true,
fillColor: const Color(0xFFF8FAFC), fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)), borderSide: BorderSide(color: colorScheme.outlineVariant),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input), borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),

View File

@@ -9,7 +9,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/account/domain/entities/address.dart'; import 'package:worker/features/account/domain/entities/address.dart';
import 'package:worker/features/account/presentation/providers/address_provider.dart'; import 'package:worker/features/account/presentation/providers/address_provider.dart';
import 'package:worker/features/cart/presentation/widgets/checkout_date_picker_field.dart'; import 'package:worker/features/cart/presentation/widgets/checkout_date_picker_field.dart';
@@ -33,6 +32,8 @@ class DeliveryInformationSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Watch the default address // Watch the default address
final defaultAddr = ref.watch(defaultAddressProvider); final defaultAddr = ref.watch(defaultAddressProvider);
@@ -54,7 +55,7 @@ class DeliveryInformationSection extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -70,18 +71,18 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Section Title // Section Title
Row( Row(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.truck, FontAwesomeIcons.truck,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 16, size: 16,
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
const Text( Text(
'Thông tin giao hàng', 'Thông tin giao hàng',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -93,12 +94,12 @@ class DeliveryInformationSection extends HookConsumerWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Địa chỉ nhận hàng', 'Địa chỉ nhận hàng',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF424242), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
@@ -125,7 +126,7 @@ class DeliveryInformationSection extends HookConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(AppSpacing.sm), padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0)), border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(AppRadius.sm), borderRadius: BorderRadius.circular(AppRadius.sm),
), ),
child: Row( child: Row(
@@ -137,10 +138,10 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Name // Name
Text( Text(
selectedAddress.value!.addressTitle, selectedAddress.value!.addressTitle,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -148,9 +149,9 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Phone // Phone
Text( Text(
selectedAddress.value!.phone, selectedAddress.value!.phone,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -158,19 +159,19 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Address // Address
Text( Text(
selectedAddress.value!.fullAddress, selectedAddress.value!.fullAddress,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
const FaIcon( FaIcon(
FontAwesomeIcons.chevronRight, FontAwesomeIcons.chevronRight,
size: 14, size: 14,
color: Color(0xFF9E9E9E), color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),
@@ -194,26 +195,26 @@ class DeliveryInformationSection extends HookConsumerWidget {
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: AppColors.primaryBlue, color: colorScheme.primary,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
borderRadius: BorderRadius.circular(AppRadius.sm), borderRadius: BorderRadius.circular(AppRadius.sm),
), ),
child: const Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.plus, FontAwesomeIcons.plus,
size: 14, size: 14,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
Text( Text(
'Thêm địa chỉ giao hàng', 'Thêm địa chỉ giao hàng',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],

View File

@@ -8,7 +8,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/account/presentation/providers/address_provider.dart'; import 'package:worker/features/account/presentation/providers/address_provider.dart';
/// Invoice Section /// Invoice Section
@@ -22,6 +21,8 @@ class InvoiceSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Watch the default address // Watch the default address
final defaultAddr = ref.watch(defaultAddressProvider); final defaultAddr = ref.watch(defaultAddressProvider);
@@ -29,7 +30,7 @@ class InvoiceSection extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -45,19 +46,19 @@ class InvoiceSection extends HookConsumerWidget {
// Header with Toggle // Header with Toggle
Row( Row(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.fileInvoice, FontAwesomeIcons.fileInvoice,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 16, size: 16,
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
const Expanded( Expanded(
child: Text( child: Text(
'Phát hành hóa đơn', 'Phát hành hóa đơn',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -67,7 +68,7 @@ class InvoiceSection extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
needsInvoice.value = value; needsInvoice.value = value;
}, },
activeTrackColor: AppColors.primaryBlue, activeTrackColor: colorScheme.primary,
), ),
], ],
), ),
@@ -75,7 +76,7 @@ class InvoiceSection extends HookConsumerWidget {
// Invoice Information (visible when toggle is ON) // Invoice Information (visible when toggle is ON)
if (needsInvoice.value) ...[ if (needsInvoice.value) ...[
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
const Divider(color: Color(0xFFE0E0E0)), Divider(color: colorScheme.outlineVariant),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Address Card // Address Card
@@ -89,7 +90,7 @@ class InvoiceSection extends HookConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(AppSpacing.sm), padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(AppRadius.sm), borderRadius: BorderRadius.circular(AppRadius.sm),
), ),
child: Row( child: Row(
@@ -101,10 +102,10 @@ class InvoiceSection extends HookConsumerWidget {
// Company/Address Title // Company/Address Title
Text( Text(
defaultAddr.addressTitle, defaultAddr.addressTitle,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -114,9 +115,9 @@ class InvoiceSection extends HookConsumerWidget {
defaultAddr.taxCode!.isNotEmpty) ...[ defaultAddr.taxCode!.isNotEmpty) ...[
Text( Text(
'Mã số thuế: ${defaultAddr.taxCode}', 'Mã số thuế: ${defaultAddr.taxCode}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -125,9 +126,9 @@ class InvoiceSection extends HookConsumerWidget {
// Phone // Phone
Text( Text(
'Số điện thoại: ${defaultAddr.phone}', 'Số điện thoại: ${defaultAddr.phone}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -137,9 +138,9 @@ class InvoiceSection extends HookConsumerWidget {
defaultAddr.email!.isNotEmpty) ...[ defaultAddr.email!.isNotEmpty) ...[
Text( Text(
'Email: ${defaultAddr.email}', 'Email: ${defaultAddr.email}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -148,19 +149,19 @@ class InvoiceSection extends HookConsumerWidget {
// Address // Address
Text( Text(
'Địa chỉ: ${defaultAddr.fullAddress}', 'Địa chỉ: ${defaultAddr.fullAddress}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF757575), color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
const FaIcon( FaIcon(
FontAwesomeIcons.chevronRight, FontAwesomeIcons.chevronRight,
size: 14, size: 14,
color: Color(0xFF9E9E9E), color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),
@@ -177,26 +178,26 @@ class InvoiceSection extends HookConsumerWidget {
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: AppColors.primaryBlue, color: colorScheme.primary,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
borderRadius: BorderRadius.circular(AppRadius.sm), borderRadius: BorderRadius.circular(AppRadius.sm),
), ),
child: const Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.plus, FontAwesomeIcons.plus,
size: 14, size: 14,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
Text( Text(
'Thêm địa chỉ xuất hóa đơn', 'Thêm địa chỉ xuất hóa đơn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],

View File

@@ -29,11 +29,13 @@ class OrderSummarySection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -47,32 +49,32 @@ class OrderSummarySection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Section Title // Section Title
const Text( Text(
'Tóm tắt đơn hàng', 'Tóm tắt đơn hàng',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Cart Items with conversion details // Cart Items with conversion details
...cartItems.map((item) => _buildCartItemWithConversion(item)), ...cartItems.map((item) => _buildCartItemWithConversion(context, item)),
const Divider(height: 32), const Divider(height: 32),
// Subtotal // Subtotal
_buildSummaryRow('Tạm tính', subtotal), _buildSummaryRow(context, 'Tạm tính', subtotal),
const SizedBox(height: 8), const SizedBox(height: 8),
// Member Tier Discount (Diamond 15%) // Member Tier Discount (Diamond 15%)
_buildSummaryRow('Giảm giá Diamond', -discount, isDiscount: true), _buildSummaryRow(context, 'Giảm giá Diamond', -discount, isDiscount: true),
const SizedBox(height: 8), const SizedBox(height: 8),
// Shipping // Shipping
_buildSummaryRow('Phí vận chuyển', shipping, isFree: shipping == 0), _buildSummaryRow(context, 'Phí vận chuyển', shipping, isFree: shipping == 0),
const Divider(height: 24), const Divider(height: 24),
@@ -80,20 +82,20 @@ class OrderSummarySection extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( Text(
'Tổng thanh toán', 'Tổng thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
Text( Text(
_formatCurrency(total), _formatCurrency(total),
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -104,7 +106,9 @@ class OrderSummarySection extends StatelessWidget {
} }
/// Build cart item with conversion details on two lines /// Build cart item with conversion details on two lines
Widget _buildCartItemWithConversion(Map<String, dynamic> item) { Widget _buildCartItemWithConversion(BuildContext context, Map<String, dynamic> item) {
final colorScheme = Theme.of(context).colorScheme;
// Get real conversion data from CartItemData // Get real conversion data from CartItemData
final quantity = item['quantity'] as double; final quantity = item['quantity'] as double;
final quantityConverted = item['quantityConverted'] as double; final quantityConverted = item['quantityConverted'] as double;
@@ -125,10 +129,10 @@ class OrderSummarySection extends StatelessWidget {
// Line 1: Product name // Line 1: Product name
Text( Text(
item['name'] as String, item['name'] as String,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -137,9 +141,9 @@ class OrderSummarySection extends StatelessWidget {
// Line 2: Conversion details (muted text) // Line 2: Conversion details (muted text)
Text( Text(
'${quantity.toStringAsFixed(2)} m² ($boxes viên / ${quantityConverted.toStringAsFixed(2)} m²)', '${quantity.toStringAsFixed(2)} m² ($boxes viên / ${quantityConverted.toStringAsFixed(2)} m²)',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -151,10 +155,10 @@ class OrderSummarySection extends StatelessWidget {
// Price (right side) - using converted quantity for accurate billing // Price (right side) - using converted quantity for accurate billing
Text( Text(
_formatCurrency(price * quantityConverted), _formatCurrency(price * quantityConverted),
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -164,24 +168,27 @@ class OrderSummarySection extends StatelessWidget {
/// Build summary row /// Build summary row
Widget _buildSummaryRow( Widget _buildSummaryRow(
BuildContext context,
String label, String label,
double amount, { double amount, {
bool isDiscount = false, bool isDiscount = false,
bool isFree = false, bool isFree = false,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
label, label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
Text( Text(
isFree ? 'Miễn phí' : _formatCurrency(amount), isFree ? 'Miễn phí' : _formatCurrency(amount),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: isDiscount ? AppColors.success : const Color(0xFF212121), color: isDiscount ? AppColors.success : colorScheme.onSurface,
), ),
), ),
], ],

View File

@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/orders/domain/entities/payment_term.dart'; import 'package:worker/features/orders/domain/entities/payment_term.dart';
/// Payment Method Section /// Payment Method Section
@@ -25,13 +24,15 @@ class PaymentMethodSection extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Show empty state if no payment terms available // Show empty state if no payment terms available
if (paymentTerms.isEmpty) { if (paymentTerms.isEmpty) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -41,12 +42,12 @@ class PaymentMethodSection extends HookWidget {
), ),
], ],
), ),
child: const Center( child: Center(
child: Text( child: Text(
'Không có phương thức thanh toán khả dụng', 'Không có phương thức thanh toán khả dụng',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -57,7 +58,7 @@ class PaymentMethodSection extends HookWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -71,12 +72,12 @@ class PaymentMethodSection extends HookWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Section Title // Section Title
const Text( Text(
'Phương thức thanh toán', 'Phương thức thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
@@ -109,12 +110,12 @@ class PaymentMethodSection extends HookWidget {
onChanged: (value) { onChanged: (value) {
paymentMethod.value = value!; paymentMethod.value = value!;
}, },
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Icon( Icon(
icon, icon,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 24, size: 24,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -132,9 +133,9 @@ class PaymentMethodSection extends HookWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
term.customDescription, term.customDescription,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],

View File

@@ -18,6 +18,8 @@ class PriceNegotiationSection extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
@@ -35,7 +37,7 @@ class PriceNegotiationSection extends HookWidget {
}, },
activeColor: AppColors.warning, activeColor: AppColors.warning,
), ),
const Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -44,13 +46,16 @@ class PriceNegotiationSection extends HookWidget {
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Gửi yêu cầu đàm phán giá cho đơn hàng này', 'Gửi yêu cầu đàm phán giá cho đơn hàng này',
style: TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
),
), ),
], ],
), ),

View File

@@ -78,6 +78,8 @@ class FavoritesPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Search controller // Search controller
final searchController = useTextEditingController(); final searchController = useTextEditingController();
final searchQuery = useState(''); final searchQuery = useState('');
@@ -104,20 +106,20 @@ class FavoritesPage extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text('Yêu thích', style: TextStyle(color: Colors.black)), title: Text('Yêu thích', style: TextStyle(color: colorScheme.onSurface)),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
// Count badge // Count badge
@@ -127,10 +129,10 @@ class FavoritesPage extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text( child: Text(
'($favoriteCount)', '($favoriteCount)',
style: const TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 16.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
), ),
@@ -139,9 +141,9 @@ class FavoritesPage extends HookConsumerWidget {
// Clear all button // Clear all button
if (favoriteCount > 0) if (favoriteCount > 0)
IconButton( IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
tooltip: 'Xóa tất cả', tooltip: 'Xóa tất cả',
@@ -177,16 +179,16 @@ class FavoritesPage extends HookConsumerWidget {
onChanged: (value) => searchQuery.value = value, onChanged: (value) => searchQuery.value = value,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Tìm kiếm sản phẩm...', hintText: 'Tìm kiếm sản phẩm...',
hintStyle: const TextStyle(color: AppColors.grey500), hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
prefixIcon: const Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
suffixIcon: searchQuery.value.isNotEmpty suffixIcon: searchQuery.value.isNotEmpty
? IconButton( ? IconButton(
icon: const Icon( icon: Icon(
Icons.clear, Icons.clear,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
onPressed: () { onPressed: () {
searchController.clear(); searchController.clear();
@@ -195,19 +197,19 @@ class FavoritesPage extends HookConsumerWidget {
) )
: null, : null,
filled: true, filled: true,
fillColor: Colors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -229,9 +231,9 @@ class FavoritesPage extends HookConsumerWidget {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
'Tìm thấy ${filteredProducts.length} sản phẩm', 'Tìm thấy ${filteredProducts.length} sản phẩm',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -245,26 +247,26 @@ class FavoritesPage extends HookConsumerWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.magnifyingGlass, FontAwesomeIcons.magnifyingGlass,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
Text( Text(
'Không tìm thấy "${searchQuery.value}"', 'Không tìm thấy "${searchQuery.value}"',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
const Text( Text(
'Thử tìm kiếm với từ khóa khác', 'Thử tìm kiếm với từ khóa khác',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -431,6 +433,8 @@ class _EmptyState extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(AppSpacing.xl), padding: const EdgeInsets.all(AppSpacing.xl),
@@ -441,18 +445,18 @@ class _EmptyState extends StatelessWidget {
FaIcon( FaIcon(
FontAwesomeIcons.heart, FontAwesomeIcons.heart,
size: 80.0, size: 80.0,
color: AppColors.grey500.withValues(alpha: 0.5), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Heading // Heading
const Text( Text(
'Chưa có sản phẩm yêu thích', 'Chưa có sản phẩm yêu thích',
style: TextStyle( style: TextStyle(
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -460,9 +464,9 @@ class _EmptyState extends StatelessWidget {
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
// Subtext // Subtext
const Text( Text(
'Thêm sản phẩm vào danh sách yêu thích để xem lại sau', 'Thêm sản phẩm vào danh sách yêu thích để xem lại sau',
style: TextStyle(fontSize: 14.0, color: AppColors.grey500), style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -475,8 +479,8 @@ class _EmptyState extends StatelessWidget {
context.pushReplacement('/products'); context.pushReplacement('/products');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.surface,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.xl, horizontal: AppSpacing.xl,
vertical: AppSpacing.md, vertical: AppSpacing.md,
@@ -527,23 +531,25 @@ class _LoadingState extends StatelessWidget {
class _ShimmerCard extends StatelessWidget { class _ShimmerCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: ProductCardSpecs.elevation, elevation: ProductCardSpecs.elevation,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ProductCardSpecs.borderRadius), borderRadius: BorderRadius.circular(ProductCardSpecs.borderRadius),
), ),
child: Shimmer.fromColors( child: Shimmer.fromColors(
baseColor: AppColors.grey100, baseColor: colorScheme.surfaceContainerHighest,
highlightColor: AppColors.grey50, highlightColor: colorScheme.surfaceContainerLowest,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Image placeholder // Image placeholder
Expanded( Expanded(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(ProductCardSpecs.borderRadius), top: Radius.circular(ProductCardSpecs.borderRadius),
), ),
), ),
@@ -561,7 +567,7 @@ class _ShimmerCard extends StatelessWidget {
height: 14.0, height: 14.0,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4.0), borderRadius: BorderRadius.circular(4.0),
), ),
), ),
@@ -573,7 +579,7 @@ class _ShimmerCard extends StatelessWidget {
height: 12.0, height: 12.0,
width: 80.0, width: 80.0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4.0), borderRadius: BorderRadius.circular(4.0),
), ),
), ),
@@ -585,7 +591,7 @@ class _ShimmerCard extends StatelessWidget {
height: 16.0, height: 16.0,
width: 100.0, width: 100.0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4.0), borderRadius: BorderRadius.circular(4.0),
), ),
), ),
@@ -597,7 +603,7 @@ class _ShimmerCard extends StatelessWidget {
height: 36.0, height: 36.0,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
), ),
), ),
@@ -626,6 +632,8 @@ class _ErrorState extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(AppSpacing.xl), padding: const EdgeInsets.all(AppSpacing.xl),
@@ -642,12 +650,12 @@ class _ErrorState extends StatelessWidget {
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
// Title // Title
const Text( Text(
'Có lỗi xảy ra', 'Có lỗi xảy ra',
style: TextStyle( style: TextStyle(
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -657,7 +665,7 @@ class _ErrorState extends StatelessWidget {
// Error message // Error message
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(fontSize: 14.0, color: AppColors.grey500), style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -669,8 +677,8 @@ class _ErrorState extends StatelessWidget {
ElevatedButton.icon( ElevatedButton.icon(
onPressed: onRetry, onPressed: onRetry,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.surface,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.xl, horizontal: AppSpacing.xl,
vertical: AppSpacing.md, vertical: AppSpacing.md,

View File

@@ -12,7 +12,7 @@ import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart'; // Keep for AppColors.danger and AppColors.white
import 'package:worker/features/favorites/presentation/providers/favorites_provider.dart'; import 'package:worker/features/favorites/presentation/providers/favorites_provider.dart';
import 'package:worker/features/products/domain/entities/product.dart'; import 'package:worker/features/products/domain/entities/product.dart';
@@ -76,6 +76,8 @@ class FavoriteProductCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: ProductCardSpecs.elevation, elevation: ProductCardSpecs.elevation,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -101,16 +103,16 @@ class FavoriteProductCard extends ConsumerWidget {
memCacheWidth: ImageSpecs.productImageCacheWidth, memCacheWidth: ImageSpecs.productImageCacheWidth,
memCacheHeight: ImageSpecs.productImageCacheHeight, memCacheHeight: ImageSpecs.productImageCacheHeight,
placeholder: (context, url) => Shimmer.fromColors( placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100, baseColor: colorScheme.surfaceContainerHighest,
highlightColor: AppColors.grey50, highlightColor: colorScheme.surfaceContainerLowest,
child: Container(color: AppColors.grey100), child: Container(color: colorScheme.surfaceContainerHighest),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 48.0, size: 48.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -122,7 +124,7 @@ class FavoriteProductCard extends ConsumerWidget {
right: AppSpacing.sm, right: AppSpacing.sm,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -176,9 +178,9 @@ class FavoriteProductCard extends ConsumerWidget {
if (product.erpnextItemCode != null) if (product.erpnextItemCode != null)
Text( Text(
'Mã: ${product.erpnextItemCode}', 'Mã: ${product.erpnextItemCode}',
style: const TextStyle( style: TextStyle(
fontSize: 12.0, fontSize: 12.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -189,10 +191,10 @@ class FavoriteProductCard extends ConsumerWidget {
// Price // Price
Text( Text(
_formatPrice(product.effectivePrice), _formatPrice(product.effectivePrice),
style: const TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 16.0,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
@@ -208,9 +210,9 @@ class FavoriteProductCard extends ConsumerWidget {
context.push('/products/${product.productId}'); context.push('/products/${product.productId}');
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue, foregroundColor: colorScheme.primary,
side: const BorderSide( side: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 1.5, width: 1.5,
), ),
elevation: 0, elevation: 0,

View File

@@ -9,7 +9,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart'; import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart';
/// Loyalty Page /// Loyalty Page
@@ -24,18 +23,19 @@ class LoyaltyPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final loyaltyPoints = ref.watch(loyaltyPointsProvider); final loyaltyPoints = ref.watch(loyaltyPointsProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
title: const Text( title: Text(
'Hội viên thân thiết', 'Hội viên thân thiết',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
@@ -50,17 +50,17 @@ class LoyaltyPage extends ConsumerWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Progress Card // Progress Card
_buildProgressCard(), _buildProgressCard(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// Loyalty Features Menu // Loyalty Features Menu
..._buildLoyaltyMenu(context), ..._buildLoyaltyMenu(context, colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// Current Benefits Card // Current Benefits Card
_buildBenefitsCard(), _buildBenefitsCard(colorScheme),
], ],
), ),
), ),
@@ -201,7 +201,7 @@ class LoyaltyPage extends ConsumerWidget {
} }
/// Build Progress Card /// Build Progress Card
Widget _buildProgressCard() { Widget _buildProgressCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 5, elevation: 5,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
@@ -211,12 +211,12 @@ class LoyaltyPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Tiến trình lên hạng', 'Tiến trình lên hạng',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -227,11 +227,11 @@ class LoyaltyPage extends ConsumerWidget {
children: [ children: [
Text( Text(
'Hạng hiện tại: DIAMOND', 'Hạng hiện tại: DIAMOND',
style: TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
), ),
Text( Text(
'Hạng kế tiếp: PLATINUM', 'Hạng kế tiếp: PLATINUM',
style: TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -244,7 +244,7 @@ class LoyaltyPage extends ConsumerWidget {
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: 0.65, value: 0.65,
minHeight: 8, minHeight: 8,
backgroundColor: AppColors.grey100, backgroundColor: colorScheme.surfaceContainerHighest,
valueColor: const AlwaysStoppedAnimation<Color>( valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF4A00E0), Color(0xFF4A00E0),
), ),
@@ -257,18 +257,18 @@ class LoyaltyPage extends ConsumerWidget {
Center( Center(
child: RichText( child: RichText(
textAlign: TextAlign.center, textAlign: TextAlign.center,
text: const TextSpan( text: TextSpan(
style: TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
children: [ children: [
TextSpan(text: 'Còn '), const TextSpan(text: 'Còn '),
TextSpan( TextSpan(
text: '2,250 điểm', text: '2,250 điểm',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
TextSpan(text: ' nữa để lên hạng Platinum'), const TextSpan(text: ' nữa để lên hạng Platinum'),
], ],
), ),
), ),
@@ -280,7 +280,7 @@ class LoyaltyPage extends ConsumerWidget {
} }
/// Build Loyalty Menu Items /// Build Loyalty Menu Items
List<Widget> _buildLoyaltyMenu(BuildContext context) { List<Widget> _buildLoyaltyMenu(BuildContext context, ColorScheme colorScheme) {
final menuItems = [ final menuItems = [
{ {
'icon': FontAwesomeIcons.gift, 'icon': FontAwesomeIcons.gift,
@@ -326,7 +326,7 @@ class LoyaltyPage extends ConsumerWidget {
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: AppColors.grey100), side: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
@@ -348,12 +348,12 @@ class LoyaltyPage extends ConsumerWidget {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.1), color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Icon( child: Icon(
item['icon'] as IconData, item['icon'] as IconData,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 24, size: 24,
), ),
), ),
@@ -367,18 +367,18 @@ class LoyaltyPage extends ConsumerWidget {
children: [ children: [
Text( Text(
item['title'] as String, item['title'] as String,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
item['subtitle'] as String, item['subtitle'] as String,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -386,9 +386,9 @@ class LoyaltyPage extends ConsumerWidget {
), ),
// Arrow // Arrow
const Icon( Icon(
FontAwesomeIcons.chevronRight, FontAwesomeIcons.chevronRight,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 20, size: 20,
), ),
], ],
@@ -400,7 +400,7 @@ class LoyaltyPage extends ConsumerWidget {
} }
/// Build Benefits Card /// Build Benefits Card
Widget _buildBenefitsCard() { Widget _buildBenefitsCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
@@ -410,22 +410,23 @@ class LoyaltyPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Quyền lợi hạng Diamond', 'Quyền lợi hạng Diamond',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildBenefitItem('Chiết khấu 15% cho tất cả sản phẩm'), _buildBenefitItem('Chiết khấu 15% cho tất cả sản phẩm', colorScheme),
_buildBenefitItem('Giao hàng miễn phí cho đơn từ 5 triệu'), _buildBenefitItem('Giao hàng miễn phí cho đơn từ 5 triệu', colorScheme),
_buildBenefitItem('Ưu tiên xử lý đơn hàng'), _buildBenefitItem('Ưu tiên xử lý đơn hàng', colorScheme),
_buildBenefitItem('Tặng 500 điểm vào ngày sinh nhật'), _buildBenefitItem('Tặng 500 điểm vào ngày sinh nhật', colorScheme),
_buildBenefitItem('Tư vấn thiết kế miễn phí'), _buildBenefitItem('Tư vấn thiết kế miễn phí', colorScheme),
_buildBenefitItem( _buildBenefitItem(
'Mời tham gia sự kiện VIP độc quyền', 'Mời tham gia sự kiện VIP độc quyền',
colorScheme,
isLast: true, isLast: true,
), ),
], ],
@@ -435,7 +436,7 @@ class LoyaltyPage extends ConsumerWidget {
} }
/// Build Benefit Item /// Build Benefit Item
Widget _buildBenefitItem(String text, {bool isLast = false}) { Widget _buildBenefitItem(String text, ColorScheme colorScheme, {bool isLast = false}) {
return Padding( return Padding(
padding: EdgeInsets.only(bottom: isLast ? 0 : 12), padding: EdgeInsets.only(bottom: isLast ? 0 : 12),
child: Row( child: Row(
@@ -446,9 +447,9 @@ class LoyaltyPage extends ConsumerWidget {
Expanded( Expanded(
child: Text( child: Text(
text, text,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.4, height: 1.4,
), ),
), ),

View File

@@ -27,22 +27,23 @@ class PointsHistoryPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final historyAsync = ref.watch(pointsHistoryProvider); final historyAsync = ref.watch(pointsHistoryProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Lịch sử điểm', 'Lịch sử điểm',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
), ),
body: RefreshIndicator( body: RefreshIndicator(
@@ -52,7 +53,7 @@ class PointsHistoryPage extends ConsumerWidget {
child: historyAsync.when( child: historyAsync.when(
data: (entries) { data: (entries) {
if (entries.isEmpty) { if (entries.isEmpty) {
return _buildEmptyState(); return _buildEmptyState(colorScheme);
} }
return SingleChildScrollView( return SingleChildScrollView(
@@ -61,27 +62,27 @@ class PointsHistoryPage extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Filter Section // Filter Section
_buildFilterSection(), _buildFilterSection(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// Transaction List // Transaction List
...entries.map( ...entries.map(
(entry) => _buildTransactionCard(context, ref, entry), (entry) => _buildTransactionCard(context, ref, entry, colorScheme),
), ),
], ],
), ),
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => _buildErrorState(error), error: (error, stack) => _buildErrorState(error, colorScheme),
), ),
), ),
); );
} }
/// Build filter section /// Build filter section
Widget _buildFilterSection() { Widget _buildFilterSection(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
@@ -94,21 +95,21 @@ class PointsHistoryPage extends ConsumerWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( Text(
'Bộ lọc', 'Bộ lọc',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
FaIcon(FontAwesomeIcons.sliders, color: AppColors.primaryBlue, size: 18), FaIcon(FontAwesomeIcons.sliders, color: colorScheme.primary, size: 18),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Thời gian hiệu lực: 01/01/2023 - 31/12/2023', 'Thời gian hiệu lực: 01/01/2023 - 31/12/2023',
style: TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -121,6 +122,7 @@ class PointsHistoryPage extends ConsumerWidget {
BuildContext context, BuildContext context,
WidgetRef ref, WidgetRef ref,
LoyaltyPointEntryModel entry, LoyaltyPointEntryModel entry,
ColorScheme colorScheme,
) { ) {
final dateFormatter = DateFormat('dd/MM/yyyy HH:mm:ss'); final dateFormatter = DateFormat('dd/MM/yyyy HH:mm:ss');
final currencyFormatter = NumberFormat.currency( final currencyFormatter = NumberFormat.currency(
@@ -155,10 +157,10 @@ class PointsHistoryPage extends ConsumerWidget {
// Description // Description
Text( Text(
entry.description, entry.description,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -166,9 +168,9 @@ class PointsHistoryPage extends ConsumerWidget {
// Timestamp // Timestamp
Text( Text(
'Thời gian: ${dateFormatter.format(entry.timestamp)}', 'Thời gian: ${dateFormatter.format(entry.timestamp)}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
@@ -177,9 +179,9 @@ class PointsHistoryPage extends ConsumerWidget {
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'Giao dịch: ${currencyFormatter.format(transactionAmount)}', 'Giao dịch: ${currencyFormatter.format(transactionAmount)}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -203,8 +205,8 @@ class PointsHistoryPage extends ConsumerWidget {
horizontal: 12, horizontal: 12,
vertical: 6, vertical: 6,
), ),
side: const BorderSide(color: AppColors.grey500), side: BorderSide(color: colorScheme.onSurfaceVariant),
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
textStyle: const TextStyle(fontSize: 12), textStyle: const TextStyle(fontSize: 12),
minimumSize: const Size(0, 32), minimumSize: const Size(0, 32),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -235,7 +237,7 @@ class PointsHistoryPage extends ConsumerWidget {
? AppColors.success ? AppColors.success
: entry.points < 0 : entry.points < 0
? AppColors.danger ? AppColors.danger
: AppColors.grey900, : colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -243,9 +245,9 @@ class PointsHistoryPage extends ConsumerWidget {
// New balance // New balance
Text( Text(
'Điểm mới: ${entry.balanceAfter}', 'Điểm mới: ${entry.balanceAfter}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -259,7 +261,7 @@ class PointsHistoryPage extends ConsumerWidget {
} }
/// Build empty state /// Build empty state
Widget _buildEmptyState() { Widget _buildEmptyState(ColorScheme colorScheme) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -267,21 +269,21 @@ class PointsHistoryPage extends ConsumerWidget {
FaIcon( FaIcon(
FontAwesomeIcons.clockRotateLeft, FontAwesomeIcons.clockRotateLeft,
size: 80, size: 80,
color: AppColors.grey500.withValues(alpha: 0.5), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Chưa có lịch sử điểm', 'Chưa có lịch sử điểm',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Kéo xuống để làm mới', 'Kéo xuống để làm mới',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -289,7 +291,7 @@ class PointsHistoryPage extends ConsumerWidget {
} }
/// Build error state /// Build error state
Widget _buildErrorState(Object error) { Widget _buildErrorState(Object error, ColorScheme colorScheme) {
print(error.toString()); print(error.toString());
return Center( return Center(
child: Column( child: Column(
@@ -301,18 +303,18 @@ class PointsHistoryPage extends ConsumerWidget {
color: AppColors.danger.withValues(alpha: 0.7), color: AppColors.danger.withValues(alpha: 0.7),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Có lỗi xảy ra', 'Có lỗi xảy ra',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],

View File

@@ -19,30 +19,31 @@ class PointsRecordsPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final recordsAsync = ref.watch(filteredPointsRecordsProvider); final recordsAsync = ref.watch(filteredPointsRecordsProvider);
final filter = ref.watch(pointsRecordsFilterProvider); final filter = ref.watch(pointsRecordsFilterProvider);
final selectedStatus = filter.selectedStatus; final selectedStatus = filter.selectedStatus;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Danh sách Ghi nhận điểm', 'Danh sách Ghi nhận điểm',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.plus, FontAwesomeIcons.plus,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () { onPressed: () {
@@ -57,7 +58,7 @@ class PointsRecordsPage extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
centerTitle: false, centerTitle: false,
), ),
body: Column( body: Column(
@@ -68,16 +69,16 @@ class PointsRecordsPage extends ConsumerWidget {
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mã yêu cầu', hintText: 'Mã yêu cầu',
prefixIcon: const Icon(Icons.search, color: AppColors.grey500), prefixIcon: Icon(Icons.search, color: colorScheme.onSurfaceVariant),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
), ),
onChanged: (value) { onChanged: (value) {
@@ -95,6 +96,7 @@ class PointsRecordsPage extends ConsumerWidget {
_buildFilterChip( _buildFilterChip(
context, context,
ref, ref,
colorScheme,
label: 'Tất cả', label: 'Tất cả',
isSelected: selectedStatus == null, isSelected: selectedStatus == null,
onTap: () => onTap: () =>
@@ -107,6 +109,7 @@ class PointsRecordsPage extends ConsumerWidget {
child: _buildFilterChip( child: _buildFilterChip(
context, context,
ref, ref,
colorScheme,
label: status.displayName, label: status.displayName,
isSelected: selectedStatus == status, isSelected: selectedStatus == status,
onTap: () => onTap: () =>
@@ -134,28 +137,28 @@ class PointsRecordsPage extends ConsumerWidget {
children: [ children: [
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * 0.5, height: MediaQuery.of(context).size.height * 0.5,
child: const Center( child: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.folderOpen, FontAwesomeIcons.folderOpen,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Không có ghi nhận điểm nào', 'Không có ghi nhận điểm nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Không tìm thấy ghi nhận điểm phù hợp', 'Không tìm thấy ghi nhận điểm phù hợp',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -175,7 +178,7 @@ class PointsRecordsPage extends ConsumerWidget {
itemCount: records.length, itemCount: records.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final record = records[index]; final record = records[index];
return _buildRecordCard(context, record); return _buildRecordCard(context, colorScheme, record);
}, },
), ),
); );
@@ -202,24 +205,24 @@ class PointsRecordsPage extends ConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Có lỗi xảy ra', 'Có lỗi xảy ra',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Kéo xuống để thử lại', 'Kéo xuống để thử lại',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -237,7 +240,8 @@ class PointsRecordsPage extends ConsumerWidget {
Widget _buildFilterChip( Widget _buildFilterChip(
BuildContext context, BuildContext context,
WidgetRef ref, { WidgetRef ref,
ColorScheme colorScheme, {
required String label, required String label,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
@@ -247,16 +251,16 @@ class PointsRecordsPage extends ConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.white, color: isSelected ? colorScheme.primary : colorScheme.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
), ),
), ),
child: Text( child: Text(
label, label,
style: TextStyle( style: TextStyle(
color: isSelected ? AppColors.white : AppColors.grey900, color: isSelected ? colorScheme.surface : colorScheme.onSurface,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
), ),
), ),
@@ -264,7 +268,7 @@ class PointsRecordsPage extends ConsumerWidget {
); );
} }
Widget _buildRecordCard(BuildContext context, PointsRecord record) { Widget _buildRecordCard(BuildContext context, ColorScheme colorScheme, PointsRecord record) {
final currencyFormat = NumberFormat.currency( final currencyFormat = NumberFormat.currency(
locale: 'vi_VN', locale: 'vi_VN',
symbol: '', symbol: '',
@@ -293,29 +297,29 @@ class PointsRecordsPage extends ConsumerWidget {
children: [ children: [
Text( Text(
'#${record.recordId}', '#${record.recordId}',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
_buildStatusBadge(record.status), _buildStatusBadge(colorScheme, record.status),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Ngày gửi: ${DateFormat('dd/MM/yyyy').format(record.submittedAt)}', 'Ngày gửi: ${DateFormat('dd/MM/yyyy').format(record.submittedAt)}',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Giá trị đơn hàng: ${currencyFormat.format(record.invoiceAmount)}', 'Giá trị đơn hàng: ${currencyFormat.format(record.invoiceAmount)}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (record.rejectReason != null) ...[ if (record.rejectReason != null) ...[
@@ -354,7 +358,7 @@ class PointsRecordsPage extends ConsumerWidget {
); );
} }
Widget _buildStatusBadge(PointsStatus status) { Widget _buildStatusBadge(ColorScheme colorScheme, PointsStatus status) {
final color = _getStatusColor(status); final color = _getStatusColor(status);
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),

View File

@@ -28,28 +28,29 @@ class RewardsPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final filteredGifts = ref.watch(filteredGiftsProvider); final filteredGifts = ref.watch(filteredGiftsProvider);
final selectedCategory = ref.watch(selectedGiftCategoryProvider); final selectedCategory = ref.watch(selectedGiftCategoryProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Đổi quà tặng', 'Đổi quà tặng',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.circleInfo, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.circleInfo, color: colorScheme.onSurface, size: 20),
onPressed: () => _showInfoDialog(context), onPressed: () => _showInfoDialog(context, colorScheme),
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
@@ -71,14 +72,14 @@ class RewardsPage extends ConsumerWidget {
// Category Filter Pills // Category Filter Pills
SliverToBoxAdapter( SliverToBoxAdapter(
child: _buildCategoryFilter(context, ref, selectedCategory), child: _buildCategoryFilter(context, ref, selectedCategory, colorScheme),
), ),
// Rewards Grid // Rewards Grid
SliverPadding( SliverPadding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 24), padding: const EdgeInsets.fromLTRB(12, 8, 12, 24),
sliver: filteredGifts.isEmpty sliver: filteredGifts.isEmpty
? _buildEmptyState() ? _buildEmptyState(colorScheme)
: SliverGrid( : SliverGrid(
gridDelegate: gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount( const SliverGridDelegateWithFixedCrossAxisCount(
@@ -91,7 +92,7 @@ class RewardsPage extends ConsumerWidget {
final gift = filteredGifts[index]; final gift = filteredGifts[index];
return RewardCard( return RewardCard(
gift: gift, gift: gift,
onRedeem: () => _handleRedeemGift(context, ref, gift), onRedeem: () => _handleRedeemGift(context, ref, gift, colorScheme),
); );
}, childCount: filteredGifts.length), }, childCount: filteredGifts.length),
), ),
@@ -103,7 +104,7 @@ class RewardsPage extends ConsumerWidget {
} }
/// Show info dialog with usage instructions /// Show info dialog with usage instructions
void _showInfoDialog(BuildContext context) { void _showInfoDialog(BuildContext context, ColorScheme colorScheme) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@@ -123,18 +124,23 @@ class RewardsPage extends ConsumerWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoItem( _buildInfoItem(
'Sử dụng điểm tích lũy của bạn để đổi các phần quà giá trị trong danh mục.', 'Sử dụng điểm tích lũy của bạn để đổi các phần quà giá trị trong danh mục.',
colorScheme,
), ),
_buildInfoItem( _buildInfoItem(
'Bấm vào một phần quà để xem chi tiết và điều kiện áp dụng.', 'Bấm vào một phần quà để xem chi tiết và điều kiện áp dụng.',
colorScheme,
), ),
_buildInfoItem( _buildInfoItem(
'Khi xác nhận đổi quà, bạn có thể chọn "Nhận hàng tại Showroom".', 'Khi xác nhận đổi quà, bạn có thể chọn "Nhận hàng tại Showroom".',
colorScheme,
), ),
_buildInfoItem( _buildInfoItem(
'Nếu chọn "Nhận hàng tại Showroom", bạn sẽ cần chọn Showroom bạn muốn đến nhận từ danh sách thả xuống.', 'Nếu chọn "Nhận hàng tại Showroom", bạn sẽ cần chọn Showroom bạn muốn đến nhận từ danh sách thả xuống.',
colorScheme,
), ),
_buildInfoItem( _buildInfoItem(
'Quà đã đổi sẽ được chuyển vào mục "Quà của tôi" (trong trang Hội viên).', 'Quà đã đổi sẽ được chuyển vào mục "Quà của tôi" (trong trang Hội viên).',
colorScheme,
), ),
], ],
), ),
@@ -143,8 +149,8 @@ class RewardsPage extends ConsumerWidget {
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
minimumSize: const Size(double.infinity, 44), minimumSize: const Size(double.infinity, 44),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -161,19 +167,19 @@ class RewardsPage extends ConsumerWidget {
} }
/// Build info item with bullet point /// Build info item with bullet point
Widget _buildInfoItem(String text) { Widget _buildInfoItem(String text, ColorScheme colorScheme) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.only(top: 6), padding: const EdgeInsets.only(top: 6),
child: FaIcon(FontAwesomeIcons.solidCircle, size: 6, color: AppColors.grey500), child: FaIcon(FontAwesomeIcons.solidCircle, size: 6, color: colorScheme.onSurfaceVariant),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text(text, style: TextStyle(fontSize: 14, height: 1.5)), child: Text(text, style: const TextStyle(fontSize: 14, height: 1.5)),
), ),
], ],
), ),
@@ -185,6 +191,7 @@ class RewardsPage extends ConsumerWidget {
BuildContext context, BuildContext context,
WidgetRef ref, WidgetRef ref,
GiftCategory? selectedCategory, GiftCategory? selectedCategory,
ColorScheme colorScheme,
) { ) {
return Container( return Container(
height: 48, height: 48,
@@ -201,6 +208,7 @@ class RewardsPage extends ConsumerWidget {
onTap: () { onTap: () {
ref.read(selectedGiftCategoryProvider.notifier).clearSelection(); ref.read(selectedGiftCategoryProvider.notifier).clearSelection();
}, },
colorScheme: colorScheme,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -215,6 +223,7 @@ class RewardsPage extends ConsumerWidget {
.read(selectedGiftCategoryProvider.notifier) .read(selectedGiftCategoryProvider.notifier)
.setCategory(GiftCategory.voucher); .setCategory(GiftCategory.voucher);
}, },
colorScheme: colorScheme,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -229,6 +238,7 @@ class RewardsPage extends ConsumerWidget {
.read(selectedGiftCategoryProvider.notifier) .read(selectedGiftCategoryProvider.notifier)
.setCategory(GiftCategory.product); .setCategory(GiftCategory.product);
}, },
colorScheme: colorScheme,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -243,6 +253,7 @@ class RewardsPage extends ConsumerWidget {
.read(selectedGiftCategoryProvider.notifier) .read(selectedGiftCategoryProvider.notifier)
.setCategory(GiftCategory.service); .setCategory(GiftCategory.service);
}, },
colorScheme: colorScheme,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -257,6 +268,7 @@ class RewardsPage extends ConsumerWidget {
.read(selectedGiftCategoryProvider.notifier) .read(selectedGiftCategoryProvider.notifier)
.setCategory(GiftCategory.discount); .setCategory(GiftCategory.discount);
}, },
colorScheme: colorScheme,
), ),
], ],
), ),
@@ -270,13 +282,14 @@ class RewardsPage extends ConsumerWidget {
required String label, required String label,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
required ColorScheme colorScheme,
}) { }) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Center( child: Center(
@@ -285,7 +298,7 @@ class RewardsPage extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected ? Colors.white : AppColors.grey900, color: isSelected ? colorScheme.onPrimary : colorScheme.onSurface,
), ),
), ),
), ),
@@ -294,7 +307,7 @@ class RewardsPage extends ConsumerWidget {
} }
/// Build empty state /// Build empty state
Widget _buildEmptyState() { Widget _buildEmptyState(ColorScheme colorScheme) {
return SliverFillRemaining( return SliverFillRemaining(
child: Center( child: Center(
child: Column( child: Column(
@@ -303,21 +316,21 @@ class RewardsPage extends ConsumerWidget {
FaIcon( FaIcon(
FontAwesomeIcons.gift, FontAwesomeIcons.gift,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không có quà tặng nào', 'Không có quà tặng nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Vui lòng thử lại sau', 'Vui lòng thử lại sau',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -330,6 +343,7 @@ class RewardsPage extends ConsumerWidget {
BuildContext context, BuildContext context,
WidgetRef ref, WidgetRef ref,
GiftCatalog gift, GiftCatalog gift,
ColorScheme colorScheme,
) { ) {
final numberFormat = NumberFormat('#,###', 'vi_VN'); final numberFormat = NumberFormat('#,###', 'vi_VN');
final pointsState = ref.read(loyaltyPointsProvider); final pointsState = ref.read(loyaltyPointsProvider);
@@ -342,15 +356,15 @@ class RewardsPage extends ConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( const Text(
'Bạn có chắc muốn đổi quà này?', 'Bạn có chắc muốn đổi quà này?',
style: const TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
@@ -370,10 +384,10 @@ class RewardsPage extends ConsumerWidget {
const Text('Chi phí:', style: TextStyle(fontSize: 13)), const Text('Chi phí:', style: TextStyle(fontSize: 13)),
Text( Text(
'${numberFormat.format(gift.pointsCost)} điểm', '${numberFormat.format(gift.pointsCost)} điểm',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -411,8 +425,8 @@ class RewardsPage extends ConsumerWidget {
_processRedemption(context, ref, gift); _processRedemption(context, ref, gift);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
), ),
child: const Text('Xác nhận'), child: const Text('Xác nhận'),
), ),

View File

@@ -22,6 +22,7 @@ class PointsBalanceCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// Note: This widget uses brand colors (gradient) that don't change with theme
final pointsState = ref.watch(loyaltyPointsProvider); final pointsState = ref.watch(loyaltyPointsProvider);
final numberFormat = NumberFormat('#,###', 'vi_VN'); final numberFormat = NumberFormat('#,###', 'vi_VN');
final dateFormat = DateFormat('dd/MM/yyyy', 'vi_VN'); final dateFormat = DateFormat('dd/MM/yyyy', 'vi_VN');

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/loyalty/domain/entities/gift_catalog.dart'; import 'package:worker/features/loyalty/domain/entities/gift_catalog.dart';
import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart'; import 'package:worker/features/loyalty/presentation/providers/loyalty_points_provider.dart';
@@ -27,6 +26,7 @@ class RewardCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final hasEnoughPoints = ref.watch(hasEnoughPointsProvider(gift.pointsCost)); final hasEnoughPoints = ref.watch(hasEnoughPointsProvider(gift.pointsCost));
final numberFormat = NumberFormat('#,###', 'vi_VN'); final numberFormat = NumberFormat('#,###', 'vi_VN');
@@ -39,7 +39,7 @@ class RewardCard extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Gift Image // Gift Image
_buildImage(), _buildImage(colorScheme),
// Gift Info // Gift Info
Expanded( Expanded(
@@ -51,10 +51,10 @@ class RewardCard extends ConsumerWidget {
// Gift Name // Gift Name
Text( Text(
gift.name, gift.name,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.3, height: 1.3,
), ),
maxLines: 2, maxLines: 2,
@@ -66,9 +66,9 @@ class RewardCard extends ConsumerWidget {
if (gift.description != null && gift.description!.isNotEmpty) if (gift.description != null && gift.description!.isNotEmpty)
Text( Text(
gift.description!, gift.description!,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
height: 1.2, height: 1.2,
), ),
maxLines: 1, maxLines: 1,
@@ -81,10 +81,10 @@ class RewardCard extends ConsumerWidget {
// Points Cost (at bottom) // Points Cost (at bottom)
Text( Text(
'${numberFormat.format(gift.pointsCost)} điểm', '${numberFormat.format(gift.pointsCost)} điểm',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -101,8 +101,8 @@ class RewardCard extends ConsumerWidget {
FontAwesomeIcons.gift, FontAwesomeIcons.gift,
size: 14, size: 14,
color: hasEnoughPoints && gift.isAvailable color: hasEnoughPoints && gift.isAvailable
? Colors.white ? colorScheme.onPrimary
: AppColors.grey500, : colorScheme.onSurfaceVariant,
), ),
label: Text( label: Text(
hasEnoughPoints && gift.isAvailable hasEnoughPoints && gift.isAvailable
@@ -115,11 +115,11 @@ class RewardCard extends ConsumerWidget {
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: hasEnoughPoints && gift.isAvailable backgroundColor: hasEnoughPoints && gift.isAvailable
? AppColors.primaryBlue ? colorScheme.primary
: AppColors.grey100, : colorScheme.surfaceContainerHighest,
foregroundColor: hasEnoughPoints && gift.isAvailable foregroundColor: hasEnoughPoints && gift.isAvailable
? Colors.white ? colorScheme.onPrimary
: AppColors.grey500, : colorScheme.onSurfaceVariant,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -141,7 +141,7 @@ class RewardCard extends ConsumerWidget {
} }
/// Build gift image /// Build gift image
Widget _buildImage() { Widget _buildImage(ColorScheme colorScheme) {
return SizedBox( return SizedBox(
height: 120, height: 120,
child: gift.imageUrl != null && gift.imageUrl!.isNotEmpty child: gift.imageUrl != null && gift.imageUrl!.isNotEmpty
@@ -149,26 +149,26 @@ class RewardCard extends ConsumerWidget {
imageUrl: gift.imageUrl!, imageUrl: gift.imageUrl!,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.gift, FontAwesomeIcons.gift,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
) )
: Container( : Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.gift, FontAwesomeIcons.gift,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
); );

View File

@@ -6,7 +6,6 @@ library;
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -17,7 +16,6 @@ import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/news/domain/entities/news_article.dart'; import 'package:worker/features/news/domain/entities/news_article.dart';
import 'package:worker/features/news/presentation/providers/news_provider.dart'; import 'package:worker/features/news/presentation/providers/news_provider.dart';
import 'package:worker/features/news/presentation/widgets/highlight_box.dart';
import 'package:worker/features/news/presentation/widgets/related_article_card.dart'; import 'package:worker/features/news/presentation/widgets/related_article_card.dart';
/// News Detail Page /// News Detail Page
@@ -49,49 +47,53 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final articleAsync = ref.watch(newsArticleByIdProvider(widget.articleId)); final articleAsync = ref.watch(newsArticleByIdProvider(widget.articleId));
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
appBar: _buildAppBar(context), appBar: _buildAppBar(context),
body: articleAsync.when( body: articleAsync.when(
data: (article) { data: (article) {
if (article == null) { if (article == null) {
return _buildNotFoundState(); return _buildNotFoundState(context);
} }
return _buildContent(context, article); return _buildContent(context, article);
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => _buildErrorState(error.toString()), error: (error, stack) => _buildErrorState(context, error.toString()),
), ),
); );
} }
/// Build AppBar /// Build AppBar
PreferredSizeWidget _buildAppBar(BuildContext context) { PreferredSizeWidget _buildAppBar(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return AppBar( return AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
title: Text( title: Text(
'Chi tiết bài viết', 'Chi tiết bài viết',
style: const TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
centerTitle: false, centerTitle: false,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
actions: [ actions: [
// Share button // Share button
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.shareNodes, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.shareNodes, color: colorScheme.onSurface, size: 20),
onPressed: _onShareTap, onPressed: _onShareTap,
), ),
// Bookmark button // Bookmark button
IconButton( IconButton(
icon: FaIcon( icon: FaIcon(
_isBookmarked ? FontAwesomeIcons.solidBookmark : FontAwesomeIcons.bookmark, _isBookmarked ? FontAwesomeIcons.solidBookmark : FontAwesomeIcons.bookmark,
color: _isBookmarked ? AppColors.warning : Colors.black, // Keep AppColors.warning for bookmarked state - semantic status color
color: _isBookmarked ? AppColors.warning : colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: _onBookmarkTap, onPressed: _onBookmarkTap,
@@ -103,6 +105,7 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
/// Build content /// Build content
Widget _buildContent(BuildContext context, NewsArticle article) { Widget _buildContent(BuildContext context, NewsArticle article) {
final colorScheme = Theme.of(context).colorScheme;
final relatedArticles = ref final relatedArticles = ref
.watch(filteredNewsArticlesProvider) .watch(filteredNewsArticlesProvider)
.value .value
@@ -122,16 +125,16 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
height: 250, height: 250,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center(child: CircularProgressIndicator()), child: const Center(child: CircularProgressIndicator()),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
height: 250, height: 250,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -143,17 +146,17 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Metadata // Metadata
_buildMetadata(article), _buildMetadata(context, article),
const SizedBox(height: 16), const SizedBox(height: 16),
// Title // Title
Text( Text(
article.title, article.title,
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
height: 1.3, height: 1.3,
), ),
), ),
@@ -171,29 +174,29 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
padding: HtmlPaddings.zero, padding: HtmlPaddings.zero,
fontSize: FontSize(16), fontSize: FontSize(16),
lineHeight: const LineHeight(1.7), lineHeight: const LineHeight(1.7),
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
"h2": Style( "h2": Style(
fontSize: FontSize(20), fontSize: FontSize(20),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
margin: Margins.only(top: 32, bottom: 16), margin: Margins.only(top: 32, bottom: 16),
), ),
"h3": Style( "h3": Style(
fontSize: FontSize(18), fontSize: FontSize(18),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
margin: Margins.only(top: 24, bottom: 12), margin: Margins.only(top: 24, bottom: 12),
), ),
"p": Style( "p": Style(
fontSize: FontSize(16), fontSize: FontSize(16),
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
lineHeight: const LineHeight(1.7), lineHeight: const LineHeight(1.7),
margin: Margins.only(bottom: 16), margin: Margins.only(bottom: 16),
), ),
"strong": Style( "strong": Style(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
"img": Style( "img": Style(
margin: Margins.symmetric(vertical: 16), margin: Margins.symmetric(vertical: 16),
@@ -206,14 +209,14 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
), ),
"li": Style( "li": Style(
fontSize: FontSize(16), fontSize: FontSize(16),
color: const Color(0xFF1E293B), color: colorScheme.onSurface,
lineHeight: const LineHeight(1.5), lineHeight: const LineHeight(1.5),
margin: Margins.only(bottom: 8), margin: Margins.only(bottom: 8),
), ),
"blockquote": Style( "blockquote": Style(
backgroundColor: const Color(0xFFF0F9FF), backgroundColor: colorScheme.primaryContainer,
border: const Border( border: Border(
left: BorderSide(color: AppColors.primaryBlue, width: 4), left: BorderSide(color: colorScheme.primary, width: 4),
), ),
padding: HtmlPaddings.all(16), padding: HtmlPaddings.all(16),
margin: Margins.symmetric(vertical: 24), margin: Margins.symmetric(vertical: 24),
@@ -235,18 +238,18 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
const SizedBox(height: 32), const SizedBox(height: 32),
// Tags Section // Tags Section
if (article.tags.isNotEmpty) _buildTagsSection(article.tags), if (article.tags.isNotEmpty) _buildTagsSection(context, article.tags),
const SizedBox(height: 32), const SizedBox(height: 32),
// Social Actions // Social Actions
_buildSocialActions(article), _buildSocialActions(context, article),
const SizedBox(height: 32), const SizedBox(height: 32),
// Related Articles // Related Articles
if (relatedArticles != null && relatedArticles.isNotEmpty) if (relatedArticles != null && relatedArticles.isNotEmpty)
_buildRelatedArticles(relatedArticles), _buildRelatedArticles(context, relatedArticles),
], ],
), ),
), ),
@@ -256,7 +259,9 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
} }
/// Build metadata /// Build metadata
Widget _buildMetadata(NewsArticle article) { Widget _buildMetadata(BuildContext context, NewsArticle article) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
spacing: 16, spacing: 16,
children: [ children: [
@@ -264,27 +269,28 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Text( child: Text(
article.category.displayName, article.category.displayName,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white, color: colorScheme.onPrimary,
), ),
), ),
), ),
// Date // Date
_buildMetaItem(FontAwesomeIcons.calendar, article.formattedDate), _buildMetaItem(context, FontAwesomeIcons.calendar, article.formattedDate),
// Reading time // Reading time
// _buildMetaItem(Icons.schedule, article.readingTimeText), // _buildMetaItem(context, Icons.schedule, article.readingTimeText),
// Views // Views
// _buildMetaItem( // _buildMetaItem(
// context,
// Icons.visibility, // Icons.visibility,
// '${article.formattedViewCount} lượt xem', // '${article.formattedViewCount} lượt xem',
// ), // ),
@@ -293,37 +299,41 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
} }
/// Build metadata item /// Build metadata item
Widget _buildMetaItem(IconData icon, String text) { Widget _buildMetaItem(BuildContext context, IconData icon, String text) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FaIcon(icon, size: 12, color: const Color(0xFF64748B)), FaIcon(icon, size: 12, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
text, text,
style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
); );
} }
/// Build tags section /// Build tags section
Widget _buildTagsSection(List<String> tags) { Widget _buildTagsSection(BuildContext context, List<String> tags) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.lg), borderRadius: BorderRadius.circular(AppRadius.lg),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thẻ liên quan', 'Thẻ liên quan',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -338,15 +348,15 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
vertical: 4, vertical: 4,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Text( child: Text(
tag, tag,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -359,84 +369,96 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
} }
/// Build social actions section /// Build social actions section
Widget _buildSocialActions(NewsArticle article) { Widget _buildSocialActions(BuildContext context, NewsArticle article) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
horizontal: BorderSide(color: Color(0xFFE2E8F0)), horizontal: BorderSide(color: colorScheme.outlineVariant),
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildActionButton( _buildActionButton(
context,
icon: _isLiked ? FontAwesomeIcons.solidHeart : FontAwesomeIcons.heart, icon: _isLiked ? FontAwesomeIcons.solidHeart : FontAwesomeIcons.heart,
onPressed: _onLikeTap, onPressed: _onLikeTap,
color: _isLiked ? Colors.red : null, color: _isLiked ? Colors.red : null,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildActionButton( _buildActionButton(
context,
icon: _isBookmarked ? FontAwesomeIcons.solidBookmark : FontAwesomeIcons.bookmark, icon: _isBookmarked ? FontAwesomeIcons.solidBookmark : FontAwesomeIcons.bookmark,
onPressed: _onBookmarkTap, onPressed: _onBookmarkTap,
// Keep AppColors.warning for bookmarked state - semantic status color
color: _isBookmarked ? AppColors.warning : null, color: _isBookmarked ? AppColors.warning : null,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildActionButton(icon: FontAwesomeIcons.shareNodes, onPressed: _onShareTap), _buildActionButton(context, icon: FontAwesomeIcons.shareNodes, onPressed: _onShareTap),
], ],
), ),
); );
} }
/// Build stat item /// Build stat item
Widget _buildStatItem(IconData icon, String text) { Widget _buildStatItem(BuildContext context, IconData icon, String text) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, size: 14, color: const Color(0xFF64748B)), Icon(icon, size: 14, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
text, text,
style: const TextStyle(fontSize: 14, color: Color(0xFF64748B)), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
], ],
); );
} }
/// Build action button /// Build action button
Widget _buildActionButton({ Widget _buildActionButton(
BuildContext context, {
required IconData icon, required IconData icon,
required VoidCallback onPressed, required VoidCallback onPressed,
Color? color, Color? color,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return OutlinedButton( return OutlinedButton(
onPressed: onPressed, onPressed: onPressed,
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
side: BorderSide(color: color ?? const Color(0xFFE2E8F0), width: 2), side: BorderSide(color: color ?? colorScheme.outlineVariant, width: 2),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
), ),
child: FaIcon(icon, size: 18, color: color ?? const Color(0xFF64748B)), child: FaIcon(icon, size: 18, color: color ?? colorScheme.onSurfaceVariant),
); );
} }
/// Build related articles section /// Build related articles section
Widget _buildRelatedArticles(List<NewsArticle> articles) { Widget _buildRelatedArticles(BuildContext context, List<NewsArticle> articles) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.lg), borderRadius: BorderRadius.circular(AppRadius.lg),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Bài viết liên quan', 'Bài viết liên quan',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -455,25 +477,27 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
} }
/// Build not found state /// Build not found state
Widget _buildNotFoundState() { Widget _buildNotFoundState(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon(FontAwesomeIcons.fileLines, size: 64, color: AppColors.grey500), FaIcon(FontAwesomeIcons.fileLines, size: 64, color: colorScheme.onSurfaceVariant),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không tìm thấy bài viết', 'Không tìm thấy bài viết',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Bài viết này không tồn tại hoặc đã bị xóa', 'Bài viết này không tồn tại hoặc đã bị xóa',
style: TextStyle(fontSize: 14, color: Color(0xFF64748B)), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -487,19 +511,22 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
} }
/// Build error state /// Build error state
Widget _buildErrorState(String error) { Widget _buildErrorState(BuildContext context, String error) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// Keep AppColors.danger for error state - semantic status color
FaIcon(FontAwesomeIcons.circleExclamation, size: 64, color: AppColors.danger), FaIcon(FontAwesomeIcons.circleExclamation, size: 64, color: AppColors.danger),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không thể tải bài viết', 'Không thể tải bài viết',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -507,7 +534,7 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text( child: Text(
error, error,
style: const TextStyle(fontSize: 14, color: Color(0xFF64748B)), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),

View File

@@ -38,6 +38,8 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Watch providers // Watch providers
final featuredArticleAsync = ref.watch(featuredArticleProvider); final featuredArticleAsync = ref.watch(featuredArticleProvider);
final newsArticlesAsync = ref.watch(newsArticlesProvider); final newsArticlesAsync = ref.watch(newsArticlesProvider);
@@ -52,7 +54,7 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
}); });
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
appBar: _buildAppBar(context), appBar: _buildAppBar(context),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@@ -107,23 +109,23 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
child: SizedBox(height: AppSpacing.xl), child: SizedBox(height: AppSpacing.xl),
), ),
// Latest News Section // Latest News Section
const SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Row( child: Row(
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.newspaper, FontAwesomeIcons.newspaper,
size: 16, size: 16,
color: AppColors.primaryBlue, color: Theme.of(context).colorScheme.primary,
), ),
SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Mới nhất', 'Mới nhất',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
], ],
@@ -173,13 +175,15 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
/// Build standard AppBar /// Build standard AppBar
PreferredSizeWidget _buildAppBar(BuildContext context) { PreferredSizeWidget _buildAppBar(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return AppBar( return AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
title: const Text( title: Text(
'Tin tức & chuyên môn', 'Tin tức & chuyên môn',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -191,24 +195,26 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
/// Build empty state /// Build empty state
Widget _buildEmptyState() { Widget _buildEmptyState() {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon(FontAwesomeIcons.newspaper, size: 64, color: AppColors.grey500), FaIcon(FontAwesomeIcons.newspaper, size: 64, color: colorScheme.onSurfaceVariant),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Chưa có tin tức', 'Chưa có tin tức',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Hãy quay lại sau để xem các bài viết mới', 'Hãy quay lại sau để xem các bài viết mới',
style: TextStyle(fontSize: 14, color: Color(0xFF64748B)), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -218,18 +224,21 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
/// Build error state /// Build error state
Widget _buildErrorState(String error) { Widget _buildErrorState(String error) {
final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// Keep AppColors.danger for error state - semantic status color
FaIcon(FontAwesomeIcons.circleExclamation, size: 64, color: AppColors.danger), FaIcon(FontAwesomeIcons.circleExclamation, size: 64, color: AppColors.danger),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không thể tải tin tức', 'Không thể tải tin tức',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -237,7 +246,7 @@ class _NewsListPageState extends ConsumerState<NewsListPage> {
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text( child: Text(
error, error,
style: const TextStyle(fontSize: 14, color: Color(0xFF64748B)), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),

View File

@@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/news/domain/entities/blog_category.dart'; import 'package:worker/features/news/domain/entities/blog_category.dart';
import 'package:worker/features/news/presentation/providers/news_provider.dart'; import 'package:worker/features/news/presentation/providers/news_provider.dart';
@@ -42,14 +41,14 @@ class CategoryFilterChips extends ConsumerWidget {
final categoriesAsync = ref.watch(blogCategoriesProvider); final categoriesAsync = ref.watch(blogCategoriesProvider);
return categoriesAsync.when( return categoriesAsync.when(
data: (categories) => _buildCategoryChips(categories), data: (categories) => _buildCategoryChips(context, categories),
loading: () => _buildLoadingState(), loading: () => _buildLoadingState(context),
error: (error, stack) => _buildErrorState(error, ref), error: (error, stack) => _buildErrorState(context, error, ref),
); );
} }
/// Build category chips with data /// Build category chips with data
Widget _buildCategoryChips(List<BlogCategory> categories) { Widget _buildCategoryChips(BuildContext context, List<BlogCategory> categories) {
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -57,6 +56,7 @@ class CategoryFilterChips extends ConsumerWidget {
children: [ children: [
// "Tất cả" chip // "Tất cả" chip
_buildCategoryChip( _buildCategoryChip(
context,
label: 'Tất cả', label: 'Tất cả',
isSelected: selectedCategoryName == null, isSelected: selectedCategoryName == null,
onTap: () => onCategorySelected(null), onTap: () => onCategorySelected(null),
@@ -69,6 +69,7 @@ class CategoryFilterChips extends ConsumerWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(right: AppSpacing.sm), padding: const EdgeInsets.only(right: AppSpacing.sm),
child: _buildCategoryChip( child: _buildCategoryChip(
context,
label: category.title, label: category.title,
isSelected: selectedCategoryName == category.name, isSelected: selectedCategoryName == category.name,
onTap: () => onCategorySelected(category.name), onTap: () => onCategorySelected(category.name),
@@ -81,7 +82,9 @@ class CategoryFilterChips extends ConsumerWidget {
} }
/// Build loading state with shimmer placeholders /// Build loading state with shimmer placeholders
Widget _buildLoadingState() { Widget _buildLoadingState(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -93,7 +96,7 @@ class CategoryFilterChips extends ConsumerWidget {
width: 80, width: 80,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
), ),
@@ -104,7 +107,9 @@ class CategoryFilterChips extends ConsumerWidget {
} }
/// Build error state with retry /// Build error state with retry
Widget _buildErrorState(Object error, WidgetRef ref) { Widget _buildErrorState(BuildContext context, Object error, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -116,25 +121,25 @@ class CategoryFilterChips extends ConsumerWidget {
vertical: AppSpacing.sm, vertical: AppSpacing.sm,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FaIcon(FontAwesomeIcons.circleExclamation, size: 16, color: AppColors.grey500), FaIcon(FontAwesomeIcons.circleExclamation, size: 16, color: colorScheme.onSurfaceVariant),
const SizedBox(width: AppSpacing.xs), const SizedBox(width: AppSpacing.xs),
Text( Text(
'Lỗi tải danh mục', 'Lỗi tải danh mục',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(width: AppSpacing.xs), const SizedBox(width: AppSpacing.xs),
GestureDetector( GestureDetector(
onTap: () => ref.refresh(blogCategoriesProvider), onTap: () => ref.refresh(blogCategoriesProvider),
child: FaIcon(FontAwesomeIcons.arrowsRotate, size: 14, color: AppColors.primaryBlue), child: FaIcon(FontAwesomeIcons.arrowsRotate, size: 14, color: colorScheme.primary),
), ),
], ],
), ),
@@ -145,11 +150,14 @@ class CategoryFilterChips extends ConsumerWidget {
} }
/// Build individual category chip /// Build individual category chip
Widget _buildCategoryChip({ Widget _buildCategoryChip(
BuildContext context, {
required String label, required String label,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
@@ -158,7 +166,7 @@ class CategoryFilterChips extends ConsumerWidget {
vertical: AppSpacing.sm, vertical: AppSpacing.sm,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: Text( child: Text(
@@ -166,7 +174,7 @@ class CategoryFilterChips extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : AppColors.grey500, color: isSelected ? colorScheme.onPrimary : colorScheme.onSurfaceVariant,
), ),
), ),
), ),

View File

@@ -8,7 +8,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/news/domain/entities/news_article.dart'; import 'package:worker/features/news/domain/entities/news_article.dart';
/// Featured News Card /// Featured News Card
@@ -32,14 +31,16 @@ class FeaturedNewsCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl), borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.08), color: Colors.black.withValues(alpha: 0.08),
@@ -63,16 +64,16 @@ class FeaturedNewsCard extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
height: 200, height: 200,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center(child: CircularProgressIndicator()), child: const Center(child: CircularProgressIndicator()),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
height: 200, height: 200,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -87,10 +88,10 @@ class FeaturedNewsCard extends StatelessWidget {
// Title // Title
Text( Text(
article.title, article.title,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
height: 1.4, height: 1.4,
), ),
), ),
@@ -100,9 +101,9 @@ class FeaturedNewsCard extends StatelessWidget {
// Excerpt // Excerpt
Text( Text(
article.excerpt, article.excerpt,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
height: 1.5, height: 1.5,
), ),
maxLines: 3, maxLines: 3,
@@ -123,18 +124,21 @@ class FeaturedNewsCard extends StatelessWidget {
children: [ children: [
// Date // Date
_buildMetaItem( _buildMetaItem(
context,
icon: FontAwesomeIcons.calendar, icon: FontAwesomeIcons.calendar,
text: article.formattedDate, text: article.formattedDate,
), ),
// // Views // // Views
// _buildMetaItem( // _buildMetaItem(
// context,
// icon: Icons.visibility, // icon: Icons.visibility,
// text: '${article.formattedViewCount} lượt xem', // text: '${article.formattedViewCount} lượt xem',
// ), // ),
// //
// // Reading time // // Reading time
// _buildMetaItem( // _buildMetaItem(
// context,
// icon: Icons.schedule, // icon: Icons.schedule,
// text: article.readingTimeText, // text: article.readingTimeText,
// ), // ),
@@ -149,15 +153,15 @@ class FeaturedNewsCard extends StatelessWidget {
vertical: 4, vertical: 4,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Text( child: Text(
article.category.displayName, article.category.displayName,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.white, color: colorScheme.onPrimary,
), ),
), ),
), ),
@@ -173,15 +177,17 @@ class FeaturedNewsCard extends StatelessWidget {
} }
/// Build metadata item /// Build metadata item
Widget _buildMetaItem({required IconData icon, required String text}) { Widget _buildMetaItem(BuildContext context, {required IconData icon, required String text}) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FaIcon(icon, size: 12, color: const Color(0xFF64748B)), FaIcon(icon, size: 12, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
text, text,
style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
); );

View File

@@ -8,7 +8,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/news/domain/entities/news_article.dart'; import 'package:worker/features/news/domain/entities/news_article.dart';
/// News Card /// News Card
@@ -31,15 +30,17 @@ class NewsCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md), margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -55,7 +56,7 @@ class NewsCard extends StatelessWidget {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 80, width: 80,
height: 80, height: 80,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: SizedBox( child: SizedBox(
width: 20, width: 20,
@@ -67,11 +68,11 @@ class NewsCard extends StatelessWidget {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 80, width: 80,
height: 80, height: 80,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 24, size: 24,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -87,10 +88,10 @@ class NewsCard extends StatelessWidget {
// Title (max 2 lines) // Title (max 2 lines)
Text( Text(
article.title, article.title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
height: 1.3, height: 1.3,
), ),
maxLines: 2, maxLines: 2,
@@ -102,9 +103,9 @@ class NewsCard extends StatelessWidget {
// Excerpt (max 2 lines) // Excerpt (max 2 lines)
Text( Text(
article.excerpt, article.excerpt,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
height: 1.4, height: 1.4,
), ),
maxLines: 2, maxLines: 2,
@@ -117,17 +118,17 @@ class NewsCard extends StatelessWidget {
Row( Row(
children: [ children: [
// Date // Date
const FaIcon( FaIcon(
FontAwesomeIcons.calendar, FontAwesomeIcons.calendar,
size: 12, size: 12,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
article.formattedDate, article.formattedDate,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
), ),
), ),
@@ -137,14 +138,14 @@ class NewsCard extends StatelessWidget {
// Icon( // Icon(
// Icons.visibility, // Icons.visibility,
// size: 12, // size: 12,
// color: const Color(0xFF64748B), // color: colorScheme.onSurfaceVariant,
// ), // ),
// const SizedBox(width: 4), // const SizedBox(width: 4),
// Text( // Text(
// '${article.formattedViewCount} lượt xem', // '${article.formattedViewCount} lượt xem',
// style: const TextStyle( // style: TextStyle(
// fontSize: 12, // fontSize: 12,
// color: Color(0xFF64748B), // color: colorScheme.onSurfaceVariant,
// ), // ),
// ), // ),
], ],

View File

@@ -8,7 +8,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/news/domain/entities/news_article.dart'; import 'package:worker/features/news/domain/entities/news_article.dart';
/// Related Article Card /// Related Article Card
@@ -31,15 +30,17 @@ class RelatedArticleCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md), margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg), borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
), ),
child: Row( child: Row(
children: [ children: [
@@ -54,7 +55,7 @@ class RelatedArticleCard extends StatelessWidget {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 60, width: 60,
height: 60, height: 60,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: SizedBox( child: SizedBox(
width: 16, width: 16,
@@ -66,11 +67,11 @@ class RelatedArticleCard extends StatelessWidget {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 60, width: 60,
height: 60, height: 60,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 20, size: 20,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -86,10 +87,10 @@ class RelatedArticleCard extends StatelessWidget {
// Title (max 2 lines) // Title (max 2 lines)
Text( Text(
article.title, article.title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1E293B), color: colorScheme.onSurface,
height: 1.3, height: 1.3,
), ),
maxLines: 2, maxLines: 2,
@@ -101,9 +102,9 @@ class RelatedArticleCard extends StatelessWidget {
// Metadata // Metadata
Text( Text(
'${article.formattedDate}${article.formattedViewCount} lượt xem', '${article.formattedDate}${article.formattedViewCount} lượt xem',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Color(0xFF64748B), color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],

File diff suppressed because it is too large Load Diff

View File

@@ -33,11 +33,12 @@ class OrderSuccessPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final now = DateTime.now(); final now = DateTime.now();
final dateFormat = DateFormat('dd/MM/yyyy HH:mm'); final dateFormat = DateFormat('dd/MM/yyyy HH:mm');
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
body: SafeArea( body: SafeArea(
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -67,10 +68,10 @@ class OrderSuccessPage extends StatelessWidget {
isNegotiation isNegotiation
? 'Gửi yêu cầu thành công!' ? 'Gửi yêu cầu thành công!'
: 'Tạo đơn hàng thành công!', : 'Tạo đơn hàng thành công!',
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -82,9 +83,9 @@ class OrderSuccessPage extends StatelessWidget {
isNegotiation isNegotiation
? 'Chúng tôi sẽ liên hệ với bạn để đàm phán giá trong vòng 24 giờ.' ? 'Chúng tôi sẽ liên hệ với bạn để đàm phán giá trong vòng 24 giờ.'
: 'Cảm ơn bạn đã đặt hàng. Chúng tôi sẽ liên hệ xác nhận trong vòng 24 giờ.', : 'Cảm ơn bạn đã đặt hàng. Chúng tôi sẽ liên hệ xác nhận trong vòng 24 giờ.',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -96,7 +97,7 @@ class OrderSuccessPage extends StatelessWidget {
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF4F6F8), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
), ),
child: Column( child: Column(
@@ -104,20 +105,20 @@ class OrderSuccessPage extends StatelessWidget {
// Order Number // Order Number
Column( Column(
children: [ children: [
const Text( Text(
'Mã đơn hàng', 'Mã đơn hàng',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
orderNumber, orderNumber,
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -127,6 +128,7 @@ class OrderSuccessPage extends StatelessWidget {
// Order Date // Order Date
_buildInfoRow( _buildInfoRow(
context,
'Ngày đặt', 'Ngày đặt',
dateFormat.format(now), dateFormat.format(now),
), ),
@@ -136,12 +138,13 @@ class OrderSuccessPage extends StatelessWidget {
// Total Amount // Total Amount
if (total != null) if (total != null)
_buildInfoRow( _buildInfoRow(
context,
'Tổng tiền', 'Tổng tiền',
_formatCurrency(total!), _formatCurrency(total!),
valueStyle: const TextStyle( valueStyle: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
@@ -150,6 +153,7 @@ class OrderSuccessPage extends StatelessWidget {
// Payment Method // Payment Method
if (paymentMethod != null && !isNegotiation) if (paymentMethod != null && !isNegotiation)
_buildInfoRow( _buildInfoRow(
context,
'Phương thức thanh toán', 'Phương thức thanh toán',
paymentMethod!, paymentMethod!,
), ),
@@ -159,14 +163,13 @@ class OrderSuccessPage extends StatelessWidget {
// Status // Status
_buildInfoRow( _buildInfoRow(
context,
'Trạng thái', 'Trạng thái',
isNegotiation ? 'Chờ đàm phán' : 'Chờ xác nhận', isNegotiation ? 'Chờ đàm phán' : 'Chờ xác nhận',
valueStyle: TextStyle( valueStyle: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: isNegotiation color: AppColors.warning,
? AppColors.warning
: AppColors.warning,
), ),
), ),
], ],
@@ -195,8 +198,8 @@ class OrderSuccessPage extends StatelessWidget {
), ),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -225,9 +228,9 @@ class OrderSuccessPage extends StatelessWidget {
), ),
), ),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
side: BorderSide( side: BorderSide(
color: AppColors.grey100, color: colorScheme.outlineVariant,
width: 1.5, width: 1.5,
), ),
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
@@ -247,26 +250,29 @@ class OrderSuccessPage extends StatelessWidget {
/// Build info row /// Build info row
Widget _buildInfoRow( Widget _buildInfoRow(
BuildContext context,
String label, String label,
String value, { String value, {
TextStyle? valueStyle, TextStyle? valueStyle,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
Text( Text(
value, value,
style: valueStyle ?? style: valueStyle ??
const TextStyle( TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
], ],

View File

@@ -50,24 +50,25 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final ordersAsync = ref.watch(ordersProvider); final ordersAsync = ref.watch(ordersProvider);
final selectedStatus = ref.watch(selectedOrderStatusProvider); final selectedStatus = ref.watch(selectedOrderStatusProvider);
final searchQuery = ref.watch(orderSearchQueryProvider); final searchQuery = ref.watch(orderSearchQueryProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Danh sách đơn hàng', 'Danh sách đơn hàng',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: const [SizedBox(width: AppSpacing.sm)], actions: const [SizedBox(width: AppSpacing.sm)],
), ),
@@ -82,9 +83,9 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
pinned: true, pinned: true,
delegate: _SearchBarDelegate( delegate: _SearchBarDelegate(
child: Container( child: Container(
color: const Color(0xFFF4F6F8), color: colorScheme.surfaceContainerLowest,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: _buildSearchBar(), child: _buildSearchBar(colorScheme),
), ),
), ),
), ),
@@ -94,8 +95,8 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
pinned: true, pinned: true,
delegate: _FilterPillsDelegate( delegate: _FilterPillsDelegate(
child: Container( child: Container(
color: const Color(0xFFF4F6F8), color: colorScheme.surfaceContainerLowest,
child: _buildFilterPills(selectedStatus), child: _buildFilterPills(selectedStatus, colorScheme),
), ),
), ),
), ),
@@ -134,7 +135,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
}); });
if (filtered.isEmpty) { if (filtered.isEmpty) {
return _buildEmptyState(); return _buildEmptyState(colorScheme);
} }
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
@@ -149,7 +150,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
); );
}, },
loading: () => _buildLoadingState(), loading: () => _buildLoadingState(),
error: (error, stack) => _buildErrorState(error), error: (error, stack) => _buildErrorState(error, colorScheme),
), ),
), ),
], ],
@@ -159,14 +160,14 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
} }
/// Build search bar /// Build search bar
Widget _buildSearchBar() { Widget _buildSearchBar(ColorScheme colorScheme) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -176,20 +177,20 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mã đơn hàng', hintText: 'Mã đơn hàng',
hintStyle: const TextStyle(color: AppColors.grey500, fontSize: 14), hintStyle: TextStyle(color: colorScheme.onSurfaceVariant, fontSize: 14),
prefixIcon: const Padding( prefixIcon: Padding(
padding: EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: FaIcon( child: FaIcon(
FontAwesomeIcons.magnifyingGlass, FontAwesomeIcons.magnifyingGlass,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 18, size: 18,
), ),
), ),
suffixIcon: _searchController.text.isNotEmpty suffixIcon: _searchController.text.isNotEmpty
? IconButton( ? IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.xmark, FontAwesomeIcons.xmark,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 18, size: 18,
), ),
onPressed: () { onPressed: () {
@@ -211,7 +212,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
} }
/// Build filter pills (dynamically from cached status list) /// Build filter pills (dynamically from cached status list)
Widget _buildFilterPills(String? selectedStatus) { Widget _buildFilterPills(String? selectedStatus, ColorScheme colorScheme) {
final statusListAsync = ref.watch(orderStatusListProvider); final statusListAsync = ref.watch(orderStatusListProvider);
return SizedBox( return SizedBox(
@@ -229,6 +230,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
onTap: () { onTap: () {
ref.read(selectedOrderStatusProvider.notifier).clearSelection(); ref.read(selectedOrderStatusProvider.notifier).clearSelection();
}, },
colorScheme: colorScheme,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -244,6 +246,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
.read(selectedOrderStatusProvider.notifier) .read(selectedOrderStatusProvider.notifier)
.selectStatus(status.label); .selectStatus(status.label);
}, },
colorScheme: colorScheme,
), ),
); );
}), }),
@@ -260,6 +263,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
label: 'Tất cả', label: 'Tất cả',
isSelected: true, isSelected: true,
onTap: () {}, onTap: () {},
colorScheme: colorScheme,
), ),
], ],
); );
@@ -274,6 +278,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
label: 'Tất cả', label: 'Tất cả',
isSelected: true, isSelected: true,
onTap: () {}, onTap: () {},
colorScheme: colorScheme,
), ),
], ],
); );
@@ -287,13 +292,14 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
required String label, required String label,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
required ColorScheme colorScheme,
}) { }) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Center( child: Center(
@@ -302,7 +308,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected ? Colors.white : AppColors.grey900, color: isSelected ? colorScheme.onPrimary : colorScheme.onSurface,
), ),
), ),
), ),
@@ -311,7 +317,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
} }
/// Build empty state /// Build empty state
Widget _buildEmptyState() { Widget _buildEmptyState(ColorScheme colorScheme) {
return SliverFillRemaining( return SliverFillRemaining(
child: Center( child: Center(
child: Column( child: Column(
@@ -320,21 +326,21 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
FaIcon( FaIcon(
FontAwesomeIcons.receipt, FontAwesomeIcons.receipt,
size: 80, size: 80,
color: AppColors.grey500.withValues(alpha: 0.5), color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không có đơn hàng nào', 'Không có đơn hàng nào',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Thử tìm kiếm với từ khóa khác', 'Thử tìm kiếm với từ khóa khác',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -350,7 +356,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
} }
/// Build error state /// Build error state
Widget _buildErrorState(Object error) { Widget _buildErrorState(Object error, ColorScheme colorScheme) {
return SliverFillRemaining( return SliverFillRemaining(
child: Center( child: Center(
child: Column( child: Column(
@@ -362,18 +368,18 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
color: AppColors.danger.withValues(alpha: 0.7), color: AppColors.danger.withValues(alpha: 0.7),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Có lỗi xảy ra', 'Có lỗi xảy ra',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],

View File

@@ -30,26 +30,27 @@ class PaymentDetailPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final invoicesAsync = ref.watch(invoicesProvider); final invoicesAsync = ref.watch(invoicesProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Chi tiết Hóa đơn', 'Chi tiết Hóa đơn',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.shareNodes, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.shareNodes, color: colorScheme.onSurface, size: 20),
onPressed: () { onPressed: () {
// TODO: Implement share functionality // TODO: Implement share functionality
ScaffoldMessenger.of( ScaffoldMessenger.of(
@@ -75,6 +76,7 @@ class PaymentDetailPage extends ConsumerWidget {
children: [ children: [
// Invoice Header Card // Invoice Header Card
_buildInvoiceHeader( _buildInvoiceHeader(
context,
invoice.invoiceNumber, invoice.invoiceNumber,
invoice.orderId, invoice.orderId,
invoice.issueDate, invoice.issueDate,
@@ -87,6 +89,7 @@ class PaymentDetailPage extends ConsumerWidget {
// Dates and Customer Info Card // Dates and Customer Info Card
_buildCustomerInfo( _buildCustomerInfo(
context,
invoice.issueDate, invoice.issueDate,
invoice.dueDate, invoice.dueDate,
invoice.isOverdue, invoice.isOverdue,
@@ -94,18 +97,19 @@ class PaymentDetailPage extends ConsumerWidget {
// Product List Card // Product List Card
_buildProductList(), _buildProductList(context),
// Payment History Card // Payment History Card
_buildPaymentHistory( _buildPaymentHistory(
context,
invoice.amountPaid, invoice.amountPaid,
invoice.issueDate invoice.issueDate
), ),
// Download Section Card // Download Section Card
_buildDownloadSection(invoice.invoiceNumber), _buildDownloadSection(context, invoice.invoiceNumber),
// Support Button // Support Button
@@ -126,11 +130,11 @@ class PaymentDetailPage extends ConsumerWidget {
vertical: 16, vertical: 16,
horizontal: 16, horizontal: 16,
), ),
side: const BorderSide( side: BorderSide(
color: AppColors.grey100, color: colorScheme.outlineVariant,
width: 2, width: 2,
), ),
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@@ -172,10 +176,10 @@ class PaymentDetailPage extends ConsumerWidget {
(invoice.status == InvoiceStatus.paid || (invoice.status == InvoiceStatus.paid ||
invoice.isPaid) invoice.isPaid)
? AppColors.success ? AppColors.success
: AppColors.primaryBlue, : colorScheme.primary,
disabledBackgroundColor: AppColors.success, disabledBackgroundColor: AppColors.success,
foregroundColor: Colors.white, foregroundColor: colorScheme.surface,
disabledForegroundColor: Colors.white, disabledForegroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -201,9 +205,10 @@ class PaymentDetailPage extends ConsumerWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Không tìm thấy hóa đơn', 'Không tìm thấy hóa đơn',
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -220,6 +225,7 @@ class PaymentDetailPage extends ConsumerWidget {
/// Build invoice header section /// Build invoice header section
Widget _buildInvoiceHeader( Widget _buildInvoiceHeader(
BuildContext context,
String invoiceNumber, String invoiceNumber,
String? orderId, String? orderId,
DateTime issueDate, DateTime issueDate,
@@ -228,6 +234,8 @@ class PaymentDetailPage extends ConsumerWidget {
double amountPaid, double amountPaid,
double amountRemaining, double amountRemaining,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -240,19 +248,19 @@ class PaymentDetailPage extends ConsumerWidget {
children: [ children: [
Text( Text(
'#$invoiceNumber', '#$invoiceNumber',
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (orderId != null) if (orderId != null)
Text( Text(
'Đơn hàng: #$orderId | Ngày đặt: ${_formatDate(issueDate)}', 'Đơn hàng: #$orderId | Ngày đặt: ${_formatDate(issueDate)}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -269,17 +277,19 @@ class PaymentDetailPage extends ConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
children: [ children: [
_buildSummaryRow( _buildSummaryRow(
context,
'Tổng tiền hóa đơn:', 'Tổng tiền hóa đơn:',
totalAmount.toVNCurrency, totalAmount.toVNCurrency,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSummaryRow( _buildSummaryRow(
context,
'Đã thanh toán:', 'Đã thanh toán:',
amountPaid.toVNCurrency, amountPaid.toVNCurrency,
), ),
@@ -288,6 +298,7 @@ class PaymentDetailPage extends ConsumerWidget {
child: Divider(height: 2, thickness: 2), child: Divider(height: 2, thickness: 2),
), ),
_buildSummaryRow( _buildSummaryRow(
context,
'Còn lại:', 'Còn lại:',
amountRemaining.toVNCurrency, amountRemaining.toVNCurrency,
isHighlighted: true, isHighlighted: true,
@@ -306,10 +317,13 @@ class PaymentDetailPage extends ConsumerWidget {
/// Build customer info and dates section /// Build customer info and dates section
Widget _buildCustomerInfo( Widget _buildCustomerInfo(
BuildContext context,
DateTime issueDate, DateTime issueDate,
DateTime dueDate, DateTime dueDate,
bool isOverdue, bool isOverdue,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -324,20 +338,20 @@ class PaymentDetailPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ngày đặt hàng', 'Ngày đặt hàng',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
_formatDate(issueDate), _formatDate(issueDate),
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -347,11 +361,11 @@ class PaymentDetailPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Hạn thanh toán', 'Hạn thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -362,7 +376,7 @@ class PaymentDetailPage extends ConsumerWidget {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isOverdue color: isOverdue
? AppColors.danger ? AppColors.danger
: AppColors.grey900, : colorScheme.onSurface,
), ),
), ),
], ],
@@ -377,28 +391,28 @@ class PaymentDetailPage extends ConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thông tin khách hàng', 'Thông tin khách hàng',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Công ty TNHH Xây Dựng Minh An\n' 'Công ty TNHH Xây Dựng Minh An\n'
'Địa chỉ: 123 Nguyễn Văn Linh, Quận 7, TP.HCM\n' 'Địa chỉ: 123 Nguyễn Văn Linh, Quận 7, TP.HCM\n'
'SĐT: 0901234567 | Email: contact@minhan.com', 'SĐT: 0901234567 | Email: contact@minhan.com',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
height: 1.5, height: 1.5,
), ),
), ),
@@ -412,7 +426,9 @@ class PaymentDetailPage extends ConsumerWidget {
} }
/// Build product list section /// Build product list section
Widget _buildProductList() { Widget _buildProductList(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Mock product data - in real app, this would come from order items // Mock product data - in real app, this would come from order items
final products = [ final products = [
{ {
@@ -445,14 +461,14 @@ class PaymentDetailPage extends ConsumerWidget {
children: [ children: [
Row( Row(
children: [ children: [
FaIcon(FontAwesomeIcons.box, color: AppColors.primaryBlue, size: 18), FaIcon(FontAwesomeIcons.box, color: colorScheme.primary, size: 18),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Danh sách sản phẩm', 'Danh sách sản phẩm',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -465,7 +481,7 @@ class PaymentDetailPage extends ConsumerWidget {
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -476,12 +492,12 @@ class PaymentDetailPage extends ConsumerWidget {
width: 60, width: 60,
height: 60, height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 22, size: 22,
), ),
), ),
@@ -493,18 +509,18 @@ class PaymentDetailPage extends ConsumerWidget {
children: [ children: [
Text( Text(
product['name']!, product['name']!,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'SKU: ${product['sku']}', 'SKU: ${product['sku']}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -514,17 +530,17 @@ class PaymentDetailPage extends ConsumerWidget {
children: [ children: [
Text( Text(
'Số lượng: ${product['quantity']}', 'Số lượng: ${product['quantity']}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
Text( Text(
product['price']!, product['price']!,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -545,9 +561,11 @@ class PaymentDetailPage extends ConsumerWidget {
/// Build payment history section /// Build payment history section
Widget _buildPaymentHistory( Widget _buildPaymentHistory(
BuildContext context,
double amountPaid, double amountPaid,
DateTime paymentDate, DateTime paymentDate,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
final hasHistory = amountPaid > 0; final hasHistory = amountPaid > 0;
return Card( return Card(
@@ -558,16 +576,16 @@ class PaymentDetailPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
FaIcon(FontAwesomeIcons.clockRotateLeft, color: AppColors.primaryBlue, size: 18), FaIcon(FontAwesomeIcons.clockRotateLeft, color: colorScheme.primary, size: 18),
SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Lịch sử thanh toán', 'Lịch sử thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -578,7 +596,7 @@ class PaymentDetailPage extends ConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -603,28 +621,28 @@ class PaymentDetailPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thanh toán lần 1', 'Thanh toán lần 1',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Chuyển khoản | Ref: TK20241020001', 'Chuyển khoản | Ref: TK20241020001',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'${_formatDate(paymentDate)} - 14:30', '${_formatDate(paymentDate)} - 14:30',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -650,23 +668,23 @@ class PaymentDetailPage extends ConsumerWidget {
FaIcon( FaIcon(
FontAwesomeIcons.receipt, FontAwesomeIcons.receipt,
size: 48, size: 48,
color: AppColors.grey100, color: colorScheme.outlineVariant,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'Chưa có lịch sử thanh toán', 'Chưa có lịch sử thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Hóa đơn này chưa được thanh toán', 'Hóa đơn này chưa được thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -680,7 +698,9 @@ class PaymentDetailPage extends ConsumerWidget {
} }
/// Build download section /// Build download section
Widget _buildDownloadSection(String invoiceNumber) { Widget _buildDownloadSection(BuildContext context, String invoiceNumber) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -689,22 +709,23 @@ class PaymentDetailPage extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
FaIcon(FontAwesomeIcons.download, color: AppColors.primaryBlue, size: 18), FaIcon(FontAwesomeIcons.download, color: colorScheme.primary, size: 18),
SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Tải chứng từ', 'Tải chứng từ',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDownloadButton( _buildDownloadButton(
context,
icon: FontAwesomeIcons.filePdf, icon: FontAwesomeIcons.filePdf,
label: 'Hóa đơn PDF', label: 'Hóa đơn PDF',
onTap: () { onTap: () {
@@ -713,6 +734,7 @@ class PaymentDetailPage extends ConsumerWidget {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildDownloadButton( _buildDownloadButton(
context,
icon: FontAwesomeIcons.receipt, icon: FontAwesomeIcons.receipt,
label: 'Phiếu thu PDF', label: 'Phiếu thu PDF',
onTap: () { onTap: () {
@@ -726,33 +748,36 @@ class PaymentDetailPage extends ConsumerWidget {
} }
/// Build download button /// Build download button
Widget _buildDownloadButton({ Widget _buildDownloadButton(
BuildContext context, {
required IconData icon, required IconData icon,
required String label, required String label,
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon(icon, size: 18, color: AppColors.grey500), FaIcon(icon, size: 18, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 8), const SizedBox(width: 8),
Flexible( Flexible(
child: Text( child: Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -788,11 +813,14 @@ class PaymentDetailPage extends ConsumerWidget {
/// Build summary row /// Build summary row
Widget _buildSummaryRow( Widget _buildSummaryRow(
BuildContext context,
String label, String label,
String value, { String value, {
bool isHighlighted = false, bool isHighlighted = false,
Color? valueColor, Color? valueColor,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -801,7 +829,7 @@ class PaymentDetailPage extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: isHighlighted ? 18 : 16, fontSize: isHighlighted ? 18 : 16,
fontWeight: isHighlighted ? FontWeight.w700 : FontWeight.w400, fontWeight: isHighlighted ? FontWeight.w700 : FontWeight.w400,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
Text( Text(
@@ -809,7 +837,7 @@ class PaymentDetailPage extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: isHighlighted ? 18 : 16, fontSize: isHighlighted ? 18 : 16,
fontWeight: isHighlighted ? FontWeight.w700 : FontWeight.w600, fontWeight: isHighlighted ? FontWeight.w700 : FontWeight.w600,
color: valueColor ?? AppColors.grey900, color: valueColor ?? colorScheme.onSurface,
), ),
), ),
], ],
@@ -822,7 +850,7 @@ class PaymentDetailPage extends ConsumerWidget {
case InvoiceStatus.draft: case InvoiceStatus.draft:
return AppColors.grey500; return AppColors.grey500;
case InvoiceStatus.issued: case InvoiceStatus.issued:
return const Color(0xFFF59E0B); return AppColors.warning;
case InvoiceStatus.partiallyPaid: case InvoiceStatus.partiallyPaid:
return AppColors.info; return AppColors.info;
case InvoiceStatus.paid: case InvoiceStatus.paid:
@@ -832,7 +860,7 @@ class PaymentDetailPage extends ConsumerWidget {
case InvoiceStatus.cancelled: case InvoiceStatus.cancelled:
return AppColors.grey500; return AppColors.grey500;
case InvoiceStatus.refunded: case InvoiceStatus.refunded:
return const Color(0xFFF97316); return AppColors.warning;
} }
} }

View File

@@ -38,6 +38,8 @@ class PaymentQrPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// QR code data state // QR code data state
final qrCodeData = useState<Map<String, dynamic>?>(null); final qrCodeData = useState<Map<String, dynamic>?>(null);
final isLoadingQr = useState<bool>(true); final isLoadingQr = useState<bool>(true);
@@ -93,18 +95,18 @@ class PaymentQrPage extends HookConsumerWidget {
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Thanh toán', 'Thanh toán',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -112,7 +114,7 @@ class PaymentQrPage extends HookConsumerWidget {
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.circleInfo, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.circleInfo, color: colorScheme.onSurface, size: 20),
onPressed: () => _showInfoDialog(context), onPressed: () => _showInfoDialog(context),
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
@@ -125,6 +127,7 @@ class PaymentQrPage extends HookConsumerWidget {
// QR Code Card // QR Code Card
_buildQrCodeCard( _buildQrCodeCard(
context,
orderId, orderId,
qrCodeData.value?['qr_code'] as String?, qrCodeData.value?['qr_code'] as String?,
isLoadingQr.value, isLoadingQr.value,
@@ -169,7 +172,7 @@ class PaymentQrPage extends HookConsumerWidget {
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Timer // Timer
_buildTimer(timerDisplay, remainingSeconds.value), _buildTimer(context, timerDisplay, remainingSeconds.value),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
], ],
@@ -180,19 +183,22 @@ class PaymentQrPage extends HookConsumerWidget {
/// Build QR code card /// Build QR code card
Widget _buildQrCodeCard( Widget _buildQrCodeCard(
BuildContext context,
String orderId, String orderId,
String? qrCodeData, String? qrCodeData,
bool isLoading, bool isLoading,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -200,12 +206,12 @@ class PaymentQrPage extends HookConsumerWidget {
), ),
child: Column( child: Column(
children: [ children: [
const Text( Text(
'Quét mã QR để thanh toán', 'Quét mã QR để thanh toán',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
@@ -214,9 +220,9 @@ class PaymentQrPage extends HookConsumerWidget {
height: 220, height: 220,
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFE2E8F0)), border: Border.all(color: colorScheme.outlineVariant),
), ),
child: isLoading child: isLoading
? const Center( ? const Center(
@@ -227,26 +233,26 @@ class PaymentQrPage extends HookConsumerWidget {
data: qrCodeData, data: qrCodeData,
version: QrVersions.auto, version: QrVersions.auto,
size: 200.0, size: 200.0,
backgroundColor: Colors.white, backgroundColor: colorScheme.surface,
errorCorrectionLevel: QrErrorCorrectLevel.M, errorCorrectionLevel: QrErrorCorrectLevel.M,
) )
: const Column( : Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon(FontAwesomeIcons.qrcode, size: 80, color: AppColors.grey500), FaIcon(FontAwesomeIcons.qrcode, size: 80, color: colorScheme.onSurfaceVariant),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Không thể tải mã QR', 'Không thể tải mã QR',
style: TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
const Text( Text(
'Quét mã QR bằng ứng dụng ngân hàng để thanh toán nhanh chóng', 'Quét mã QR bằng ứng dụng ngân hàng để thanh toán nhanh chóng',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -262,15 +268,17 @@ class PaymentQrPage extends HookConsumerWidget {
String? accountName, String? accountName,
bool isLoading, bool isLoading,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -279,12 +287,12 @@ class PaymentQrPage extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thông tin chuyển khoản', 'Thông tin chuyển khoản',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
@@ -329,25 +337,25 @@ class PaymentQrPage extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFE3F2FD), color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFF90CAF9)), border: Border.all(color: colorScheme.primary.withValues(alpha: 0.3)),
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.lightbulb, FontAwesomeIcons.lightbulb,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 18, size: 18,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Color(0xFF1565C0), color: colorScheme.primary,
height: 1.4, height: 1.4,
), ),
children: [ children: [
@@ -377,28 +385,30 @@ class PaymentQrPage extends HookConsumerWidget {
required String label, required String label,
required String value, required String value,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
children: [ children: [
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
label, label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
), ),
Expanded( Expanded(
flex: 3, flex: 3,
child: Text( child: Text(
value, value,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
), ),
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.copy, size: 18, color: AppColors.primaryBlue), icon: FaIcon(FontAwesomeIcons.copy, size: 18, color: colorScheme.primary),
onPressed: () => _copyToClipboard(context, value), onPressed: () => _copyToClipboard(context, value),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
@@ -413,15 +423,17 @@ class PaymentQrPage extends HookConsumerWidget {
String? imagePath, String? imagePath,
VoidCallback onSelectImage, VoidCallback onSelectImage,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: colorScheme.onSurface.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -430,12 +442,12 @@ class PaymentQrPage extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ảnh hóa đơn', 'Ảnh hóa đơn',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
@@ -448,10 +460,10 @@ class PaymentQrPage extends HookConsumerWidget {
width: double.infinity, width: double.infinity,
height: 200, height: 200,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF4F6F8), color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all( border: Border.all(
color: const Color(0xFFE2E8F0), color: colorScheme.outlineVariant,
width: 2, width: 2,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
@@ -472,7 +484,7 @@ class PaymentQrPage extends HookConsumerWidget {
right: 8, right: 8,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.6), color: colorScheme.onSurface.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -481,17 +493,17 @@ class PaymentQrPage extends HookConsumerWidget {
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.pen, FontAwesomeIcons.pen,
color: Colors.white, color: colorScheme.surface,
size: 12, size: 12,
), ),
SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
'Đổi ảnh', 'Đổi ảnh',
style: TextStyle( style: TextStyle(
color: Colors.white, color: colorScheme.surface,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@@ -510,30 +522,30 @@ class PaymentQrPage extends HookConsumerWidget {
width: 60, width: 60,
height: 60, height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.1), color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
), ),
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 24, size: 24,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'Chạm để chọn ảnh hóa đơn', 'Chạm để chọn ảnh hóa đơn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Hỗ trợ: JPG, PNG', 'Hỗ trợ: JPG, PNG',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -553,6 +565,8 @@ class PaymentQrPage extends HookConsumerWidget {
bool hasImage, bool hasImage,
VoidCallback onUpload, VoidCallback onUpload,
) { ) {
final colorScheme = Theme.of(context).colorScheme;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Column( child: Column(
@@ -563,12 +577,12 @@ class PaymentQrPage extends HookConsumerWidget {
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: (isUploading || !hasImage) ? null : onUpload, onPressed: (isUploading || !hasImage) ? null : onUpload,
icon: isUploading icon: isUploading
? const SizedBox( ? SizedBox(
width: 18, width: 18,
height: 18, height: 18,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white), valueColor: AlwaysStoppedAnimation<Color>(colorScheme.surface),
), ),
) )
: const FaIcon(FontAwesomeIcons.camera, size: 18), : const FaIcon(FontAwesomeIcons.camera, size: 18),
@@ -577,15 +591,15 @@ class PaymentQrPage extends HookConsumerWidget {
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.button), borderRadius: BorderRadius.circular(AppRadius.button),
), ),
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.surfaceContainerLowest,
disabledForegroundColor: AppColors.grey500, disabledForegroundColor: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -602,9 +616,9 @@ class PaymentQrPage extends HookConsumerWidget {
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
), ),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
side: const BorderSide( side: BorderSide(
color: AppColors.grey100, color: colorScheme.outlineVariant,
width: 1.5, width: 1.5,
), ),
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
@@ -620,22 +634,24 @@ class PaymentQrPage extends HookConsumerWidget {
} }
/// Build countdown timer /// Build countdown timer
Widget _buildTimer(String timerDisplay, int remainingSeconds) { Widget _buildTimer(BuildContext context, String timerDisplay, int remainingSeconds) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card), borderRadius: BorderRadius.circular(AppRadius.card),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const FaIcon(FontAwesomeIcons.clock, size: 16, color: AppColors.grey500), FaIcon(FontAwesomeIcons.clock, size: 16, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Thời gian thanh toán: ', 'Thời gian thanh toán: ',
style: TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
Text( Text(
timerDisplay, timerDisplay,
@@ -644,7 +660,7 @@ class PaymentQrPage extends HookConsumerWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: remainingSeconds < 300 color: remainingSeconds < 300
? AppColors.danger ? AppColors.danger
: AppColors.grey900, : colorScheme.onSurface,
), ),
), ),
], ],
@@ -715,6 +731,8 @@ class PaymentQrPage extends HookConsumerWidget {
BuildContext context, BuildContext context,
ValueNotifier<String?> selectedImagePath, ValueNotifier<String?> selectedImagePath,
) async { ) async {
final colorScheme = Theme.of(context).colorScheme;
// Show bottom sheet to select camera or gallery // Show bottom sheet to select camera or gallery
final ImageSource? source = await showModalBottomSheet<ImageSource>( final ImageSource? source = await showModalBottomSheet<ImageSource>(
context: context, context: context,
@@ -726,27 +744,27 @@ class PaymentQrPage extends HookConsumerWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
'Chọn ảnh hóa đơn', 'Chọn ảnh hóa đơn',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF212121), color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
ListTile( ListTile(
leading: const FaIcon( leading: FaIcon(
FontAwesomeIcons.camera, FontAwesomeIcons.camera,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
title: const Text('Chụp ảnh'), title: const Text('Chụp ảnh'),
onTap: () => Navigator.of(context).pop(ImageSource.camera), onTap: () => Navigator.of(context).pop(ImageSource.camera),
), ),
ListTile( ListTile(
leading: const FaIcon( leading: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
title: const Text('Chọn từ thư viện'), title: const Text('Chọn từ thư viện'),
onTap: () => Navigator.of(context).pop(ImageSource.gallery), onTap: () => Navigator.of(context).pop(ImageSource.gallery),
@@ -811,6 +829,8 @@ class PaymentQrPage extends HookConsumerWidget {
ValueNotifier<String?> selectedImagePath, ValueNotifier<String?> selectedImagePath,
ValueNotifier<bool> isUploadingBill, ValueNotifier<bool> isUploadingBill,
) async { ) async {
final colorScheme = Theme.of(context).colorScheme;
if (selectedImagePath.value == null) { if (selectedImagePath.value == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
@@ -839,7 +859,7 @@ class PaymentQrPage extends HookConsumerWidget {
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
), ),
child: const Text('Upload'), child: const Text('Upload'),
), ),

View File

@@ -26,7 +26,7 @@ class PaymentsPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final paymentsAsync = ref.watch(paymentsProvider); final paymentsAsync = ref.watch(paymentsProvider);
final colorScheme = context.colorScheme; final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: colorScheme.surfaceContainerLowest, backgroundColor: colorScheme.surfaceContainerLowest,
@@ -79,7 +79,7 @@ class PaymentsPage extends ConsumerWidget {
/// Build error state /// Build error state
Widget _buildErrorState(BuildContext context, WidgetRef ref, Object error) { Widget _buildErrorState(BuildContext context, WidgetRef ref, Object error) {
final colorScheme = context.colorScheme; final colorScheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -116,7 +116,7 @@ class PaymentsPage extends ConsumerWidget {
/// Build empty state /// Build empty state
Widget _buildEmptyState(BuildContext context, WidgetRef ref) { Widget _buildEmptyState(BuildContext context, WidgetRef ref) {
final colorScheme = context.colorScheme; final colorScheme = Theme.of(context).colorScheme;
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
await ref.read(paymentsProvider.notifier).refresh(); await ref.read(paymentsProvider.notifier).refresh();
@@ -164,7 +164,7 @@ class PaymentsPage extends ConsumerWidget {
/// Show transaction detail modal /// Show transaction detail modal
void _showTransactionDetail(BuildContext context, Payment payment) { void _showTransactionDetail(BuildContext context, Payment payment) {
final colorScheme = context.colorScheme; final colorScheme = Theme.of(context).colorScheme;
final currencyFormatter = NumberFormat.currency( final currencyFormatter = NumberFormat.currency(
locale: 'vi_VN', locale: 'vi_VN',
symbol: 'đ', symbol: 'đ',
@@ -302,8 +302,8 @@ class _TransactionCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final dateFormatter = DateFormat('dd/MM/yyyy'); final dateFormatter = DateFormat('dd/MM/yyyy');
final colorScheme = context.colorScheme;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -416,7 +416,7 @@ class _DetailRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = context.colorScheme; final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@@ -32,6 +32,8 @@ class InvoiceCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final currencyFormatter = NumberFormat.currency( final currencyFormatter = NumberFormat.currency(
locale: 'vi_VN', locale: 'vi_VN',
symbol: 'đ', symbol: 'đ',
@@ -61,10 +63,10 @@ class InvoiceCard extends StatelessWidget {
// Invoice number // Invoice number
Text( Text(
'Mã hoá đơn #${invoice.invoiceNumber}', 'Mã hoá đơn #${invoice.invoiceNumber}',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -72,9 +74,9 @@ class InvoiceCard extends StatelessWidget {
if (invoice.orderId != null) if (invoice.orderId != null)
Text( Text(
'Đơn hàng: #${invoice.orderId}', 'Đơn hàng: #${invoice.orderId}',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -88,10 +90,18 @@ class InvoiceCard extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
// Invoice dates // Invoice dates
_buildDetailRow('Ngày hóa đơn:', _formatDate(invoice.issueDate)), _buildDetailRow(
context,
'Ngày hóa đơn:',
_formatDate(invoice.issueDate),
),
const SizedBox(height: 6), const SizedBox(height: 6),
_buildDetailRow('Hạn thanh toán:', _formatDate(invoice.dueDate)), _buildDetailRow(
context,
'Hạn thanh toán:',
_formatDate(invoice.dueDate),
),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -99,13 +109,14 @@ class InvoiceCard extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
spacing: 2, spacing: 2,
children: [ children: [
_buildPaymentRow( _buildPaymentRow(
context,
'Tổng tiền:', 'Tổng tiền:',
currencyFormatter.format(invoice.totalAmount), currencyFormatter.format(invoice.totalAmount),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -113,6 +124,7 @@ class InvoiceCard extends StatelessWidget {
if (invoice.amountPaid > 0) ...[ if (invoice.amountPaid > 0) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
_buildPaymentRow( _buildPaymentRow(
context,
'Đã thanh toán:', 'Đã thanh toán:',
currencyFormatter.format(invoice.amountPaid), currencyFormatter.format(invoice.amountPaid),
valueColor: AppColors.success, valueColor: AppColors.success,
@@ -122,6 +134,7 @@ class InvoiceCard extends StatelessWidget {
if (invoice.amountRemaining > 0) ...[ if (invoice.amountRemaining > 0) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
_buildPaymentRow( _buildPaymentRow(
context,
'Còn lại:', 'Còn lại:',
currencyFormatter.format(invoice.amountRemaining), currencyFormatter.format(invoice.amountRemaining),
valueColor: invoice.isOverdue valueColor: invoice.isOverdue
@@ -137,7 +150,7 @@ class InvoiceCard extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
// Action button // Action button
_buildActionButton(), _buildActionButton(context),
], ],
), ),
), ),
@@ -146,7 +159,9 @@ class InvoiceCard extends StatelessWidget {
} }
/// Build detail row /// Build detail row
Widget _buildDetailRow(String label, String value) { Widget _buildDetailRow(BuildContext context, String label, String value) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -154,14 +169,14 @@ class InvoiceCard extends StatelessWidget {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
Text( Text(
value, value,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -171,24 +186,27 @@ class InvoiceCard extends StatelessWidget {
/// Build payment summary row /// Build payment summary row
Widget _buildPaymentRow( Widget _buildPaymentRow(
BuildContext context,
String label, String label,
String value, { String value, {
Color? valueColor, Color? valueColor,
FontWeight? fontWeight, FontWeight? fontWeight,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
label, label,
style: const TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
), ),
Text( Text(
value, value,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: fontWeight ?? FontWeight.w400, fontWeight: fontWeight ?? FontWeight.w400,
color: valueColor ?? AppColors.grey900, color: valueColor ?? colorScheme.onSurface,
), ),
), ),
], ],
@@ -219,10 +237,11 @@ class InvoiceCard extends StatelessWidget {
} }
/// Build action button /// Build action button
Widget _buildActionButton() { Widget _buildActionButton(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isPaid = invoice.status == InvoiceStatus.paid || invoice.isPaid; final isPaid = invoice.status == InvoiceStatus.paid || invoice.isPaid;
final buttonText = isPaid ? 'Đã hoàn tất' : 'Thanh toán'; final buttonText = isPaid ? 'Đã hoàn tất' : 'Thanh toán';
final buttonColor = isPaid ? AppColors.success : AppColors.primaryBlue; final buttonColor = isPaid ? AppColors.success : colorScheme.primary;
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
@@ -230,9 +249,9 @@ class InvoiceCard extends StatelessWidget {
onPressed: isPaid ? null : onPaymentTap, onPressed: isPaid ? null : onPaymentTap,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: buttonColor, backgroundColor: buttonColor,
disabledBackgroundColor: AppColors.grey100, disabledBackgroundColor: colorScheme.surfaceContainerHighest,
foregroundColor: Colors.white, foregroundColor: colorScheme.surface,
disabledForegroundColor: AppColors.grey500, disabledForegroundColor: colorScheme.onSurfaceVariant,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0, elevation: 0,

View File

@@ -6,7 +6,6 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:worker/core/enums/status_color.dart'; import 'package:worker/core/enums/status_color.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/orders/domain/entities/order.dart'; import 'package:worker/features/orders/domain/entities/order.dart';
/// Order Card Widget /// Order Card Widget
@@ -23,6 +22,7 @@ class OrderCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final currencyFormatter = NumberFormat.currency( final currencyFormatter = NumberFormat.currency(
locale: 'vi_VN', locale: 'vi_VN',
symbol: 'đ', symbol: 'đ',
@@ -49,20 +49,20 @@ class OrderCard extends StatelessWidget {
// Order number // Order number
Text( Text(
'#${order.name}', '#${order.name}',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
// Amount // Amount
Text( Text(
currencyFormatter.format(order.grandTotal), currencyFormatter.format(order.grandTotal),
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -71,13 +71,21 @@ class OrderCard extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
// Order details // Order details
_buildDetailRow('Ngày đặt:', _formatDate(order.transactionDate)), _buildDetailRow(
context,
'Ngày đặt:',
_formatDate(order.transactionDate),
),
const SizedBox(height: 6), const SizedBox(height: 6),
_buildDetailRow('Ngày giao:', _formatDate(order.deliveryDate)), _buildDetailRow(
context,
'Ngày giao:',
_formatDate(order.deliveryDate),
),
const SizedBox(height: 6), const SizedBox(height: 6),
_buildDetailRow('Địa chỉ:', order.address), _buildDetailRow(context, 'Địa chỉ:', order.address),
const SizedBox(height: 12), const SizedBox(height: 12),
// Status badge // Status badge
@@ -90,19 +98,27 @@ class OrderCard extends StatelessWidget {
} }
/// Build detail row /// Build detail row
Widget _buildDetailRow(String label, String value) { Widget _buildDetailRow(BuildContext context, String label, String value) {
final colorScheme = Theme.of(context).colorScheme;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
label, label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(
fontSize: 14,
color: colorScheme.onSurfaceVariant,
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
value, value,
style: const TextStyle(fontSize: 14, color: AppColors.grey900), style: TextStyle(
fontSize: 14,
color: colorScheme.onSurface,
),
), ),
), ),
], ],

View File

@@ -38,24 +38,26 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.grey50, backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Chính sách giá', 'Chính sách giá',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.info_outline, color: Colors.black), icon: Icon(Icons.info_outline, color: colorScheme.onSurface),
onPressed: _showInfoDialog, onPressed: _showInfoDialog,
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
@@ -69,16 +71,16 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
child: Container( child: Container(
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: TabBar( child: TabBar(
controller: _tabController, controller: _tabController,
labelColor: AppColors.white, labelColor: colorScheme.surface,
unselectedLabelColor: AppColors.grey900, unselectedLabelColor: colorScheme.onSurface,
indicatorSize: TabBarIndicatorSize.tab, indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration( indicator: BoxDecoration(
color: AppColors.primaryBlue, color: colorScheme.primary,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
@@ -115,24 +117,25 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
} }
Widget _buildDocumentList(DocumentCategory category) { Widget _buildDocumentList(DocumentCategory category) {
final colorScheme = Theme.of(context).colorScheme;
final documentsAsync = ref.watch(filteredPriceDocumentsProvider(category)); final documentsAsync = ref.watch(filteredPriceDocumentsProvider(category));
return documentsAsync.when( return documentsAsync.when(
data: (documents) { data: (documents) {
if (documents.isEmpty) { if (documents.isEmpty) {
return const Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.description_outlined, Icons.description_outlined,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
Text( Text(
'Chưa có tài liệu', 'Chưa có tài liệu',
style: TextStyle(fontSize: 16, color: AppColors.grey500), style: TextStyle(fontSize: 16, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -166,11 +169,11 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.error_outline, size: 64, color: AppColors.danger), const Icon(Icons.error_outline, size: 64, color: AppColors.danger),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
Text( Text(
'Không thể tải tài liệu', 'Không thể tải tài liệu',
style: TextStyle(fontSize: 16, color: AppColors.grey500), style: TextStyle(fontSize: 16, color: colorScheme.onSurfaceVariant),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
ElevatedButton( ElevatedButton(

View File

@@ -16,11 +16,13 @@ class DocumentCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.surfaceContainerHighest),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: Colors.black.withValues(alpha: 0.05),
@@ -47,15 +49,15 @@ class DocumentCard extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
_buildIcon(), _buildIcon(colorScheme),
const SizedBox(width: AppSpacing.md), const SizedBox(width: AppSpacing.md),
Expanded(child: _buildInfo()), Expanded(child: _buildInfo(colorScheme)),
], ],
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: _buildDownloadButton(), child: _buildDownloadButton(colorScheme),
), ),
], ],
); );
@@ -63,11 +65,11 @@ class DocumentCard extends StatelessWidget {
return Row( return Row(
children: [ children: [
_buildIcon(), _buildIcon(colorScheme),
const SizedBox(width: AppSpacing.md), const SizedBox(width: AppSpacing.md),
Expanded(child: _buildInfo()), Expanded(child: _buildInfo(colorScheme)),
const SizedBox(width: AppSpacing.md), const SizedBox(width: AppSpacing.md),
_buildDownloadButton(), _buildDownloadButton(colorScheme),
], ],
); );
}, },
@@ -78,7 +80,7 @@ class DocumentCard extends StatelessWidget {
); );
} }
Widget _buildIcon() { Widget _buildIcon(ColorScheme colorScheme) {
final iconData = document.isPdf ? Icons.picture_as_pdf : Icons.table_chart; final iconData = document.isPdf ? Icons.picture_as_pdf : Icons.table_chart;
final iconColor = document.isPdf final iconColor = document.isPdf
? Colors.red.shade600 ? Colors.red.shade600
@@ -88,46 +90,46 @@ class DocumentCard extends StatelessWidget {
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Icon(iconData, size: 28, color: iconColor), child: Icon(iconData, size: 28, color: iconColor),
); );
} }
Widget _buildInfo() { Widget _buildInfo(ColorScheme colorScheme) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
document.title, document.title,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
const Icon( Icon(
Icons.calendar_today, Icons.calendar_today,
size: 13, size: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
document.formattedDateWithPrefix, document.formattedDateWithPrefix,
style: const TextStyle(fontSize: 13, color: AppColors.grey500), style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
document.title, document.title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
height: 1.4, height: 1.4,
), ),
maxLines: 2, maxLines: 2,
@@ -137,9 +139,9 @@ class DocumentCard extends StatelessWidget {
); );
} }
Widget _buildDownloadButton() { Widget _buildDownloadButton(ColorScheme colorScheme) {
final isDownloaded = document.filePath != null; final isDownloaded = document.filePath != null;
final buttonColor = isDownloaded ? AppColors.success : AppColors.primaryBlue; final buttonColor = isDownloaded ? AppColors.success : colorScheme.primary;
final buttonIcon = isDownloaded ? Icons.folder_open : Icons.download; final buttonIcon = isDownloaded ? Icons.folder_open : Icons.download;
final buttonText = isDownloaded ? 'Mở file' : 'Tải về'; final buttonText = isDownloaded ? 'Mở file' : 'Tải về';

View File

@@ -79,14 +79,16 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
} }
void _shareProduct(Product product) { void _shareProduct(Product product) {
final colorScheme = Theme.of(context).colorScheme;
// Show share options // Show share options
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: (context) => Container( builder: (context) => Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppRadius.xl), top: Radius.circular(AppRadius.xl),
), ),
), ),
@@ -99,7 +101,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@@ -114,7 +116,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
// Share options // Share options
ListTile( ListTile(
leading: const FaIcon(FontAwesomeIcons.message, color: AppColors.primaryBlue, size: 20), leading: FaIcon(FontAwesomeIcons.message, color: colorScheme.primary, size: 20),
title: const Text('Chia sẻ qua tin nhắn'), title: const Text('Chia sẻ qua tin nhắn'),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@@ -127,7 +129,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
}, },
), ),
ListTile( ListTile(
leading: const FaIcon(FontAwesomeIcons.shareNodes, color: AppColors.primaryBlue, size: 20), leading: FaIcon(FontAwesomeIcons.shareNodes, color: colorScheme.primary, size: 20),
title: const Text('Chia sẻ khác'), title: const Text('Chia sẻ khác'),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@@ -135,7 +137,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
}, },
), ),
ListTile( ListTile(
leading: const FaIcon(FontAwesomeIcons.copy, color: AppColors.primaryBlue, size: 20), leading: FaIcon(FontAwesomeIcons.copy, color: colorScheme.primary, size: 20),
title: const Text('Sao chép link'), title: const Text('Sao chép link'),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@@ -171,6 +173,8 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Use productDetailProvider with productId parameter // Use productDetailProvider with productId parameter
final productAsync = ref.watch(productDetailProvider(productId: widget.productId)); final productAsync = ref.watch(productDetailProvider(productId: widget.productId));
@@ -178,24 +182,24 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
final isFavorite = ref.watch(isFavoriteProvider(widget.productId)); final isFavorite = ref.watch(isFavoriteProvider(widget.productId));
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Chi tiết sản phẩm', 'Chi tiết sản phẩm',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
// Share button // Share button
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.shareNodes, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.shareNodes, color: colorScheme.onSurface, size: 20),
onPressed: () { onPressed: () {
productAsync.whenData((product) { productAsync.whenData((product) {
_shareProduct(product); _shareProduct(product);
@@ -206,7 +210,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
IconButton( IconButton(
icon: Icon( icon: Icon(
isFavorite ? FontAwesomeIcons.solidHeart : FontAwesomeIcons.heart, isFavorite ? FontAwesomeIcons.solidHeart : FontAwesomeIcons.heart,
color: isFavorite ? AppColors.danger : Colors.black, color: isFavorite ? AppColors.danger : colorScheme.onSurface,
), ),
onPressed: _toggleFavorite, onPressed: _toggleFavorite,
), ),
@@ -258,8 +262,8 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
], ],
); );
}, },
loading: () => const Center( loading: () => Center(
child: CircularProgressIndicator(color: AppColors.primaryBlue), child: CircularProgressIndicator(color: colorScheme.primary),
), ),
error: (error, stack) => Center( error: (error, stack) => Center(
child: Padding( child: Padding(
@@ -281,9 +285,9 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -296,8 +300,8 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18), icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18),
label: const Text('Thử lại'), label: const Text('Thử lại'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
), ),
), ),
], ],

View File

@@ -32,6 +32,7 @@ class ProductsPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
final productsAsync = ref.watch(productsProvider); final productsAsync = ref.watch(productsProvider);
@@ -39,17 +40,17 @@ class ProductsPage extends ConsumerWidget {
ref.watch(productFilterOptionsProvider); ref.watch(productFilterOptionsProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), // Match HTML background backgroundColor: colorScheme.surfaceContainerLowest,
endDrawer: const ProductFilterDrawer(), endDrawer: const ProductFilterDrawer(),
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text('Sản phẩm', style: TextStyle(color: Colors.black)), title: Text('Sản phẩm', style: TextStyle(color: colorScheme.onSurface)),
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
centerTitle: false, centerTitle: false,
actions: [ actions: [
// Cart Icon with Badge (extracted to Consumer to prevent full page rebuild) // Cart Icon with Badge (extracted to Consumer to prevent full page rebuild)
@@ -60,9 +61,9 @@ class ProductsPage extends ConsumerWidget {
icon: Badge( icon: Badge(
label: Text('$cartItemCount'), label: Text('$cartItemCount'),
backgroundColor: AppColors.danger, backgroundColor: AppColors.danger,
textColor: AppColors.white, textColor: colorScheme.surface,
isLabelVisible: cartItemCount > 0, isLabelVisible: cartItemCount > 0,
child: const FaIcon(FontAwesomeIcons.cartShopping, color: Colors.black, size: 20), child: FaIcon(FontAwesomeIcons.cartShopping, color: colorScheme.onSurface, size: 20),
), ),
onPressed: () => context.push(RouteNames.cart), onPressed: () => context.push(RouteNames.cart),
); );
@@ -88,7 +89,7 @@ class ProductsPage extends ConsumerWidget {
height: InputFieldSpecs.height, height: InputFieldSpecs.height,
width: InputFieldSpecs.height, width: InputFieldSpecs.height,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
), ),
child: IconButton( child: IconButton(
@@ -97,7 +98,7 @@ class ProductsPage extends ConsumerWidget {
Scaffold.of(scaffoldContext).openEndDrawer(); Scaffold.of(scaffoldContext).openEndDrawer();
}, },
icon: const FaIcon(FontAwesomeIcons.sliders, size: 18), icon: const FaIcon(FontAwesomeIcons.sliders, size: 18),
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -112,7 +113,7 @@ class ProductsPage extends ConsumerWidget {
child: productsAsync.when( child: productsAsync.when(
data: (products) { data: (products) {
if (products.isEmpty) { if (products.isEmpty) {
return _buildEmptyState(context, l10n); return _buildEmptyState(context, l10n, colorScheme);
} }
final productsNotifier = ref.read(productsProvider.notifier); final productsNotifier = ref.read(productsProvider.notifier);
@@ -159,8 +160,8 @@ class ProductsPage extends ConsumerWidget {
}, },
); );
}, },
loading: () => _buildLoadingState(), loading: () => _buildLoadingState(colorScheme),
error: (error, stack) => _buildErrorState(context, l10n, error, ref), error: (error, stack) => _buildErrorState(context, l10n, error, ref, colorScheme),
), ),
), ),
], ],
@@ -171,31 +172,31 @@ class ProductsPage extends ConsumerWidget {
} }
/// Build empty state /// Build empty state
Widget _buildEmptyState(BuildContext context, AppLocalizations l10n) { Widget _buildEmptyState(BuildContext context, AppLocalizations l10n, ColorScheme colorScheme) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon(FontAwesomeIcons.boxOpen, size: 80.0, color: AppColors.grey500.withAlpha(128)), FaIcon(FontAwesomeIcons.boxOpen, size: 80.0, color: colorScheme.onSurfaceVariant.withAlpha(128)),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
Text( Text(
l10n.noProductsFound, l10n.noProductsFound,
style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.w500, color: AppColors.grey900), style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w500, color: colorScheme.onSurface),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
Text(l10n.noResults, style: const TextStyle(fontSize: 14.0, color: AppColors.grey500)), Text(l10n.noResults, style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant)),
], ],
), ),
); );
} }
/// Build loading state /// Build loading state
Widget _buildLoadingState() { Widget _buildLoadingState(ColorScheme colorScheme) {
return const Center(child: CircularProgressIndicator(color: AppColors.primaryBlue)); return Center(child: CircularProgressIndicator(color: colorScheme.primary));
} }
/// Build error state /// Build error state
Widget _buildErrorState(BuildContext context, AppLocalizations l10n, Object error, WidgetRef ref) { Widget _buildErrorState(BuildContext context, AppLocalizations l10n, Object error, WidgetRef ref, ColorScheme colorScheme) {
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(AppSpacing.xl), padding: const EdgeInsets.all(AppSpacing.xl),
@@ -206,12 +207,12 @@ class ProductsPage extends ConsumerWidget {
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
Text( Text(
l10n.error, l10n.error,
style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600, color: AppColors.grey900), style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600, color: colorScheme.onSurface),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(fontSize: 14.0, color: AppColors.grey500), style: TextStyle(fontSize: 14.0, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: AppSpacing.lg), const SizedBox(height: AppSpacing.lg),
@@ -222,8 +223,8 @@ class ProductsPage extends ConsumerWidget {
icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18), icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18),
label: Text(l10n.tryAgain), label: Text(l10n.tryAgain),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md), padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md),
), ),
), ),

View File

@@ -102,14 +102,16 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
); );
if (mounted) { if (mounted) {
final colorScheme = Theme.of(context).colorScheme;
// Show success message // Show success message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( SnackBar(
content: Row( content: Row(
children: [ children: [
Icon(FontAwesomeIcons.circleCheck, color: AppColors.white), Icon(FontAwesomeIcons.circleCheck, color: colorScheme.onPrimary),
SizedBox(width: 12), const SizedBox(width: 12),
Expanded( const Expanded(
child: Text('Đánh giá của bạn đã được gửi thành công!'), child: Text('Đánh giá của bạn đã được gửi thành công!'),
), ),
], ],
@@ -128,15 +130,16 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
final colorScheme = Theme.of(context).colorScheme;
// Show error message // Show error message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Row( content: Row(
children: [ children: [
const Icon( Icon(
FontAwesomeIcons.triangleExclamation, FontAwesomeIcons.triangleExclamation,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
@@ -154,26 +157,27 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final productAsync = ref.watch(productDetailProvider(productId: widget.productId)); final productAsync = ref.watch(productDetailProvider(productId: widget.productId));
return Scaffold( return Scaffold(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
// Standard AppBar // Standard AppBar
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
centerTitle: false, centerTitle: false,
leading: IconButton( leading: IconButton(
icon: const Icon( icon: Icon(
Icons.arrow_back, Icons.arrow_back,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Viết đánh giá sản phẩm', 'Viết đánh giá sản phẩm',
style: TextStyle( style: TextStyle(
color: AppColors.grey900, color: colorScheme.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -187,17 +191,17 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Icon(
FontAwesomeIcons.circleExclamation, FontAwesomeIcons.circleExclamation,
size: 48, size: 48,
color: AppColors.danger, color: colorScheme.error,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Không thể tải thông tin sản phẩm', 'Không thể tải thông tin sản phẩm',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -213,6 +217,8 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
} }
Widget _buildForm(Product product) { Widget _buildForm(Product product) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView( return SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@@ -223,8 +229,8 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all(color: const Color(0xFFe0e0e0), width: 2), border: Border.all(color: colorScheme.outlineVariant, width: 2),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Row( child: Row(
@@ -242,7 +248,7 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 80, width: 80,
height: 80, height: 80,
color: AppColors.grey100, color: colorScheme.surfaceContainerLowest,
child: const Center( child: const Center(
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
@@ -250,10 +256,10 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 80, width: 80,
height: 80, height: 80,
color: AppColors.grey100, color: colorScheme.surfaceContainerLowest,
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -268,10 +274,10 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
children: [ children: [
Text( Text(
product.name, product.name,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -279,9 +285,9 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
'Mã: ${product.productId}', 'Mã: ${product.productId}',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -298,27 +304,27 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: const TextSpan( text: TextSpan(
text: 'Xếp hạng của bạn', text: 'Xếp hạng của bạn',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
children: [ children: [
TextSpan( TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: AppColors.danger), style: TextStyle(color: colorScheme.error),
), ),
], ],
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Bấm vào ngôi sao để chọn đánh giá', 'Bấm vào ngôi sao để chọn đánh giá',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -339,17 +345,17 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
const Icon( Icon(
FontAwesomeIcons.triangleExclamation, FontAwesomeIcons.triangleExclamation,
size: 14, size: 14,
color: AppColors.danger, color: colorScheme.error,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
_ratingError!, _ratingError!,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.danger, color: colorScheme.error,
), ),
), ),
], ],
@@ -365,17 +371,17 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: const TextSpan( text: TextSpan(
text: 'Nội dung đánh giá', text: 'Nội dung đánh giá',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
children: [ children: [
TextSpan( TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: AppColors.danger), style: TextStyle(color: colorScheme.error),
), ),
], ],
), ),
@@ -390,38 +396,38 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText:
'Chia sẻ trải nghiệm của bạn về sản phẩm này...', 'Chia sẻ trải nghiệm của bạn về sản phẩm này...',
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: 15, fontSize: 15,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: Color(0xFFe0e0e0), color: colorScheme.outlineVariant,
width: 2, width: 2,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: Color(0xFFe0e0e0), color: colorScheme.outlineVariant,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
contentPadding: const EdgeInsets.all(14), contentPadding: const EdgeInsets.all(14),
counterText: '', // Hide default counter counterText: '', // Hide default counter
), ),
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
height: 1.6, height: 1.6,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@@ -441,15 +447,15 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: _contentController.text.length < _minContentLength color: _contentController.text.length < _minContentLength
? AppColors.danger ? colorScheme.error
: AppColors.grey500, : colorScheme.onSurfaceVariant,
), ),
), ),
Text( Text(
' / $_maxContentLength ký tự', ' / $_maxContentLength ký tự',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -460,18 +466,18 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
const SizedBox(height: 6), const SizedBox(height: 6),
Row( Row(
children: [ children: [
const Icon( Icon(
FontAwesomeIcons.triangleExclamation, FontAwesomeIcons.triangleExclamation,
size: 14, size: 14,
color: AppColors.danger, color: colorScheme.error,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
_contentError!, _contentError!,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.danger, color: colorScheme.error,
), ),
), ),
), ),
@@ -494,8 +500,9 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _isSubmitting ? null : _submitReview, onPressed: _isSubmitting ? null : _submitReview,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
disabledBackgroundColor: const Color(0xFFe0e0e0), disabledBackgroundColor: colorScheme.outlineVariant,
foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -503,30 +510,30 @@ class _WriteReviewPageState extends ConsumerState<WriteReviewPage> {
elevation: 0, elevation: 0,
), ),
child: _isSubmitting child: _isSubmitting
? const SizedBox( ? SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: valueColor:
AlwaysStoppedAnimation<Color>(AppColors.white), AlwaysStoppedAnimation<Color>(colorScheme.onPrimary),
), ),
) )
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Icon(
FontAwesomeIcons.paperPlane, FontAwesomeIcons.paperPlane,
size: 16, size: 16,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( Text(
'Gửi đánh giá', 'Gửi đánh giá',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
), ),
], ],

View File

@@ -6,7 +6,6 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/products/presentation/providers/product_filter_options_provider.dart'; import 'package:worker/features/products/presentation/providers/product_filter_options_provider.dart';
import 'package:worker/features/products/presentation/providers/product_filters_provider.dart'; import 'package:worker/features/products/presentation/providers/product_filters_provider.dart';
@@ -20,6 +19,7 @@ class BrandFilterChips extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final filtersState = ref.watch(productFiltersProvider); final filtersState = ref.watch(productFiltersProvider);
final filterOptionsAsync = ref.watch(productFilterOptionsProvider); final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
@@ -55,7 +55,7 @@ class BrandFilterChips extends ConsumerWidget {
style: TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? AppColors.white : AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
selected: isSelected, selected: isSelected,
@@ -84,11 +84,11 @@ class BrandFilterChips extends ConsumerWidget {
} }
} }
}, },
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
selectedColor: AppColors.primaryBlue, selectedColor: colorScheme.primary,
checkmarkColor: AppColors.white, checkmarkColor: colorScheme.onPrimary,
side: BorderSide( side: BorderSide(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
width: isSelected ? 2.0 : 1.0, width: isSelected ? 2.0 : 1.0,
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@@ -38,7 +38,7 @@ class ProductCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
// final isFavorited = ref.watch(isFavoriteProvider(product.productId)); final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: ProductCardSpecs.elevation, elevation: ProductCardSpecs.elevation,
@@ -62,23 +62,25 @@ class ProductCard extends ConsumerWidget {
top: Radius.circular(ProductCardSpecs.borderRadius), top: Radius.circular(ProductCardSpecs.borderRadius),
), ),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: product.thumbnail ?? '', imageUrl: product.thumbnail,
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
memCacheWidth: ImageSpecs.productImageCacheWidth, memCacheWidth: ImageSpecs.productImageCacheWidth,
memCacheHeight: ImageSpecs.productImageCacheHeight, memCacheHeight: ImageSpecs.productImageCacheHeight,
placeholder: (context, url) => Shimmer.fromColors( placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100, baseColor: colorScheme.surfaceContainerHighest,
highlightColor: AppColors.grey50, highlightColor: colorScheme.surfaceContainerLowest,
child: Container(color: AppColors.grey100), child: Container(
color: colorScheme.surfaceContainerHighest,
),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 48.0, size: 48.0,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -170,7 +172,7 @@ class ProductCard extends ConsumerWidget {
// width: 36, // width: 36,
// height: 36, // height: 36,
// decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColors.white, // color: colorScheme.surface,
// shape: BoxShape.circle, // shape: BoxShape.circle,
// boxShadow: [ // boxShadow: [
// BoxShadow( // BoxShadow(
@@ -186,7 +188,7 @@ class ProductCard extends ConsumerWidget {
// : Icons.favorite_border, // : Icons.favorite_border,
// color: isFavorited // color: isFavorited
// ? AppColors.danger // ? AppColors.danger
// : AppColors.grey500, // : colorScheme.onSurfaceVariant,
// size: 20, // size: 20,
// ), // ),
// ), // ),
@@ -221,10 +223,10 @@ class ProductCard extends ConsumerWidget {
// Price // Price
Text( Text(
'${_formatPrice(product.effectivePrice)}/m²', '${_formatPrice(product.effectivePrice)}/m²',
style: const TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 16.0,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
@@ -241,8 +243,8 @@ class ProductCard extends ConsumerWidget {
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: onAddToCart, onPressed: onAddToCart,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@@ -254,11 +256,12 @@ class ProductCard extends ConsumerWidget {
), ),
), ),
icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 14.0), icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 14.0),
label: const Text( label: Text(
'Thêm vào giỏ', 'Thêm vào giỏ',
style: TextStyle( style: TextStyle(
fontSize: 12.0, fontSize: 12.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colorScheme.surface
), ),
), ),
), ),
@@ -283,9 +286,9 @@ class ProductCard extends ConsumerWidget {
); );
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue, foregroundColor: colorScheme.primary,
side: const BorderSide( side: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 1.5, width: 1.5,
), ),
elevation: 0, elevation: 0,
@@ -299,11 +302,12 @@ class ProductCard extends ConsumerWidget {
), ),
), ),
icon: const FaIcon(FontAwesomeIcons.cube, size: 14.0), icon: const FaIcon(FontAwesomeIcons.cube, size: 14.0),
label: const Text( label: Text(
'Phối cảnh 360°', 'Phối cảnh 360°',
style: TextStyle( style: TextStyle(
fontSize: 12.0, fontSize: 12.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colorScheme.onSurface
), ),
), ),
), ),

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/products/domain/entities/product.dart'; import 'package:worker/features/products/domain/entities/product.dart';
/// Image Gallery Section /// Image Gallery Section
@@ -75,10 +74,11 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final images = widget.product.images; final images = widget.product.images;
return Container( return Container(
color: AppColors.white, color: colorScheme.surface,
child: Column( child: Column(
children: [ children: [
// Main Image with PageView // Main Image with PageView
@@ -102,16 +102,16 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
imageUrl: images[index], imageUrl: images[index],
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Shimmer.fromColors( placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100, baseColor: colorScheme.surfaceContainerHighest,
highlightColor: AppColors.grey50, highlightColor: colorScheme.surface,
child: Container(color: AppColors.grey100), child: Container(color: colorScheme.surfaceContainerHighest),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -144,10 +144,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
builder: (context, double value, child) { builder: (context, double value, child) {
return Transform.rotate( return Transform.rotate(
angle: value * 2 * 3.14159, angle: value * 2 * 3.14159,
child: const Icon( child: Icon(
FontAwesomeIcons.arrowsRotate, FontAwesomeIcons.arrowsRotate,
size: 10, size: 10,
color: AppColors.white, color: colorScheme.surface,
), ),
); );
}, },
@@ -157,10 +157,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
}, },
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
const Text( Text(
'360°', '360°',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: colorScheme.surface,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -189,8 +189,8 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: _currentImageIndex == index color: _currentImageIndex == index
? AppColors.white ? colorScheme.surface
: AppColors.white.withAlpha(128), : colorScheme.surface.withAlpha(128),
), ),
), ),
), ),
@@ -228,10 +228,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
padding: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.only(bottom: 4),
child: Text( child: Text(
imageName, imageName,
style: const TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -246,7 +246,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: isActive color: isActive
? AppColors.primaryBlue ? colorScheme.primary
: Colors.transparent, : Colors.transparent,
width: 2, width: 2,
), ),
@@ -257,13 +257,13 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
imageUrl: imageUrl, imageUrl: imageUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => placeholder: (context, url) =>
Container(color: AppColors.grey100), Container(color: colorScheme.surfaceContainerHighest),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 20, size: 20,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -336,11 +336,13 @@ class _ImageLightboxState extends State<_ImageLightbox> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: AppColors.white, foregroundColor: colorScheme.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.xmark, size: 28), icon: const FaIcon(FontAwesomeIcons.xmark, size: 28),
@@ -348,7 +350,7 @@ class _ImageLightboxState extends State<_ImageLightbox> {
), ),
title: Text( title: Text(
'${_currentIndex + 1} / ${widget.images.length}', '${_currentIndex + 1} / ${widget.images.length}',
style: const TextStyle(color: AppColors.white, fontSize: 16), style: TextStyle(color: colorScheme.surface, fontSize: 16),
), ),
), ),
body: Stack( body: Stack(
@@ -370,9 +372,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: widget.images[index], imageUrl: widget.images[index],
fit: BoxFit.contain, fit: BoxFit.contain,
errorWidget: (context, url, error) => const Icon( errorWidget: (context, url, error) => Icon(
FontAwesomeIcons.circleExclamation, FontAwesomeIcons.circleExclamation,
color: AppColors.white, color: colorScheme.surface,
size: 64, size: 64,
), ),
), ),
@@ -391,9 +393,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
bottom: 0, bottom: 0,
child: Center( child: Center(
child: IconButton( child: IconButton(
icon: const Icon( icon: Icon(
FontAwesomeIcons.chevronLeft, FontAwesomeIcons.chevronLeft,
color: AppColors.white, color: colorScheme.surface,
size: 32, size: 32,
), ),
onPressed: _previousImage, onPressed: _previousImage,
@@ -412,9 +414,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
bottom: 0, bottom: 0,
child: Center( child: Center(
child: IconButton( child: IconButton(
icon: const Icon( icon: Icon(
FontAwesomeIcons.chevronRight, FontAwesomeIcons.chevronRight,
color: AppColors.white, color: colorScheme.surface,
size: 32, size: 32,
), ),
onPressed: _nextImage, onPressed: _nextImage,
@@ -443,7 +445,7 @@ class _ImageLightboxState extends State<_ImageLightbox> {
), ),
child: Text( child: Text(
widget.imageCaptions[widget.images[_currentIndex]] ?? '', widget.imageCaptions[widget.images[_currentIndex]] ?? '',
style: const TextStyle(color: AppColors.white, fontSize: 16), style: TextStyle(color: colorScheme.surface, fontSize: 16),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),

View File

@@ -29,8 +29,10 @@ class ProductInfoSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
color: AppColors.white, color: colorScheme.surface,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -38,7 +40,7 @@ class ProductInfoSection extends StatelessWidget {
// SKU // SKU
Text( Text(
'SKU: ${product.erpnextItemCode ?? product.productId}', 'SKU: ${product.erpnextItemCode ?? product.productId}',
style: const TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -46,10 +48,10 @@ class ProductInfoSection extends StatelessWidget {
// Product Title // Product Title
Text( Text(
product.name, product.name,
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.3, height: 1.3,
), ),
), ),
@@ -62,10 +64,10 @@ class ProductInfoSection extends StatelessWidget {
// Current Price // Current Price
Text( Text(
'${_formatPrice(product.basePrice)}/m²', '${_formatPrice(product.basePrice)}/m²',
style: const TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
@@ -74,9 +76,9 @@ class ProductInfoSection extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(
_formatPrice(product.basePrice * 1.12), // Mock original price _formatPrice(product.basePrice * 1.12), // Mock original price
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
@@ -95,8 +97,8 @@ class ProductInfoSection extends StatelessWidget {
), ),
child: Text( child: Text(
'-${product.discountPercentage}%', '-${product.discountPercentage}%',
style: const TextStyle( style: TextStyle(
color: AppColors.white, color: colorScheme.surface,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -109,10 +111,10 @@ class ProductInfoSection extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Rating & Reviews Section // Rating & Reviews Section
const Row( Row(
children: [ children: [
// Rating Stars // Rating Stars
Row( const Row(
children: [ children: [
Icon(FontAwesomeIcons.solidStar, color: Color(0xFFffc107), size: 16), Icon(FontAwesomeIcons.solidStar, color: Color(0xFFffc107), size: 16),
SizedBox(width: 2), SizedBox(width: 2),
@@ -126,14 +128,14 @@ class ProductInfoSection extends StatelessWidget {
], ],
), ),
SizedBox(width: 12), const SizedBox(width: 12),
// Rating Text // Rating Text
Text( Text(
'4.8 (125 đánh giá)', '4.8 (125 đánh giá)',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -142,9 +144,9 @@ class ProductInfoSection extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Intro attributes quick info cards (dynamic based on non-null values) // Intro attributes quick info cards (dynamic based on non-null values)
if (_buildIntroAttributeCards(product).isNotEmpty) if (_buildIntroAttributeCards(product, colorScheme).isNotEmpty)
Row( Row(
children: _buildIntroAttributeCards(product), children: _buildIntroAttributeCards(product, colorScheme),
), ),
], ],
), ),
@@ -152,7 +154,7 @@ class ProductInfoSection extends StatelessWidget {
} }
/// Build intro attribute cards dynamically based on non-null values /// Build intro attribute cards dynamically based on non-null values
List<Widget> _buildIntroAttributeCards(Product product) { List<Widget> _buildIntroAttributeCards(Product product, ColorScheme colorScheme) {
final cards = <Widget>[]; final cards = <Widget>[];
// Define available intro attributes with their display info // Define available intro attributes with their display info
@@ -184,6 +186,7 @@ class ProductInfoSection extends StatelessWidget {
icon: attr['icon'] as IconData, icon: attr['icon'] as IconData,
label: attr['label'] as String, label: attr['label'] as String,
value: value, value: value,
colorScheme: colorScheme,
), ),
), ),
); );
@@ -210,30 +213,32 @@ class _QuickInfoCard extends StatelessWidget {
required this.icon, required this.icon,
required this.label, required this.label,
required this.value, required this.value,
required this.colorScheme,
}); });
final IconData icon; final IconData icon;
final String label; final String label;
final String value; final String value;
final ColorScheme colorScheme;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
Icon(icon, color: AppColors.primaryBlue, size: 24), Icon(icon, color: colorScheme.primary, size: 24),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
label, label,
style: const TextStyle(fontSize: 12, color: AppColors.grey500), style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
value, value,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),

View File

@@ -50,21 +50,23 @@ class _ProductTabsSectionState extends State<ProductTabsSection>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
color: AppColors.white, color: colorScheme.surface,
child: Column( child: Column(
children: [ children: [
// Tab Navigation // Tab Navigation
Container( Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0), width: 1), bottom: BorderSide(color: colorScheme.outlineVariant, width: 1),
), ),
), ),
child: TabBar( child: TabBar(
controller: _tabController, controller: _tabController,
labelColor: AppColors.primaryBlue, labelColor: colorScheme.primary,
unselectedLabelColor: AppColors.grey500, unselectedLabelColor: colorScheme.onSurfaceVariant,
labelStyle: const TextStyle( labelStyle: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -73,7 +75,7 @@ class _ProductTabsSectionState extends State<ProductTabsSection>
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
indicatorColor: AppColors.primaryBlue, indicatorColor: colorScheme.primary,
indicatorWeight: 2, indicatorWeight: 2,
tabs: const [ tabs: const [
Tab(text: 'Thông số'), Tab(text: 'Thông số'),
@@ -104,18 +106,20 @@ class _DescriptionTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Main description // Main description
const Text( Text(
'Bộ sưu tập Cao cấp', 'Bộ sưu tập Cao cấp',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -123,22 +127,22 @@ class _DescriptionTab extends StatelessWidget {
Text( Text(
product.description ?? product.description ??
'Sản phẩm gạch cao cấp với chất lượng vượt trội, mang đến vẻ đẹp tự nhiên và sang trọng cho không gian của bạn. Với bề mặt có texture tinh tế, sản phẩm tạo nên những đường vân tự nhiên chân thực.', 'Sản phẩm gạch cao cấp với chất lượng vượt trội, mang đến vẻ đẹp tự nhiên và sang trọng cho không gian của bạn. Với bề mặt có texture tinh tế, sản phẩm tạo nên những đường vân tự nhiên chân thực.',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
height: 1.6, height: 1.6,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Features heading // Features heading
const Text( Text(
'Đặc điểm nổi bật:', 'Đặc điểm nổi bật:',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -165,9 +169,9 @@ class _DescriptionTab extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
feature, feature,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
), ),
@@ -179,22 +183,22 @@ class _DescriptionTab extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Application section // Application section
const Text( Text(
'Ứng dụng:', 'Ứng dụng:',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'Phù hợp cho phòng khách, phòng ngủ, hành lang, văn phòng và các không gian thương mại. Đặc biệt phù hợp với phong cách nội thất hiện đại, tối giản và Scandinavian.', 'Phù hợp cho phòng khách, phòng ngủ, hành lang, văn phòng và các không gian thương mại. Đặc biệt phù hợp với phong cách nội thất hiện đại, tối giản và Scandinavian.',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
height: 1.6, height: 1.6,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -211,6 +215,8 @@ class _SpecificationsTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Default specifications if not available // Default specifications if not available
final specs = product.specifications.isNotEmpty final specs = product.specifications.isNotEmpty
? product.specifications ? product.specifications
@@ -230,15 +236,15 @@ class _SpecificationsTab extends StatelessWidget {
return Container( return Container(
margin: const EdgeInsets.all(12), margin: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFe0e0e0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Column( child: Column(
children: [ children: [
Container( Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)), bottom: BorderSide(color: colorScheme.outlineVariant),
), ),
), ),
child: IntrinsicHeight( child: IntrinsicHeight(
@@ -249,15 +255,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8), color: colorScheme.surface,
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
"Thương hiệu", "Thương hiệu",
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
), ),
@@ -268,7 +274,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider // Divider
Container( Container(
width: 1, width: 1,
color: const Color(0xFFe0e0e0), color: colorScheme.outlineVariant,
), ),
// Value // Value
@@ -277,9 +283,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text( child: Text(
'${product.brand}', '${product.brand}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
softWrap: true, softWrap: true,
@@ -291,9 +297,9 @@ class _SpecificationsTab extends StatelessWidget {
), ),
), ),
Container( Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)), bottom: BorderSide(color: colorScheme.outlineVariant),
), ),
), ),
child: IntrinsicHeight( child: IntrinsicHeight(
@@ -304,15 +310,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8), color: colorScheme.surface,
child: const Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
"Dòng sản phẩm", "Dòng sản phẩm",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
), ),
@@ -323,7 +329,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider // Divider
Container( Container(
width: 1, width: 1,
color: const Color(0xFFe0e0e0), color: colorScheme.outlineVariant,
), ),
// Value // Value
@@ -332,9 +338,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text( child: Text(
'${product.itemGroupName}', '${product.itemGroupName}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
softWrap: true, softWrap: true,
@@ -351,8 +357,8 @@ class _SpecificationsTab extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: isLast border: isLast
? null ? null
: const Border( : Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)), bottom: BorderSide(color: colorScheme.outlineVariant),
), ),
), ),
child: IntrinsicHeight( child: IntrinsicHeight(
@@ -363,15 +369,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8), color: colorScheme.surface,
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
entry.key, entry.key,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
), ),
@@ -382,7 +388,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider // Divider
Container( Container(
width: 1, width: 1,
color: const Color(0xFFe0e0e0), color: colorScheme.outlineVariant,
), ),
// Value // Value
@@ -391,9 +397,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text( child: Text(
'${entry.value}', '${entry.value}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.5, height: 1.5,
), ),
softWrap: true, softWrap: true,
@@ -419,6 +425,7 @@ class _ReviewsTab extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final reviewsAsync = ref.watch(productReviewsProvider(productId)); final reviewsAsync = ref.watch(productReviewsProvider(productId));
final avgRatingAsync = ref.watch(productAverageRatingProvider(productId)); final avgRatingAsync = ref.watch(productAverageRatingProvider(productId));
@@ -437,7 +444,7 @@ class _ReviewsTab extends ConsumerWidget {
data: (reviews) { data: (reviews) {
if (reviews.isEmpty) { if (reviews.isEmpty) {
// Empty state // Empty state
return _buildEmptyState(); return _buildEmptyState(colorScheme);
} }
return Column( return Column(
@@ -445,9 +452,9 @@ class _ReviewsTab extends ConsumerWidget {
children: [ children: [
// Rating Overview // Rating Overview
avgRatingAsync.when( avgRatingAsync.when(
data: (avgRating) => _buildRatingOverview(reviews, avgRating), data: (avgRating) => _buildRatingOverview(colorScheme, reviews, avgRating),
loading: () => _buildRatingOverview(reviews, 0), loading: () => _buildRatingOverview(colorScheme, reviews, 0),
error: (_, __) => _buildRatingOverview(reviews, 0), error: (_, __) => _buildRatingOverview(colorScheme, reviews, 0),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -459,22 +466,22 @@ class _ReviewsTab extends ConsumerWidget {
], ],
); );
}, },
loading: () => const Center( loading: () => Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(40), padding: const EdgeInsets.all(40),
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
), ),
error: (error, stack) => _buildErrorState(error.toString()), error: (error, stack) => _buildErrorState(colorScheme, error.toString()),
), ),
], ],
), ),
); );
} }
Widget _buildRatingOverview(List<Review> reviews, double avgRating) { Widget _buildRatingOverview(ColorScheme colorScheme, List<Review> reviews, double avgRating) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -486,10 +493,10 @@ class _ReviewsTab extends ConsumerWidget {
// Rating Score // Rating Score
Text( Text(
avgRating.toStringAsFixed(2), avgRating.toStringAsFixed(2),
style: const TextStyle( style: TextStyle(
fontSize: 36, fontSize: 36,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
@@ -505,19 +512,19 @@ class _ReviewsTab extends ConsumerWidget {
if (index < avgRating.floor()) { if (index < avgRating.floor()) {
return const Icon( return const Icon(
FontAwesomeIcons.solidStar, FontAwesomeIcons.solidStar,
color: Color(0xFFffc107), color: AppColors.warning,
size: 18, size: 18,
); );
} else if (index < avgRating) { } else if (index < avgRating) {
return const Icon( return const Icon(
FontAwesomeIcons.starHalfStroke, FontAwesomeIcons.starHalfStroke,
color: Color(0xFFffc107), color: AppColors.warning,
size: 18, size: 18,
); );
} else { } else {
return const Icon( return const Icon(
FontAwesomeIcons.star, FontAwesomeIcons.star,
color: Color(0xFFffc107), color: AppColors.warning,
size: 18, size: 18,
); );
} }
@@ -529,9 +536,9 @@ class _ReviewsTab extends ConsumerWidget {
// Review count // Review count
Text( Text(
'${reviews.length} đánh giá', '${reviews.length} đánh giá',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -541,33 +548,33 @@ class _ReviewsTab extends ConsumerWidget {
); );
} }
Widget _buildEmptyState() { Widget _buildEmptyState(ColorScheme colorScheme) {
return const Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(40), padding: const EdgeInsets.all(40),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
FontAwesomeIcons.commentSlash, FontAwesomeIcons.commentSlash,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Chưa có đánh giá nào', 'Chưa có đánh giá nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Hãy là người đầu tiên đánh giá sản phẩm này', 'Hãy là người đầu tiên đánh giá sản phẩm này',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -577,7 +584,7 @@ class _ReviewsTab extends ConsumerWidget {
); );
} }
Widget _buildErrorState(String error) { Widget _buildErrorState(ColorScheme colorScheme, String error) {
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(40), padding: const EdgeInsets.all(40),
@@ -590,20 +597,20 @@ class _ReviewsTab extends ConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Không thể tải đánh giá', 'Không thể tải đánh giá',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.length > 100 ? '${error.substring(0, 100)}...' : error, error.length > 100 ? '${error.substring(0, 100)}...' : error,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -622,11 +629,13 @@ class _ReviewItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 16),
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFFe0e0e0))), border: Border(bottom: BorderSide(color: colorScheme.outlineVariant)),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -642,9 +651,9 @@ class _ReviewItem extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
color: Color(0xFFF4F6F8), color: Color(0xFFF4F6F8),
), ),
child: const Icon( child: Icon(
FontAwesomeIcons.solidUser, FontAwesomeIcons.solidUser,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: 20, size: 20,
), ),
), ),
@@ -658,17 +667,17 @@ class _ReviewItem extends StatelessWidget {
children: [ children: [
Text( Text(
review.reviewerName ?? 'Người dùng', review.reviewerName ?? 'Người dùng',
style: const TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (review.reviewDate != null) if (review.reviewDate != null)
Text( Text(
_formatDate(review.reviewDate!), _formatDate(review.reviewDate!),
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -687,7 +696,7 @@ class _ReviewItem extends StatelessWidget {
index < review.starsRating index < review.starsRating
? FontAwesomeIcons.solidStar ? FontAwesomeIcons.solidStar
: FontAwesomeIcons.star, : FontAwesomeIcons.star,
color: const Color(0xFFffc107), color: AppColors.warning,
size: 14, size: 14,
), ),
), ),
@@ -698,10 +707,10 @@ class _ReviewItem extends StatelessWidget {
// Review Text // Review Text
Text( Text(
review.comment, review.comment,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
height: 1.5, height: 1.5,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],

View File

@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Sticky Action Bar /// Sticky Action Bar
/// ///
@@ -61,11 +60,13 @@ class StickyActionBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: const Border( border: Border(
top: BorderSide(color: Color(0xFFe0e0e0), width: 1), top: BorderSide(color: colorScheme.outlineVariant, width: 1),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -88,9 +89,9 @@ class StickyActionBar extends StatelessWidget {
// Label // Label
Text( Text(
'Số lượng ($unit)', 'Số lượng ($unit)',
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -101,7 +102,7 @@ class StickyActionBar extends StatelessWidget {
Container( Container(
width: 142, width: 142,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFe0e0e0)), border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -152,9 +153,9 @@ class StickyActionBar extends StatelessWidget {
if (_getConversionText().isNotEmpty) if (_getConversionText().isNotEmpty)
Text( Text(
_getConversionText(), _getConversionText(),
style: const TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -167,8 +168,8 @@ class StickyActionBar extends StatelessWidget {
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: onAddToCart, onPressed: onAddToCart,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 12, horizontal: 12,
@@ -179,11 +180,12 @@ class StickyActionBar extends StatelessWidget {
), ),
), ),
icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 18), icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 18),
label: const Text( label: Text(
'Thêm vào giỏ hàng', 'Thêm vào giỏ hàng',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colorScheme.onSurface
), ),
), ),
), ),
@@ -204,17 +206,19 @@ class _QuantityButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SizedBox( return SizedBox(
width: 40, width: 40,
height: 40, height: 40,
child: Material( child: Material(
color: const Color(0xFFF4F6F8), color: colorScheme.surfaceContainerHighest,
child: InkWell( child: InkWell(
onTap: onPressed, onTap: onPressed,
child: Icon( child: Icon(
icon, icon,
size: 20, size: 20,
color: onPressed != null ? AppColors.grey900 : AppColors.grey500, color: onPressed != null ? colorScheme.onSurface : colorScheme.onSurfaceVariant,
), ),
), ),
), ),

View File

@@ -6,12 +6,11 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:worker/core/theme/colors.dart';
/// Write Review Button /// Write Review Button
/// ///
/// Displays a prominent button for users to write a review: /// Displays a prominent button for users to write a review:
/// - Primary blue background /// - Primary background from theme
/// - Edit icon /// - Edit icon
/// - Text: "Viết đánh giá của bạn" /// - Text: "Viết đánh giá của bạn"
/// - Navigates to WriteReviewPage with productId /// - Navigates to WriteReviewPage with productId
@@ -26,6 +25,8 @@ class WriteReviewButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: const EdgeInsets.only(bottom: 20), margin: const EdgeInsets.only(bottom: 20),
@@ -35,28 +36,28 @@ class WriteReviewButton extends StatelessWidget {
context.push('/products/$productId/write-review'); context.push('/products/$productId/write-review');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 28), padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 28),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
elevation: 0, elevation: 0,
), ),
child: const Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
FontAwesomeIcons.penToSquare, FontAwesomeIcons.penToSquare,
size: 16, size: 16,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
'Viết đánh giá của bạn', 'Viết đánh giá của bạn',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.white, color: colorScheme.onPrimary,
), ),
), ),
], ],

View File

@@ -24,6 +24,7 @@ class ProductFilterDrawer extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final filtersState = ref.watch(productFiltersProvider); final filtersState = ref.watch(productFiltersProvider);
final filterOptionsAsync = ref.watch(productFilterOptionsProvider); final filterOptionsAsync = ref.watch(productFilterOptionsProvider);
@@ -35,26 +36,26 @@ class ProductFilterDrawer extends ConsumerWidget {
// Header // Header
Container( Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: AppColors.grey100, width: 1), bottom: BorderSide(color: colorScheme.outlineVariant, width: 1),
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( Text(
'Bộ lọc sản phẩm', 'Bộ lọc sản phẩm',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.xmark, size: 20), icon: const FaIcon(FontAwesomeIcons.xmark, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),
@@ -69,6 +70,7 @@ class ProductFilterDrawer extends ConsumerWidget {
children: [ children: [
// Nhóm sản phẩm (Item Groups) - from API // Nhóm sản phẩm (Item Groups) - from API
_buildFilterGroup( _buildFilterGroup(
context: context,
title: 'Nhóm sản phẩm', title: 'Nhóm sản phẩm',
options: filterOptions.groups, options: filterOptions.groups,
initiallyExpanded: true, initiallyExpanded: true,
@@ -82,6 +84,7 @@ class ProductFilterDrawer extends ConsumerWidget {
// Thương hiệu (Brands) - from API // Thương hiệu (Brands) - from API
_buildFilterGroup( _buildFilterGroup(
context: context,
title: 'Thương hiệu', title: 'Thương hiệu',
options: filterOptions.brands, options: filterOptions.brands,
selectedValues: filtersState.brands, selectedValues: filtersState.brands,
@@ -103,6 +106,7 @@ class ProductFilterDrawer extends ConsumerWidget {
return Column( return Column(
children: [ children: [
_buildAttributeGroup( _buildAttributeGroup(
context: context,
title: attrGroup.attributeName, title: attrGroup.attributeName,
attributeGroup: attrGroup, attributeGroup: attrGroup,
selectedValues: selectedValues, selectedValues: selectedValues,
@@ -126,9 +130,9 @@ class ProductFilterDrawer extends ConsumerWidget {
// Footer Buttons // Footer Buttons
Container( Container(
padding: const EdgeInsets.all(AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide(color: AppColors.grey100, width: 1), top: BorderSide(color: colorScheme.outlineVariant, width: 1),
), ),
), ),
child: Row( child: Row(
@@ -140,9 +144,9 @@ class ProductFilterDrawer extends ConsumerWidget {
ref.read(productFiltersProvider.notifier).reset(); ref.read(productFiltersProvider.notifier).reset();
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.grey900, foregroundColor: colorScheme.onSurface,
side: const BorderSide( side: BorderSide(
color: AppColors.grey100, color: colorScheme.outlineVariant,
width: 1, width: 1,
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -181,8 +185,8 @@ class ProductFilterDrawer extends ConsumerWidget {
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
elevation: 0, elevation: 0,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: AppSpacing.md, vertical: AppSpacing.md,
@@ -205,9 +209,9 @@ class ProductFilterDrawer extends ConsumerWidget {
), ),
], ],
), ),
loading: () => const Center( loading: () => Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryBlue), valueColor: AlwaysStoppedAnimation<Color>(colorScheme.primary),
), ),
), ),
error: (error, stack) => Center( error: (error, stack) => Center(
@@ -222,20 +226,20 @@ class ProductFilterDrawer extends ConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
const Text( Text(
'Không thể tải bộ lọc', 'Không thể tải bộ lọc',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: AppSpacing.sm), const SizedBox(height: AppSpacing.sm),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -245,8 +249,8 @@ class ProductFilterDrawer extends ConsumerWidget {
ref.invalidate(productFilterOptionsProvider); ref.invalidate(productFilterOptionsProvider);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.onPrimary,
), ),
child: const Text('Thử lại'), child: const Text('Thử lại'),
), ),
@@ -260,12 +264,15 @@ class ProductFilterDrawer extends ConsumerWidget {
} }
Widget _buildFilterGroup({ Widget _buildFilterGroup({
required BuildContext context,
required String title, required String title,
required List<FilterOption> options, required List<FilterOption> options,
required Set<String> selectedValues, required Set<String> selectedValues,
required Function(String) onToggle, required Function(String) onToggle,
bool initiallyExpanded = false, bool initiallyExpanded = false,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Theme( return Theme(
data: ThemeData( data: ThemeData(
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
@@ -277,10 +284,10 @@ class ProductFilterDrawer extends ConsumerWidget {
childrenPadding: const EdgeInsets.only(left: 8), childrenPadding: const EdgeInsets.only(left: 8),
title: Text( title: Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
initiallyExpanded: initiallyExpanded, initiallyExpanded: initiallyExpanded,
@@ -288,9 +295,9 @@ class ProductFilterDrawer extends ConsumerWidget {
return CheckboxListTile( return CheckboxListTile(
title: Text( title: Text(
option.label, option.label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
value: selectedValues.contains(option.value), value: selectedValues.contains(option.value),
@@ -300,7 +307,7 @@ class ProductFilterDrawer extends ConsumerWidget {
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
dense: true, dense: true,
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
); );
}).toList(), }).toList(),
), ),
@@ -308,11 +315,14 @@ class ProductFilterDrawer extends ConsumerWidget {
} }
Widget _buildAttributeGroup({ Widget _buildAttributeGroup({
required BuildContext context,
required String title, required String title,
required AttributeGroup attributeGroup, required AttributeGroup attributeGroup,
required Set<String> selectedValues, required Set<String> selectedValues,
required Function(String) onToggle, required Function(String) onToggle,
}) { }) {
final colorScheme = Theme.of(context).colorScheme;
return Theme( return Theme(
data: ThemeData( data: ThemeData(
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
@@ -324,10 +334,10 @@ class ProductFilterDrawer extends ConsumerWidget {
childrenPadding: const EdgeInsets.only(left: 8), childrenPadding: const EdgeInsets.only(left: 8),
title: Text( title: Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
initiallyExpanded: false, initiallyExpanded: false,
@@ -335,17 +345,17 @@ class ProductFilterDrawer extends ConsumerWidget {
return CheckboxListTile( return CheckboxListTile(
title: Text( title: Text(
value.attributeValue, value.attributeValue,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
// subtitle: value.abbr != null // subtitle: value.abbr != null
// ? Text( // ? Text(
// 'Mã: ${value.abbr}', // 'Mã: ${value.abbr}',
// style: const TextStyle( // style: TextStyle(
// fontSize: 12, // fontSize: 12,
// color: AppColors.grey500, // color: colorScheme.onSurfaceVariant,
// ), // ),
// ) // )
// : null, // : null,
@@ -356,7 +366,7 @@ class ProductFilterDrawer extends ConsumerWidget {
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
dense: true, dense: true,
activeColor: AppColors.primaryBlue, activeColor: colorScheme.primary,
); );
}).toList(), }).toList(),
), ),

View File

@@ -11,6 +11,7 @@ import 'package:worker/features/products/presentation/widgets/product_card.dart'
/// Product Grid Widget /// Product Grid Widget
/// ///
/// Displays products in a 2-column grid layout with scroll-to-load-more. /// Displays products in a 2-column grid layout with scroll-to-load-more.
/// Fully theme-compliant using Material 3 ColorScheme.
class ProductGrid extends StatefulWidget { class ProductGrid extends StatefulWidget {
final List<Product> products; final List<Product> products;
final void Function(Product)? onProductTap; final void Function(Product)? onProductTap;
@@ -61,6 +62,8 @@ class _ProductGridState extends State<ProductGrid> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GridView.builder( return GridView.builder(
controller: _scrollController, controller: _scrollController,
padding: const EdgeInsets.all(AppSpacing.xs), padding: const EdgeInsets.all(AppSpacing.xs),
@@ -74,10 +77,12 @@ class _ProductGridState extends State<ProductGrid> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
// Show loading indicator at the end // Show loading indicator at the end
if (index == widget.products.length) { if (index == widget.products.length) {
return const Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md),
child: CircularProgressIndicator(), child: CircularProgressIndicator(
color: colorScheme.primary,
),
), ),
); );
} }

View File

@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/products/presentation/providers/search_query_provider.dart'; import 'package:worker/features/products/presentation/providers/search_query_provider.dart';
import 'package:worker/generated/l10n/app_localizations.dart'; import 'package:worker/generated/l10n/app_localizations.dart';
@@ -62,6 +61,7 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
final colorScheme = Theme.of(context).colorScheme;
return SizedBox( return SizedBox(
height: InputFieldSpecs.height, height: InputFieldSpecs.height,
@@ -71,27 +71,27 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
onChanged: _onSearchChanged, onChanged: _onSearchChanged,
decoration: InputDecoration( decoration: InputDecoration(
hintText: l10n.searchProducts, hintText: l10n.searchProducts,
hintStyle: const TextStyle( hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize, fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
prefixIcon: const Icon( prefixIcon: Icon(
FontAwesomeIcons.magnifyingGlass, FontAwesomeIcons.magnifyingGlass,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: AppIconSize.md, size: AppIconSize.md,
), ),
suffixIcon: _hasText suffixIcon: _hasText
? IconButton( ? IconButton(
icon: const Icon( icon: Icon(
FontAwesomeIcons.xmark, FontAwesomeIcons.xmark,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
size: AppIconSize.md, size: AppIconSize.md,
), ),
onPressed: _onClearSearch, onPressed: _onClearSearch,
) )
: null, : null,
filled: true, filled: true,
fillColor: Colors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -102,8 +102,8 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2.0, width: 2.0,
), ),
), ),

View File

@@ -5,21 +5,17 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/theme/colors.dart';
/// Review Guidelines Card Widget /// Review Guidelines Card Widget
/// ///
/// Displays helpful tips for writing product reviews: /// Displays helpful tips for writing product reviews:
/// - Light blue background (#f0f7ff) /// - Light blue background (colorScheme.primaryContainer)
/// - Blue left border /// - Blue left border (colorScheme.primary)
/// - Lightbulb icon /// - Lightbulb icon
/// - 4 bullet points with guidelines /// - 4 bullet points with guidelines
class ReviewGuidelinesCard extends StatelessWidget { class ReviewGuidelinesCard extends StatelessWidget {
const ReviewGuidelinesCard({super.key}); const ReviewGuidelinesCard({super.key});
// Guidelines background color
static const Color _backgroundColor = Color(0xFFF0F7FF);
// Guidelines list // Guidelines list
static const List<String> _guidelines = [ static const List<String> _guidelines = [
'Chia sẻ trải nghiệm thực tế của bạn về sản phẩm', 'Chia sẻ trải nghiệm thực tế của bạn về sản phẩm',
@@ -30,14 +26,16 @@ class ReviewGuidelinesCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _backgroundColor, color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: const Border( border: Border(
left: BorderSide( left: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 4, width: 4,
), ),
), ),
@@ -46,20 +44,20 @@ class ReviewGuidelinesCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Header // Header
const Row( Row(
children: [ children: [
Icon( Icon(
FontAwesomeIcons.lightbulb, FontAwesomeIcons.lightbulb,
size: 16, size: 16,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Gợi ý viết đánh giá tốt', 'Gợi ý viết đánh giá tốt',
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryBlue, color: colorScheme.primary,
), ),
), ),
], ],
@@ -76,20 +74,20 @@ class ReviewGuidelinesCard extends StatelessWidget {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'', '',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.6, height: 1.6,
), ),
), ),
Expanded( Expanded(
child: Text( child: Text(
_guidelines[index], _guidelines[index],
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
height: 1.6, height: 1.6,
), ),
), ),

View File

@@ -6,7 +6,6 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Star Rating Selector Widget /// Star Rating Selector Widget
/// ///
@@ -44,7 +43,7 @@ class _StarRatingSelectorState extends State<StarRatingSelector> {
5: 'Rất hài lòng', 5: 'Rất hài lòng',
}; };
// Colors // Star colors (semantic - keep as AppColors)
static const Color _starUnselected = Color(0xFFe0e0e0); static const Color _starUnselected = Color(0xFFe0e0e0);
static const Color _starHover = Color(0xFFffc107); static const Color _starHover = Color(0xFFffc107);
static const Color _starSelected = Color(0xFFff9800); static const Color _starSelected = Color(0xFFff9800);
@@ -54,6 +53,8 @@ class _StarRatingSelectorState extends State<StarRatingSelector> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column( return Column(
children: [ children: [
// Stars Row // Stars Row
@@ -99,7 +100,7 @@ class _StarRatingSelectorState extends State<StarRatingSelector> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.rating > 0 color: widget.rating > 0
? _labelBackgroundSelected ? _labelBackgroundSelected
: AppColors.grey50, : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Text( child: Text(
@@ -107,7 +108,7 @@ class _StarRatingSelectorState extends State<StarRatingSelector> {
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: widget.rating > 0 ? _starSelected : AppColors.grey500, color: widget.rating > 0 ? _starSelected : colorScheme.onSurfaceVariant,
), ),
), ),
), ),

View File

@@ -131,26 +131,28 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: Text( title: Text(
isEditing ? 'Chỉnh sửa Dự án' : 'Đăng ký Công trình', isEditing ? 'Chỉnh sửa Dự án' : 'Đăng ký Công trình',
style: const TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.circleInfo, FontAwesomeIcons.circleInfo,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: _showInfoDialog, onPressed: _showInfoDialog,
@@ -158,19 +160,19 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
centerTitle: false, centerTitle: false,
), ),
body: _isLoadingDetail body: _isLoadingDetail
? const Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(), const CircularProgressIndicator(),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Đang tải thông tin dự án...', 'Đang tải thông tin dự án...',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -181,23 +183,23 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
children: [ children: [
// Basic Information // Basic Information
_buildBasicInfoCard(), _buildBasicInfoCard(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// Project Details // Project Details
_buildProjectDetailsCard(), _buildProjectDetailsCard(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// Additional Information // Additional Information
_buildAdditionalInfoCard(), _buildAdditionalInfoCard(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
// File Upload // File Upload
_buildFileUploadCard(), _buildFileUploadCard(colorScheme),
const SizedBox(height: 24), const SizedBox(height: 24),
// Submit Button // Submit Button
_buildSubmitButton(), _buildSubmitButton(colorScheme),
const SizedBox(height: 40), const SizedBox(height: 40),
], ],
), ),
@@ -205,7 +207,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildBasicInfoCard() { Widget _buildBasicInfoCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -214,17 +216,18 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thông tin cơ bản', 'Thông tin cơ bản',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _projectNameController, controller: _projectNameController,
label: 'Tên công trình', label: 'Tên công trình',
required: true, required: true,
@@ -233,6 +236,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _addressController, controller: _addressController,
label: 'Địa chỉ', label: 'Địa chỉ',
required: true, required: true,
@@ -242,6 +246,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _ownerController, controller: _ownerController,
label: 'Chủ đầu tư', label: 'Chủ đầu tư',
required: true, required: true,
@@ -250,6 +255,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _designUnitController, controller: _designUnitController,
label: 'Đơn vị thiết kế', label: 'Đơn vị thiết kế',
hint: 'Tên đơn vị thiết kế', hint: 'Tên đơn vị thiết kế',
@@ -257,6 +263,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _constructionUnitController, controller: _constructionUnitController,
label: 'Đơn vị thi công', label: 'Đơn vị thi công',
hint: 'Tên đơn vị thi công', hint: 'Tên đơn vị thi công',
@@ -267,7 +274,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildProjectDetailsCard() { Widget _buildProjectDetailsCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -276,17 +283,18 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Chi tiết dự án', 'Chi tiết dự án',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _areaController, controller: _areaController,
label: 'Tổng diện tích', label: 'Tổng diện tích',
required: true, required: true,
@@ -296,6 +304,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _productsController, controller: _productsController,
label: 'Sản phẩm đưa vào thiết kế', label: 'Sản phẩm đưa vào thiết kế',
required: true, required: true,
@@ -305,17 +314,17 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildProgressDropdown(), _buildProgressDropdown(colorScheme),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildExpectedDateField(), _buildExpectedDateField(colorScheme),
], ],
), ),
), ),
); );
} }
Widget _buildAdditionalInfoCard() { Widget _buildAdditionalInfoCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -324,17 +333,18 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Thông tin bổ sung', 'Thông tin bổ sung',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
colorScheme: colorScheme,
controller: _descriptionController, controller: _descriptionController,
label: 'Mô tả công trình', label: 'Mô tả công trình',
hint: 'Mô tả ngắn gọn về công trình, diện tích, đặc điểm nổi bật...', hint: 'Mô tả ngắn gọn về công trình, diện tích, đặc điểm nổi bật...',
@@ -346,7 +356,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildFileUploadCard() { Widget _buildFileUploadCard(ColorScheme colorScheme) {
return Card( return Card(
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -355,12 +365,12 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ảnh minh chứng', 'Ảnh minh chứng',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -374,34 +384,34 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Column( child: Column(
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.cloudArrowUp, FontAwesomeIcons.cloudArrowUp,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'Kéo thả ảnh vào đây', 'Kéo thả ảnh vào đây',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'hoặc nhấn để chọn file', 'hoặc nhấn để chọn file',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -412,12 +422,12 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
// Existing files from API // Existing files from API
if (_existingFiles.isNotEmpty) ...[ if (_existingFiles.isNotEmpty) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Ảnh đã tải lên', 'Ảnh đã tải lên',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -426,7 +436,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
final file = entry.value; final file = entry.value;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: _buildExistingFileItem(file, index), child: _buildExistingFileItem(file, index, colorScheme),
); );
}), }),
], ],
@@ -435,12 +445,12 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
if (_uploadedFiles.isNotEmpty) ...[ if (_uploadedFiles.isNotEmpty) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
if (_existingFiles.isNotEmpty) if (_existingFiles.isNotEmpty)
const Text( Text(
'Ảnh mới', 'Ảnh mới',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
if (_existingFiles.isNotEmpty) const SizedBox(height: 8), if (_existingFiles.isNotEmpty) const SizedBox(height: 8),
@@ -449,17 +459,17 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
final file = entry.value; final file = entry.value;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: _buildFileItem(file, index), child: _buildFileItem(file, index, colorScheme),
); );
}), }),
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Hỗ trợ: JPG, PNG, PDF. Tối đa 10MB mỗi file.', 'Hỗ trợ: JPG, PNG, PDF. Tối đa 10MB mỗi file.',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -469,6 +479,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
} }
Widget _buildTextField({ Widget _buildTextField({
required ColorScheme colorScheme,
required TextEditingController controller, required TextEditingController controller,
required String label, required String label,
String? hint, String? hint,
@@ -484,10 +495,10 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (required) if (required)
@@ -507,20 +518,20 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
keyboardType: keyboardType, keyboardType: keyboardType,
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: const TextStyle(color: AppColors.grey500), hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2), borderSide: BorderSide(color: colorScheme.primary, width: 2),
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -544,9 +555,9 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
helperText, helperText,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -554,23 +565,23 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildProgressDropdown() { Widget _buildProgressDropdown(ColorScheme colorScheme) {
final progressListAsync = ref.watch(projectProgressListProvider); final progressListAsync = ref.watch(projectProgressListProvider);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
Text( Text(
'Tiến độ công trình', 'Tiến độ công trình',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
Text( const Text(
' *', ' *',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
@@ -585,14 +596,14 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
initialValue: _selectedProgress, initialValue: _selectedProgress,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
@@ -621,26 +632,26 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
loading: () => Container( loading: () => Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.surfaceContainerHighest),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Row( child: Row(
children: [ children: [
SizedBox( const SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
SizedBox(width: 12), const SizedBox(width: 12),
Text('Đang tải...', style: TextStyle(color: AppColors.grey500)), Text('Đang tải...', style: TextStyle(color: colorScheme.onSurfaceVariant)),
], ],
), ),
), ),
error: (error, _) => Container( error: (error, _) => Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all(color: AppColors.danger), border: Border.all(color: AppColors.danger),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@@ -666,16 +677,16 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildExpectedDateField() { Widget _buildExpectedDateField(ColorScheme colorScheme) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ngày dự kiến khởi công', 'Ngày dự kiến khởi công',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -684,8 +695,8 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
border: Border.all(color: AppColors.grey100), border: Border.all(color: colorScheme.surfaceContainerHighest),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -697,14 +708,14 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
: 'Chọn ngày', : 'Chọn ngày',
style: TextStyle( style: TextStyle(
color: _expectedStartDate != null color: _expectedStartDate != null
? AppColors.grey900 ? colorScheme.onSurface
: AppColors.grey500, : colorScheme.onSurfaceVariant,
), ),
), ),
const FaIcon( FaIcon(
FontAwesomeIcons.calendar, FontAwesomeIcons.calendar,
size: 16, size: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
], ],
), ),
@@ -714,7 +725,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildFileItem(File file, int index) { Widget _buildFileItem(File file, int index, ColorScheme colorScheme) {
final fileName = file.path.split('/').last; final fileName = file.path.split('/').last;
final fileSizeInBytes = file.lengthSync(); final fileSizeInBytes = file.lengthSync();
final fileSizeInMB = (fileSizeInBytes / (1024 * 1024)).toStringAsFixed(2); final fileSizeInMB = (fileSizeInBytes / (1024 * 1024)).toStringAsFixed(2);
@@ -728,13 +739,13 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8F9FA), color: colorScheme.surfaceContainerLowest,
border: Border.all( border: Border.all(
color: hasError color: hasError
? AppColors.danger ? AppColors.danger
: isUploaded : isUploaded
? AppColors.success ? AppColors.success
: AppColors.grey100, : colorScheme.surfaceContainerHighest,
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
@@ -754,11 +765,11 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
return Container( return Container(
width: 48, width: 48,
height: 48, height: 48,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 24, size: 24,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
); );
}, },
@@ -810,9 +821,9 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
children: [ children: [
Text( Text(
fileName, fileName,
style: const TextStyle( style: TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -832,7 +843,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
? AppColors.danger ? AppColors.danger
: isUploaded : isUploaded
? AppColors.success ? AppColors.success
: AppColors.grey500, : colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -857,16 +868,16 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
); );
} }
Widget _buildExistingFileItem(ProjectFile file, int index) { Widget _buildExistingFileItem(ProjectFile file, int index, ColorScheme colorScheme) {
final fileName = file.fileUrl.split('/').last; final fileName = file.fileUrl.split('/').last;
final isDeleting = _deletingFileId == file.id; final isDeleting = _deletingFileId == file.id;
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8F9FA), color: colorScheme.surfaceContainerLowest,
border: Border.all( border: Border.all(
color: isDeleting ? AppColors.grey500 : AppColors.success, color: isDeleting ? colorScheme.onSurfaceVariant : AppColors.success,
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
@@ -885,7 +896,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 48, width: 48,
height: 48, height: 48,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: SizedBox( child: SizedBox(
width: 16, width: 16,
@@ -897,12 +908,12 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 48, width: 48,
height: 48, height: 48,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
size: 24, size: 24,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -939,7 +950,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
fileName, fileName,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: isDeleting ? AppColors.grey500 : AppColors.grey900, color: isDeleting ? colorScheme.onSurfaceVariant : colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -949,7 +960,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
isDeleting ? 'Đang xóa...' : 'Đã tải lên', isDeleting ? 'Đang xóa...' : 'Đã tải lên',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: isDeleting ? AppColors.grey500 : AppColors.success, color: isDeleting ? colorScheme.onSurfaceVariant : AppColors.success,
), ),
), ),
], ],
@@ -980,6 +991,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
/// Show confirmation dialog before deleting an existing file /// Show confirmation dialog before deleting an existing file
Future<void> _showDeleteConfirmDialog(ProjectFile file) async { Future<void> _showDeleteConfirmDialog(ProjectFile file) async {
final fileName = file.fileUrl.split('/').last; final fileName = file.fileUrl.split('/').last;
final colorScheme = Theme.of(context).colorScheme;
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
@@ -1004,7 +1016,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -1019,12 +1031,12 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
width: 48, width: 48,
height: 48, height: 48,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 48, width: 48,
height: 48, height: 48,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center(child: FaIcon(FontAwesomeIcons.image, size: 20)), child: const Center(child: FaIcon(FontAwesomeIcons.image, size: 20)),
), ),
), ),
@@ -1033,9 +1045,9 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
Expanded( Expanded(
child: Text( child: Text(
fileName, fileName,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -1118,17 +1130,17 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
} }
} }
Widget _buildSubmitButton() { Widget _buildSubmitButton(ColorScheme colorScheme) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 48, height: 48,
child: ElevatedButton( child: ElevatedButton(
onPressed: _isSubmitting ? null : _handleSubmit, onPressed: _isSubmitting ? null : _handleSubmit,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.primaryBlue.withValues(alpha: 0.6), disabledBackgroundColor: colorScheme.primary.withValues(alpha: 0.6),
disabledForegroundColor: AppColors.white, disabledForegroundColor: Colors.white,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -1140,7 +1152,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
height: 24, height: 24,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(AppColors.white), valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
), ),
) )
: const Row( : const Row(

View File

@@ -20,25 +20,26 @@ class SubmissionsPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final submissionsAsync = ref.watch(filteredSubmissionsProvider); final submissionsAsync = ref.watch(filteredSubmissionsProvider);
final statusListAsync = ref.watch(projectStatusListProvider); final statusListAsync = ref.watch(projectStatusListProvider);
final filter = ref.watch(submissionsFilterProvider); final filter = ref.watch(submissionsFilterProvider);
final selectedStatus = filter.selectedStatus; final selectedStatus = filter.selectedStatus;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: const Text( title: Text(
'Danh sách Dự án', 'Danh sách Dự án',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
actions: [ actions: [
IconButton( IconButton(
icon: const FaIcon(FontAwesomeIcons.plus, color: Colors.black, size: 20), icon: FaIcon(FontAwesomeIcons.plus, color: colorScheme.onSurface, size: 20),
onPressed: () async { onPressed: () async {
final result = await context.push<bool>(RouteNames.submissionCreate); final result = await context.push<bool>(RouteNames.submissionCreate);
if (result == true) { if (result == true) {
@@ -50,7 +51,7 @@ class SubmissionsPage extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
centerTitle: false, centerTitle: false,
), ),
body: Column( body: Column(
@@ -61,16 +62,16 @@ class SubmissionsPage extends ConsumerWidget {
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mã dự án hoặc tên công trình', hintText: 'Mã dự án hoặc tên công trình',
prefixIcon: const Icon(Icons.search, color: AppColors.grey500), prefixIcon: Icon(Icons.search, color: colorScheme.onSurfaceVariant),
filled: true, filled: true,
fillColor: AppColors.white, fillColor: colorScheme.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100), borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
), ),
), ),
onChanged: (value) { onChanged: (value) {
@@ -88,6 +89,7 @@ class SubmissionsPage extends ConsumerWidget {
_buildFilterChip( _buildFilterChip(
context, context,
ref, ref,
colorScheme,
label: 'Tất cả', label: 'Tất cả',
isSelected: selectedStatus == null, isSelected: selectedStatus == null,
onTap: () => ref.read(submissionsFilterProvider.notifier).clearStatusFilter(), onTap: () => ref.read(submissionsFilterProvider.notifier).clearStatusFilter(),
@@ -101,6 +103,7 @@ class SubmissionsPage extends ConsumerWidget {
child: _buildFilterChip( child: _buildFilterChip(
context, context,
ref, ref,
colorScheme,
label: status.label, label: status.label,
isSelected: selectedStatus == status.label, isSelected: selectedStatus == status.label,
onTap: () => ref.read(submissionsFilterProvider.notifier).selectStatus(status.label), onTap: () => ref.read(submissionsFilterProvider.notifier).selectStatus(status.label),
@@ -130,28 +133,28 @@ class SubmissionsPage extends ConsumerWidget {
children: [ children: [
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * 0.5, height: MediaQuery.of(context).size.height * 0.5,
child: const Center( child: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
FaIcon( FaIcon(
FontAwesomeIcons.folderOpen, FontAwesomeIcons.folderOpen,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Không có dự án nào', 'Không có dự án nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Không tìm thấy dự án phù hợp', 'Không tìm thấy dự án phù hợp',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -171,7 +174,7 @@ class SubmissionsPage extends ConsumerWidget {
itemCount: submissions.length, itemCount: submissions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final submission = submissions[index]; final submission = submissions[index];
return _buildSubmissionCard(context, ref, submission); return _buildSubmissionCard(context, ref, colorScheme, submission);
}, },
), ),
); );
@@ -198,24 +201,24 @@ class SubmissionsPage extends ConsumerWidget {
color: AppColors.danger, color: AppColors.danger,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Có lỗi xảy ra', 'Có lỗi xảy ra',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
error.toString(), error.toString(),
style: const TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'Kéo xuống để thử lại', 'Kéo xuống để thử lại',
style: TextStyle(color: AppColors.grey500), style: TextStyle(color: colorScheme.onSurfaceVariant),
), ),
], ],
), ),
@@ -233,7 +236,8 @@ class SubmissionsPage extends ConsumerWidget {
Widget _buildFilterChip( Widget _buildFilterChip(
BuildContext context, BuildContext context,
WidgetRef ref, { WidgetRef ref,
ColorScheme colorScheme, {
required String label, required String label,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
@@ -243,16 +247,16 @@ class SubmissionsPage extends ConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.white, color: isSelected ? colorScheme.primary : colorScheme.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100, color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
), ),
), ),
child: Text( child: Text(
label, label,
style: TextStyle( style: TextStyle(
color: isSelected ? AppColors.white : AppColors.grey900, color: isSelected ? colorScheme.onPrimary : colorScheme.onSurface,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
), ),
), ),
@@ -260,7 +264,7 @@ class SubmissionsPage extends ConsumerWidget {
); );
} }
Widget _buildSubmissionCard(BuildContext context, WidgetRef ref, ProjectSubmission submission) { Widget _buildSubmissionCard(BuildContext context, WidgetRef ref, ColorScheme colorScheme, ProjectSubmission submission) {
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
elevation: 1, elevation: 1,
@@ -288,13 +292,13 @@ class SubmissionsPage extends ConsumerWidget {
children: [ children: [
Text( Text(
submission.designedArea, submission.designedArea,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
_buildStatusBadge(submission.status, submission.statusColor), _buildStatusBadge(colorScheme, submission.status, submission.statusColor),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -308,17 +312,17 @@ class SubmissionsPage extends ConsumerWidget {
// const SizedBox(height: 4), // const SizedBox(height: 4),
Text( Text(
'Ngày nộp: ${DateFormat('dd/MM/yyyy HH:mm').format(submission.requestDate)}', 'Ngày nộp: ${DateFormat('dd/MM/yyyy HH:mm').format(submission.requestDate)}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Diện tích: ${submission.designArea}', 'Diện tích: ${submission.designArea}',
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (submission.reasonForRejection != null) ...[ if (submission.reasonForRejection != null) ...[
@@ -357,8 +361,8 @@ class SubmissionsPage extends ConsumerWidget {
); );
} }
Widget _buildStatusBadge(String status, String statusColor) { Widget _buildStatusBadge(ColorScheme colorScheme, String status, String statusColor) {
final color = _getColorFromStatusColor(statusColor); final color = _getColorFromStatusColor(colorScheme, statusColor);
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -376,7 +380,7 @@ class SubmissionsPage extends ConsumerWidget {
); );
} }
Color _getColorFromStatusColor(String statusColor) { Color _getColorFromStatusColor(ColorScheme colorScheme, String statusColor) {
switch (statusColor) { switch (statusColor) {
case 'Warning': case 'Warning':
return AppColors.warning; return AppColors.warning;
@@ -387,7 +391,7 @@ class SubmissionsPage extends ConsumerWidget {
case 'Info': case 'Info':
return AppColors.info; return AppColors.info;
default: default:
return AppColors.grey500; return colorScheme.onSurfaceVariant;
} }
} }
} }

View File

@@ -14,6 +14,8 @@ import 'package:intl/intl.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart'; import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart';
import 'package:worker/features/showrooms/presentation/widgets/file_preview_item.dart';
import 'package:worker/features/showrooms/presentation/widgets/form_field_widget.dart';
/// Design Request Create Page /// Design Request Create Page
/// ///
@@ -27,6 +29,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final formKey = useMemoized(() => GlobalKey<FormState>()); final formKey = useMemoized(() => GlobalKey<FormState>());
final projectNameController = useTextEditingController(); final projectNameController = useTextEditingController();
final areaController = useTextEditingController(); final areaController = useTextEditingController();
@@ -227,19 +230,19 @@ class DesignRequestCreatePage extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
backgroundColor: AppColors.grey50, backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
centerTitle: false, centerTitle: false,
title: const Text( title: Text(
'Tạo yêu cầu thiết kế mới', 'Tạo yêu cầu thiết kế mới',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -268,16 +271,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [ children: [
Icon( Icon(
Icons.info_outline, Icons.info_outline,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Thông tin cơ bản', 'Thông tin cơ bản',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -286,7 +289,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Project Name // Project Name
_FormField( FormFieldWidget(
label: 'Tên dự án/Khách hàng', label: 'Tên dự án/Khách hàng',
required: true, required: true,
controller: projectNameController, controller: projectNameController,
@@ -302,7 +305,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Area // Area
_FormField( FormFieldWidget(
label: 'Diện tích (m²)', label: 'Diện tích (m²)',
required: true, required: true,
controller: areaController, controller: areaController,
@@ -323,7 +326,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Location // Location
_FormField( FormFieldWidget(
label: 'Khu vực (Tỉnh/ Thành phố)', label: 'Khu vực (Tỉnh/ Thành phố)',
required: true, required: true,
controller: locationController, controller: locationController,
@@ -343,14 +346,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: const TextSpan( text: TextSpan(
text: 'Phong cách mong muốn', text: 'Phong cách mong muốn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
children: [ children: const [
TextSpan( TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: AppColors.danger), style: TextStyle(color: AppColors.danger),
@@ -367,22 +370,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
hintText: '-- Chọn phong cách --', hintText: '-- Chọn phong cách --',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -444,12 +447,12 @@ class DesignRequestCreatePage extends HookConsumerWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Ngân sách dự kiến', 'Ngân sách dự kiến',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -461,22 +464,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
hintText: '-- Chọn ngân sách --', hintText: '-- Chọn ngân sách --',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -525,14 +528,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: const TextSpan( text: TextSpan(
text: 'Thời hạn mong muốn', text: 'Thời hạn mong muốn',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
children: [ children: const [
TextSpan( TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: AppColors.danger), style: TextStyle(color: AppColors.danger),
@@ -550,22 +553,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
suffixIcon: const Icon(Icons.calendar_today, size: 20), suffixIcon: const Icon(Icons.calendar_today, size: 20),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -606,16 +609,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [ children: [
Icon( Icon(
Icons.edit_outlined, Icons.edit_outlined,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Yêu cầu chi tiết', 'Yêu cầu chi tiết',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -628,14 +631,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RichText( RichText(
text: const TextSpan( text: TextSpan(
text: 'Ghi chú chi tiết', text: 'Ghi chú chi tiết',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
children: [ children: const [
TextSpan( TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: AppColors.danger), style: TextStyle(color: AppColors.danger),
@@ -652,22 +655,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
'Mô tả chi tiết về yêu cầu thiết kế, số phòng, công năng sử dụng, sở thích cá nhân...', 'Mô tả chi tiết về yêu cầu thiết kế, số phòng, công năng sử dụng, sở thích cá nhân...',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.primaryBlue, color: colorScheme.primary,
width: 2, width: 2,
), ),
), ),
@@ -706,16 +709,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [ children: [
Icon( Icon(
Icons.cloud_upload_outlined, Icons.cloud_upload_outlined,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Đính kèm tài liệu', 'Đính kèm tài liệu',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -730,34 +733,34 @@ class DesignRequestCreatePage extends HookConsumerWidget {
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
width: 2, width: 2,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: AppColors.grey50, color: colorScheme.surfaceContainerLowest,
), ),
child: Column( child: Column(
children: [ children: [
Icon( Icon(
Icons.cloud_upload_outlined, Icons.cloud_upload_outlined,
size: 32, size: 32,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'Nhấn để chọn file hoặc kéo thả vào đây', 'Nhấn để chọn file hoặc kéo thả vào đây',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'Hỗ trợ: JPG, PNG, PDF (Tối đa 10MB mỗi file)', 'Hỗ trợ: JPG, PNG, PDF (Tối đa 10MB mỗi file)',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -771,7 +774,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
...selectedFiles.value.asMap().entries.map((entry) { ...selectedFiles.value.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final file = entry.value; final file = entry.value;
return _FilePreviewItem( return FilePreviewItem(
file: file, file: file,
onRemove: () => removeFile(index), onRemove: () => removeFile(index),
); );
@@ -790,10 +793,10 @@ class DesignRequestCreatePage extends HookConsumerWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: isSubmitting.value ? null : submitForm, onPressed: isSubmitting.value ? null : submitForm,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: AppColors.white, foregroundColor: colorScheme.surface,
disabledBackgroundColor: AppColors.primaryBlue.withValues(alpha: 0.7), disabledBackgroundColor: colorScheme.primary.withValues(alpha: 0.7),
disabledForegroundColor: AppColors.white, disabledForegroundColor: colorScheme.surface,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -804,12 +807,12 @@ class DesignRequestCreatePage extends HookConsumerWidget {
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox( SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: AppColors.white, color: colorScheme.surface,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -850,167 +853,3 @@ class DesignRequestCreatePage extends HookConsumerWidget {
} }
} }
/// Progress Step Widget
/// Form Field Widget
class _FormField extends StatelessWidget {
final String label;
final bool required;
final TextEditingController controller;
final String hint;
final TextInputType? keyboardType;
final String? Function(String?)? validator;
const _FormField({
required this.label,
this.required = false,
required this.controller,
required this.hint,
this.keyboardType,
this.validator,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
text: label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
),
children: required
? const [
TextSpan(
text: ' *',
style: TextStyle(color: AppColors.danger),
),
]
: null,
),
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hint,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
validator: validator,
),
],
);
}
}
/// File Preview Item Widget
class _FilePreviewItem extends StatelessWidget {
final PlatformFile file;
final VoidCallback onRemove;
const _FilePreviewItem({required this.file, required this.onRemove});
IconData _getFileIcon() {
final extension = file.extension?.toLowerCase();
if (extension == 'pdf') return Icons.picture_as_pdf;
if (extension == 'jpg' || extension == 'jpeg' || extension == 'png') {
return Icons.image;
}
return Icons.insert_drive_file;
}
String _formatFileSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.grey100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColors.primaryBlue,
borderRadius: BorderRadius.circular(6),
),
child: Icon(_getFileIcon(), color: AppColors.white, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
file.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
_formatFileSize(file.size),
style: const TextStyle(
fontSize: 12,
color: AppColors.grey500,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.close, size: 20),
color: AppColors.danger,
onPressed: onRemove,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
),
],
),
);
}
}

View File

@@ -15,6 +15,9 @@ import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/showrooms/domain/entities/design_request.dart'; import 'package:worker/features/showrooms/domain/entities/design_request.dart';
import 'package:worker/features/showrooms/domain/entities/sample_project.dart'; import 'package:worker/features/showrooms/domain/entities/sample_project.dart';
import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart'; import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart';
import 'package:worker/features/showrooms/presentation/widgets/description_item.dart';
import 'package:worker/features/showrooms/presentation/widgets/file_item.dart';
import 'package:worker/features/showrooms/presentation/widgets/image_viewer_dialog.dart';
/// Design Request Detail Page /// Design Request Detail Page
/// ///
@@ -161,7 +164,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
showDialog<void>( showDialog<void>(
context: context, context: context,
barrierColor: Colors.black87, barrierColor: Colors.black87,
builder: (context) => _ImageViewerDialog( builder: (context) => ImageViewerDialog(
images: images, images: images,
initialIndex: initialIndex, initialIndex: initialIndex,
), ),
@@ -171,21 +174,22 @@ class DesignRequestDetailPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final detailAsync = ref.watch(designRequestDetailProvider(requestId)); final detailAsync = ref.watch(designRequestDetailProvider(requestId));
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.grey50, backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
centerTitle: false, centerTitle: false,
title: const Text( title: Text(
'Chi tiết Yêu cầu', 'Chi tiết Yêu cầu',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -193,7 +197,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
actions: [ actions: [
detailAsync.maybeWhen( detailAsync.maybeWhen(
data: (request) => IconButton( data: (request) => IconButton(
icon: const Icon(Icons.share, color: Colors.black), icon: Icon(Icons.share, color: colorScheme.onSurface),
onPressed: () => _shareRequest(context, request), onPressed: () => _shareRequest(context, request),
), ),
orElse: () => const SizedBox.shrink(), orElse: () => const SizedBox.shrink(),
@@ -226,10 +230,10 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Request ID // Request ID
Text( Text(
'#${request.id}', '#${request.id}',
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -238,9 +242,9 @@ class DesignRequestDetailPage extends ConsumerWidget {
if (request.dateline != null) if (request.dateline != null)
Text( Text(
'Ngày gửi: ${request.dateline}', 'Ngày gửi: ${request.dateline}',
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -269,7 +273,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const Divider(height: 1, color: AppColors.grey100), Divider(height: 1, color: colorScheme.surfaceContainerHighest),
const SizedBox(height: 24), const SizedBox(height: 24),
// Design Information Section // Design Information Section
@@ -277,16 +281,16 @@ class DesignRequestDetailPage extends ConsumerWidget {
children: [ children: [
Icon( Icon(
Icons.info_outline, Icons.info_outline,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Thông tin thiết kế', 'Thông tin thiết kế',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -295,14 +299,14 @@ class DesignRequestDetailPage extends ConsumerWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Description List // Description List
_DescriptionItem( DescriptionItem(
label: 'Tên công trình:', label: 'Tên công trình:',
value: request.subject, value: request.subject,
), ),
if (request.plainDescription.isNotEmpty) ...[ if (request.plainDescription.isNotEmpty) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
_DescriptionItem( DescriptionItem(
label: 'Mô tả chi tiết:', label: 'Mô tả chi tiết:',
value: request.plainDescription, value: request.plainDescription,
isMultiLine: true, isMultiLine: true,
@@ -439,16 +443,16 @@ class DesignRequestDetailPage extends ConsumerWidget {
children: [ children: [
Icon( Icon(
Icons.attach_file, Icons.attach_file,
color: AppColors.primaryBlue, color: colorScheme.primary,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Tài liệu đính kèm', 'Tài liệu đính kèm',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -468,8 +472,8 @@ class DesignRequestDetailPage extends ConsumerWidget {
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => _contactSupport(context), onPressed: () => _contactSupport(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -505,9 +509,9 @@ class DesignRequestDetailPage extends ConsumerWidget {
Text( Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -524,6 +528,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
} }
Widget _buildFilesSection(BuildContext context, List<ProjectFile> files) { Widget _buildFilesSection(BuildContext context, List<ProjectFile> files) {
final colorScheme = Theme.of(context).colorScheme;
// Separate images and other files // Separate images and other files
final images = files.where((f) => _isImageFile(f.fileUrl)).toList(); final images = files.where((f) => _isImageFile(f.fileUrl)).toList();
final otherFiles = files.where((f) => !_isImageFile(f.fileUrl)).toList(); final otherFiles = files.where((f) => !_isImageFile(f.fileUrl)).toList();
@@ -553,13 +558,13 @@ class DesignRequestDetailPage extends ConsumerWidget {
imageUrl: image.fileUrl, imageUrl: image.fileUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon(Icons.error), child: const Icon(Icons.error),
), ),
), ),
@@ -575,7 +580,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Other Files as file items // Other Files as file items
...otherFiles.map( ...otherFiles.map(
(file) => _FileItem( (file) => FileItem(
fileUrl: file.fileUrl, fileUrl: file.fileUrl,
icon: _getFileIcon(file.fileUrl), icon: _getFileIcon(file.fileUrl),
), ),
@@ -584,7 +589,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Show image files as file items too (for download/naming) // Show image files as file items too (for download/naming)
if (images.isNotEmpty && otherFiles.isEmpty) if (images.isNotEmpty && otherFiles.isEmpty)
...images.map( ...images.map(
(file) => _FileItem( (file) => FileItem(
fileUrl: file.fileUrl, fileUrl: file.fileUrl,
icon: Icons.image, icon: Icons.image,
), ),
@@ -593,246 +598,3 @@ class DesignRequestDetailPage extends ConsumerWidget {
); );
} }
} }
/// Description Item Widget
class _DescriptionItem extends StatelessWidget {
const _DescriptionItem({
required this.label,
required this.value,
this.isMultiLine = false,
});
final String label;
final String value;
final bool isMultiLine;
@override
Widget build(BuildContext context) {
if (isMultiLine) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 13,
color: AppColors.grey500,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 15,
color: AppColors.grey900,
fontWeight: FontWeight.w500,
height: 1.6,
),
),
],
);
}
return Container(
padding: const EdgeInsets.only(bottom: 12),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.grey100)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(
fontSize: 13,
color: AppColors.grey500,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 15,
color: AppColors.grey900,
fontWeight: FontWeight.w500,
height: 1.6,
),
),
),
],
),
);
}
}
/// File Item Widget
class _FileItem extends StatelessWidget {
const _FileItem({required this.fileUrl, required this.icon});
final String fileUrl;
final IconData icon;
String get fileName {
final uri = Uri.parse(fileUrl);
return uri.pathSegments.isNotEmpty ? uri.pathSegments.last : fileUrl;
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.grey100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppColors.primaryBlue,
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, color: Colors.white, size: 14),
),
const SizedBox(width: 12),
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
/// Image Viewer Dialog with Swipe Navigation
class _ImageViewerDialog extends StatefulWidget {
const _ImageViewerDialog({
required this.images,
required this.initialIndex,
});
final List<ProjectFile> images;
final int initialIndex;
@override
State<_ImageViewerDialog> createState() => _ImageViewerDialogState();
}
class _ImageViewerDialogState extends State<_ImageViewerDialog> {
late PageController _pageController;
late int _currentIndex;
@override
void initState() {
super.initState();
_currentIndex = widget.initialIndex;
_pageController = PageController(initialPage: widget.initialIndex);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: Container(
color: Colors.black,
child: Stack(
children: [
// Main PageView
Center(
child: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
itemCount: widget.images.length,
itemBuilder: (context, index) {
return Center(
child: CachedNetworkImage(
imageUrl: widget.images[index].fileUrl,
fit: BoxFit.contain,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(color: Colors.white),
),
errorWidget: (context, url, error) => const Icon(
Icons.error,
color: Colors.white,
size: 48,
),
),
);
},
),
),
// Top bar with counter and close button
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.7),
Colors.transparent,
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${_currentIndex + 1} / ${widget.images.length}',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
],
),
),
),
),
],
),
),
);
}
}

View File

@@ -26,29 +26,30 @@ class ModelHouseDetailPage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final detailAsync = ref.watch(sampleProjectDetailProvider(modelId)); final detailAsync = ref.watch(sampleProjectDetailProvider(modelId));
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.arrowLeft, FontAwesomeIcons.arrowLeft,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
title: const Text( title: Text(
'Chi tiết Nhà mẫu', 'Chi tiết Nhà mẫu',
style: TextStyle(color: Colors.black), style: TextStyle(color: colorScheme.onSurface),
), ),
actions: [ actions: [
detailAsync.maybeWhen( detailAsync.maybeWhen(
data: (project) => IconButton( data: (project) => IconButton(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.shareNodes, FontAwesomeIcons.shareNodes,
color: Colors.black, color: colorScheme.onSurface,
size: 20, size: 20,
), ),
onPressed: () => _shareModel(context, project), onPressed: () => _shareModel(context, project),
@@ -58,7 +59,7 @@ class ModelHouseDetailPage extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
centerTitle: false, centerTitle: false,
), ),
body: detailAsync.when( body: detailAsync.when(
@@ -72,7 +73,7 @@ class ModelHouseDetailPage extends ConsumerWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Project Information // Project Information
_buildProjectInfo(project), _buildProjectInfo(context, project),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -102,9 +103,9 @@ class ModelHouseDetailPage extends ConsumerWidget {
Text( Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -299,12 +300,13 @@ class ModelHouseDetailPage extends ConsumerWidget {
); );
} }
Widget _buildProjectInfo(SampleProject project) { Widget _buildProjectInfo(BuildContext context, SampleProject project) {
final colorScheme = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 4), margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -320,10 +322,10 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Title // Title
Text( Text(
project.projectName, project.projectName,
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
if (project.plainDescription.isNotEmpty) ...[ if (project.plainDescription.isNotEmpty) ...[
@@ -331,9 +333,9 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Description // Description
Text( Text(
project.plainDescription, project.plainDescription,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF4b5563), color: colorScheme.onSurfaceVariant,
height: 1.6, height: 1.6,
), ),
), ),
@@ -344,13 +346,14 @@ class ModelHouseDetailPage extends ConsumerWidget {
} }
Widget _buildImageGallery(BuildContext context, SampleProject project) { Widget _buildImageGallery(BuildContext context, SampleProject project) {
final colorScheme = Theme.of(context).colorScheme;
final images = project.filesList; final images = project.filesList;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 4), margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -366,18 +369,18 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Gallery Title // Gallery Title
Row( Row(
children: [ children: [
const FaIcon( FaIcon(
FontAwesomeIcons.images, FontAwesomeIcons.images,
size: 18, size: 18,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Thư viện Hình ảnh (${images.length})', 'Thư viện Hình ảnh (${images.length})',
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
], ],
@@ -405,13 +408,13 @@ class ModelHouseDetailPage extends ConsumerWidget {
imageUrl: image.fileUrl, imageUrl: image.fileUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center( child: const Center(
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon(Icons.error), child: const Icon(Icons.error),
), ),
), ),

View File

@@ -92,41 +92,43 @@ class _ModelHousesPageState extends ConsumerState<ModelHousesPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColors.grey50, backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.white, backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation, elevation: AppBarSpecs.elevation,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
centerTitle: false, centerTitle: false,
title: const Text( title: Text(
'Nhà mẫu', 'Nhà mẫu',
style: TextStyle( style: TextStyle(
color: Colors.black, color: colorScheme.onSurface,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.info_outline, color: Colors.black), icon: Icon(Icons.info_outline, color: colorScheme.onSurface),
onPressed: _showInfoDialog, onPressed: _showInfoDialog,
), ),
const SizedBox(width: AppSpacing.sm), const SizedBox(width: AppSpacing.sm),
], ],
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
indicatorColor: AppColors.primaryBlue, indicatorColor: colorScheme.primary,
indicatorWeight: 3, indicatorWeight: 3,
labelColor: AppColors.primaryBlue, labelColor: colorScheme.primary,
labelStyle: const TextStyle( labelStyle: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
unselectedLabelColor: AppColors.grey500, unselectedLabelColor: colorScheme.onSurfaceVariant,
unselectedLabelStyle: const TextStyle( unselectedLabelStyle: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -148,11 +150,11 @@ class _ModelHousesPageState extends ConsumerState<ModelHousesPage>
return _tabController.index == 1 return _tabController.index == 1
? FloatingActionButton( ? FloatingActionButton(
onPressed: _createNewRequest, onPressed: _createNewRequest,
backgroundColor: AppColors.primaryBlue, backgroundColor: colorScheme.primary,
elevation: 4, elevation: 4,
child: const Icon( child: Icon(
Icons.add, Icons.add,
color: AppColors.white, color: colorScheme.onPrimary,
size: 28, size: 28,
), ),
) )
@@ -169,28 +171,29 @@ class _LibraryTab extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final sampleProjectsAsync = ref.watch(sampleProjectsListProvider); final sampleProjectsAsync = ref.watch(sampleProjectsListProvider);
return sampleProjectsAsync.when( return sampleProjectsAsync.when(
data: (projects) { data: (projects) {
if (projects.isEmpty) { if (projects.isEmpty) {
return const Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(40), padding: const EdgeInsets.all(40),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.home_work_outlined, Icons.home_work_outlined,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Chưa có mẫu nhà nào', 'Chưa có mẫu nhà nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -229,9 +232,9 @@ class _LibraryTab extends ConsumerWidget {
Text( Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -255,6 +258,8 @@ class _LibraryCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: 2, elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -282,26 +287,26 @@ class _LibraryCard extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
height: 200, height: 200,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Center(child: CircularProgressIndicator()), child: const Center(child: CircularProgressIndicator()),
), ),
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
height: 200, height: 200,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
Icons.image_not_supported, Icons.image_not_supported,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
) )
: Container( : Container(
height: 200, height: 200,
color: AppColors.grey100, color: colorScheme.surfaceContainerHighest,
child: const Icon( child: Icon(
Icons.home_work, Icons.home_work,
size: 48, size: 48,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
), ),
@@ -315,13 +320,13 @@ class _LibraryCard extends StatelessWidget {
vertical: 6, vertical: 6,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.9), color: colorScheme.primary.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: const Text( child: Text(
'Xem 360°', 'Xem 360°',
style: TextStyle( style: TextStyle(
color: AppColors.white, color: colorScheme.onPrimary,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -340,10 +345,10 @@ class _LibraryCard extends StatelessWidget {
// Title // Title
Text( Text(
project.projectName, project.projectName,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
@@ -352,9 +357,9 @@ class _LibraryCard extends StatelessWidget {
// Description // Description
Text( Text(
project.plainDescription, project.plainDescription,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
height: 1.5, height: 1.5,
), ),
maxLines: 3, maxLines: 3,
@@ -377,28 +382,29 @@ class _DesignRequestsTab extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final requestsAsync = ref.watch(designRequestsListProvider); final requestsAsync = ref.watch(designRequestsListProvider);
return requestsAsync.when( return requestsAsync.when(
data: (requests) { data: (requests) {
if (requests.isEmpty) { if (requests.isEmpty) {
return const Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(40), padding: const EdgeInsets.all(40),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.design_services_outlined, Icons.design_services_outlined,
size: 64, size: 64,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Chưa có yêu cầu thiết kế nào', 'Chưa có yêu cầu thiết kế nào',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -437,9 +443,9 @@ class _DesignRequestsTab extends ConsumerWidget {
Text( Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: AppColors.grey500, color: colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -489,6 +495,8 @@ class _RequestCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card( return Card(
elevation: 2, elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -510,10 +518,10 @@ class _RequestCard extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
'Mã yêu cầu: #${request.id}', 'Mã yêu cầu: #${request.id}',
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -546,7 +554,7 @@ class _RequestCard extends StatelessWidget {
if (request.dateline != null) if (request.dateline != null)
Text( Text(
'Deadline: ${request.dateline}', 'Deadline: ${request.dateline}',
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -554,10 +562,10 @@ class _RequestCard extends StatelessWidget {
// Subject // Subject
Text( Text(
request.subject, request.subject,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.grey900, color: colorScheme.onSurface,
), ),
), ),
@@ -566,7 +574,7 @@ class _RequestCard extends StatelessWidget {
// Description // Description
Text( Text(
request.plainDescription, request.plainDescription,
style: const TextStyle(fontSize: 14, color: AppColors.grey500), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@@ -0,0 +1,92 @@
/// Widget: Description Item
///
/// Displays a label-value pair for design request details.
library;
import 'package:flutter/material.dart';
/// Description Item Widget
///
/// Shows a label and value pair with:
/// - Inline layout (label: value) for single line
/// - Stacked layout for multi-line values
/// - Theme-aware colors
class DescriptionItem extends StatelessWidget {
const DescriptionItem({
super.key,
required this.label,
required this.value,
this.isMultiLine = false,
});
final String label;
final String value;
final bool isMultiLine;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
if (isMultiLine) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 15,
color: colorScheme.onSurface,
fontWeight: FontWeight.w500,
height: 1.6,
),
),
],
);
}
return Container(
padding: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: colorScheme.surfaceContainerHighest),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: TextStyle(
fontSize: 15,
color: colorScheme.onSurface,
fontWeight: FontWeight.w500,
height: 1.6,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,67 @@
/// Widget: File Item
///
/// Displays a file attachment item with icon and name.
library;
import 'package:flutter/material.dart';
/// File Item Widget
///
/// Shows a file attachment with:
/// - File type icon
/// - File name extracted from URL
/// - Theme-aware styling
class FileItem extends StatelessWidget {
const FileItem({
super.key,
required this.fileUrl,
required this.icon,
});
final String fileUrl;
final IconData icon;
String get fileName {
final uri = Uri.parse(fileUrl);
return uri.pathSegments.isNotEmpty ? uri.pathSegments.last : fileUrl;
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, color: colorScheme.onPrimary, size: 14),
),
const SizedBox(width: 12),
Expanded(
child: Text(
fileName,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,101 @@
/// Widget: File Preview Item
///
/// Displays a file preview with icon, name, size, and remove button.
library;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:worker/core/theme/colors.dart';
/// File Preview Item Widget
///
/// Shows uploaded file with:
/// - File type icon (PDF, image, etc.)
/// - File name
/// - File size
/// - Remove button
class FilePreviewItem extends StatelessWidget {
const FilePreviewItem({
super.key,
required this.file,
required this.onRemove,
});
final PlatformFile file;
final VoidCallback onRemove;
IconData _getFileIcon() {
final extension = file.extension?.toLowerCase();
if (extension == 'pdf') return Icons.picture_as_pdf;
if (extension == 'jpg' || extension == 'jpeg' || extension == 'png') {
return Icons.image;
}
return Icons.insert_drive_file;
}
String _formatFileSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(6),
),
child: Icon(_getFileIcon(), color: colorScheme.surface, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
file.name,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
_formatFileSize(file.size),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.close, size: 20),
color: AppColors.danger,
onPressed: onRemove,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
),
],
),
);
}
}

View File

@@ -0,0 +1,104 @@
/// Widget: Form Field
///
/// Reusable form field with label, validation, and theming support.
library;
import 'package:flutter/material.dart';
import 'package:worker/core/theme/colors.dart';
/// Form Field Widget
///
/// A styled text form field with:
/// - Label with optional required indicator
/// - Hint text
/// - Theme-aware borders
/// - Validation support
class FormFieldWidget extends StatelessWidget {
const FormFieldWidget({
super.key,
required this.label,
this.required = false,
required this.controller,
required this.hint,
this.keyboardType,
this.validator,
});
final String label;
final bool required;
final TextEditingController controller;
final String hint;
final TextInputType? keyboardType;
final String? Function(String?)? validator;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
text: label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
children: required
? const [
TextSpan(
text: ' *',
style: TextStyle(color: AppColors.danger),
),
]
: null,
),
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hint,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
validator: validator,
),
],
);
}
}

View File

@@ -0,0 +1,132 @@
/// Widget: Image Viewer Dialog
///
/// Full-screen image viewer with swipe navigation.
library;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:worker/features/showrooms/domain/entities/sample_project.dart';
/// Image Viewer Dialog
///
/// Full-screen dialog for viewing images with:
/// - PageView for swipe navigation
/// - Image counter (1/5)
/// - Close button
/// - Gradient overlay for visibility
class ImageViewerDialog extends StatefulWidget {
const ImageViewerDialog({
super.key,
required this.images,
required this.initialIndex,
});
final List<ProjectFile> images;
final int initialIndex;
@override
State<ImageViewerDialog> createState() => _ImageViewerDialogState();
}
class _ImageViewerDialogState extends State<ImageViewerDialog> {
late PageController _pageController;
late int _currentIndex;
@override
void initState() {
super.initState();
_currentIndex = widget.initialIndex;
_pageController = PageController(initialPage: widget.initialIndex);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: Container(
color: Colors.black,
child: Stack(
children: [
// Main PageView
Center(
child: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
itemCount: widget.images.length,
itemBuilder: (context, index) {
return Center(
child: CachedNetworkImage(
imageUrl: widget.images[index].fileUrl,
fit: BoxFit.contain,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(color: Colors.white),
),
errorWidget: (context, url, error) => const Icon(
Icons.error,
color: Colors.white,
size: 48,
),
),
);
},
),
),
// Top bar with counter and close button
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.7),
Colors.transparent,
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${_currentIndex + 1} / ${widget.images.length}',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
],
),
),
),
),
],
),
),
);
}
}