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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -166,9 +166,10 @@ class _LoginPageState extends ConsumerState<LoginPage> {
// Watch auth state for loading indicator
final authState = ref.watch(authProvider);
final isPasswordVisible = ref.watch(passwordVisibilityProvider);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: const Color(0xFFF4F6F8),
backgroundColor: colorScheme.surfaceContainerLowest,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.lg),
@@ -185,22 +186,22 @@ class _LoginPageState extends ConsumerState<LoginPage> {
const SizedBox(height: AppSpacing.xl),
// Welcome Message
_buildWelcomeMessage(),
_buildWelcomeMessage(colorScheme),
const SizedBox(height: AppSpacing.xl),
// Login Form Card
_buildLoginForm(authState, isPasswordVisible),
_buildLoginForm(authState, isPasswordVisible, colorScheme),
const SizedBox(height: AppSpacing.lg),
// Register Link
_buildRegisterLink(),
_buildRegisterLink(colorScheme),
const SizedBox(height: AppSpacing.xl),
// Support Link
_buildSupportLink(),
_buildSupportLink(colorScheme),
],
),
@@ -228,7 +229,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Text(
'EUROTILE',
style: TextStyle(
color: AppColors.white,
color: Colors.white,
fontSize: 32.0,
fontWeight: FontWeight.w700,
letterSpacing: 1.5,
@@ -238,7 +239,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Text(
'Worker App',
style: TextStyle(
color: AppColors.white,
color: Colors.white,
fontSize: 12.0,
letterSpacing: 0.5,
),
@@ -250,21 +251,21 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}
/// Build welcome message
Widget _buildWelcomeMessage() {
return const Column(
Widget _buildWelcomeMessage(ColorScheme colorScheme) {
return Column(
children: [
Text(
'Xin chào!',
style: TextStyle(
fontSize: 32.0,
fontWeight: FontWeight.bold,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
SizedBox(height: AppSpacing.xs),
const SizedBox(height: AppSpacing.xs),
Text(
'Đă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(
AsyncValue<dynamic> authState,
bool isPasswordVisible,
ColorScheme colorScheme,
) {
final isLoading = authState.isLoading;
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -314,30 +316,30 @@ class _LoginPageState extends ConsumerState<LoginPage> {
enabled: !isLoading,
obscureText: !isPasswordVisible,
textInputAction: TextInputAction.done,
style: const TextStyle(
style: TextStyle(
fontSize: InputFieldSpecs.fontSize,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
decoration: InputDecoration(
labelText: 'Mật khẩu',
labelStyle: const TextStyle(
labelStyle: TextStyle(
fontSize: InputFieldSpecs.labelFontSize,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
hintText: 'Nhập mật khẩu',
hintStyle: const TextStyle(
hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
prefixIcon: const Icon(
prefixIcon: Icon(
FontAwesomeIcons.lock,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: AppIconSize.md,
),
suffixIcon: IconButton(
icon: Icon(
isPasswordVisible ? FontAwesomeIcons.eye : FontAwesomeIcons.eyeSlash,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: AppIconSize.md,
),
onPressed: () {
@@ -345,14 +347,14 @@ class _LoginPageState extends ConsumerState<LoginPage> {
},
),
filled: true,
fillColor: AppColors.white,
fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius,
),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 1.0,
),
),
@@ -360,8 +362,8 @@ class _LoginPageState extends ConsumerState<LoginPage> {
borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius,
),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 1.0,
),
),
@@ -369,8 +371,8 @@ class _LoginPageState extends ConsumerState<LoginPage> {
borderRadius: BorderRadius.circular(
InputFieldSpecs.borderRadius,
),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2.0,
),
),
@@ -424,7 +426,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
_rememberMe = value ?? false;
});
},
activeColor: AppColors.primaryBlue,
activeColor: colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
@@ -437,11 +439,11 @@ class _LoginPageState extends ConsumerState<LoginPage> {
_rememberMe = !_rememberMe;
});
},
child: const Text(
child: Text(
'Ghi nhớ đăng nhập',
style: TextStyle(
fontSize: 14.0,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -455,7 +457,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
'Quên mật khẩu?',
style: TextStyle(
fontSize: 14.0,
color: isLoading ? AppColors.grey500 : AppColors.primaryBlue,
color: isLoading ? colorScheme.onSurfaceVariant : colorScheme.primary,
fontWeight: FontWeight.w500,
),
),
@@ -471,23 +473,23 @@ class _LoginPageState extends ConsumerState<LoginPage> {
child: ElevatedButton(
onPressed: isLoading ? null : _handleLogin,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
disabledBackgroundColor: AppColors.grey100,
disabledForegroundColor: AppColors.grey500,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor: colorScheme.surfaceContainerHighest,
disabledForegroundColor: colorScheme.onSurfaceVariant,
elevation: ButtonSpecs.elevation,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ButtonSpecs.borderRadius),
),
),
child: isLoading
? const SizedBox(
? SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white,
colorScheme.onPrimary,
),
),
)
@@ -506,21 +508,21 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}
/// Build register link
Widget _buildRegisterLink() {
Widget _buildRegisterLink(ColorScheme colorScheme) {
return Center(
child: RichText(
text: TextSpan(
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: [
WidgetSpan(
child: GestureDetector(
onTap: _navigateToRegister,
child: const Text(
child: Text(
'Đăng ký ngay',
style: TextStyle(
fontSize: 14.0,
color: AppColors.primaryBlue,
color: colorScheme.primary,
fontWeight: FontWeight.w500,
decoration: TextDecoration.none,
),
@@ -534,20 +536,20 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}
/// Build support link
Widget _buildSupportLink() {
Widget _buildSupportLink(ColorScheme colorScheme) {
return Center(
child: TextButton.icon(
onPressed: _showSupport,
icon: const Icon(
icon: Icon(
FontAwesomeIcons.headset,
size: AppIconSize.sm,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
label: const Text(
label: Text(
'Hỗ trợ khách hàng',
style: TextStyle(
fontSize: 14.0,
color: AppColors.primaryBlue,
color: colorScheme.primary,
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/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
///
@@ -237,19 +237,21 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: AppColors.grey50,
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
backgroundColor: AppColors.white,
backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation,
leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20),
icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'Xác thực OTP',
style: TextStyle(
color: Colors.black,
color: colorScheme.onSurface,
fontSize: 18,
fontWeight: FontWeight.w600,
),
@@ -276,14 +278,14 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.primaryBlue, AppColors.lightBlue],
colors: [AppColors.primaryBlue, AppColors.lightBlue], // Keep brand colors
),
shape: BoxShape.circle,
),
child: const Icon(
child: Icon(
FontAwesomeIcons.shieldHalved,
size: 36,
color: AppColors.white,
color: colorScheme.onPrimary,
),
),
),
@@ -291,24 +293,24 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
const SizedBox(height: AppSpacing.lg),
// Instructions
const Text(
Text(
'Nhập mã xác thực',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 12),
const Text(
Text(
'Mã OTP đã được gửi đến số điện thoại',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
@@ -317,10 +319,10 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
Text(
_formatPhoneNumber(widget.phoneNumber),
textAlign: TextAlign.center,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
@@ -329,7 +331,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
// OTP Input Card
Container(
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -351,7 +353,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
padding: EdgeInsets.only(
left: index > 0 ? 8 : 0,
),
child: _buildOtpInput(index),
child: _buildOtpInput(index, colorScheme),
),
),
),
@@ -365,8 +367,8 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
child: ElevatedButton(
onPressed: _isLoading ? null : _handleVerifyOtp,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
@@ -375,13 +377,13 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
),
),
child: _isLoading
? const SizedBox(
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white,
colorScheme.onPrimary,
),
),
)
@@ -405,9 +407,9 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
child: Text.rich(
TextSpan(
text: 'Không nhận được mã? ',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
children: [
WidgetSpan(
@@ -421,8 +423,8 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
fontSize: 12,
fontWeight: FontWeight.w500,
color: _countdown > 0
? AppColors.grey500
: AppColors.primaryBlue,
? colorScheme.onSurfaceVariant
: colorScheme.primary,
decoration: _countdown == 0
? TextDecoration.none
: TextDecoration.none,
@@ -445,7 +447,7 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
}
/// Build single OTP input box
Widget _buildOtpInput(int index) {
Widget _buildOtpInput(int index, ColorScheme colorScheme) {
return SizedBox(
width: 48,
height: 48,
@@ -455,10 +457,10 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
style: const TextStyle(
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
@@ -468,20 +470,20 @@ class _OtpVerificationPageState extends ConsumerState<OtpVerificationPage> {
contentPadding: EdgeInsets.zero,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
filled: false,
fillColor: AppColors.white,
fillColor: colorScheme.surface,
),
onChanged: (value) => _onOtpChanged(index, value),
onTap: () {

View File

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

View File

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

View File

@@ -77,25 +77,27 @@ class FileUploadCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
if (file != null) {
// Show preview with remove option
return _buildPreview(context);
return _buildPreview(context, colorScheme);
} else {
// Show upload area
return _buildUploadArea(context);
return _buildUploadArea(context, colorScheme);
}
}
/// Build upload area
Widget _buildUploadArea(BuildContext context) {
Widget _buildUploadArea(BuildContext context, ColorScheme colorScheme) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.lg),
child: Container(
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
border: Border.all(
color: const Color(0xFFCBD5E1),
color: colorScheme.outlineVariant,
width: 2,
strokeAlign: BorderSide.strokeAlignInside,
),
@@ -105,16 +107,16 @@ class FileUploadCard extends StatelessWidget {
child: Column(
children: [
// Icon
Icon(icon, size: 32, color: AppColors.grey500),
Icon(icon, size: 32, color: colorScheme.onSurfaceVariant),
const SizedBox(height: AppSpacing.sm),
// Title
Text(
title,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.xs),
@@ -122,7 +124,7 @@ class FileUploadCard extends StatelessWidget {
// Subtitle
Text(
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
Widget _buildPreview(BuildContext context) {
Widget _buildPreview(BuildContext context, ColorScheme colorScheme) {
return Container(
decoration: BoxDecoration(
color: AppColors.white,
border: Border.all(color: AppColors.grey100, width: 1),
color: colorScheme.surface,
border: Border.all(color: colorScheme.surfaceContainerHighest, width: 1),
borderRadius: BorderRadius.circular(AppRadius.md),
),
padding: const EdgeInsets.all(AppSpacing.sm),
@@ -153,10 +155,10 @@ class FileUploadCard extends StatelessWidget {
return Container(
width: 50,
height: 50,
color: AppColors.grey100,
child: const Icon(
color: colorScheme.surfaceContainerHighest,
child: Icon(
FontAwesomeIcons.image,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: 24,
),
);
@@ -173,10 +175,10 @@ class FileUploadCard extends StatelessWidget {
children: [
Text(
_getFileName(file!.path),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -188,9 +190,9 @@ class FileUploadCard extends StatelessWidget {
if (snapshot.hasData) {
return Text(
_formatFileSize(snapshot.data!),
style: const TextStyle(
style: TextStyle(
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:font_awesome_flutter/font_awesome_flutter.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
///
@@ -65,6 +65,8 @@ class PhoneInputField extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return TextFormField(
controller: controller,
focusNode: focusNode,
@@ -78,41 +80,41 @@ class PhoneInputField extends StatelessWidget {
// Limit to reasonable phone length
LengthLimitingTextInputFormatter(15),
],
style: const TextStyle(
style: TextStyle(
fontSize: InputFieldSpecs.fontSize,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
decoration: InputDecoration(
labelText: 'Số điện thoại',
labelStyle: const TextStyle(
labelStyle: TextStyle(
fontSize: InputFieldSpecs.labelFontSize,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
hintText: 'Nhập số điện thoại',
hintStyle: const TextStyle(
hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
prefixIcon: const Icon(
prefixIcon: Icon(
FontAwesomeIcons.phone,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: AppIconSize.md,
),
filled: true,
fillColor: AppColors.white,
fillColor: colorScheme.surface,
contentPadding: InputFieldSpecs.contentPadding,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0),
borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(color: AppColors.grey100, width: 1.0),
borderSide: BorderSide(color: colorScheme.surfaceContainerHighest, width: 1.0),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2.0,
),
),

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:worker/core/theme/colors.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_state.dart';
@@ -74,6 +73,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final cartState = ref.watch(cartProvider);
final isSelected =
cartState.selectedItems[widget.item.product.productId] ?? false;
@@ -88,7 +88,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
@@ -127,7 +127,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
placeholder: (context, url) => Container(
width: 100,
height: 100,
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
@@ -135,10 +135,10 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
errorWidget: (context, url, error) => Container(
width: 100,
height: 100,
color: AppColors.grey100,
child: const FaIcon(
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: 32,
),
),
@@ -169,7 +169,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
Text(
'${currencyFormatter.format(widget.item.product.basePrice)}/m²',
style: AppTypography.titleMedium.copyWith(
color: AppColors.primaryBlue,
color: colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 16,
),
@@ -209,22 +209,22 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Color(0xFFE0E0E0),
borderSide: BorderSide(
color: colorScheme.outlineVariant,
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Color(0xFFE0E0E0),
borderSide: BorderSide(
color: colorScheme.outlineVariant,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
@@ -254,7 +254,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
Text(
'',
style: AppTypography.bodySmall.copyWith(
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -266,7 +266,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
RichText(
text: TextSpan(
style: AppTypography.bodySmall.copyWith(
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
fontSize: 13,
),
children: [
@@ -305,24 +305,25 @@ class _CustomCheckbox extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => onChanged?.call(!value),
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: value ? AppColors.primaryBlue : AppColors.white,
color: value ? colorScheme.primary : colorScheme.surface,
border: Border.all(
color: value ? AppColors.primaryBlue : const Color(0xFFCBD5E1),
color: value ? colorScheme.primary : colorScheme.outlineVariant,
width: 2,
),
borderRadius: BorderRadius.circular(6),
),
child: value
? const Icon(
? Icon(
FontAwesomeIcons.check,
size: 14,
color: AppColors.white,
color: colorScheme.surface,
)
: null,
),
@@ -341,6 +342,7 @@ class _QuantityButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(6),
@@ -348,11 +350,11 @@ class _QuantityButton extends StatelessWidget {
width: 32,
height: 32,
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0), width: 2),
border: Border.all(color: colorScheme.outlineVariant, width: 2),
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:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Checkout Date Picker Field
///
@@ -24,15 +23,17 @@ class CheckoutDatePickerField extends HookWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Ngày nhận hàng mong muốn',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
@@ -51,9 +52,9 @@ class CheckoutDatePickerField extends HookWidget {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFF8FAFC),
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.input),
border: Border.all(color: const Color(0xFFE2E8F0)),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -65,14 +66,14 @@ class CheckoutDatePickerField extends HookWidget {
style: TextStyle(
fontSize: 14,
color: selectedDate.value != null
? const Color(0xFF212121)
: AppColors.grey500.withValues(alpha: 0.6),
? colorScheme.onSurface
: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
),
),
const Icon(
Icon(
FontAwesomeIcons.calendar,
size: 20,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
],
),

View File

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

View File

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

View File

@@ -32,16 +32,18 @@ class CheckoutTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
text: label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
color: colorScheme.onSurface,
),
children: [
if (required)
@@ -61,27 +63,27 @@ class CheckoutTextField extends StatelessWidget {
decoration: InputDecoration(
hintText: hintText ?? 'Nhập $label',
hintStyle: TextStyle(
color: AppColors.grey500.withValues(alpha: 0.6),
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
fontSize: 14,
),
filled: true,
fillColor: const Color(0xFFF8FAFC),
fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
borderSide: BorderSide(color: colorScheme.outlineVariant),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
borderSide: BorderSide(color: colorScheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
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:hooks_riverpod/hooks_riverpod.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/presentation/providers/address_provider.dart';
import 'package:worker/features/cart/presentation/widgets/checkout_date_picker_field.dart';
@@ -33,6 +32,8 @@ class DeliveryInformationSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Watch the default address
final defaultAddr = ref.watch(defaultAddressProvider);
@@ -54,7 +55,7 @@ class DeliveryInformationSection extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -70,18 +71,18 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Section Title
Row(
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.truck,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 16,
),
const SizedBox(width: AppSpacing.sm),
const Text(
Text(
'Thông tin giao hàng',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
],
@@ -93,12 +94,12 @@ class DeliveryInformationSection extends HookConsumerWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Địa chỉ nhận hàng',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Color(0xFF424242),
color: colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.sm),
@@ -125,7 +126,7 @@ class DeliveryInformationSection extends HookConsumerWidget {
child: Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0)),
border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Row(
@@ -137,10 +138,10 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Name
Text(
selectedAddress.value!.addressTitle,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
@@ -148,9 +149,9 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Phone
Text(
selectedAddress.value!.phone,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 2),
@@ -158,19 +159,19 @@ class DeliveryInformationSection extends HookConsumerWidget {
// Address
Text(
selectedAddress.value!.fullAddress,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(width: AppSpacing.sm),
const FaIcon(
FaIcon(
FontAwesomeIcons.chevronRight,
size: 14,
color: Color(0xFF9E9E9E),
color: colorScheme.onSurfaceVariant,
),
],
),
@@ -194,26 +195,26 @@ class DeliveryInformationSection extends HookConsumerWidget {
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.primaryBlue,
color: colorScheme.primary,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: const Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FaIcon(
FontAwesomeIcons.plus,
size: 14,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
SizedBox(width: AppSpacing.sm),
const SizedBox(width: AppSpacing.sm),
Text(
'Thêm địa chỉ giao hàng',
style: TextStyle(
fontSize: 14,
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:hooks_riverpod/hooks_riverpod.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';
/// Invoice Section
@@ -22,6 +21,8 @@ class InvoiceSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Watch the default address
final defaultAddr = ref.watch(defaultAddressProvider);
@@ -29,7 +30,7 @@ class InvoiceSection extends HookConsumerWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -45,19 +46,19 @@ class InvoiceSection extends HookConsumerWidget {
// Header with Toggle
Row(
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.fileInvoice,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 16,
),
const SizedBox(width: AppSpacing.sm),
const Expanded(
Expanded(
child: Text(
'Phát hành hóa đơn',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
),
@@ -67,7 +68,7 @@ class InvoiceSection extends HookConsumerWidget {
onChanged: (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)
if (needsInvoice.value) ...[
const SizedBox(height: AppSpacing.md),
const Divider(color: Color(0xFFE0E0E0)),
Divider(color: colorScheme.outlineVariant),
const SizedBox(height: AppSpacing.md),
// Address Card
@@ -89,7 +90,7 @@ class InvoiceSection extends HookConsumerWidget {
child: Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE0E0E0)),
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Row(
@@ -101,10 +102,10 @@ class InvoiceSection extends HookConsumerWidget {
// Company/Address Title
Text(
defaultAddr.addressTitle,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
@@ -114,9 +115,9 @@ class InvoiceSection extends HookConsumerWidget {
defaultAddr.taxCode!.isNotEmpty) ...[
Text(
'Mã số thuế: ${defaultAddr.taxCode}',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 2),
@@ -125,9 +126,9 @@ class InvoiceSection extends HookConsumerWidget {
// Phone
Text(
'Số điện thoại: ${defaultAddr.phone}',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 2),
@@ -137,9 +138,9 @@ class InvoiceSection extends HookConsumerWidget {
defaultAddr.email!.isNotEmpty) ...[
Text(
'Email: ${defaultAddr.email}',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 2),
@@ -148,19 +149,19 @@ class InvoiceSection extends HookConsumerWidget {
// Address
Text(
'Địa chỉ: ${defaultAddr.fullAddress}',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF757575),
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(width: AppSpacing.sm),
const FaIcon(
FaIcon(
FontAwesomeIcons.chevronRight,
size: 14,
color: Color(0xFF9E9E9E),
color: colorScheme.onSurfaceVariant,
),
],
),
@@ -177,26 +178,26 @@ class InvoiceSection extends HookConsumerWidget {
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.primaryBlue,
color: colorScheme.primary,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: const Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FaIcon(
FontAwesomeIcons.plus,
size: 14,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
SizedBox(width: AppSpacing.sm),
const SizedBox(width: AppSpacing.sm),
Text(
'Thêm địa chỉ xuất hóa đơn',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
],

View File

@@ -29,11 +29,13 @@ class OrderSummarySection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -47,32 +49,32 @@ class OrderSummarySection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Title
const Text(
Text(
'Tóm tắt đơn hàng',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.md),
// Cart Items with conversion details
...cartItems.map((item) => _buildCartItemWithConversion(item)),
...cartItems.map((item) => _buildCartItemWithConversion(context, item)),
const Divider(height: 32),
// Subtotal
_buildSummaryRow('Tạm tính', subtotal),
_buildSummaryRow(context, 'Tạm tính', subtotal),
const SizedBox(height: 8),
// 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),
// 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),
@@ -80,20 +82,20 @@ class OrderSummarySection extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
Text(
'Tổng thanh toán',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
Text(
_formatCurrency(total),
style: const TextStyle(
style: TextStyle(
fontSize: 18,
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
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
final quantity = item['quantity'] as double;
final quantityConverted = item['quantityConverted'] as double;
@@ -125,10 +129,10 @@ class OrderSummarySection extends StatelessWidget {
// Line 1: Product name
Text(
item['name'] as String,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -137,9 +141,9 @@ class OrderSummarySection extends StatelessWidget {
// Line 2: Conversion details (muted text)
Text(
'${quantity.toStringAsFixed(2)} m² ($boxes viên / ${quantityConverted.toStringAsFixed(2)} m²)',
style: const TextStyle(
style: TextStyle(
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
Text(
_formatCurrency(price * quantityConverted),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
],
@@ -164,24 +168,27 @@ class OrderSummarySection extends StatelessWidget {
/// Build summary row
Widget _buildSummaryRow(
BuildContext context,
String label,
double amount, {
bool isDiscount = false,
bool isFree = false,
}) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
),
Text(
isFree ? 'Miễn phí' : _formatCurrency(amount),
style: TextStyle(
fontSize: 14,
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:font_awesome_flutter/font_awesome_flutter.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';
/// Payment Method Section
@@ -25,13 +24,15 @@ class PaymentMethodSection extends HookWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Show empty state if no payment terms available
if (paymentTerms.isEmpty) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -41,12 +42,12 @@ class PaymentMethodSection extends HookWidget {
),
],
),
child: const Center(
child: Center(
child: Text(
'Không có phương thức thanh toán khả dụng',
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -57,7 +58,7 @@ class PaymentMethodSection extends HookWidget {
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
@@ -71,12 +72,12 @@ class PaymentMethodSection extends HookWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Title
const Text(
Text(
'Phương thức thanh toán',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
@@ -109,12 +110,12 @@ class PaymentMethodSection extends HookWidget {
onChanged: (value) {
paymentMethod.value = value!;
},
activeColor: AppColors.primaryBlue,
activeColor: colorScheme.primary,
),
const SizedBox(width: 12),
Icon(
icon,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: 24,
),
const SizedBox(width: 12),
@@ -132,9 +133,9 @@ class PaymentMethodSection extends HookWidget {
const SizedBox(height: 4),
Text(
term.customDescription,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],

View File

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

View File

@@ -12,7 +12,7 @@ import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:shimmer/shimmer.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/products/domain/entities/product.dart';
@@ -76,6 +76,8 @@ class FavoriteProductCard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
return Card(
elevation: ProductCardSpecs.elevation,
shape: RoundedRectangleBorder(
@@ -101,16 +103,16 @@ class FavoriteProductCard extends ConsumerWidget {
memCacheWidth: ImageSpecs.productImageCacheWidth,
memCacheHeight: ImageSpecs.productImageCacheHeight,
placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100,
highlightColor: AppColors.grey50,
child: Container(color: AppColors.grey100),
baseColor: colorScheme.surfaceContainerHighest,
highlightColor: colorScheme.surfaceContainerLowest,
child: Container(color: colorScheme.surfaceContainerHighest),
),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
child: const FaIcon(
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
size: 48.0,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -122,7 +124,7 @@ class FavoriteProductCard extends ConsumerWidget {
right: AppSpacing.sm,
child: Container(
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@@ -176,9 +178,9 @@ class FavoriteProductCard extends ConsumerWidget {
if (product.erpnextItemCode != null)
Text(
'Mã: ${product.erpnextItemCode}',
style: const TextStyle(
style: TextStyle(
fontSize: 12.0,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -189,10 +191,10 @@ class FavoriteProductCard extends ConsumerWidget {
// Price
Text(
_formatPrice(product.effectivePrice),
style: const TextStyle(
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
@@ -208,9 +210,9 @@ class FavoriteProductCard extends ConsumerWidget {
context.push('/products/${product.productId}');
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
side: const BorderSide(
color: AppColors.primaryBlue,
foregroundColor: colorScheme.primary,
side: BorderSide(
color: colorScheme.primary,
width: 1.5,
),
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:qr_flutter/qr_flutter.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';
/// Loyalty Page
@@ -24,18 +23,19 @@ class LoyaltyPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final loyaltyPoints = ref.watch(loyaltyPointsProvider);
return Scaffold(
backgroundColor: const Color(0xFFF4F6F8),
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
title: const Text(
title: Text(
'Hội viên thân thiết',
style: TextStyle(color: Colors.black),
style: TextStyle(color: colorScheme.onSurface),
),
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
centerTitle: false,
automaticallyImplyLeading: false,
),
@@ -50,17 +50,17 @@ class LoyaltyPage extends ConsumerWidget {
const SizedBox(height: 16),
// Progress Card
_buildProgressCard(),
_buildProgressCard(colorScheme),
const SizedBox(height: 16),
// Loyalty Features Menu
..._buildLoyaltyMenu(context),
..._buildLoyaltyMenu(context, colorScheme),
const SizedBox(height: 16),
// Current Benefits Card
_buildBenefitsCard(),
_buildBenefitsCard(colorScheme),
],
),
),
@@ -201,7 +201,7 @@ class LoyaltyPage extends ConsumerWidget {
}
/// Build Progress Card
Widget _buildProgressCard() {
Widget _buildProgressCard(ColorScheme colorScheme) {
return Card(
elevation: 5,
margin: EdgeInsets.zero,
@@ -211,12 +211,12 @@ class LoyaltyPage extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Tiến trình lên hạng',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 16),
@@ -227,11 +227,11 @@ class LoyaltyPage extends ConsumerWidget {
children: [
Text(
'Hạng hiện tại: DIAMOND',
style: TextStyle(fontSize: 13, color: AppColors.grey500),
style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
),
Text(
'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(
value: 0.65,
minHeight: 8,
backgroundColor: AppColors.grey100,
backgroundColor: colorScheme.surfaceContainerHighest,
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF4A00E0),
),
@@ -257,18 +257,18 @@ class LoyaltyPage extends ConsumerWidget {
Center(
child: RichText(
textAlign: TextAlign.center,
text: const TextSpan(
style: TextStyle(fontSize: 13, color: AppColors.grey500),
text: TextSpan(
style: TextStyle(fontSize: 13, color: colorScheme.onSurfaceVariant),
children: [
TextSpan(text: 'Còn '),
const TextSpan(text: 'Còn '),
TextSpan(
text: '2,250 điểm',
style: TextStyle(
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
List<Widget> _buildLoyaltyMenu(BuildContext context) {
List<Widget> _buildLoyaltyMenu(BuildContext context, ColorScheme colorScheme) {
final menuItems = [
{
'icon': FontAwesomeIcons.gift,
@@ -326,7 +326,7 @@ class LoyaltyPage extends ConsumerWidget {
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: AppColors.grey100),
side: BorderSide(color: colorScheme.surfaceContainerHighest),
),
child: InkWell(
onTap: () {
@@ -348,12 +348,12 @@ class LoyaltyPage extends ConsumerWidget {
width: 48,
height: 48,
decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.1),
color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
item['icon'] as IconData,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 24,
),
),
@@ -367,18 +367,18 @@ class LoyaltyPage extends ConsumerWidget {
children: [
Text(
item['title'] as String,
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 2),
Text(
item['subtitle'] as String,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -386,9 +386,9 @@ class LoyaltyPage extends ConsumerWidget {
),
// Arrow
const Icon(
Icon(
FontAwesomeIcons.chevronRight,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: 20,
),
],
@@ -400,7 +400,7 @@ class LoyaltyPage extends ConsumerWidget {
}
/// Build Benefits Card
Widget _buildBenefitsCard() {
Widget _buildBenefitsCard(ColorScheme colorScheme) {
return Card(
elevation: 1,
margin: EdgeInsets.zero,
@@ -410,22 +410,23 @@ class LoyaltyPage extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Quyền lợi hạng Diamond',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 16),
_buildBenefitItem('Chiết khấu 15% cho tất cả sản phẩm'),
_buildBenefitItem('Giao hàng miễn phí cho đơn từ 5 triệu'),
_buildBenefitItem('Ưu tiên xử lý đơn hàng'),
_buildBenefitItem('Tặng 500 điểm vào ngày sinh nhật'),
_buildBenefitItem('Tư vấn thiết kế miễn phí'),
_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', colorScheme),
_buildBenefitItem('Ưu tiên xử lý đơn hàng', colorScheme),
_buildBenefitItem('Tặng 500 điểm vào ngày sinh nhật', colorScheme),
_buildBenefitItem('Tư vấn thiết kế miễn phí', colorScheme),
_buildBenefitItem(
'Mời tham gia sự kiện VIP độc quyền',
colorScheme,
isLast: true,
),
],
@@ -435,7 +436,7 @@ class LoyaltyPage extends ConsumerWidget {
}
/// Build Benefit Item
Widget _buildBenefitItem(String text, {bool isLast = false}) {
Widget _buildBenefitItem(String text, ColorScheme colorScheme, {bool isLast = false}) {
return Padding(
padding: EdgeInsets.only(bottom: isLast ? 0 : 12),
child: Row(
@@ -446,9 +447,9 @@ class LoyaltyPage extends ConsumerWidget {
Expanded(
child: Text(
text,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.4,
),
),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.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/presentation/providers/news_provider.dart';
@@ -42,14 +41,14 @@ class CategoryFilterChips extends ConsumerWidget {
final categoriesAsync = ref.watch(blogCategoriesProvider);
return categoriesAsync.when(
data: (categories) => _buildCategoryChips(categories),
loading: () => _buildLoadingState(),
error: (error, stack) => _buildErrorState(error, ref),
data: (categories) => _buildCategoryChips(context, categories),
loading: () => _buildLoadingState(context),
error: (error, stack) => _buildErrorState(context, error, ref),
);
}
/// Build category chips with data
Widget _buildCategoryChips(List<BlogCategory> categories) {
Widget _buildCategoryChips(BuildContext context, List<BlogCategory> categories) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -57,6 +56,7 @@ class CategoryFilterChips extends ConsumerWidget {
children: [
// "Tất cả" chip
_buildCategoryChip(
context,
label: 'Tất cả',
isSelected: selectedCategoryName == null,
onTap: () => onCategorySelected(null),
@@ -69,6 +69,7 @@ class CategoryFilterChips extends ConsumerWidget {
return Padding(
padding: const EdgeInsets.only(right: AppSpacing.sm),
child: _buildCategoryChip(
context,
label: category.title,
isSelected: selectedCategoryName == category.name,
onTap: () => onCategorySelected(category.name),
@@ -81,7 +82,9 @@ class CategoryFilterChips extends ConsumerWidget {
}
/// Build loading state with shimmer placeholders
Widget _buildLoadingState() {
Widget _buildLoadingState(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -93,7 +96,7 @@ class CategoryFilterChips extends ConsumerWidget {
width: 80,
height: 32,
decoration: BoxDecoration(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
),
),
@@ -104,7 +107,9 @@ class CategoryFilterChips extends ConsumerWidget {
}
/// 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(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
@@ -116,25 +121,25 @@ class CategoryFilterChips extends ConsumerWidget {
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
FaIcon(FontAwesomeIcons.circleExclamation, size: 16, color: AppColors.grey500),
FaIcon(FontAwesomeIcons.circleExclamation, size: 16, color: colorScheme.onSurfaceVariant),
const SizedBox(width: AppSpacing.xs),
Text(
'Lỗi tải danh mục',
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: AppSpacing.xs),
GestureDetector(
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
Widget _buildCategoryChip({
Widget _buildCategoryChip(
BuildContext context, {
required String label,
required bool isSelected,
required VoidCallback onTap,
}) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
@@ -158,7 +166,7 @@ class CategoryFilterChips extends ConsumerWidget {
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100,
color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
),
child: Text(
@@ -166,7 +174,7 @@ class CategoryFilterChips extends ConsumerWidget {
style: TextStyle(
fontSize: 14,
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:font_awesome_flutter/font_awesome_flutter.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';
/// Featured News Card
@@ -32,14 +31,16 @@ class FeaturedNewsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: const Color(0xFFE2E8F0)),
border: Border.all(color: colorScheme.outlineVariant),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
@@ -63,16 +64,16 @@ class FeaturedNewsCard extends StatelessWidget {
fit: BoxFit.cover,
placeholder: (context, url) => Container(
height: 200,
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(child: CircularProgressIndicator()),
),
errorWidget: (context, url, error) => Container(
height: 200,
color: AppColors.grey100,
child: const FaIcon(
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
size: 48,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -87,10 +88,10 @@ class FeaturedNewsCard extends StatelessWidget {
// Title
Text(
article.title,
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
color: colorScheme.onSurface,
height: 1.4,
),
),
@@ -100,9 +101,9 @@ class FeaturedNewsCard extends StatelessWidget {
// Excerpt
Text(
article.excerpt,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Color(0xFF64748B),
color: colorScheme.onSurfaceVariant,
height: 1.5,
),
maxLines: 3,
@@ -123,18 +124,21 @@ class FeaturedNewsCard extends StatelessWidget {
children: [
// Date
_buildMetaItem(
context,
icon: FontAwesomeIcons.calendar,
text: article.formattedDate,
),
// // Views
// _buildMetaItem(
// context,
// icon: Icons.visibility,
// text: '${article.formattedViewCount} lượt xem',
// ),
//
// // Reading time
// _buildMetaItem(
// context,
// icon: Icons.schedule,
// text: article.readingTimeText,
// ),
@@ -149,15 +153,15 @@ class FeaturedNewsCard extends StatelessWidget {
vertical: 4,
),
decoration: BoxDecoration(
color: AppColors.primaryBlue,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(16),
),
child: Text(
article.category.displayName,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
color: colorScheme.onPrimary,
),
),
),
@@ -173,15 +177,17 @@ class FeaturedNewsCard extends StatelessWidget {
}
/// 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(
mainAxisSize: MainAxisSize.min,
children: [
FaIcon(icon, size: 12, color: const Color(0xFF64748B)),
FaIcon(icon, size: 12, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 4),
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:font_awesome_flutter/font_awesome_flutter.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';
/// News Card
@@ -31,15 +30,17 @@ class NewsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFE2E8F0)),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -55,7 +56,7 @@ class NewsCard extends StatelessWidget {
placeholder: (context, url) => Container(
width: 80,
height: 80,
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: SizedBox(
width: 20,
@@ -67,11 +68,11 @@ class NewsCard extends StatelessWidget {
errorWidget: (context, url, error) => Container(
width: 80,
height: 80,
color: AppColors.grey100,
child: const FaIcon(
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
size: 24,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -87,10 +88,10 @@ class NewsCard extends StatelessWidget {
// Title (max 2 lines)
Text(
article.title,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
color: colorScheme.onSurface,
height: 1.3,
),
maxLines: 2,
@@ -102,9 +103,9 @@ class NewsCard extends StatelessWidget {
// Excerpt (max 2 lines)
Text(
article.excerpt,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
color: colorScheme.onSurfaceVariant,
height: 1.4,
),
maxLines: 2,
@@ -117,17 +118,17 @@ class NewsCard extends StatelessWidget {
Row(
children: [
// Date
const FaIcon(
FaIcon(
FontAwesomeIcons.calendar,
size: 12,
color: Color(0xFF64748B),
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
article.formattedDate,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
color: colorScheme.onSurfaceVariant,
),
),
@@ -137,14 +138,14 @@ class NewsCard extends StatelessWidget {
// Icon(
// Icons.visibility,
// size: 12,
// color: const Color(0xFF64748B),
// color: colorScheme.onSurfaceVariant,
// ),
// const SizedBox(width: 4),
// Text(
// '${article.formattedViewCount} lượt xem',
// style: const TextStyle(
// style: TextStyle(
// 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:font_awesome_flutter/font_awesome_flutter.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';
/// Related Article Card
@@ -31,15 +30,17 @@ class RelatedArticleCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: const Color(0xFFE2E8F0)),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
children: [
@@ -54,7 +55,7 @@ class RelatedArticleCard extends StatelessWidget {
placeholder: (context, url) => Container(
width: 60,
height: 60,
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: SizedBox(
width: 16,
@@ -66,11 +67,11 @@ class RelatedArticleCard extends StatelessWidget {
errorWidget: (context, url, error) => Container(
width: 60,
height: 60,
color: AppColors.grey100,
child: const FaIcon(
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
size: 20,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -86,10 +87,10 @@ class RelatedArticleCard extends StatelessWidget {
// Title (max 2 lines)
Text(
article.title,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
color: colorScheme.onSurface,
height: 1.3,
),
maxLines: 2,
@@ -101,9 +102,9 @@ class RelatedArticleCard extends StatelessWidget {
// Metadata
Text(
'${article.formattedDate}${article.formattedViewCount} lượt xem',
style: const TextStyle(
style: TextStyle(
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
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final now = DateTime.now();
final dateFormat = DateFormat('dd/MM/yyyy HH:mm');
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: colorScheme.surface,
body: SafeArea(
child: Center(
child: SingleChildScrollView(
@@ -67,10 +68,10 @@ class OrderSuccessPage extends StatelessWidget {
isNegotiation
? 'Gửi yêu cầu thành công!'
: 'Tạo đơn hàng thành công!',
style: const TextStyle(
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
@@ -82,9 +83,9 @@ class OrderSuccessPage extends StatelessWidget {
isNegotiation
? '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ờ.',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
@@ -96,7 +97,7 @@ class OrderSuccessPage extends StatelessWidget {
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: const Color(0xFFF4F6F8),
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.card),
),
child: Column(
@@ -104,20 +105,20 @@ class OrderSuccessPage extends StatelessWidget {
// Order Number
Column(
children: [
const Text(
Text(
'Mã đơn hàng',
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
Text(
orderNumber,
style: const TextStyle(
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
],
@@ -127,6 +128,7 @@ class OrderSuccessPage extends StatelessWidget {
// Order Date
_buildInfoRow(
context,
'Ngày đặt',
dateFormat.format(now),
),
@@ -136,12 +138,13 @@ class OrderSuccessPage extends StatelessWidget {
// Total Amount
if (total != null)
_buildInfoRow(
context,
'Tổng tiền',
_formatCurrency(total!),
valueStyle: const TextStyle(
valueStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
@@ -150,6 +153,7 @@ class OrderSuccessPage extends StatelessWidget {
// Payment Method
if (paymentMethod != null && !isNegotiation)
_buildInfoRow(
context,
'Phương thức thanh toán',
paymentMethod!,
),
@@ -159,14 +163,13 @@ class OrderSuccessPage extends StatelessWidget {
// Status
_buildInfoRow(
context,
'Trạng thái',
isNegotiation ? 'Chờ đàm phán' : 'Chờ xác nhận',
valueStyle: TextStyle(
valueStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isNegotiation
? AppColors.warning
: AppColors.warning,
color: AppColors.warning,
),
),
],
@@ -195,8 +198,8 @@ class OrderSuccessPage extends StatelessWidget {
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0,
shape: RoundedRectangleBorder(
@@ -225,9 +228,9 @@ class OrderSuccessPage extends StatelessWidget {
),
),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.grey900,
foregroundColor: colorScheme.onSurface,
side: BorderSide(
color: AppColors.grey100,
color: colorScheme.outlineVariant,
width: 1.5,
),
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -247,26 +250,29 @@ class OrderSuccessPage extends StatelessWidget {
/// Build info row
Widget _buildInfoRow(
BuildContext context,
String label,
String value, {
TextStyle? valueStyle,
}) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
Text(
value,
style: valueStyle ??
const TextStyle(
TextStyle(
fontSize: 14,
color: Color(0xFF212121),
color: colorScheme.onSurface,
),
),
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ library;
import 'package:flutter/material.dart';
import 'package:intl/intl.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';
/// Order Card Widget
@@ -23,6 +22,7 @@ class OrderCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final currencyFormatter = NumberFormat.currency(
locale: 'vi_VN',
symbol: 'đ',
@@ -49,20 +49,20 @@ class OrderCard extends StatelessWidget {
// Order number
Text(
'#${order.name}',
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
// Amount
Text(
currencyFormatter.format(order.grandTotal),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
],
@@ -71,13 +71,21 @@ class OrderCard extends StatelessWidget {
const SizedBox(height: 12),
// Order details
_buildDetailRow('Ngày đặt:', _formatDate(order.transactionDate)),
_buildDetailRow(
context,
'Ngày đặt:',
_formatDate(order.transactionDate),
),
const SizedBox(height: 6),
_buildDetailRow('Ngày giao:', _formatDate(order.deliveryDate)),
_buildDetailRow(
context,
'Ngày giao:',
_formatDate(order.deliveryDate),
),
const SizedBox(height: 6),
_buildDetailRow('Địa chỉ:', order.address),
_buildDetailRow(context, 'Địa chỉ:', order.address),
const SizedBox(height: 12),
// Status badge
@@ -90,19 +98,27 @@ class OrderCard extends StatelessWidget {
}
/// 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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
style: TextStyle(
fontSize: 14,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
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
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: AppColors.grey50,
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'Chính sách giá',
style: TextStyle(color: Colors.black),
style: TextStyle(color: colorScheme.onSurface),
),
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
centerTitle: false,
actions: [
IconButton(
icon: const Icon(Icons.info_outline, color: Colors.black),
icon: Icon(Icons.info_outline, color: colorScheme.onSurface),
onPressed: _showInfoDialog,
),
const SizedBox(width: AppSpacing.sm),
@@ -69,16 +71,16 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
child: Container(
height: 40,
decoration: BoxDecoration(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.white,
unselectedLabelColor: AppColors.grey900,
labelColor: colorScheme.surface,
unselectedLabelColor: colorScheme.onSurface,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
color: AppColors.primaryBlue,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(8),
),
dividerColor: Colors.transparent,
@@ -115,24 +117,25 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
}
Widget _buildDocumentList(DocumentCategory category) {
final colorScheme = Theme.of(context).colorScheme;
final documentsAsync = ref.watch(filteredPriceDocumentsProvider(category));
return documentsAsync.when(
data: (documents) {
if (documents.isEmpty) {
return const Center(
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.description_outlined,
size: 64,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
SizedBox(height: AppSpacing.md),
const SizedBox(height: AppSpacing.md),
Text(
'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(
mainAxisAlignment: MainAxisAlignment.center,
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),
Text(
'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),
ElevatedButton(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ class ProductCard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
// final isFavorited = ref.watch(isFavoriteProvider(product.productId));
final colorScheme = Theme.of(context).colorScheme;
return Card(
elevation: ProductCardSpecs.elevation,
@@ -62,23 +62,25 @@ class ProductCard extends ConsumerWidget {
top: Radius.circular(ProductCardSpecs.borderRadius),
),
child: CachedNetworkImage(
imageUrl: product.thumbnail ?? '',
imageUrl: product.thumbnail,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
memCacheWidth: ImageSpecs.productImageCacheWidth,
memCacheHeight: ImageSpecs.productImageCacheHeight,
placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100,
highlightColor: AppColors.grey50,
child: Container(color: AppColors.grey100),
baseColor: colorScheme.surfaceContainerHighest,
highlightColor: colorScheme.surfaceContainerLowest,
child: Container(
color: colorScheme.surfaceContainerHighest,
),
),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
child: const Icon(
color: colorScheme.surfaceContainerHighest,
child: Icon(
FontAwesomeIcons.image,
size: 48.0,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -170,7 +172,7 @@ class ProductCard extends ConsumerWidget {
// width: 36,
// height: 36,
// decoration: BoxDecoration(
// color: AppColors.white,
// color: colorScheme.surface,
// shape: BoxShape.circle,
// boxShadow: [
// BoxShadow(
@@ -186,7 +188,7 @@ class ProductCard extends ConsumerWidget {
// : Icons.favorite_border,
// color: isFavorited
// ? AppColors.danger
// : AppColors.grey500,
// : colorScheme.onSurfaceVariant,
// size: 20,
// ),
// ),
@@ -221,10 +223,10 @@ class ProductCard extends ConsumerWidget {
// Price
Text(
'${_formatPrice(product.effectivePrice)}/m²',
style: const TextStyle(
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
@@ -241,8 +243,8 @@ class ProductCard extends ConsumerWidget {
child: ElevatedButton.icon(
onPressed: onAddToCart,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
@@ -254,11 +256,12 @@ class ProductCard extends ConsumerWidget {
),
),
icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 14.0),
label: const Text(
label: Text(
'Thêm vào giỏ',
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
color: colorScheme.surface
),
),
),
@@ -283,9 +286,9 @@ class ProductCard extends ConsumerWidget {
);
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
side: const BorderSide(
color: AppColors.primaryBlue,
foregroundColor: colorScheme.primary,
side: BorderSide(
color: colorScheme.primary,
width: 1.5,
),
elevation: 0,
@@ -299,11 +302,12 @@ class ProductCard extends ConsumerWidget {
),
),
icon: const FaIcon(FontAwesomeIcons.cube, size: 14.0),
label: const Text(
label: Text(
'Phối cảnh 360°',
style: TextStyle(
fontSize: 12.0,
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:shimmer/shimmer.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';
/// Image Gallery Section
@@ -75,10 +74,11 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final images = widget.product.images;
return Container(
color: AppColors.white,
color: colorScheme.surface,
child: Column(
children: [
// Main Image with PageView
@@ -102,16 +102,16 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
imageUrl: images[index],
fit: BoxFit.cover,
placeholder: (context, url) => Shimmer.fromColors(
baseColor: AppColors.grey100,
highlightColor: AppColors.grey50,
child: Container(color: AppColors.grey100),
baseColor: colorScheme.surfaceContainerHighest,
highlightColor: colorScheme.surface,
child: Container(color: colorScheme.surfaceContainerHighest),
),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
child: const Icon(
color: colorScheme.surfaceContainerHighest,
child: Icon(
FontAwesomeIcons.image,
size: 64,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -144,10 +144,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
builder: (context, double value, child) {
return Transform.rotate(
angle: value * 2 * 3.14159,
child: const Icon(
child: Icon(
FontAwesomeIcons.arrowsRotate,
size: 10,
color: AppColors.white,
color: colorScheme.surface,
),
);
},
@@ -157,10 +157,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
},
),
const SizedBox(width: 6),
const Text(
Text(
'360°',
style: TextStyle(
color: AppColors.white,
color: colorScheme.surface,
fontSize: 12,
fontWeight: FontWeight.w600,
),
@@ -189,8 +189,8 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentImageIndex == index
? AppColors.white
: AppColors.white.withAlpha(128),
? colorScheme.surface
: colorScheme.surface.withAlpha(128),
),
),
),
@@ -228,10 +228,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
padding: const EdgeInsets.only(bottom: 4),
child: Text(
imageName,
style: const TextStyle(
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -246,7 +246,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isActive
? AppColors.primaryBlue
? colorScheme.primary
: Colors.transparent,
width: 2,
),
@@ -257,13 +257,13 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
imageUrl: imageUrl,
fit: BoxFit.cover,
placeholder: (context, url) =>
Container(color: AppColors.grey100),
Container(color: colorScheme.surfaceContainerHighest),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
child: const Icon(
color: colorScheme.surfaceContainerHighest,
child: Icon(
FontAwesomeIcons.image,
size: 20,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -336,11 +336,13 @@ class _ImageLightboxState extends State<_ImageLightbox> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
foregroundColor: AppColors.white,
foregroundColor: colorScheme.surface,
elevation: 0,
leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.xmark, size: 28),
@@ -348,7 +350,7 @@ class _ImageLightboxState extends State<_ImageLightbox> {
),
title: Text(
'${_currentIndex + 1} / ${widget.images.length}',
style: const TextStyle(color: AppColors.white, fontSize: 16),
style: TextStyle(color: colorScheme.surface, fontSize: 16),
),
),
body: Stack(
@@ -370,9 +372,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
child: CachedNetworkImage(
imageUrl: widget.images[index],
fit: BoxFit.contain,
errorWidget: (context, url, error) => const Icon(
errorWidget: (context, url, error) => Icon(
FontAwesomeIcons.circleExclamation,
color: AppColors.white,
color: colorScheme.surface,
size: 64,
),
),
@@ -391,9 +393,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
bottom: 0,
child: Center(
child: IconButton(
icon: const Icon(
icon: Icon(
FontAwesomeIcons.chevronLeft,
color: AppColors.white,
color: colorScheme.surface,
size: 32,
),
onPressed: _previousImage,
@@ -412,9 +414,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
bottom: 0,
child: Center(
child: IconButton(
icon: const Icon(
icon: Icon(
FontAwesomeIcons.chevronRight,
color: AppColors.white,
color: colorScheme.surface,
size: 32,
),
onPressed: _nextImage,
@@ -443,7 +445,7 @@ class _ImageLightboxState extends State<_ImageLightbox> {
),
child: Text(
widget.imageCaptions[widget.images[_currentIndex]] ?? '',
style: const TextStyle(color: AppColors.white, fontSize: 16),
style: TextStyle(color: colorScheme.surface, fontSize: 16),
textAlign: TextAlign.center,
),
),

View File

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

View File

@@ -50,21 +50,23 @@ class _ProductTabsSectionState extends State<ProductTabsSection>
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
color: AppColors.white,
color: colorScheme.surface,
child: Column(
children: [
// Tab Navigation
Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0), width: 1),
bottom: BorderSide(color: colorScheme.outlineVariant, width: 1),
),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primaryBlue,
unselectedLabelColor: AppColors.grey500,
labelColor: colorScheme.primary,
unselectedLabelColor: colorScheme.onSurfaceVariant,
labelStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -73,7 +75,7 @@ class _ProductTabsSectionState extends State<ProductTabsSection>
fontSize: 14,
fontWeight: FontWeight.w500,
),
indicatorColor: AppColors.primaryBlue,
indicatorColor: colorScheme.primary,
indicatorWeight: 2,
tabs: const [
Tab(text: 'Thông số'),
@@ -104,18 +106,20 @@ class _DescriptionTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Main description
const Text(
Text(
'Bộ sưu tập Cao cấp',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 12),
@@ -123,22 +127,22 @@ class _DescriptionTab extends StatelessWidget {
Text(
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.',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
height: 1.6,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 20),
// Features heading
const Text(
Text(
'Đặc điểm nổi bật:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 12),
@@ -165,9 +169,9 @@ class _DescriptionTab extends StatelessWidget {
Expanded(
child: Text(
feature,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
),
@@ -179,22 +183,22 @@ class _DescriptionTab extends StatelessWidget {
const SizedBox(height: 20),
// Application section
const Text(
Text(
'Ứng dụng:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
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.',
style: TextStyle(
fontSize: 14,
height: 1.6,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -211,6 +215,8 @@ class _SpecificationsTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Default specifications if not available
final specs = product.specifications.isNotEmpty
? product.specifications
@@ -230,15 +236,15 @@ class _SpecificationsTab extends StatelessWidget {
return Container(
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFe0e0e0)),
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)),
bottom: BorderSide(color: colorScheme.outlineVariant),
),
),
child: IntrinsicHeight(
@@ -249,15 +255,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8),
color: colorScheme.surface,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Thương hiệu",
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
),
@@ -268,7 +274,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider
Container(
width: 1,
color: const Color(0xFFe0e0e0),
color: colorScheme.outlineVariant,
),
// Value
@@ -277,9 +283,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text(
'${product.brand}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
softWrap: true,
@@ -291,9 +297,9 @@ class _SpecificationsTab extends StatelessWidget {
),
),
Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)),
bottom: BorderSide(color: colorScheme.outlineVariant),
),
),
child: IntrinsicHeight(
@@ -304,15 +310,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8),
child: const Align(
color: colorScheme.surface,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Dòng sản phẩm",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
),
@@ -323,7 +329,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider
Container(
width: 1,
color: const Color(0xFFe0e0e0),
color: colorScheme.outlineVariant,
),
// Value
@@ -332,9 +338,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text(
'${product.itemGroupName}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
softWrap: true,
@@ -351,8 +357,8 @@ class _SpecificationsTab extends StatelessWidget {
decoration: BoxDecoration(
border: isLast
? null
: const Border(
bottom: BorderSide(color: Color(0xFFe0e0e0)),
: Border(
bottom: BorderSide(color: colorScheme.outlineVariant),
),
),
child: IntrinsicHeight(
@@ -363,15 +369,15 @@ class _SpecificationsTab extends StatelessWidget {
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: const Color(0xFFF4F6F8),
color: colorScheme.surface,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
entry.key,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
),
@@ -382,7 +388,7 @@ class _SpecificationsTab extends StatelessWidget {
// Divider
Container(
width: 1,
color: const Color(0xFFe0e0e0),
color: colorScheme.outlineVariant,
),
// Value
@@ -391,9 +397,9 @@ class _SpecificationsTab extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Text(
'${entry.value}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
height: 1.5,
),
softWrap: true,
@@ -419,6 +425,7 @@ class _ReviewsTab extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final reviewsAsync = ref.watch(productReviewsProvider(productId));
final avgRatingAsync = ref.watch(productAverageRatingProvider(productId));
@@ -437,7 +444,7 @@ class _ReviewsTab extends ConsumerWidget {
data: (reviews) {
if (reviews.isEmpty) {
// Empty state
return _buildEmptyState();
return _buildEmptyState(colorScheme);
}
return Column(
@@ -445,9 +452,9 @@ class _ReviewsTab extends ConsumerWidget {
children: [
// Rating Overview
avgRatingAsync.when(
data: (avgRating) => _buildRatingOverview(reviews, avgRating),
loading: () => _buildRatingOverview(reviews, 0),
error: (_, __) => _buildRatingOverview(reviews, 0),
data: (avgRating) => _buildRatingOverview(colorScheme, reviews, avgRating),
loading: () => _buildRatingOverview(colorScheme, reviews, 0),
error: (_, __) => _buildRatingOverview(colorScheme, reviews, 0),
),
const SizedBox(height: 24),
@@ -459,22 +466,22 @@ class _ReviewsTab extends ConsumerWidget {
],
);
},
loading: () => const Center(
loading: () => Center(
child: Padding(
padding: EdgeInsets.all(40),
padding: const EdgeInsets.all(40),
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(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
@@ -486,10 +493,10 @@ class _ReviewsTab extends ConsumerWidget {
// Rating Score
Text(
avgRating.toStringAsFixed(2),
style: const TextStyle(
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
color: AppColors.primaryBlue,
color: colorScheme.primary,
),
),
@@ -505,19 +512,19 @@ class _ReviewsTab extends ConsumerWidget {
if (index < avgRating.floor()) {
return const Icon(
FontAwesomeIcons.solidStar,
color: Color(0xFFffc107),
color: AppColors.warning,
size: 18,
);
} else if (index < avgRating) {
return const Icon(
FontAwesomeIcons.starHalfStroke,
color: Color(0xFFffc107),
color: AppColors.warning,
size: 18,
);
} else {
return const Icon(
FontAwesomeIcons.star,
color: Color(0xFFffc107),
color: AppColors.warning,
size: 18,
);
}
@@ -529,9 +536,9 @@ class _ReviewsTab extends ConsumerWidget {
// Review count
Text(
'${reviews.length} đánh giá',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -541,33 +548,33 @@ class _ReviewsTab extends ConsumerWidget {
);
}
Widget _buildEmptyState() {
return const Center(
Widget _buildEmptyState(ColorScheme colorScheme) {
return Center(
child: Padding(
padding: EdgeInsets.all(40),
padding: const EdgeInsets.all(40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FontAwesomeIcons.commentSlash,
size: 48,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
SizedBox(height: 16),
const SizedBox(height: 16),
Text(
'Chưa có đánh giá nào',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
Text(
'Hãy là người đầu tiên đánh giá sản phẩm này',
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
@@ -577,7 +584,7 @@ class _ReviewsTab extends ConsumerWidget {
);
}
Widget _buildErrorState(String error) {
Widget _buildErrorState(ColorScheme colorScheme, String error) {
return Center(
child: Padding(
padding: const EdgeInsets.all(40),
@@ -590,20 +597,20 @@ class _ReviewsTab extends ConsumerWidget {
color: AppColors.danger,
),
const SizedBox(height: 16),
const Text(
Text(
'Không thể tải đánh giá',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
Text(
error.length > 100 ? '${error.substring(0, 100)}...' : error,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
@@ -622,11 +629,13 @@ class _ReviewItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.only(bottom: 16),
margin: const EdgeInsets.only(bottom: 16),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFFe0e0e0))),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: colorScheme.outlineVariant)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -642,9 +651,9 @@ class _ReviewItem extends StatelessWidget {
shape: BoxShape.circle,
color: Color(0xFFF4F6F8),
),
child: const Icon(
child: Icon(
FontAwesomeIcons.solidUser,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: 20,
),
),
@@ -658,17 +667,17 @@ class _ReviewItem extends StatelessWidget {
children: [
Text(
review.reviewerName ?? 'Người dùng',
style: const TextStyle(
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
if (review.reviewDate != null)
Text(
_formatDate(review.reviewDate!),
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -687,7 +696,7 @@ class _ReviewItem extends StatelessWidget {
index < review.starsRating
? FontAwesomeIcons.solidStar
: FontAwesomeIcons.star,
color: const Color(0xFFffc107),
color: AppColors.warning,
size: 14,
),
),
@@ -698,10 +707,10 @@ class _ReviewItem extends StatelessWidget {
// Review Text
Text(
review.comment,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
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:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
/// Sticky Action Bar
///
@@ -61,11 +60,13 @@ class StickyActionBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
color: AppColors.white,
border: const Border(
top: BorderSide(color: Color(0xFFe0e0e0), width: 1),
color: colorScheme.surface,
border: Border(
top: BorderSide(color: colorScheme.outlineVariant, width: 1),
),
boxShadow: [
BoxShadow(
@@ -88,9 +89,9 @@ class StickyActionBar extends StatelessWidget {
// Label
Text(
'Số lượng ($unit)',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
@@ -101,7 +102,7 @@ class StickyActionBar extends StatelessWidget {
Container(
width: 142,
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFe0e0e0)),
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(8),
),
child: Row(
@@ -152,9 +153,9 @@ class StickyActionBar extends StatelessWidget {
if (_getConversionText().isNotEmpty)
Text(
_getConversionText(),
style: const TextStyle(
style: TextStyle(
fontSize: 11,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -167,8 +168,8 @@ class StickyActionBar extends StatelessWidget {
child: ElevatedButton.icon(
onPressed: onAddToCart,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 12,
@@ -179,11 +180,12 @@ class StickyActionBar extends StatelessWidget {
),
),
icon: const FaIcon(FontAwesomeIcons.cartShopping, size: 18),
label: const Text(
label: Text(
'Thêm vào giỏ hàng',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface
),
),
),
@@ -204,17 +206,19 @@ class _QuantityButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
width: 40,
height: 40,
child: Material(
color: const Color(0xFFF4F6F8),
color: colorScheme.surfaceContainerHighest,
child: InkWell(
onTap: onPressed,
child: Icon(
icon,
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:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:worker/core/theme/colors.dart';
/// Write Review Button
///
/// Displays a prominent button for users to write a review:
/// - Primary blue background
/// - Primary background from theme
/// - Edit icon
/// - Text: "Viết đánh giá của bạn"
/// - Navigates to WriteReviewPage with productId
@@ -26,6 +25,8 @@ class WriteReviewButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 20),
@@ -35,28 +36,28 @@ class WriteReviewButton extends StatelessWidget {
context.push('/products/$productId/write-review');
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
backgroundColor: colorScheme.primary,
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 28),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 0,
),
child: const Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FontAwesomeIcons.penToSquare,
size: 16,
color: AppColors.white,
color: colorScheme.onPrimary,
),
SizedBox(width: 10),
const SizedBox(width: 10),
Text(
'Viết đánh giá của bạn',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.white,
color: colorScheme.onPrimary,
),
),
],

View File

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

View File

@@ -11,6 +11,7 @@ import 'package:worker/features/products/presentation/widgets/product_card.dart'
/// Product Grid Widget
///
/// Displays products in a 2-column grid layout with scroll-to-load-more.
/// Fully theme-compliant using Material 3 ColorScheme.
class ProductGrid extends StatefulWidget {
final List<Product> products;
final void Function(Product)? onProductTap;
@@ -61,6 +62,8 @@ class _ProductGridState extends State<ProductGrid> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GridView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(AppSpacing.xs),
@@ -74,10 +77,12 @@ class _ProductGridState extends State<ProductGrid> {
itemBuilder: (context, index) {
// Show loading indicator at the end
if (index == widget.products.length) {
return const Center(
return Center(
child: Padding(
padding: EdgeInsets.all(AppSpacing.md),
child: CircularProgressIndicator(),
padding: const EdgeInsets.all(AppSpacing.md),
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:font_awesome_flutter/font_awesome_flutter.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/generated/l10n/app_localizations.dart';
@@ -62,6 +61,7 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
height: InputFieldSpecs.height,
@@ -71,27 +71,27 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
onChanged: _onSearchChanged,
decoration: InputDecoration(
hintText: l10n.searchProducts,
hintStyle: const TextStyle(
hintStyle: TextStyle(
fontSize: InputFieldSpecs.hintFontSize,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
prefixIcon: const Icon(
prefixIcon: Icon(
FontAwesomeIcons.magnifyingGlass,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: AppIconSize.md,
),
suffixIcon: _hasText
? IconButton(
icon: const Icon(
icon: Icon(
FontAwesomeIcons.xmark,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
size: AppIconSize.md,
),
onPressed: _onClearSearch,
)
: null,
filled: true,
fillColor: Colors.white,
fillColor: colorScheme.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: BorderSide.none,
@@ -102,8 +102,8 @@ class _ProductSearchBarState extends ConsumerState<ProductSearchBar> {
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2.0,
),
),

View File

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

View File

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

View File

@@ -20,25 +20,26 @@ class SubmissionsPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final submissionsAsync = ref.watch(filteredSubmissionsProvider);
final statusListAsync = ref.watch(projectStatusListProvider);
final filter = ref.watch(submissionsFilterProvider);
final selectedStatus = filter.selectedStatus;
return Scaffold(
backgroundColor: const Color(0xFFF4F6F8),
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
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(),
),
title: const Text(
title: Text(
'Danh sách Dự án',
style: TextStyle(color: Colors.black),
style: TextStyle(color: colorScheme.onSurface),
),
actions: [
IconButton(
icon: const FaIcon(FontAwesomeIcons.plus, color: Colors.black, size: 20),
icon: FaIcon(FontAwesomeIcons.plus, color: colorScheme.onSurface, size: 20),
onPressed: () async {
final result = await context.push<bool>(RouteNames.submissionCreate);
if (result == true) {
@@ -50,7 +51,7 @@ class SubmissionsPage extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm),
],
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
backgroundColor: colorScheme.surface,
centerTitle: false,
),
body: Column(
@@ -61,16 +62,16 @@ class SubmissionsPage extends ConsumerWidget {
child: TextField(
decoration: InputDecoration(
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,
fillColor: AppColors.white,
fillColor: colorScheme.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100),
borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100),
borderSide: BorderSide(color: colorScheme.surfaceContainerHighest),
),
),
onChanged: (value) {
@@ -88,6 +89,7 @@ class SubmissionsPage extends ConsumerWidget {
_buildFilterChip(
context,
ref,
colorScheme,
label: 'Tất cả',
isSelected: selectedStatus == null,
onTap: () => ref.read(submissionsFilterProvider.notifier).clearStatusFilter(),
@@ -101,6 +103,7 @@ class SubmissionsPage extends ConsumerWidget {
child: _buildFilterChip(
context,
ref,
colorScheme,
label: status.label,
isSelected: selectedStatus == status.label,
onTap: () => ref.read(submissionsFilterProvider.notifier).selectStatus(status.label),
@@ -130,28 +133,28 @@ class SubmissionsPage extends ConsumerWidget {
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: const Center(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FaIcon(
FontAwesomeIcons.folderOpen,
size: 64,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
SizedBox(height: 16),
const SizedBox(height: 16),
Text(
'Không có dự án nào',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
Text(
'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,
itemBuilder: (context, 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,
),
const SizedBox(height: 16),
const Text(
Text(
'Có lỗi xảy ra',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
Text(
error.toString(),
style: const TextStyle(color: AppColors.grey500),
style: TextStyle(color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
const Text(
Text(
'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(
BuildContext context,
WidgetRef ref, {
WidgetRef ref,
ColorScheme colorScheme, {
required String label,
required bool isSelected,
required VoidCallback onTap,
@@ -243,16 +247,16 @@ class SubmissionsPage extends ConsumerWidget {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.white,
color: isSelected ? colorScheme.primary : colorScheme.surface,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected ? AppColors.primaryBlue : AppColors.grey100,
color: isSelected ? colorScheme.primary : colorScheme.surfaceContainerHighest,
),
),
child: Text(
label,
style: TextStyle(
color: isSelected ? AppColors.white : AppColors.grey900,
color: isSelected ? colorScheme.onPrimary : colorScheme.onSurface,
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(
margin: const EdgeInsets.only(bottom: 12),
elevation: 1,
@@ -288,13 +292,13 @@ class SubmissionsPage extends ConsumerWidget {
children: [
Text(
submission.designedArea,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
_buildStatusBadge(submission.status, submission.statusColor),
_buildStatusBadge(colorScheme, submission.status, submission.statusColor),
],
),
const SizedBox(height: 8),
@@ -308,17 +312,17 @@ class SubmissionsPage extends ConsumerWidget {
// const SizedBox(height: 4),
Text(
'Ngày nộp: ${DateFormat('dd/MM/yyyy HH:mm').format(submission.requestDate)}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
'Diện tích: ${submission.designArea}',
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
if (submission.reasonForRejection != null) ...[
@@ -357,8 +361,8 @@ class SubmissionsPage extends ConsumerWidget {
);
}
Widget _buildStatusBadge(String status, String statusColor) {
final color = _getColorFromStatusColor(statusColor);
Widget _buildStatusBadge(ColorScheme colorScheme, String status, String statusColor) {
final color = _getColorFromStatusColor(colorScheme, statusColor);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
@@ -376,7 +380,7 @@ class SubmissionsPage extends ConsumerWidget {
);
}
Color _getColorFromStatusColor(String statusColor) {
Color _getColorFromStatusColor(ColorScheme colorScheme, String statusColor) {
switch (statusColor) {
case 'Warning':
return AppColors.warning;
@@ -387,7 +391,7 @@ class SubmissionsPage extends ConsumerWidget {
case 'Info':
return AppColors.info;
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/theme/colors.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
///
@@ -27,6 +29,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final formKey = useMemoized(() => GlobalKey<FormState>());
final projectNameController = useTextEditingController();
final areaController = useTextEditingController();
@@ -227,19 +230,19 @@ class DesignRequestCreatePage extends HookConsumerWidget {
}
return Scaffold(
backgroundColor: AppColors.grey50,
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
backgroundColor: AppColors.white,
backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => Navigator.of(context).pop(),
),
centerTitle: false,
title: const Text(
title: Text(
'Tạo yêu cầu thiết kế mới',
style: TextStyle(
color: Colors.black,
color: colorScheme.onSurface,
fontSize: 20,
fontWeight: FontWeight.w600,
),
@@ -268,16 +271,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [
Icon(
Icons.info_outline,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'Thông tin cơ bản',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -286,7 +289,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20),
// Project Name
_FormField(
FormFieldWidget(
label: 'Tên dự án/Khách hàng',
required: true,
controller: projectNameController,
@@ -302,7 +305,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20),
// Area
_FormField(
FormFieldWidget(
label: 'Diện tích (m²)',
required: true,
controller: areaController,
@@ -323,7 +326,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
const SizedBox(height: 20),
// Location
_FormField(
FormFieldWidget(
label: 'Khu vực (Tỉnh/ Thành phố)',
required: true,
controller: locationController,
@@ -343,14 +346,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: const TextSpan(
text: TextSpan(
text: 'Phong cách mong muốn',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
children: [
children: const [
TextSpan(
text: ' *',
style: TextStyle(color: AppColors.danger),
@@ -367,22 +370,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
hintText: '-- Chọn phong cách --',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
@@ -444,12 +447,12 @@ class DesignRequestCreatePage extends HookConsumerWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Ngân sách dự kiến',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
@@ -461,22 +464,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
hintText: '-- Chọn ngân sách --',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
@@ -525,14 +528,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: const TextSpan(
text: TextSpan(
text: 'Thời hạn mong muốn',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
children: [
children: const [
TextSpan(
text: ' *',
style: TextStyle(color: AppColors.danger),
@@ -550,22 +553,22 @@ class DesignRequestCreatePage extends HookConsumerWidget {
suffixIcon: const Icon(Icons.calendar_today, size: 20),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
@@ -606,16 +609,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [
Icon(
Icons.edit_outlined,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'Yêu cầu chi tiết',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -628,14 +631,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: const TextSpan(
text: TextSpan(
text: 'Ghi chú chi tiết',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
children: [
children: const [
TextSpan(
text: ' *',
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...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.grey100,
borderSide: BorderSide(
color: colorScheme.surfaceContainerHighest,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: AppColors.primaryBlue,
borderSide: BorderSide(
color: colorScheme.primary,
width: 2,
),
),
@@ -706,16 +709,16 @@ class DesignRequestCreatePage extends HookConsumerWidget {
children: [
Icon(
Icons.cloud_upload_outlined,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'Đính kèm tài liệu',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -730,34 +733,34 @@ class DesignRequestCreatePage extends HookConsumerWidget {
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
width: 2,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.circular(8),
color: AppColors.grey50,
color: colorScheme.surfaceContainerLowest,
),
child: Column(
children: [
Icon(
Icons.cloud_upload_outlined,
size: 32,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 12),
const Text(
Text(
'Nhấn để chọn file hoặc kéo thả vào đây',
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
const Text(
Text(
'Hỗ trợ: JPG, PNG, PDF (Tối đa 10MB mỗi file)',
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -771,7 +774,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
...selectedFiles.value.asMap().entries.map((entry) {
final index = entry.key;
final file = entry.value;
return _FilePreviewItem(
return FilePreviewItem(
file: file,
onRemove: () => removeFile(index),
);
@@ -790,10 +793,10 @@ class DesignRequestCreatePage extends HookConsumerWidget {
child: ElevatedButton(
onPressed: isSubmitting.value ? null : submitForm,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
disabledBackgroundColor: AppColors.primaryBlue.withValues(alpha: 0.7),
disabledForegroundColor: AppColors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.surface,
disabledBackgroundColor: colorScheme.primary.withValues(alpha: 0.7),
disabledForegroundColor: colorScheme.surface,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
@@ -804,12 +807,12 @@ class DesignRequestCreatePage extends HookConsumerWidget {
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.white,
color: colorScheme.surface,
),
),
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/sample_project.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
///
@@ -161,7 +164,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
showDialog<void>(
context: context,
barrierColor: Colors.black87,
builder: (context) => _ImageViewerDialog(
builder: (context) => ImageViewerDialog(
images: images,
initialIndex: initialIndex,
),
@@ -171,21 +174,22 @@ class DesignRequestDetailPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final detailAsync = ref.watch(designRequestDetailProvider(requestId));
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: AppColors.grey50,
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
backgroundColor: AppColors.white,
backgroundColor: colorScheme.surface,
elevation: AppBarSpecs.elevation,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => Navigator.of(context).pop(),
),
centerTitle: false,
title: const Text(
title: Text(
'Chi tiết Yêu cầu',
style: TextStyle(
color: Colors.black,
color: colorScheme.onSurface,
fontSize: 20,
fontWeight: FontWeight.w600,
),
@@ -193,7 +197,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
actions: [
detailAsync.maybeWhen(
data: (request) => IconButton(
icon: const Icon(Icons.share, color: Colors.black),
icon: Icon(Icons.share, color: colorScheme.onSurface),
onPressed: () => _shareRequest(context, request),
),
orElse: () => const SizedBox.shrink(),
@@ -226,10 +230,10 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Request ID
Text(
'#${request.id}',
style: const TextStyle(
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
@@ -238,9 +242,9 @@ class DesignRequestDetailPage extends ConsumerWidget {
if (request.dateline != null)
Text(
'Ngày gửi: ${request.dateline}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
@@ -269,7 +273,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
),
const SizedBox(height: 24),
const Divider(height: 1, color: AppColors.grey100),
Divider(height: 1, color: colorScheme.surfaceContainerHighest),
const SizedBox(height: 24),
// Design Information Section
@@ -277,16 +281,16 @@ class DesignRequestDetailPage extends ConsumerWidget {
children: [
Icon(
Icons.info_outline,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'Thông tin thiết kế',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -295,14 +299,14 @@ class DesignRequestDetailPage extends ConsumerWidget {
const SizedBox(height: 16),
// Description List
_DescriptionItem(
DescriptionItem(
label: 'Tên công trình:',
value: request.subject,
),
if (request.plainDescription.isNotEmpty) ...[
const SizedBox(height: 12),
_DescriptionItem(
DescriptionItem(
label: 'Mô tả chi tiết:',
value: request.plainDescription,
isMultiLine: true,
@@ -439,16 +443,16 @@ class DesignRequestDetailPage extends ConsumerWidget {
children: [
Icon(
Icons.attach_file,
color: AppColors.primaryBlue,
color: colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'Tài liệu đính kèm',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -468,8 +472,8 @@ class DesignRequestDetailPage extends ConsumerWidget {
child: ElevatedButton.icon(
onPressed: () => _contactSupport(context),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
@@ -505,9 +509,9 @@ class DesignRequestDetailPage extends ConsumerWidget {
Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
@@ -524,6 +528,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
}
Widget _buildFilesSection(BuildContext context, List<ProjectFile> files) {
final colorScheme = Theme.of(context).colorScheme;
// Separate images and other files
final images = 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,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Icon(Icons.error),
),
),
@@ -575,7 +580,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Other Files as file items
...otherFiles.map(
(file) => _FileItem(
(file) => FileItem(
fileUrl: file.fileUrl,
icon: _getFileIcon(file.fileUrl),
),
@@ -584,7 +589,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
// Show image files as file items too (for download/naming)
if (images.isNotEmpty && otherFiles.isEmpty)
...images.map(
(file) => _FileItem(
(file) => FileItem(
fileUrl: file.fileUrl,
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
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final detailAsync = ref.watch(sampleProjectDetailProvider(modelId));
return Scaffold(
backgroundColor: const Color(0xFFF4F6F8),
backgroundColor: colorScheme.surfaceContainerLowest,
appBar: AppBar(
leading: IconButton(
icon: const FaIcon(
icon: FaIcon(
FontAwesomeIcons.arrowLeft,
color: Colors.black,
color: colorScheme.onSurface,
size: 20,
),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'Chi tiết Nhà mẫu',
style: TextStyle(color: Colors.black),
style: TextStyle(color: colorScheme.onSurface),
),
actions: [
detailAsync.maybeWhen(
data: (project) => IconButton(
icon: const FaIcon(
icon: FaIcon(
FontAwesomeIcons.shareNodes,
color: Colors.black,
color: colorScheme.onSurface,
size: 20,
),
onPressed: () => _shareModel(context, project),
@@ -58,7 +59,7 @@ class ModelHouseDetailPage extends ConsumerWidget {
const SizedBox(width: AppSpacing.sm),
],
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
backgroundColor: colorScheme.surface,
centerTitle: false,
),
body: detailAsync.when(
@@ -72,7 +73,7 @@ class ModelHouseDetailPage extends ConsumerWidget {
const SizedBox(height: 16),
// Project Information
_buildProjectInfo(project),
_buildProjectInfo(context, project),
const SizedBox(height: 16),
@@ -102,9 +103,9 @@ class ModelHouseDetailPage extends ConsumerWidget {
Text(
'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}',
textAlign: TextAlign.center,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
),
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(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
@@ -320,10 +322,10 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Title
Text(
project.projectName,
style: const TextStyle(
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
if (project.plainDescription.isNotEmpty) ...[
@@ -331,9 +333,9 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Description
Text(
project.plainDescription,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Color(0xFF4b5563),
color: colorScheme.onSurfaceVariant,
height: 1.6,
),
),
@@ -344,13 +346,14 @@ class ModelHouseDetailPage extends ConsumerWidget {
}
Widget _buildImageGallery(BuildContext context, SampleProject project) {
final colorScheme = Theme.of(context).colorScheme;
final images = project.filesList;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
@@ -366,18 +369,18 @@ class ModelHouseDetailPage extends ConsumerWidget {
// Gallery Title
Row(
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.images,
size: 18,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
const SizedBox(width: 8),
Text(
'Thư viện Hình ảnh (${images.length})',
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.grey900,
color: colorScheme.onSurface,
),
),
],
@@ -405,13 +408,13 @@ class ModelHouseDetailPage extends ConsumerWidget {
imageUrl: image.fileUrl,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
),
errorWidget: (context, url, error) => Container(
color: AppColors.grey100,
color: colorScheme.surfaceContainerHighest,
child: const Icon(Icons.error),
),
),

View File

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