This commit is contained in:
Phuoc Nguyen
2025-11-18 17:59:27 +07:00
parent 0dda402246
commit fc4711a18e
8 changed files with 568 additions and 285 deletions

View File

@@ -10,6 +10,7 @@
library;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -23,11 +24,21 @@ import 'package:worker/features/account/presentation/widgets/address_card.dart';
/// Addresses Page
///
/// Page for managing saved delivery addresses.
class AddressesPage extends ConsumerWidget {
const AddressesPage({super.key});
/// Supports selection mode for returning selected address.
class AddressesPage extends HookConsumerWidget {
const AddressesPage({super.key, this.extra});
final Map<String, dynamic>? extra;
@override
Widget build(BuildContext context, WidgetRef ref) {
// Check if in selection mode
final selectMode = extra?['selectMode'] == true;
final currentAddress = extra?['currentAddress'] as Address?;
// Selected address state (for selection mode)
final selectedAddress = useState<Address?>(currentAddress);
// Watch addresses from API
final addressesAsync = ref.watch(addressesProvider);
@@ -37,18 +48,26 @@ class AddressesPage extends ConsumerWidget {
backgroundColor: AppColors.white,
elevation: AppBarSpecs.elevation,
leading: IconButton(
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20),
icon: const FaIcon(
FontAwesomeIcons.arrowLeft,
color: Colors.black,
size: 20,
),
onPressed: () => context.pop(),
),
title: const Text(
'Địa chỉ của bạn',
style: TextStyle(color: Colors.black),
title: Text(
selectMode ? 'Chọn địa chỉ' : 'Địa chỉ của bạn',
style: const TextStyle(color: Colors.black),
),
foregroundColor: AppColors.grey900,
centerTitle: false,
actions: [
IconButton(
icon: const FaIcon(FontAwesomeIcons.circleInfo, color: Colors.black, size: 20),
icon: const FaIcon(
FontAwesomeIcons.circleInfo,
color: Colors.black,
size: 20,
),
onPressed: () {
_showInfoDialog(context);
},
@@ -74,6 +93,37 @@ class AddressesPage extends ConsumerWidget {
const SizedBox(height: AppSpacing.md),
itemBuilder: (context, index) {
final address = addresses[index];
final isSelected =
selectedAddress.value?.name == address.name;
// In selection mode, show radio button
if (selectMode) {
return AddressCard(
name: address.addressTitle,
phone: address.phone,
address: address.fullAddress,
isDefault: address.isDefault,
showRadio: true,
isSelected: isSelected,
onRadioTap: () {
selectedAddress.value = address;
},
// Keep edit/delete actions in selection mode
onEdit: () {
context.push(
RouteNames.addressForm,
extra: address,
);
},
onDelete: () {
_showDeleteConfirmation(context, ref, address);
},
onSetDefault:
null, // Hide set default in selection mode
);
}
// Normal mode - show all actions
return AddressCard(
name: address.addressTitle,
phone: address.phone,
@@ -97,31 +147,107 @@ class AddressesPage extends ConsumerWidget {
),
),
// Add New Address Button
// Bottom Buttons
Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
context.push(RouteNames.addressForm);
},
icon: const FaIcon(FontAwesomeIcons.plus, size: 18),
label: const Text(
'Thêm địa chỉ mới',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.button),
child: selectMode
? Row(
children: [
// Add New Address Button (Selection Mode)
Expanded(
child: OutlinedButton.icon(
onPressed: () {
context.push(RouteNames.addressForm);
},
icon: const FaIcon(FontAwesomeIcons.plus, size: 16),
label: const Text(
'Thêm mới',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
side: const BorderSide(
color: AppColors.primaryBlue,
width: 1.5,
),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppRadius.button,
),
),
),
),
),
const SizedBox(width: AppSpacing.md),
// Select Address Button
Expanded(
flex: 2,
child: ElevatedButton.icon(
onPressed: selectedAddress.value == null
? null
: () {
// Return selected address without setting as default
context.pop(selectedAddress.value);
},
icon: const FaIcon(
FontAwesomeIcons.check,
size: 16,
),
label: const Text(
'Chọn địa chỉ này',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.grey100,
disabledForegroundColor: AppColors.grey500,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppRadius.button,
),
),
),
),
),
],
)
: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
context.push(RouteNames.addressForm);
},
icon: const FaIcon(FontAwesomeIcons.plus, size: 18),
label: const Text(
'Thêm địa chỉ mới',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppRadius.button,
),
),
),
),
),
),
),
),
),
],
),
@@ -166,7 +292,6 @@ class AddressesPage extends ConsumerWidget {
),
),
),
);
}
@@ -224,7 +349,11 @@ class AddressesPage extends ConsumerWidget {
}
/// Set address as default
void _setDefaultAddress(BuildContext context, WidgetRef ref, Address address) {
void _setDefaultAddress(
BuildContext context,
WidgetRef ref,
Address address,
) {
ref.read(addressesProvider.notifier).setDefaultAddress(address.name);
ScaffoldMessenger.of(context).showSnackBar(

View File

@@ -12,6 +12,7 @@ import 'package:worker/core/theme/colors.dart';
///
/// Shows address details with name, phone, address text, default badge,
/// and action buttons (edit/delete).
/// Supports selection mode with radio button.
class AddressCard extends StatelessWidget {
final String name;
final String phone;
@@ -20,6 +21,9 @@ class AddressCard extends StatelessWidget {
final VoidCallback? onEdit;
final VoidCallback? onDelete;
final VoidCallback? onSetDefault;
final bool showRadio;
final bool isSelected;
final VoidCallback? onRadioTap;
const AddressCard({
super.key,
@@ -30,6 +34,9 @@ class AddressCard extends StatelessWidget {
this.onEdit,
this.onDelete,
this.onSetDefault,
this.showRadio = false,
this.isSelected = false,
this.onRadioTap,
});
@override
@@ -61,6 +68,21 @@ class AddressCard extends StatelessWidget {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Radio Button (Selection Mode)
if (showRadio)
GestureDetector(
onTap: onRadioTap,
child: Padding(
padding: const EdgeInsets.only(right: 12, top: 2),
child: Radio<bool>(
value: true,
groupValue: isSelected,
onChanged: (_) => onRadioTap?.call(),
activeColor: AppColors.primaryBlue,
),
),
),
// Address Content
Expanded(
child: Column(
@@ -111,7 +133,9 @@ class AddressCard extends StatelessWidget {
),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.primaryBlue.withValues(alpha: 0.3),
color: AppColors.primaryBlue.withValues(
alpha: 0.3,
),
),
borderRadius: BorderRadius.circular(4),
),