Files
intaleq/lib/views/home/profile/passenger_profile_page.dart

1066 lines
37 KiB
Dart

import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
import 'package:Intaleq/constant/api_key.dart';
import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/controller/profile/profile_controller.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/widgets/mycircular.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/log_out.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Main Page
// ─────────────────────────────────────────────────────────────────────────────
class PassengerProfilePage extends StatefulWidget {
const PassengerProfilePage({super.key});
@override
State<PassengerProfilePage> createState() => _PassengerProfilePageState();
}
class _PassengerProfilePageState extends State<PassengerProfilePage> {
late final LogOutController logOutController;
File? _pickedImage;
bool _isUploadingImage = false;
@override
void initState() {
super.initState();
logOutController = Get.put(LogOutController());
Get.put(ProfileController());
}
// ─── Image Handling ─────────────────────────────────────────────────────────
Future<void> _pickAndUploadImage() async {
// 1. Pick source
final ImageSource? source = await _showImageSourceSheet();
if (source == null) return;
// 2. Pick image
final XFile? picked =
await ImagePicker().pickImage(source: source, imageQuality: 85);
if (picked == null) return;
// 3. Crop
final CroppedFile? cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Crop Photo'.tr,
toolbarColor: AppColor.primaryColor,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.square,
lockAspectRatio: true,
hideBottomControls: false,
),
IOSUiSettings(
title: 'Crop Photo'.tr,
aspectRatioLockEnabled: true,
resetAspectRatioEnabled: false,
),
],
);
if (cropped == null) return;
// 4. Upload
setState(() {
_pickedImage = File(cropped.path);
_isUploadingImage = true;
});
try {
final passengerId = box.read(BoxName.passengerID).toString();
final uri = Uri.parse('${AK.serverPHP}/upload_passenger_image.php');
final request = http.MultipartRequest('POST', uri)
..fields['passenger_id'] = passengerId
..files.add(await http.MultipartFile.fromPath('image', cropped.path));
final response = await request.send();
if (response.statusCode == 200) {
Get.snackbar(
'Success'.tr,
'Profile photo updated'.tr,
backgroundColor: Colors.green.withOpacity(0.9),
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 12,
);
} else {
_showUploadError();
}
} catch (_) {
_showUploadError();
} finally {
setState(() => _isUploadingImage = false);
}
}
void _showUploadError() {
Get.snackbar(
'Error'.tr,
'Failed to upload photo'.tr,
backgroundColor: Colors.red.withOpacity(0.9),
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 12,
);
}
Future<ImageSource?> _showImageSourceSheet() async {
return await showModalBottomSheet<ImageSource>(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (_) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.only(top: 10, bottom: 6),
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(2)),
),
const SizedBox(height: 8),
Text('Change Photo'.tr,
style:
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.camera_alt_outlined,
color: AppColor.primaryColor),
),
title: Text('Take a Photo'.tr,
style: const TextStyle(fontWeight: FontWeight.w500)),
onTap: () => Navigator.pop(context, ImageSource.camera),
),
ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.photo_library_outlined,
color: Colors.blue),
),
title: Text('Choose from Gallery'.tr,
style: const TextStyle(fontWeight: FontWeight.w500)),
onTap: () => Navigator.pop(context, ImageSource.gallery),
),
const SizedBox(height: 12),
],
),
),
);
}
// ─── Build ──────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.secondaryColor.withOpacity(0.96),
appBar: AppBar(
title: Text('My Profile'.tr,
style: AppStyle.headTitle2.copyWith(fontSize: 20)),
backgroundColor: AppColor.secondaryColor,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: AppColor.writeColor),
),
body: GetBuilder<ProfileController>(
builder: (controller) {
if (controller.isloading) {
return const MyCircularProgressIndicator();
}
return ListView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
physics: const BouncingScrollPhysics(),
children: [
_buildProfileHeader(controller),
const SizedBox(height: 32),
_buildSectionCard(
title: 'Personal Information'.tr,
sectionIcon: Icons.person_outline_rounded,
children: [
_buildTile(
icon: Icons.badge_outlined,
color: Colors.blueAccent,
title: 'Name'.tr,
subtitle:
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}'
.trim(),
onTap: () => _showNameSheet(controller),
),
_divider(),
_buildTile(
icon: Icons.wc_outlined,
color: Colors.pinkAccent,
title: 'Gender'.tr,
subtitle: controller.prfoileData['gender']?.toString() ??
'Not set'.tr,
onTap: () => _showGenderSheet(controller),
),
_divider(),
_buildTile(
icon: Icons.school_outlined,
color: Colors.orangeAccent,
title: 'Education'.tr,
subtitle: controller.prfoileData['education']?.toString() ??
'Not set'.tr,
onTap: () => _showEducationSheet(controller),
),
],
),
const SizedBox(height: 20),
_buildSectionCard(
title: 'Work & Contact'.tr,
sectionIcon: Icons.work_outline_rounded,
children: [
_buildTile(
icon: Icons.work_outline,
color: Colors.green,
title: 'Employment Type'.tr,
subtitle:
controller.prfoileData['employmentType']?.toString() ??
'Not set'.tr,
onTap: () => _showFieldSheet(
controller: controller,
fieldKey: 'employmentType',
title: 'Employment Type'.tr,
icon: Icons.work_outline,
iconColor: Colors.green,
keyboardType: TextInputType.text,
),
),
_divider(),
_buildTile(
icon: Icons.favorite_border,
color: Colors.purpleAccent,
title: 'Marital Status'.tr,
subtitle:
controller.prfoileData['maritalStatus']?.toString() ??
'Not set'.tr,
onTap: () => _showFieldSheet(
controller: controller,
fieldKey: 'maritalStatus',
title: 'Marital Status'.tr,
icon: Icons.favorite_border,
iconColor: Colors.purpleAccent,
keyboardType: TextInputType.text,
),
),
_divider(),
_buildTile(
icon: Icons.sos_outlined,
color: Colors.redAccent,
title: 'SOS Phone'.tr,
subtitle: controller.prfoileData['sosPhone']?.toString() ??
'Not set'.tr,
onTap: () => _showFieldSheet(
controller: controller,
fieldKey: 'sosPhone',
title: 'SOS Phone'.tr,
icon: Icons.sos_outlined,
iconColor: Colors.redAccent,
keyboardType: TextInputType.phone,
onSaved: () {
box.write(BoxName.sosPhonePassenger,
controller.prfoileData['sosPhone']);
},
),
),
],
),
const SizedBox(height: 20),
_buildSectionCard(
title: 'Account'.tr,
sectionIcon: Icons.manage_accounts_outlined,
children: [
_buildTile(
icon: Icons.logout_rounded,
color: Colors.blueGrey,
title: 'Sign Out'.tr,
subtitle: '',
onTap: () => logOutController.logOutPassenger(),
trailing: const SizedBox.shrink(),
),
_divider(),
_buildTile(
icon: Icons.delete_forever_outlined,
color: Colors.redAccent,
title: 'Delete My Account'.tr,
subtitle: '',
onTap: () => _showDeleteSheet(),
trailing: const SizedBox.shrink(),
),
],
),
const SizedBox(height: 32),
],
);
},
),
);
}
// ─── Profile Header ─────────────────────────────────────────────────────────
Widget _buildProfileHeader(ProfileController controller) {
final firstName = controller.prfoileData['first_name']?.toString() ?? '';
final lastName = controller.prfoileData['last_name']?.toString() ?? '';
final fullName = '$firstName $lastName'.trim();
final passengerId = box.read(BoxName.passengerID)?.toString() ?? '';
String email = box.read(BoxName.email) ?? '';
if (email.contains('intaleqapp.com')) email = '';
final initials = fullName.isNotEmpty
? fullName
.split(' ')
.where((e) => e.isNotEmpty)
.map((e) => e[0].toUpperCase())
.take(2)
.join()
: '?';
final avatarUrl =
'${AK.serverPHP}/portrate_passenger_image/$passengerId.jpg';
return Center(
child: Column(
children: [
// ── Avatar with camera button ──────────────────────────────
Stack(
alignment: Alignment.bottomRight,
children: [
// Outer glow ring
Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
AppColor.primaryColor.withOpacity(0.6),
AppColor.primaryColor.withOpacity(0.1),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: CircleAvatar(
radius: 52,
backgroundColor: AppColor.secondaryColor,
child: ClipOval(
child: _isUploadingImage
? const SizedBox(
width: 104,
height: 104,
child: Center(
child: CircularProgressIndicator(
color: AppColor.primaryColor,
strokeWidth: 2,
),
),
)
: _pickedImage != null
? Image.file(
_pickedImage!,
width: 104,
height: 104,
fit: BoxFit.cover,
)
: CachedNetworkImage(
imageUrl: avatarUrl,
width: 104,
height: 104,
fit: BoxFit.cover,
placeholder: (_, __) => Container(
width: 104,
height: 104,
color:
AppColor.primaryColor.withOpacity(0.08),
child: Center(
child: Text(
initials,
style: const TextStyle(
fontSize: 34,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor,
),
),
),
),
errorWidget: (_, __, ___) => Container(
width: 104,
height: 104,
color:
AppColor.primaryColor.withOpacity(0.08),
child: Center(
child: Text(
initials,
style: const TextStyle(
fontSize: 34,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor,
),
),
),
),
),
),
),
),
// Camera button
GestureDetector(
onTap: _isUploadingImage ? null : _pickAndUploadImage,
child: Container(
width: 36,
height: 36,
margin: const EdgeInsets.only(right: 2, bottom: 2),
decoration: BoxDecoration(
color: AppColor.primaryColor,
shape: BoxShape.circle,
border:
Border.all(color: AppColor.secondaryColor, width: 2.5),
boxShadow: [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
),
child: const Icon(Icons.camera_alt_rounded,
color: Colors.white, size: 17),
),
),
],
),
const SizedBox(height: 16),
// Name + edit hint
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
fullName.isEmpty ? 'Passenger'.tr : fullName,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColor.writeColor,
),
),
const SizedBox(width: 6),
GestureDetector(
onTap: () => _showNameSheet(Get.find<ProfileController>()),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.edit_rounded,
size: 14, color: AppColor.primaryColor),
),
),
],
),
if (email.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
email,
style: TextStyle(
fontSize: 14,
color: AppColor.grayColor,
fontWeight: FontWeight.w500),
),
],
const SizedBox(height: 10),
// Passenger badge
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.verified_user_rounded,
size: 13, color: AppColor.primaryColor),
const SizedBox(width: 5),
Text(
'Verified Passenger'.tr,
style: const TextStyle(
fontSize: 12,
color: AppColor.primaryColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
// ─── Section Card ────────────────────────────────────────────────────────────
Widget _buildSectionCard({
required String title,
required IconData sectionIcon,
required List<Widget> children,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 12),
child: Row(
children: [
Icon(sectionIcon, size: 15, color: AppColor.grayColor),
const SizedBox(width: 8),
Text(
title.toUpperCase(),
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: AppColor.grayColor,
letterSpacing: 1.2,
),
),
],
),
),
Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Get.isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.04),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(children: children),
),
],
);
}
Widget _buildTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required VoidCallback onTap,
Widget? trailing,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(18),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 21),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: AppColor.writeColor)),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 4),
Text(subtitle,
style:
TextStyle(color: AppColor.grayColor, fontSize: 13)),
],
],
),
),
trailing ??
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: AppColor.grayColor.withOpacity(0.5)),
],
),
),
);
}
Widget _divider() => Divider(
height: 1,
thickness: 1,
indent: 72,
endIndent: 16,
color: AppColor.grayColor.withOpacity(0.1));
// ─── Bottom Sheets ───────────────────────────────────────────────────────────
/// Name: first + last together
void _showNameSheet(ProfileController controller) {
final firstCtrl = TextEditingController(
text: controller.prfoileData['first_name']?.toString() ?? '');
final lastCtrl = TextEditingController(
text: controller.prfoileData['last_name']?.toString() ?? '');
_openSheet(
title: 'Update Name'.tr,
headerIcon: Icons.badge_outlined,
headerColor: Colors.blueAccent,
child: StatefulBuilder(
builder: (ctx, setStateInner) => Column(
children: [
_styledField(
controller: firstCtrl,
label: 'First Name'.tr,
hint: 'Enter your first name'.tr,
icon: Icons.person_outline,
type: TextInputType.name,
),
const SizedBox(height: 14),
_styledField(
controller: lastCtrl,
label: 'Last Name'.tr,
hint: 'Enter your last name'.tr,
icon: Icons.person_outline,
type: TextInputType.name,
),
const SizedBox(height: 24),
_sheetSaveButton(
label: 'Save Name'.tr,
onPressed: () async {
Get.back();
// Update both columns sequentially
await controller.updateColumn({
'id': box.read(BoxName.passengerID).toString(),
'first_name': firstCtrl.text.trim(),
'last_name': lastCtrl.text.trim(),
});
},
),
],
),
),
);
}
/// Single text field update
void _showFieldSheet({
required ProfileController controller,
required String fieldKey,
required String title,
required IconData icon,
required Color iconColor,
required TextInputType keyboardType,
VoidCallback? onSaved,
}) {
final tempCtrl = TextEditingController(
text: controller.prfoileData[fieldKey]?.toString() ?? '');
_openSheet(
title: title,
headerIcon: icon,
headerColor: iconColor,
child: Column(
children: [
_styledField(
controller: tempCtrl,
label: title,
hint: '',
icon: icon,
type: keyboardType,
),
const SizedBox(height: 24),
_sheetSaveButton(
onPressed: () async {
Get.back();
await controller.updateColumn({
'id': box.read(BoxName.passengerID).toString(),
fieldKey: tempCtrl.text.trim(),
});
onSaved?.call();
},
),
],
),
);
}
/// Gender picker sheet
void _showGenderSheet(ProfileController controller) {
final options = ['Male'.tr, 'Female'.tr, 'Other'.tr];
_openSheet(
title: 'Select Gender'.tr,
headerIcon: Icons.wc_outlined,
headerColor: Colors.pinkAccent,
child: Column(
children: options
.map((g) => _choiceTile(
label: g,
isSelected: controller.prfoileData['gender']?.toString() == g,
onTap: () async {
Get.back();
await controller.updateColumn({
'id': box.read(BoxName.passengerID).toString(),
'gender': g,
});
},
))
.toList(),
),
);
}
/// Education picker sheet
void _showEducationSheet(ProfileController controller) {
final options = [
'High School Diploma'.tr,
'Associate Degree'.tr,
"Bachelor's Degree".tr,
"Master's Degree".tr,
'Doctoral Degree'.tr,
];
_openSheet(
title: 'Select Education'.tr,
headerIcon: Icons.school_outlined,
headerColor: Colors.orangeAccent,
child: Column(
children: options
.map((d) => _choiceTile(
label: d,
isSelected:
controller.prfoileData['education']?.toString() == d,
onTap: () async {
Get.back();
await controller.updateColumn({
'id': box.read(BoxName.passengerID).toString(),
'education': d,
});
},
))
.toList(),
),
);
}
/// Delete account sheet
void _showDeleteSheet() {
_openSheet(
title: 'Delete Account'.tr,
headerIcon: Icons.delete_forever_outlined,
headerColor: Colors.redAccent,
child: Column(
children: [
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.red.withOpacity(0.15)),
),
child: Row(
children: [
const Icon(Icons.warning_amber_rounded,
color: Colors.redAccent, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
'This action is permanent and cannot be undone.'.tr,
style: TextStyle(
color: Colors.red.withOpacity(0.8), fontSize: 13),
),
),
],
),
),
const SizedBox(height: 16),
_styledField(
controller: logOutController.emailTextController,
label: 'Confirm your Email'.tr,
hint: 'example@domain.com',
icon: Icons.email_outlined,
type: TextInputType.emailAddress,
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
logOutController.emailTextController.clear();
Get.back();
},
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
side: BorderSide(color: Colors.grey[300]!),
),
child: Text('Cancel'.tr,
style: TextStyle(color: AppColor.grayColor)),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
await logOutController.deletePassengerAccount();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text('Delete'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
),
),
],
),
],
),
);
}
// ─── Sheet Primitives ────────────────────────────────────────────────────────
void _openSheet({
required String title,
required IconData headerIcon,
required Color headerColor,
required Widget child,
}) {
Get.bottomSheet(
isScrollControlled: true,
_SheetWrapper(
title: title,
headerIcon: headerIcon,
headerColor: headerColor,
child: child,
),
);
}
Widget _styledField({
required TextEditingController controller,
required String label,
required String hint,
required IconData icon,
required TextInputType type,
}) {
return TextField(
controller: controller,
keyboardType: type,
style: TextStyle(fontSize: 16, color: AppColor.writeColor),
decoration: InputDecoration(
labelText: label,
hintText: hint,
hintStyle:
TextStyle(color: AppColor.grayColor.withOpacity(0.5), fontSize: 14),
prefixIcon: Icon(icon, size: 20, color: AppColor.grayColor),
filled: true,
fillColor: AppColor.secondaryColor,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(13),
borderSide:
BorderSide(color: AppColor.grayColor.withOpacity(0.15))),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(13),
borderSide:
BorderSide(color: AppColor.grayColor.withOpacity(0.15))),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(13),
borderSide: BorderSide(color: AppColor.primaryColor, width: 1.5)),
labelStyle: TextStyle(color: AppColor.grayColor, fontSize: 14),
),
);
}
Widget _sheetSaveButton({required VoidCallback onPressed, String? label}) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 15),
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(13)),
),
child: Text(
label ?? 'Save Changes'.tr,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
);
}
Widget _choiceTile({
required String label,
required bool isSelected,
required VoidCallback onTap,
}) {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
onTap: onTap,
leading: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 22,
height: 22,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? AppColor.primaryColor : Colors.grey[300]!,
width: 2,
),
color: isSelected
? AppColor.primaryColor.withOpacity(0.1)
: Colors.transparent,
),
child: isSelected
? const Icon(Icons.check_rounded,
size: 14, color: AppColor.primaryColor)
: null,
),
title: Text(label,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
fontSize: 16,
color: isSelected ? AppColor.primaryColor : AppColor.writeColor)),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Reusable Sheet Wrapper
// ─────────────────────────────────────────────────────────────────────────────
class _SheetWrapper extends StatelessWidget {
final String title;
final IconData headerIcon;
final Color headerColor;
final Widget child;
const _SheetWrapper({
required this.title,
required this.headerIcon,
required this.headerColor,
required this.child,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(
24,
16,
24,
MediaQuery.of(context).viewInsets.bottom + 28,
),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(26)),
),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle bar
Center(
child: Container(
width: 38,
height: 4,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(2),
),
),
),
// Header row
Row(
children: [
Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: headerColor.withOpacity(0.12),
borderRadius: BorderRadius.circular(12),
),
child: Icon(headerIcon, color: headerColor, size: 22),
),
const SizedBox(width: 12),
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColor.writeColor,
),
),
],
),
const SizedBox(height: 24),
child,
],
),
),
);
}
}