add profile edit
This commit is contained in:
695
lib/features/account/presentation/pages/profile_edit_page.dart
Normal file
695
lib/features/account/presentation/pages/profile_edit_page.dart
Normal file
@@ -0,0 +1,695 @@
|
||||
/// Profile Edit Page
|
||||
///
|
||||
/// Allows users to edit their profile information.
|
||||
/// Features:
|
||||
/// - Avatar upload with image picker
|
||||
/// - Form fields for personal information
|
||||
/// - Form validation
|
||||
/// - Save/cancel actions
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Profile Edit Page
|
||||
///
|
||||
/// Page for editing user profile information with avatar upload.
|
||||
class ProfileEditPage extends HookConsumerWidget {
|
||||
const ProfileEditPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Form key for validation
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
|
||||
// Image picker
|
||||
final selectedImage = useState<File?>(null);
|
||||
|
||||
// Form controllers
|
||||
final nameController = useTextEditingController(text: 'Hoàng Minh Hiệp');
|
||||
final phoneController = useTextEditingController(text: '0347302911');
|
||||
final emailController = useTextEditingController(
|
||||
text: 'hoanghiep@example.com',
|
||||
);
|
||||
final birthDateController = useTextEditingController(text: '15/03/1985');
|
||||
final idNumberController = useTextEditingController(text: '123456789012');
|
||||
final taxIdController = useTextEditingController(text: '0359837618');
|
||||
final companyController = useTextEditingController(
|
||||
text: 'Công ty TNHH Xây dựng ABC',
|
||||
);
|
||||
final addressController = useTextEditingController(
|
||||
text: '123 Man Thiện, Thủ Đức, Hồ Chí Minh',
|
||||
);
|
||||
final experienceController = useTextEditingController(text: '10');
|
||||
|
||||
// Dropdown values
|
||||
final selectedGender = useState<String>('male');
|
||||
final selectedPosition = useState<String>('contractor');
|
||||
|
||||
// Has unsaved changes
|
||||
final hasChanges = useState<bool>(false);
|
||||
|
||||
return PopScope(
|
||||
canPop: !hasChanges.value,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) return;
|
||||
|
||||
final shouldPop = await _showUnsavedChangesDialog(context);
|
||||
if (shouldPop == true && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () async {
|
||||
if (hasChanges.value) {
|
||||
final shouldPop = await _showUnsavedChangesDialog(context);
|
||||
if (shouldPop == true && context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Thông tin cá nhân',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: const [SizedBox(width: AppSpacing.sm)],
|
||||
),
|
||||
body: Form(
|
||||
key: formKey,
|
||||
onChanged: () {
|
||||
hasChanges.value = true;
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Profile Avatar Section
|
||||
_buildAvatarSection(context, selectedImage),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Form Card
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Full Name
|
||||
_buildTextField(
|
||||
label: 'Họ và tên',
|
||||
controller: nameController,
|
||||
required: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Vui lòng nhập họ và tên';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Phone
|
||||
_buildTextField(
|
||||
label: 'Số điện thoại',
|
||||
controller: phoneController,
|
||||
required: true,
|
||||
keyboardType: TextInputType.phone,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Vui lòng nhập số điện thoại';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Email
|
||||
_buildTextField(
|
||||
label: 'Email',
|
||||
controller: emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Birth Date
|
||||
_buildDateField(
|
||||
context: context,
|
||||
label: 'Ngày sinh',
|
||||
controller: birthDateController,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Gender
|
||||
_buildDropdownField(
|
||||
label: 'Giới tính',
|
||||
value: selectedGender.value,
|
||||
items: const [
|
||||
{'value': 'male', 'label': 'Nam'},
|
||||
{'value': 'female', 'label': 'Nữ'},
|
||||
{'value': 'other', 'label': 'Khác'},
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
selectedGender.value = value;
|
||||
hasChanges.value = true;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// ID Number
|
||||
_buildTextField(
|
||||
label: 'Số CMND/CCCD',
|
||||
controller: idNumberController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Tax ID
|
||||
_buildTextField(
|
||||
label: 'Mã số thuế',
|
||||
controller: taxIdController,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Company
|
||||
_buildTextField(
|
||||
label: 'Công ty',
|
||||
controller: companyController,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Address
|
||||
_buildTextField(
|
||||
label: 'Địa chỉ',
|
||||
controller: addressController,
|
||||
maxLines: 2,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Position
|
||||
_buildDropdownField(
|
||||
label: 'Chức vụ',
|
||||
value: selectedPosition.value,
|
||||
items: const [
|
||||
{'value': 'contractor', 'label': 'Thầu thợ'},
|
||||
{'value': 'architect', 'label': 'Kiến trúc sư'},
|
||||
{'value': 'dealer', 'label': 'Đại lý phân phối'},
|
||||
{'value': 'broker', 'label': 'Môi giới'},
|
||||
{'value': 'other', 'label': 'Khác'},
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
selectedPosition.value = value;
|
||||
hasChanges.value = true;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Experience
|
||||
_buildTextField(
|
||||
label: 'Kinh nghiệm (năm)',
|
||||
controller: experienceController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// Action Buttons
|
||||
_buildActionButtons(
|
||||
context: context,
|
||||
formKey: formKey,
|
||||
hasChanges: hasChanges,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build avatar section with edit button
|
||||
Widget _buildAvatarSection(
|
||||
BuildContext context,
|
||||
ValueNotifier<File?> selectedImage,
|
||||
) {
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
// Avatar
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.primaryBlue,
|
||||
image: selectedImage.value != null
|
||||
? DecorationImage(
|
||||
image: FileImage(selectedImage.value!),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: selectedImage.value == null
|
||||
? const Center(
|
||||
child: Text(
|
||||
'HMH',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
// Edit Button
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
await _pickImage(context, selectedImage);
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryBlue,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.camera_alt,
|
||||
size: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build text field
|
||||
Widget _buildTextField({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
bool required = false,
|
||||
TextInputType? keyboardType,
|
||||
int maxLines = 1,
|
||||
String? Function(String?)? validator,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
children: [
|
||||
if (required)
|
||||
const TextSpan(
|
||||
text: ' *',
|
||||
style: TextStyle(color: AppColors.danger),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: keyboardType,
|
||||
maxLines: maxLines,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Nhập $label',
|
||||
hintStyle: TextStyle(
|
||||
color: AppColors.grey500.withValues(alpha: 0.6),
|
||||
fontSize: 14,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF8FAFC),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.primaryBlue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: AppColors.danger),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: AppColors.danger, width: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build date field
|
||||
Widget _buildDateField({
|
||||
required BuildContext context,
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime(1985, 3, 15),
|
||||
firstDate: DateTime(1940),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
if (date != null) {
|
||||
controller.text =
|
||||
'${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Chọn ngày sinh',
|
||||
hintStyle: TextStyle(
|
||||
color: AppColors.grey500.withValues(alpha: 0.6),
|
||||
fontSize: 14,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF8FAFC),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
suffixIcon: const Icon(Icons.calendar_today, size: 20),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.primaryBlue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build dropdown field
|
||||
Widget _buildDropdownField({
|
||||
required String label,
|
||||
required String value,
|
||||
required List<Map<String, String>> items,
|
||||
required ValueChanged<String?> onChanged,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: value,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF8FAFC),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.primaryBlue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
items: items.map((item) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: item['value'],
|
||||
child: Text(item['label']!, style: const TextStyle(fontSize: 14)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build action buttons
|
||||
Widget _buildActionButtons({
|
||||
required BuildContext context,
|
||||
required GlobalKey<FormState> formKey,
|
||||
required ValueNotifier<bool> hasChanges,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Row(
|
||||
children: [
|
||||
// Cancel Button
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: const BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Hủy bỏ',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
|
||||
// Save Button
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
// TODO: Save profile data
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Thông tin đã được cập nhật thành công!'),
|
||||
backgroundColor: AppColors.success,
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
hasChanges.value = false;
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save, size: 20),
|
||||
label: const Text(
|
||||
'Lưu thay đổ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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Pick image from gallery or camera
|
||||
Future<void> _pickImage(
|
||||
BuildContext context,
|
||||
ValueNotifier<File?> selectedImage,
|
||||
) async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
// Show dialog to choose source
|
||||
final source = await showDialog<ImageSource>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Chọn ảnh từ'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt),
|
||||
title: const Text('Máy ảnh'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.camera),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: const Text('Thư viện ảnh'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.gallery),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (source != null) {
|
||||
final XFile? image = await picker.pickImage(
|
||||
source: source,
|
||||
maxWidth: 512,
|
||||
maxHeight: 512,
|
||||
imageQuality: 85,
|
||||
);
|
||||
|
||||
if (image != null) {
|
||||
selectedImage.value = File(image.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Show unsaved changes dialog
|
||||
Future<bool?> _showUnsavedChangesDialog(BuildContext context) {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Thay đổi chưa được lưu'),
|
||||
content: const Text(
|
||||
'Bạn có thay đổi chưa được lưu. Bạn có muốn thoát không?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Ở lại'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: AppColors.danger),
|
||||
child: const Text('Thoát'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user