feat: refactor financial wallet UI components and add offline map service support

This commit is contained in:
Hamza-Ayed
2026-04-21 00:35:30 +03:00
parent 4293d20561
commit b92db3bb39
99 changed files with 22888 additions and 27387 deletions

View File

@@ -59,6 +59,14 @@ class SettingsCaptain extends StatelessWidget {
_buildSectionHeader('App Preferences'.tr, context),
_buildSettingsCard(
children: [
_buildSwitchTile(
icon: Icons.dark_mode_outlined,
title: 'App Dark Mode'.tr,
subtitle: 'Switch between light and dark themes'.tr,
controller: settingsController,
valueGetter: (ctrl) => (ctrl).isDarkMode,
onChanged: (ctrl) => (ctrl).toggleAppTheme(),
),
_buildSwitchTile(
icon: Icons.map_outlined,
color: AppColor.redColor,
@@ -68,6 +76,15 @@ class SettingsCaptain extends StatelessWidget {
valueGetter: (ctrl) => (ctrl).isGoogleMapsEnabled,
onChanged: (ctrl) => (ctrl).onChangMapApp(),
),
_buildSwitchTile(
icon: Icons.map_outlined,
title: 'Map Dark Mode'.tr,
subtitle: 'Switch between light and dark map styles'.tr,
controller: settingsController,
valueGetter: (ctrl) => (ctrl).isMapDarkMode,
onChanged: (ctrl) => (ctrl).toggleMapTheme(),
),
_buildSwitchTile(
icon: Icons.vibration,
title: 'Vibration'.tr,

View File

@@ -1,6 +1,3 @@
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -9,208 +6,171 @@ import '../../../controller/home/captin/help/assurance_controller.dart';
import 'package:flutter/cupertino.dart';
import '../../widgets/my_scafold.dart';
class AssuranceHealthPage extends StatelessWidget {
AssuranceHealthPage({super.key});
AssuranceHealthController assuranceHealthController =
final AssuranceHealthController assuranceHealthController =
Get.put(AssuranceHealthController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Health Insurance".tr),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GetBuilder<AssuranceHealthController>(
builder: (assuranceHealthController) {
return Column(
children: [
Text(
"When you complete 500 trips, you will be eligible for exclusive health insurance offers."
.tr,
textAlign: TextAlign.center,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
CupertinoButton.filled(
child: Text("Show My Trip Count".tr),
onPressed: () async {
assuranceHealthController.getTripCountByCaptain();
},
final theme = Theme.of(context);
return MyScafolld(
title: "Health Insurance".tr,
isleading: true,
body: [
GetBuilder<AssuranceHealthController>(
builder: (controller) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"When you complete 500 trips, you will be eligible for exclusive health insurance offers."
.tr,
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium?.copyWith(
color:
theme.textTheme.bodyMedium?.color?.withOpacity(0.8),
height: 1.5,
),
_buildTripCountAvatar(
assuranceHealthController.tripCount['count'] == null
? '0'
: assuranceHealthController.tripCount['count']
.toString(),
),
],
),
const SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(14),
const SizedBox(height: 24),
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () =>
controller.getTripCountByCaptain(),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding:
const EdgeInsets.symmetric(vertical: 12),
),
child: Text("Show My Trip Count".tr),
),
),
const SizedBox(width: 16),
_buildTripCountAvatar(
controller.tripCount['count'] == null
? '0'
: controller.tripCount['count'].toString(),
),
],
),
),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: theme.dividerColor),
),
child: Text(
"We have partnered with health insurance providers to offer you special health coverage. Complete 500 trips and receive a 20% discount on health insurance premiums."
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(fontWeight: FontWeight.w600),
style: theme.textTheme.bodyLarge
?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 10),
CupertinoButton.filled(
disabledColor: AppColor.blueColor,
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
borderRadius: BorderRadius.circular(12),
child: Text(
"Would you like to proceed with health insurance?".tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => _showInsuranceDialog(context, controller),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
"Would you like to proceed with health insurance?".tr,
style: const TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
onPressed: () async {
// Show confirmation dialog before proceeding
showCupertinoDialog(
context: context,
builder: (BuildContext context) {
// Variable to store the health insurance provider chosen by the driver
TextEditingController providerController =
TextEditingController();
return CupertinoAlertDialog(
title: Text(
"Confirmation".tr,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
content: Column(
children: [
Text(
"Would you like to proceed with health insurance?"
.tr,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 20),
CupertinoTextField(
controller: providerController,
placeholder:
"Do you have a disease for a long time?".tr,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(
color: CupertinoColors.systemGrey,
width: 1),
borderRadius: BorderRadius.circular(8),
),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text(
"Yes".tr,
style: const TextStyle(
color: CupertinoColors.activeBlue),
),
onPressed: () async {
// Ensure the provider name is not empty
if (providerController.text.isNotEmpty) {
// Call the function to insert data into the database
await assuranceHealthController
.addDriverHealthAssurance(
healthInsuranceProvider:
providerController.text,
);
// Close the dialog and navigate to a success screen or show a snackbar
Navigator.of(context).pop();
} else {
// Show an alert if the provider name is empty
showCupertinoDialog(
context: context,
builder: (_) => CupertinoAlertDialog(
title: Text("Error".tr),
content: Text(
"Do you have a disease for a long time?"
.tr),
actions: [
CupertinoDialogAction(
child: Text("OK".tr),
onPressed: () =>
Navigator.of(context).pop(),
),
],
),
);
}
},
),
CupertinoDialogAction(
child: Text(
"No".tr,
style: const TextStyle(
color: CupertinoColors.destructiveRed),
),
onPressed: () {
Navigator.of(context)
.pop(); // Just close the dialog
// Optionally show feedback if the driver opts out
// Get.snackbar(
// "Opted out".tr,
// "You have chosen not to proceed with health insurance."
// .tr,
// backgroundColor:
// CupertinoColors.systemGrey);
mySnackeBarError(
"You have chosen not to proceed with health insurance."
.tr);
},
),
],
);
},
);
},
),
],
],
),
);
}),
},
),
],
);
}
void _showInsuranceDialog(
BuildContext context, AssuranceHealthController controller) {
final TextEditingController providerController = TextEditingController();
Get.dialog(
AlertDialog(
title: Text("Confirmation".tr),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Would you like to proceed with health insurance?".tr),
const SizedBox(height: 16),
TextField(
controller: providerController,
decoration: InputDecoration(
hintText: "Do you have a disease for a long time?".tr,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
),
),
],
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text("No".tr, style: const TextStyle(color: Colors.red)),
),
ElevatedButton(
onPressed: () async {
if (providerController.text.isNotEmpty) {
await controller.addDriverHealthAssurance(
healthInsuranceProvider: providerController.text,
);
Get.back();
} else {
Get.snackbar("Error".tr,
"Please provide details about any long-term diseases.".tr);
}
},
child: Text("Yes".tr),
),
],
),
);
}
Widget _buildTripCountAvatar(String count) {
return Container(
width: 80,
height: 80,
width: 70,
height: 70,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const RadialGradient(
colors: [
Color(0xFF42A5F5),
Color(0xFF1976D2),
], // Health theme colors
center: Alignment.center,
radius: 0.8,
gradient: const LinearGradient(
colors: [Color(0xFF42A5F5), Color(0xFF1976D2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: CupertinoColors.black.withOpacity(0.2),
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
@@ -220,9 +180,9 @@ class AssuranceHealthPage extends StatelessWidget {
child: Text(
count,
style: const TextStyle(
fontSize: 22,
fontSize: 20,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
color: Colors.white,
),
),
),

View File

@@ -31,40 +31,32 @@ class HomeScreen extends StatelessWidget {
],
)),
bottomNavigationBar: Obx(() => BottomNavigationBar(
backgroundColor: Colors.greenAccent,
backgroundColor: Theme.of(context).cardColor,
currentIndex: controller.currentIndex.value,
onTap: controller.changePage,
selectedItemColor: AppColor.primaryColor,
unselectedItemColor: Theme.of(context).hintColor,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: const Icon(
Icons.home,
color: AppColor.primaryColor,
),
icon: const Icon(Icons.home),
label: 'Home'.tr,
),
BottomNavigationBarItem(
icon: const Icon(
Icons.person,
color: AppColor.primaryColor,
),
icon: const Icon(Icons.person),
label: 'Profile'.tr,
),
BottomNavigationBarItem(
icon: const Icon(
Icons.bar_chart,
color: AppColor.primaryColor,
),
icon: const Icon(Icons.bar_chart),
label: 'Statistics'.tr,
),
BottomNavigationBarItem(
icon: const Icon(
Icons.account_balance_wallet,
color: AppColor.primaryColor,
),
icon: const Icon(Icons.account_balance_wallet),
label: 'Wallet'.tr,
),
],
)),
);
}
}

View File

@@ -1,303 +1,302 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/controller/functions/camer_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:sefer_driver/constant/colors.dart';
// import 'package:sefer_driver/constant/style.dart';
// import 'package:sefer_driver/controller/functions/camer_controller.dart';
// import 'package:sefer_driver/views/widgets/elevated_btn.dart';
// import 'package:sefer_driver/views/widgets/my_scafold.dart';
class CameraWidgetCardId extends StatelessWidget {
final CameraClassController cameraClassController =
Get.put(CameraClassController());
// class CameraWidgetCardId extends StatelessWidget {
// final CameraClassController cameraClassController =
// Get.put(CameraClassController());
CameraWidgetCardId({super.key});
// CameraWidgetCardId({super.key});
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Scan Id'.tr,
body: [
Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>(
builder: (cameraClassController) =>
cameraClassController.isCameraInitialized
? Stack(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: FittedBox(
fit: BoxFit.fitWidth,
child: SizedBox(
width: Get.width * .9,
height: Get.width *
.9, // Set the desired aspect ratio here
child: CameraPreview(
cameraClassController.cameraController,
),
),
),
),
Positioned(
top: 72,
child: Container(
width: 230,
height: 25,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.yellowColor,
width: 2),
),
),
),
Positioned(
top: 60,
right: 5,
child: Container(
width: 230,
height: 25,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 156,
right: 5,
child: Container(
width: 140,
height: 20,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 175,
right: 5,
child: Container(
width: 140,
height: 15,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 191,
right: 5,
child: Container(
width: 140,
height: 15,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 207,
right: 5,
child: Container(
width: 140,
height: 15,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 225,
right: 5,
child: Container(
width: 140,
height: 15,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 115,
left: 25,
child: Container(
width: 120,
height: 110,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
],
)
: Container(
decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4,
width: Get.width,
child: Center(
child: Text(
'Camera not initilaized yet'.tr,
style: AppStyle.title,
),
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Scan ID MklGoogle'.tr, onPressed: () {}),
// cameraClassController.takePictureAndMLGoogleScan()),
MyElevatedButton(
title: 'Scan ID Tesseract'.tr, onPressed: () {}),
],
),
MyElevatedButton(
title: 'Scan ID Api'.tr,
onPressed: () => cameraClassController.extractCardId()),
GetBuilder<CameraClassController>(
builder: (cameraClassController) => Expanded(
child:
Text(cameraClassController.scannedText.toString())))
],
)
],
isleading: true);
}
}
// @override
// Widget build(BuildContext context) {
// return MyScafolld(
// title: 'Scan Id'.tr,
// body: [
// Column(
// children: [
// Padding(
// padding:
// const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
// child: GetBuilder<CameraClassController>(
// builder: (cameraClassController) =>
// cameraClassController.isCameraInitialized
// ? Stack(
// children: [
// Container(
// decoration: AppStyle.boxDecoration,
// child: FittedBox(
// fit: BoxFit.fitWidth,
// child: SizedBox(
// width: Get.width * .9,
// height: Get.width *
// .9, // Set the desired aspect ratio here
// child: CameraPreview(
// cameraClassController.cameraController,
// ),
// ),
// ),
// ),
// Positioned(
// top: 72,
// child: Container(
// width: 230,
// height: 25,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.yellowColor,
// width: 2),
// ),
// ),
// ),
// Positioned(
// top: 60,
// right: 5,
// child: Container(
// width: 230,
// height: 25,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 156,
// right: 5,
// child: Container(
// width: 140,
// height: 20,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 175,
// right: 5,
// child: Container(
// width: 140,
// height: 15,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 191,
// right: 5,
// child: Container(
// width: 140,
// height: 15,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 207,
// right: 5,
// child: Container(
// width: 140,
// height: 15,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 225,
// right: 5,
// child: Container(
// width: 140,
// height: 15,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 115,
// left: 25,
// child: Container(
// width: 120,
// height: 110,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// ],
// )
// : Container(
// decoration: AppStyle.boxDecoration,
// height: Get.width * 3 / 4,
// width: Get.width,
// child: Center(
// child: Text(
// 'Camera not initilaized yet'.tr,
// style: AppStyle.title,
// ),
// ),
// ),
// ),
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// MyElevatedButton(
// title: 'Scan ID MklGoogle'.tr, onPressed: () {}),
// // cameraClassController.takePictureAndMLGoogleScan()),
// MyElevatedButton(
// title: 'Scan ID Tesseract'.tr, onPressed: () {}),
// ],
// ),
// MyElevatedButton(
// title: 'Scan ID Api'.tr,
// onPressed: () => cameraClassController.extractCardId()),
// GetBuilder<CameraClassController>(
// builder: (cameraClassController) => Expanded(
// child:
// Text(cameraClassController.scannedText.toString())))
// ],
// )
// ],
// isleading: true);
// }
// }
class CameraWidgetPassPort extends StatelessWidget {
final CameraClassController cameraClassController =
Get.put(CameraClassController());
// class CameraWidgetPassPort extends StatelessWidget {
// final CameraClassController cameraClassController =
// Get.put(CameraClassController());
CameraWidgetPassPort({super.key});
// CameraWidgetPassPort({super.key});
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Scan Id'.tr,
body: [
Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>(
builder: (cameraClassController) =>
cameraClassController.isCameraInitialized
? Stack(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: FittedBox(
fit: BoxFit.fitWidth,
child: SizedBox(
width: Get.width * .9,
height: Get.width *
.9, // Set the desired aspect ratio here
child: CameraPreview(
cameraClassController.cameraController,
),
),
),
),
Positioned(
top: 35,
left: 35,
width: Get.width * .77,
height:
17, //left":95.0,"top":134.0,"width":2909.0,"height":175.0
child: Container(
// width: 230,
// height: 25,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.yellowColor,
width: 2),
),
),
),
Positioned(
top: 60,
right: 25,
width: 90,
height: 25,
child: Container(
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
Positioned(
top: 110,
right: 90,
child: Container(
width: 140,
height: 20,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.blueColor, width: 2),
),
),
),
],
)
: Container(
decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4,
width: Get.width,
child: Center(
child: Text(
'Camera not initilaized yet',
style: AppStyle.title,
),
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Scan ID MklGoogle'.tr, onPressed: () => {}),
// cameraClassController.takePictureAndMLGoogleScan()),
MyElevatedButton(
title: 'Scan ID Tesseract'.tr, onPressed: () {}),
],
),
MyElevatedButton(
title: 'Scan ID Api'.tr,
onPressed: () => cameraClassController.extractCardId()),
GetBuilder<CameraClassController>(
builder: (cameraClassController) => Expanded(
child:
Text(cameraClassController.scannedText.toString())))
],
)
],
isleading: true);
}
}
// @override
// Widget build(BuildContext context) {
// return MyScafolld(
// title: 'Scan Id'.tr,
// body: [
// Column(
// children: [
// Padding(
// padding:
// const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
// child: GetBuilder<CameraClassController>(
// builder: (cameraClassController) =>
// cameraClassController.isCameraInitialized
// ? Stack(
// children: [
// Container(
// decoration: AppStyle.boxDecoration,
// child: FittedBox(
// fit: BoxFit.fitWidth,
// child: SizedBox(
// width: Get.width * .9,
// height: Get.width *
// .9, // Set the desired aspect ratio here
// child: CameraPreview(
// cameraClassController.cameraController,
// ),
// ),
// ),
// ),
// Positioned(
// top: 35,
// left: 35,
// width: Get.width * .77,
// height:
// 17, //left":95.0,"top":134.0,"width":2909.0,"height":175.0
// child: Container(
// // width: 230,
// // height: 25,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.yellowColor,
// width: 2),
// ),
// ),
// ),
// Positioned(
// top: 60,
// right: 25,
// width: 90,
// height: 25,
// child: Container(
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// Positioned(
// top: 110,
// right: 90,
// child: Container(
// width: 140,
// height: 20,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.blueColor, width: 2),
// ),
// ),
// ),
// ],
// )
// : Container(
// decoration: AppStyle.boxDecoration,
// height: Get.width * 3 / 4,
// width: Get.width,
// child: Center(
// child: Text(
// 'Camera not initilaized yet',
// style: AppStyle.title,
// ),
// ),
// ),
// ),
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// MyElevatedButton(
// title: 'Scan ID MklGoogle'.tr, onPressed: () => {}),
// // cameraClassController.takePictureAndMLGoogleScan()),
// MyElevatedButton(
// title: 'Scan ID Tesseract'.tr, onPressed: () {}),
// ],
// ),
// MyElevatedButton(
// title: 'Scan ID Api'.tr,
// onPressed: () => cameraClassController.extractCardId()),
// GetBuilder<CameraClassController>(
// builder: (cameraClassController) => Expanded(
// child:
// Text(cameraClassController.scannedText.toString())))
// ],
// )
// ],
// isleading: true);
// }
// }

View File

@@ -152,9 +152,11 @@ class InstructionsOfRoads extends StatelessWidget {
if (controller.currentInstruction.isEmpty) return const SizedBox();
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 500),
builder: (context, value, child) {
final theme = Theme.of(context);
return Transform.translate(
offset: Offset(0, 50 * (1 - value)), // حركة انزلاق
child: Opacity(
@@ -163,16 +165,15 @@ class InstructionsOfRoads extends StatelessWidget {
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFF1F1F1F)
.withOpacity(0.95), // خلفية داكنة
color: theme.cardColor.withOpacity(0.95), // Adaptive background
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
color: Colors.black.withOpacity(0.2),
blurRadius: 15,
offset: const Offset(0, 5)),
],
border: Border.all(color: Colors.white.withOpacity(0.1)),
border: Border.all(color: theme.dividerColor.withOpacity(0.1)),
),
child: Row(
children: [
@@ -187,7 +188,6 @@ class InstructionsOfRoads extends StatelessWidget {
color: Colors.white, size: 24),
),
const SizedBox(width: 14),
// نص التعليمات
Expanded(
child: Column(
@@ -195,18 +195,15 @@ class InstructionsOfRoads extends StatelessWidget {
children: [
Text(
"NEXT STEP".tr,
style: TextStyle(
color: Colors.grey.shade500,
fontSize: 10,
style: theme.textTheme.labelSmall?.copyWith(
color: theme.hintColor,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
),
const SizedBox(height: 2),
Text(
controller.currentInstruction,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
height: 1.2),
maxLines: 2,
@@ -216,6 +213,7 @@ class InstructionsOfRoads extends StatelessWidget {
),
),
// فاصل عمودي
Container(
width: 1,
@@ -278,12 +276,13 @@ class CancelWidget extends StatelessWidget {
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
color: Theme.of(context).cardColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8)
BoxShadow(color: Theme.of(context).shadowColor.withOpacity(0.1), blurRadius: 8)
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
@@ -379,16 +378,17 @@ class PricesWindow extends StatelessWidget {
width: Get.width * 0.85,
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
color: Theme.of(context).shadowColor.withOpacity(0.2),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -411,11 +411,12 @@ class PricesWindow extends StatelessWidget {
Text(
'${controller.totalCost} ${'\$'.tr}',
style: AppStyle.headTitle2.copyWith(
color: Colors.black87,
color: Theme.of(context).textTheme.bodyLarge?.color,
fontSize: 42,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 30),
SizedBox(
width: double.infinity,

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/auth/captin/history_captain.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -13,74 +14,159 @@ class HistoryCaptain extends StatelessWidget {
Get.put(HistoryCaptainController());
return Scaffold(
backgroundColor: Colors.grey[100], // A softer background color
appBar: AppBar(
title: Text('Ride History'.tr),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 1,
),
backgroundColor: Colors.white,
body: GetBuilder<HistoryCaptainController>(
builder: (controller) {
if (controller.isloading) {
return const Center(child: CircularProgressIndicator());
}
if (controller.historyData['message'].isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.history_toggle_off,
size: 80, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'No Rides Yet'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
],
),
return const Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark),
);
}
// 动画: Wrap ListView with AnimationLimiter for staggered animations
return AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: controller.historyData['message'].length,
itemBuilder: (BuildContext context, int index) {
var trip = controller.historyData['message'][index];
final List trips = controller.historyData['message'] ?? [];
// 动画: Apply animation to each list item
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _AnimatedHistoryCard(
trip: trip,
onTap: () {
// Your original logic is preserved here
if (trip['status'] != 'Cancel') {
controller.getHistoryDetails(trip['order_id']);
} else {
MyDialog().getDialog(
'This Trip Was Cancelled'.tr,
'This Trip Was Cancelled'.tr,
() => Get.back(),
);
}
},
),
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// 1. Custom App Bar
SliverAppBar(
expandedHeight: 180,
pinned: true,
stretch: true,
backgroundColor: FinanceDesignSystem.primaryDark,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'Ride History'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
},
),
background: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: const BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
),
),
Positioned(
right: -50,
top: -20,
child: Icon(
Icons.history_rounded,
size: 200,
color: Colors.white.withOpacity(0.05),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
Text(
trips.length.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w900,
),
),
Text(
'Total Rides'.tr,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
),
// 2. Trips List
if (trips.isEmpty)
SliverFillRemaining(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.grey.shade50,
shape: BoxShape.circle,
),
child: Icon(
Icons.history_toggle_off_rounded,
size: 64,
color: Colors.grey.shade300,
),
),
const SizedBox(height: 24),
Text(
'No Rides Yet'.tr,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 8),
Text(
'Your completed trips will appear here'.tr,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade400,
),
),
],
),
),
)
else
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 40),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final trip = trips[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _TripHistoryCard(
trip: trip,
onTap: () {
if (trip['status'] != 'Cancel' &&
trip['status'] != 'CancelByPassenger') {
controller
.getHistoryDetails(trip['order_id']);
} else {
MyDialog().getDialog(
'This Trip Was Cancelled'.tr,
'This Trip Was Cancelled'.tr,
() => Get.back(),
);
}
},
),
),
),
);
},
childCount: trips.length,
),
),
),
],
);
},
),
@@ -88,97 +174,159 @@ class HistoryCaptain extends StatelessWidget {
}
}
// 动画: A new stateful widget to handle the tap animation
class _AnimatedHistoryCard extends StatefulWidget {
final Map<String, dynamic> trip;
class _TripHistoryCard extends StatelessWidget {
final Map trip;
final VoidCallback onTap;
const _AnimatedHistoryCard({required this.trip, required this.onTap});
const _TripHistoryCard({required this.trip, required this.onTap});
@override
__AnimatedHistoryCardState createState() => __AnimatedHistoryCardState();
}
class __AnimatedHistoryCardState extends State<_AnimatedHistoryCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
_controller.forward();
}
void _onTapUp(TapUpDetails details) {
_controller.reverse();
widget.onTap();
}
void _onTapCancel() {
_controller.reverse();
String _formatDateToSyria(String? dateStr) {
if (dateStr == null) return '';
try {
// Parse GMT date
DateTime date = DateTime.parse(dateStr);
// Add 3 hours for Syria (GMT+3)
DateTime syriaDate = date.add(const Duration(hours: 3));
// Format to readable string
return DateFormat('yyyy-MM-dd HH:mm').format(syriaDate);
} catch (e) {
return dateStr;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: ScaleTransition(
scale: _scaleAnimation,
child: Card(
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
margin: const EdgeInsets.only(bottom: 16.0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
return Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Icon(Icons.receipt_long,
color: Theme.of(context).primaryColor, size: 40),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'OrderId'.tr}: ${widget.trip['order_id']}',
style:
Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
widget.trip['created_at'],
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: FinanceDesignSystem.primaryDark
.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.receipt_long_rounded,
color: FinanceDesignSystem.primaryDark,
size: 20,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'OrderId'.tr} #${trip['order_id']}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
),
),
],
),
Text(
_formatDateToSyria(trip['created_at']),
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
),
],
),
],
),
_buildStatusChip(trip['status']),
],
),
const SizedBox(height: 20),
const Divider(height: 1),
const SizedBox(height: 20),
Row(
children: [
Column(
children: [
const Icon(Icons.circle,
size: 12, color: FinanceDesignSystem.accentBlue),
Container(
width: 2,
height: 20,
color: Colors.grey.shade200,
),
const Icon(Icons.location_on_rounded,
size: 14, color: FinanceDesignSystem.dangerRed),
],
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
trip['start_name'] ?? 'Pickup Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
Text(
trip['end_name'] ?? 'Destination Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
if (trip['price'] != null)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${trip['price']} ${'SYP'.tr}',
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 16,
color: FinanceDesignSystem.primaryDark,
),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 12, color: Colors.grey),
],
),
],
),
const SizedBox(width: 16),
_buildStatusChip(widget.trip['status']),
],
),
),
@@ -186,46 +334,78 @@ class __AnimatedHistoryCardState extends State<_AnimatedHistoryCard>
),
);
}
}
// 🎨 A separate function for the status chip, slightly restyled for Material
Widget _buildStatusChip(String status) {
Color chipColor;
Color textColor;
String statusText = status;
IconData iconData;
Widget _buildStatusChip(String? status) {
Color color;
IconData icon;
String label = status ?? 'Unknown';
List<Color> gradientColors;
switch (status) {
case 'Apply':
chipColor = Colors.green.shade50;
textColor = Colors.green.shade800;
iconData = Icons.check_circle;
break;
case 'Refused':
chipColor = Colors.red.shade50;
textColor = Colors.red.shade800;
iconData = Icons.cancel;
break;
case 'Cancel':
chipColor = Colors.orange.shade50;
textColor = Colors.orange.shade800;
iconData = Icons.info;
statusText = 'Cancelled';
break;
default:
chipColor = Colors.grey.shade200;
textColor = Colors.grey.shade800;
iconData = Icons.hourglass_empty;
switch (status) {
case 'Apply':
color = FinanceDesignSystem.successGreen;
icon = Icons.check_circle_rounded;
label = 'Completed'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Refused':
color = FinanceDesignSystem.dangerRed;
icon = Icons.cancel_rounded;
label = 'Refused'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Cancel':
color = Colors.orange;
icon = Icons.info_rounded;
label = 'Cancelled'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'CancelByPassenger':
color = const Color(0xFF6B4EFF);
icon = Icons.person_off_rounded;
label = 'Cancelled by Passenger'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
default:
color = Colors.grey;
icon = Icons.help_rounded;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradientColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3), width: 1),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: color,
fontSize: 11,
fontWeight: FontWeight.w800,
letterSpacing: 0.3,
),
),
],
),
);
}
return Chip(
avatar: Icon(iconData, color: textColor, size: 16),
label: Text(
statusText.tr,
style: TextStyle(color: textColor, fontWeight: FontWeight.w600),
),
backgroundColor: chipColor,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
side: BorderSide.none,
);
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/controller/auth/captin/history_captain.dart';
@@ -168,7 +169,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
return Card(
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
shadowColor: Colors.black.withValues(alpha: 0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior:
Clip.antiAlias, // Ensures the map respects the border radius
@@ -176,7 +177,8 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
children: [
SizedBox(
height: 250,
child: GoogleMap(
child: IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: initialCameraPosition,
markers: markers,
polylines: {
@@ -188,7 +190,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
width: 5,
),
},
onMapCreated: (GoogleMapController mapController) {
onMapCreated: (IntaleqMapController mapController) {
// Animate camera to fit the route
if (startLocation != null && endLocation != null) {
LatLngBounds bounds = LatLngBounds(
@@ -210,7 +212,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
),
);
mapController.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 60.0));
CameraUpdate.newLatLngBounds(bounds, left: 60, top: 60, right: 60, bottom: 60));
}
},
),
@@ -252,7 +254,7 @@ class _DetailCard extends StatelessWidget {
Widget build(BuildContext context) {
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
shadowColor: Colors.black.withValues(alpha: 0.05),
margin: const EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(

View File

@@ -5,7 +5,9 @@ import 'package:sefer_driver/views/home/Captin/home_captain/help_details_replay_
import 'package:flutter/cupertino.dart';
import '../../../../constant/colors.dart';
import '../../../../controller/functions/encrypt_decrypt.dart';
import '../../../widgets/my_scafold.dart';
class HelpCaptain extends StatelessWidget {
HelpCaptain({super.key});
@@ -13,162 +15,158 @@ class HelpCaptain extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(HelpController());
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(
'Helping Page'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
leading: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => Navigator.pop(context),
child: const Icon(CupertinoIcons.back),
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(
20.0), // Increased padding around the content
child: ListView(
// crossAxisAlignment:
// CrossAxisAlignment.stretch, // Stretch children to full width
final theme = Theme.of(context);
return MyScafolld(
title: 'Helping Page'.tr,
isleading: true,
body: [
SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding:
const EdgeInsets.all(18.0), // Slightly increased padding
padding: const EdgeInsets.all(18.0),
decoration: BoxDecoration(
color:
CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.systemGrey6
: CupertinoColors.darkBackgroundGray,
borderRadius:
BorderRadius.circular(15.0), // More rounded corners
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(15.0),
border: Border.all(color: theme.dividerColor),
),
child: Text(
'If you need any help or have questions, this is the right place to do that. You are welcome!'
.tr,
style:
CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16, // Slightly larger font size
color: CupertinoColors.label.resolveFrom(
context), // Ensure text color adapts to theme
),
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
),
),
const SizedBox(height: 20),
CupertinoFormSection.insetGrouped(
// Using CupertinoFormSection for better styling
header: Text('Submit Your Question'.tr),
margin: EdgeInsets.zero,
children: [
GetBuilder<HelpController>(
builder: (helpController) => Form(
key: helpController.formKey,
child: CupertinoTextFormFieldRow(
const SizedBox(height: 24),
Text(
'Submit Your Question'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
GetBuilder<HelpController>(
builder: (helpController) => Form(
key: helpController.formKey,
child: Column(
children: [
TextFormField(
controller: helpController.helpQuestionController,
placeholder: 'Enter your Question here'.tr,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: InputDecoration(
hintText: 'Enter your Question here'.tr,
prefixIcon:
const Icon(Icons.question_answer_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
),
maxLines: 3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your question'.tr;
}
return null;
},
prefix: const Icon(CupertinoIcons
.question_circle), // Added a prefix icon
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
child: GetBuilder<HelpController>(
builder: (helpController) => helpController.isLoading
? const CupertinoActivityIndicator()
: CupertinoButton.filled(
onPressed: () {
if (helpController.formKey.currentState!
.validate()) {
helpController.addHelpQuestion();
helpController.helpQuestionController
.clear(); // Clear the text field
}
},
child: Text('Submit Question'.tr),
),
),
),
],
),
const SizedBox(height: 20),
Text(
'Your Questions'.tr,
style: CupertinoTheme.of(context)
.textTheme
.navTitleTextStyle
.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Expanded(
child: GetBuilder<HelpController>(
builder: (helpController) =>
CupertinoListSection.insetGrouped(
margin: EdgeInsets.zero,
children: helpController.helpQuestionDate['message'] != null
? List.generate(
helpController.helpQuestionDate['message'].length,
(index) {
var list = helpController
.helpQuestionDate['message'][index];
return CupertinoListTile(
title: Text(
EncryptionHelper.instance
.decryptData(list['helpQuestion']),
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
list['datecreated'],
style: CupertinoTheme.of(context)
.textTheme
.tabLabelTextStyle,
),
const Icon(CupertinoIcons.chevron_forward),
],
),
onTap: () {
helpController.getIndex(
int.parse(EncryptionHelper.instance
.decryptData(list['id'])),
EncryptionHelper.instance
.decryptData(list['helpQuestion']));
helpController
.getHelpRepley(list['id'].toString());
Get.to(() => const HelpDetailsReplayPage());
const SizedBox(height: 16),
helpController.isLoading
? const Center(child: CircularProgressIndicator())
: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
if (helpController.formKey.currentState!
.validate()) {
helpController.addHelpQuestion();
helpController.helpQuestionController
.clear();
}
},
);
},
)
: [
Center(
child: Text('No questions asked yet.'.tr),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text('Submit Question'.tr),
),
),
],
],
),
),
),
const SizedBox(height: 32),
Text(
'Your Questions'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
GetBuilder<HelpController>(
builder: (helpController) {
final questions = helpController.helpQuestionDate['message'];
if (questions == null || questions.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text('No questions asked yet.'.tr),
),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: questions.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
var list = questions[index];
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: theme.dividerColor),
),
child: ListTile(
title: Text(
EncryptionHelper.instance
.decryptData(list['helpQuestion']),
style: const TextStyle(fontWeight: FontWeight.w500),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
list['datecreated'],
style: theme.textTheme.bodySmall,
),
trailing:
const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
helpController.getIndex(
int.parse(EncryptionHelper.instance
.decryptData(list['id'])),
EncryptionHelper.instance
.decryptData(list['helpQuestion']));
helpController.getHelpRepley(list['id'].toString());
Get.to(() => const HelpDetailsReplayPage());
},
),
);
},
);
},
),
],
),
),
),
],
);
}
}
// class HelpCaptain extends StatelessWidget {
// HelpCaptain({super.key});

View File

@@ -19,65 +19,90 @@ class HelpDetailsReplayPage extends StatelessWidget {
body: [
helpController.isLoading
? const MyCircularProgressIndicator()
: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Card(
elevation: 3,
child: Container(
width: Get.width * .66,
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
helpController.qustion,
style: AppStyle.title,
),
),
),
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Card(
elevation: 3,
child: Container(
color: Colors.transparent,
width: Get.width * .66,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: helpController.status ==
'not yet' ||
EncryptionHelper.instance
.decryptData(helpController
.helpQuestionRepleyDate[
'message']['replay'])
.toString() ==
'not yet'
? Text(
'No Response yet.'.tr,
style: AppStyle.title,
)
: Text(
EncryptionHelper.instance
.decryptData(helpController
.helpQuestionRepleyDate[
'message']['replay'])
.toString(),
style: AppStyle.title,
),
),
: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Question Bubble (Aligned to Start/End based on locale, usually start for sender)
Align(
alignment: AlignmentDirectional.centerStart,
child: Container(
constraints: BoxConstraints(maxWidth: Get.width * 0.75),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
]),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Question'.tr,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
helpController.qustion,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
),
const SizedBox(height: 24),
// Reply Bubble (Aligned to opposite side)
Align(
alignment: AlignmentDirectional.centerEnd,
child: Container(
constraints: BoxConstraints(maxWidth: Get.width * 0.75),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Support Reply'.tr,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Builder(builder: (context) {
final replayData = helpController.helpQuestionRepleyDate['message'];
final isNoReply = helpController.status == 'not yet' ||
replayData == null ||
EncryptionHelper.instance.decryptData(replayData['replay']).toString() == 'not yet';
return Text(
isNoReply
? 'No Response yet.'.tr
: EncryptionHelper.instance.decryptData(replayData['replay']).toString(),
style: Theme.of(context).textTheme.bodyLarge,
);
}),
],
),
),
),
],
),
)
],
isleading: true,
));

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../../constant/api_key.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/home_captain_controller.dart';
// هذه ويدجت بديلة للـ _MapView في الكود الخاص بك
// V3 - MapView Replacement using flutter_map
class OsmMapView extends StatelessWidget {
const OsmMapView({super.key});
@override
Widget build(BuildContext context) {
// افترض أنك تحصل على الموقع والاتجاه بنفس الطريقة من الكونترولر
final LocationController locationController =
Get.find<LocationController>();
final HomeCaptainController homeCaptainController =
Get.find<HomeCaptainController>();
// يمكنك استخدام GetBuilder لمراقبة التغييرات في الموقع
return Obx(() {
final LatLng currentLocation = LatLng(
locationController.myLocation.latitude,
locationController.myLocation.longitude);
final double currentHeading = locationController.heading;
return FlutterMap(
// يمكنك ربط هذا بـ MapController الخاص بـ flutter_map
// mapController: homeCaptainController.flutterMapController,
options: MapOptions(
initialCenter: currentLocation,
initialZoom: 15,
maxZoom: 18,
minZoom: 6,
// تدوير الخريطة (اختياري)
initialRotation: currentHeading,
return IntaleqMap(
apiKey: AK.mapSaasKey,
initialCameraPosition: CameraPosition(
target: currentLocation,
zoom: 15,
bearing: currentHeading,
),
children: [
// 1. طبقة الخريطة الأساسية (Tiles)
// هذا هو الرابط لخرائط OSM الأساسية
TileLayer(
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
userAgentPackageName:
'com.example.app', // استبدل باسم الباكج الخاص بك
// لاستخدام الخرائط الأوفلاين (بعد إعداد flutter_map_tile_caching)
// tileProvider: CachedTileProvider(),
),
// 2. طبقة العلامات (Markers)
MarkerLayer(
markers: [
Marker(
width: 80.0,
height: 80.0,
point: currentLocation,
child: Transform.rotate(
angle: currentHeading *
(3.1415926535 / 180), // تحويل من درجات إلى راديان
child: Image.asset(
'assets/images/car_icon.png', // تأكد أن لديك أيقونة السيارة
// يمكنك استخدام نفس الـ carIcon من الكونترولر
// icon: homeCaptainController.carIcon, (ملاحظة: flutter_map تستخدم ويدجت)
),
),
),
],
),
// يمكنك إضافة طبقات أخرى هنا (مثل الخطوط Polylines أو المضلعات Polygons)
],
markers: {
Marker(
markerId: const MarkerId('myLocation'),
position: currentLocation,
rotation: currentHeading,
icon: homeCaptainController.carIcon,
anchor: const Offset(0.5, 0.5),
flat: true,
zIndex: 2,
)
},
onMapCreated: (IntaleqMapController controller) {
// You can assign this controller if needed
},
);
});
}
}
// ملاحظة: ستحتاج إلى تعديل بسيط في HomeCaptainController
// لإنشاء MapController الخاص بـ flutter_map بدلاً من GoogleMapController
// import 'package:flutter_map/flutter_map.dart';
// MapController flutterMapController = MapController();

View File

@@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
import '../../../../../constant/style.dart';
import '../../../../../controller/functions/encrypt_decrypt.dart';
import '../../../../widgets/elevated_btn.dart';
import '../../../../../controller/home/captin/home_captain_controller.dart';
@@ -91,45 +90,56 @@ class ConnectWidget extends StatelessWidget {
: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: homeCaptainController.isActive
? [Colors.green.shade400, Colors.green.shade700]
: [Colors.grey.shade400, Colors.grey.shade700],
? [const Color(0xFF00C853), const Color(0xFF00E676)]
: [Colors.grey.shade600, Colors.grey.shade400],
),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: homeCaptainController.isActive
? Colors.green.withOpacity(0.3)
: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 8,
offset: const Offset(0, 2),
color: (homeCaptainController.isActive
? const Color(0xFF00C853)
: Colors.grey)
.withOpacity(0.4),
spreadRadius: 0,
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: CupertinoButton(
onPressed: homeCaptainController.onButtonSelected,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
horizontal: 20, vertical: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
homeCaptainController.isActive
? CupertinoIcons.check_mark_circled_solid
: CupertinoIcons.circle,
color: Colors.white,
size: 24,
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
homeCaptainController.isActive
? Icons.power_settings_new_rounded
: Icons.power_off_rounded,
color: Colors.white,
size: 20,
),
),
const SizedBox(width: 8),
const SizedBox(width: 10),
Text(
homeCaptainController.isActive
? 'Connected'.tr
: 'Not Connected'.tr,
? 'Online'.tr
: 'Offline'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
],

View File

@@ -2,7 +2,6 @@ import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/firebase/local_notification.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/auth/captin/otp_page.dart';
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
import 'package:flutter/material.dart';
@@ -13,7 +12,6 @@ import 'package:sefer_driver/views/widgets/mydialoug.dart';
import '../../../../../constant/colors.dart';
import '../../../../../constant/links.dart';
import '../../../../../controller/firebase/firbase_messge.dart';
import '../../../../../controller/firebase/notification_service.dart';
import '../../../../../controller/functions/crud.dart';
import '../../../../../controller/home/captin/order_request_controller.dart';
@@ -21,319 +19,279 @@ import '../../../../../controller/home/navigation/navigation_view.dart';
import '../../../../../print.dart';
import '../../../../Rate/ride_calculate_driver.dart';
// ─────────────────────────────────────────────
// Design Tokens (Responsive)
// ─────────────────────────────────────────────
class _T {
static Color surface(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF16213E)
: Colors.white;
static const Color accent = Color(0xFFF0A500);
static const Color blue = Color(0xFF3498DB);
static Color border(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF2A2A4A)
: Colors.grey.withOpacity(0.2);
static const double radius = 14.0;
}
// ─────────────────────────────────────────────
// Left Side Menu
// ─────────────────────────────────────────────
/// Returns the vertical icon column anchored to the left-center of the map.
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
return GetBuilder<HomeCaptainController>(
builder: (controller) => Positioned(
bottom: Get.height * .2,
left: 6,
child: Column(
children: [
AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: Builder(builder: (context) {
return IconButton(
onPressed: () async {
await checkForPendingOrderFromServer();
box.read(BoxName.rideArgumentsFromBackground) != 'failure'
? Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArgumentsFromBackground))
: MyDialog().getDialog(
'Ride info'.tr,
'you dont have accepted ride'.tr,
() {
Get.back();
},
);
// 'box.read(BoxName.rideArgumentsFromBackground): ${box.read(BoxName.rideArgumentsFromBackground)}');
},
icon: Icon(
Icons.directions_car_rounded,
size: 29,
color:
box.read(BoxName.rideArgumentsFromBackground) == 'failure'
? AppColor.redColor
: AppColor.greenColor,
),
);
}),
),
const SizedBox(
height: 5,
),
AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: IconButton(
onLongPress: () {
box.write(BoxName.statusDriverLocation, 'off');
},
onPressed: () {
// NotificationController1()
// .showNotification('Sefer Driver'.tr, ''.tr, '', '');
final now = DateTime.now();
DateTime? lastRequestTime =
box.read(BoxName.lastTimeStaticThrottle);
if (lastRequestTime == null ||
now.difference(lastRequestTime).inMinutes >= 2) {
// Update the last request time to now
lastRequestTime = now;
box.write(BoxName.lastTimeStaticThrottle, lastRequestTime);
// Navigate to the RideCalculateDriver page
Get.to(() => RideCalculateDriver());
} else {
// Optionally show a message or handle the throttling case
final minutesLeft =
2 - now.difference(lastRequestTime).inMinutes;
// Get.snackbar(
// '${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
// '');
NotificationController().showNotification(
'Intaleq Driver'.tr,
'${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
'ding',
'');
}
},
icon: const Icon(
FontAwesome5.chart_bar,
size: 29,
color: AppColor.blueColor,
builder: (ctrl) => Positioned(
// Place just above the bottom status bar
bottom: 100,
left: 10,
child: Builder(builder: (context) {
return Container(
decoration: BoxDecoration(
color: _T.surface(context),
borderRadius: BorderRadius.circular(_T.radius),
border: Border.all(color: _T.border(context)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(2, 4),
),
),
],
),
const SizedBox(
height: 5,
),
// Platform.isAndroid
// ?
int.parse(box.read(BoxName.carYear).toString()) > 2023
? AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: Builder(builder: (context) {
return IconButton(
onPressed: () async {
// mySnakeBarError('ad');
Get.to(() => const VipOrderPage());
},
icon: const Icon(
Octicons.watch,
size: 29,
color: AppColor.blueColor,
),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── 1. Active Ride shortcut ──────────
_MenuIcon(
icon: Icons.directions_car_rounded,
color:
box.read(BoxName.rideArgumentsFromBackground) == 'failure'
? Colors.red.shade400
: Colors.green.shade400,
tooltip: 'Active Ride'.tr,
onTap: () async {
await checkForPendingOrderFromServer();
if (box.read(BoxName.rideArgumentsFromBackground) != 'failure') {
Get.to(
() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground),
);
}),
)
: const SizedBox(),
// const SizedBox(
// height: 5,
// ),
AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: Builder(builder: (context) {
return IconButton(
onPressed: () async {
box.remove(BoxName.agreeTerms);
Get.to(() => const NavigationView());
// box.write(BoxName.statusDriverLocation, 'off');
} else {
MyDialog().getDialog(
'Ride info'.tr,
'you dont have accepted ride'.tr,
() => Get.back(),
);
}
},
icon: const Icon(
FontAwesome5.map,
size: 29,
color: AppColor.blueColor,
),
);
}),
),
// AnimatedContainer(
// duration: const Duration(microseconds: 200),
// width: controller.widthMapTypeAndTraffic,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// border: Border.all(color: AppColor.blueColor),
// borderRadius: BorderRadius.circular(15)),
// child: Builder(builder: (context) {
// return IconButton(
// onPressed: () async {
// NotificationService.sendNotification(
// target: 'service', // الإرسال لجميع المشتركين في "service"
// title: 'طلب خدمة جديد',
// body: 'تم استلام طلب خدمة جديد. الرجاء مراجعة التفاصيل.',
// isTopic: true,
// category: 'new_service_request', // فئة توضح نوع الإشعار
// );
// },
// icon: const Icon(
// FontAwesome5.grin_tears,
// size: 29,
// color: AppColor.blueColor,
// ),
// );
// }),
// ),
),
const SizedBox(
height: 5,
_Divider(context),
// ── 2. Earnings Chart ────────────────
_MenuIcon(
icon: FontAwesome5.chart_bar,
color: _T.blue,
tooltip: 'Earnings'.tr,
onTap: () {
final now = DateTime.now();
DateTime? lastTime = box.read(BoxName.lastTimeStaticThrottle);
if (lastTime == null ||
now.difference(lastTime).inMinutes >= 2) {
box.write(BoxName.lastTimeStaticThrottle, now);
Get.to(() => RideCalculateDriver());
} else {
final left = 2 - now.difference(lastTime).inMinutes;
NotificationController().showNotification(
'Intaleq Driver'.tr,
'${'Please wait'.tr} $left ${"minutes before trying again.".tr}',
'ding',
'',
);
}
},
onLongPress: () =>
box.write(BoxName.statusDriverLocation, 'off'),
),
// ── 3. VIP Orders (2023+ cars only) ──
if (int.tryParse(box.read(BoxName.carYear).toString()) != null &&
int.parse(box.read(BoxName.carYear).toString()) > 2023) ...[
_Divider(context),
_MenuIcon(
icon: Octicons.watch,
color: _T.accent,
tooltip: 'VIP Orders'.tr,
onTap: () => Get.to(() => const VipOrderPage()),
),
],
],
),
],
),
);
}),
),
);
}
// ─────────────────────────────────────────────
// Reusable sub-widgets
// ─────────────────────────────────────────────
class _MenuIcon extends StatelessWidget {
final IconData icon;
final Color color;
final String tooltip;
final VoidCallback onTap;
final VoidCallback? onLongPress;
const _MenuIcon({
required this.icon,
required this.color,
required this.tooltip,
required this.onTap,
this.onLongPress,
});
@override
Widget build(BuildContext context) => Tooltip(
message: tooltip,
child: InkWell(
onTap: onTap,
onLongPress: onLongPress,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Icon(icon, color: color, size: 26),
),
),
);
}
/// Thin separator between icons
class _Divider extends StatelessWidget {
final BuildContext context;
const _Divider(this.context);
@override
Widget build(BuildContext context) => Container(
height: 1,
width: 32,
margin: const EdgeInsets.symmetric(horizontal: 6),
color: _T.border(this.context),
);
}
// ─────────────────────────────────────────────
// Server Helpers (unchanged logic)
// ─────────────────────────────────────────────
Future<void> checkForPendingOrderFromServer() async {
bool _isProcessingOrder = false;
if (_isProcessingOrder) return;
bool isProcessing = false;
if (isProcessing) return;
final driverId = box.read(BoxName.driverID)?.toString();
if (driverId == null) return; // Can't check without a driver ID
if (driverId == null) return;
_isProcessingOrder = true; // Lock
isProcessing = true;
try {
// You need to create this CRUD method
var response = await CRUD().post(
link: AppLink.getArgumentAfterAppliedFromBackground,
payload: {'driver_id': driverId},
);
Log.print('response: ${response}');
Log.print('response: $response');
// Assuming the server returns order data if found, or 'failure'/'none' if not
if (response['status'] == 'success') {
final Map<String, dynamic> orderInfoFromServer = response['message'];
final Map<String, dynamic> rideArguments =
_transformServerDataToAppArguments(orderInfoFromServer);
// 2. Build the new arguments map, matching your Flutter structure
final Map<String, dynamic> orderInfo = response['message'];
final Map<String, dynamic> rideArgs =
_transformServerDataToAppArguments(orderInfo);
/////////////
final customerToken = (response)['message']['token_passenger'];
final orderId = (response)['message']['ride_id'].toString();
box.write(BoxName.rideArgumentsFromBackground, rideArguments);
final customerToken = response['message']['token_passenger'];
final orderId = response['message']['ride_id'].toString();
box.write(BoxName.rideArgumentsFromBackground, rideArgs);
box.write(BoxName.statusDriverLocation, 'on');
box.write(BoxName.rideStatus, 'Apply');
Get.put(OrderRequestController()).changeApplied();
// MyDialog().getDialog(orderId.toString(), customerToken, () {});
// Now proceed with the UI flow
// _sendAcceptanceNotification(customerToken, orderId.toString());
// await _bringAppToForegroundAndNavigate(orderId);
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground));
Get.to(
() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground),
);
} else {
box.write(BoxName.rideArgumentsFromBackground, 'failure');
}
} catch (e) {
} catch (_) {
// silent
} finally {
_isProcessingOrder = false; // Release lock
isProcessing = false;
}
}
Map<String, dynamic> _transformServerDataToAppArguments(
Map<String, dynamic> serverData) {
// Helper function to safely get and convert values to String
String _getString(String key, [String defaultValue = 'unknown']) {
// serverData[key] might be an int, double, or string. .toString() handles all.
// If it's null, use the default value.
return serverData[key]?.toString() ?? defaultValue;
}
Map<String, dynamic> d) {
String s(String key, [String def = 'unknown']) => d[key]?.toString() ?? def;
return {
'passengerLocation': _getString('passenger_location'),
'passengerDestination': _getString('passenger_destination'),
'Duration': _getString('duration'),
'totalCost': _getString('total_cost'),
'Distance': _getString('distance'),
'name': _getString('name'),
'phone': _getString('phone'),
'email': _getString('email'),
'tokenPassenger': _getString('token_passenger'),
'direction': _getString('direction_url'),
'DurationToPassenger': _getString('duration_to_passenger'),
'rideId': _getString('ride_id'),
'passengerId': _getString('passenger_id'),
'driverId': _getString('driver_id'),
'durationOfRideValue': _getString('duration_of_ride'),
'paymentAmount': _getString('payment_amount'),
'paymentMethod': _getString('payment_method'),
'passengerWalletBurc': _getString('passenger_wallet_burc'),
'timeOfOrder': _getString('time_of_order'),
'totalPassenger': _getString('total_passenger'),
'carType': _getString('car_type'),
'kazan': _getString('kazan'),
'startNameLocation': _getString('start_name_location'),
'endNameLocation': _getString('end_name_location'),
// --- Special Handling ---
// Steps (handle null values by providing an empty string)
'step0': _getString('step0'),
'step1': _getString('step1'),
'step2': _getString('step2'),
'step3': _getString('step3'),
'step4': _getString('step4'),
// Boolean conversion (1/0 from server to 'true'/'false' string for the app)
'WalletChecked': (serverData['wallet_checked'] == 1).toString(),
// Logic-based conversion for isHaveSteps
// Your app's `rideArguments` expects 'startEnd', so we provide that if has_steps is 1.
// You might need to adjust this logic if 'haveSteps' is also a possibility.
'isHaveSteps': (serverData['has_steps'] == 1)
? 'startEnd'
: 'noSteps', // Providing a default
'passengerLocation': s('passenger_location'),
'passengerDestination': s('passenger_destination'),
'Duration': s('duration'),
'totalCost': s('total_cost'),
'Distance': s('distance'),
'name': s('name'),
'phone': s('phone'),
'email': s('email'),
'tokenPassenger': s('token_passenger'),
'direction': s('direction_url'),
'DurationToPassenger': s('duration_to_passenger'),
'rideId': s('ride_id'),
'passengerId': s('passenger_id'),
'driverId': s('driver_id'),
'durationOfRideValue': s('duration_of_ride'),
'paymentAmount': s('payment_amount'),
'paymentMethod': s('payment_method'),
'passengerWalletBurc': s('passenger_wallet_burc'),
'timeOfOrder': s('time_of_order'),
'totalPassenger': s('total_passenger'),
'carType': s('car_type'),
'kazan': s('kazan'),
'startNameLocation': s('start_name_location'),
'endNameLocation': s('end_name_location'),
'step0': s('step0'),
'step1': s('step1'),
'step2': s('step2'),
'step3': s('step3'),
'step4': s('step4'),
'WalletChecked': (d['wallet_checked'] == 1).toString(),
'isHaveSteps': (d['has_steps'] == 1) ? 'startEnd' : 'noSteps',
};
}
void _sendAcceptanceNotification(String? customerToken, rideId) {
try {
if (customerToken == null) return;
void _sendAcceptanceNotification(String? customerToken, dynamic rideId) {
if (customerToken == null || customerToken.isEmpty) return;
List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
rideId.toString()
];
List<String> body = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
rideId.toString(),
];
// Safely check for customer token
final String? token = customerToken;
if (token != null && token.isNotEmpty) {
NotificationService.sendNotification(
target: token.toString(),
title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token
tone: 'start',
driverList: bodyToPassenger, category: 'Accepted Ride',
);
} else {}
} catch (e) {}
NotificationService.sendNotification(
target: customerToken,
title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr,
isTopic: false,
tone: 'start',
driverList: body,
category: 'Accepted Ride',
);
}

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:math';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class ZonesController extends GetxController {
Map<String, List<LatLng>> generateZoneMap(

View File

@@ -132,10 +132,11 @@ class MaintainCenterPage extends StatelessWidget {
"When you complete 600 trips, you will be eligible to receive offers for maintenance of your car."
.tr,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Colors.grey[700],
color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.8),
height: 1.4,
),
),
const SizedBox(height: 20),
// Trip Count Section in a Card
@@ -192,10 +193,11 @@ class MaintainCenterPage extends StatelessWidget {
.textTheme
.bodyMedium!
.copyWith(
color: Colors.grey[800],
color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.9),
height: 1.5,
),
),
],
),
),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
@@ -21,76 +22,24 @@ class GoogleDriverMap extends StatelessWidget {
final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3;
return GetBuilder<MapDriverController>(
builder: (controller) => GoogleMap(
builder: (controller) => IntaleqMap(
apiKey: AK.mapAPIKEY,
onMapCreated: (mapController) {
controller.onMapCreated(mapController);
// New: تطبيق الـ padding بعد إنشاء الخريطة مباشرة
mapController.setMapStyle('[]'); // يمكنك إضافة تصميم مخصص للخريطة هنا
// يمكنك استخدام CameraUpdate.scrollBy لتحريك الكاميرا إذا رغبت بذلك:
// controller.mapController!.animateCamera(CameraUpdate.scrollBy(0, mapPaddingBottom));
},
// New: إضافة padding لتحريك مركز الخريطة للأعلى، مما يجعل أيقونة السائق تظهر في الأسفل
zoomControlsEnabled: false, // Changed: تم إخفاء أزرار الزوم الافتراضية
zoomControlsEnabled: false,
initialCameraPosition: CameraPosition(
target: locationController.myLocation,
zoom: 17,
bearing: locationController.heading, // استخدام اتجاه السائق
tilt: 60, // زاوية ميل
bearing: locationController.heading,
tilt: 60,
),
onCameraMove: (position) {
CameraPosition(
target: locationController.myLocation,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
);
},
padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
myLocationEnabled: false, // Changed: تم الاعتماد على ماركر مخصص
// padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
// minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
myLocationEnabled: false,
myLocationButtonEnabled: true,
compassEnabled: true,
mapType: MapType.terrain,
trafficEnabled: true, // Changed: تفعيل عرض حركة المرور
buildingsEnabled: true,
polylines: {
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route1'),
// points: controller.polylineCoordinates,
// color: const Color.fromARGB(255, 163, 81, 246),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route'),
// points: controller.polylineCoordinatesDestination,
// color: const Color.fromARGB(255, 10, 29, 126),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
Polyline(
polylineId: const PolylineId('upcoming_route'),
points: controller.upcomingPathPoints,
color: Colors.blue, // أو أي لون آخر تختاره للمسار
width: 8,
zIndex: 2,
),
// 2. الخط المقطوع (تحت)
Polyline(
polylineId: const PolylineId('traveled_route'),
points: controller.traveledPathPoints,
color: Colors.grey.withOpacity(0.8),
width: 7,
zIndex: 1,
),
},
polylines: controller.polyLines.toSet(),
markers: {
Marker(
markerId: MarkerId('MyLocation'.tr),

View File

@@ -48,7 +48,7 @@ class GoogleMapApp extends StatelessWidget {
border: Border.all(
color: AppColor.blueColor.withOpacity(0.2), width: 1),
),
child: const Icon(
child: Icon(
MaterialCommunityIcons.google_maps,
size: 28,
color: AppColor.secondaryColor,

View File

@@ -1,11 +1,11 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:flutter/services.dart';
class MarkerGenerator {
// دالة لرسم ماركر يحتوي على نص (للوقت والمسافة)
static Future<BitmapDescriptor> createCustomMarkerBitmap({
static Future<InlqBitmap> createCustomMarkerBitmap({
required String title,
required String subtitle,
required Color color,
@@ -19,15 +19,15 @@ class MarkerGenerator {
const double height = 110.0;
const double circleRadius = 25.0;
// 1. رسم المربع (Info Box)
// 1. رسم المربع (Info Box) مع تدرج لوني بسيط
final Paint paint = Paint()..color = color;
final RRect rRect = RRect.fromRectAndRadius(
const Rect.fromLTWH(0, 0, width, 60),
const Radius.circular(15),
const Rect.fromLTWH(0, 0, width, 65),
const Radius.circular(20), // زوايا أكثر استدارة لشكل عصري
);
// ظل خفيف
canvas.drawShadow(Path()..addRRect(rRect), Colors.black, 5.0, true);
// ظل أقوى لمظهر بارز (Premium Feel)
canvas.drawShadow(Path()..addRRect(rRect), Colors.black54, 10.0, true);
canvas.drawRRect(rRect, paint);
// 2. رسم مثلث صغير أسفل المربع (Arrow Tail)
@@ -96,11 +96,11 @@ class MarkerGenerator {
);
final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(data!.buffer.asUint8List());
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
}
// دالة خاصة لرسم ماركر السائق (دائرة وخلفها سهم)
static Future<BitmapDescriptor> createDriverMarker() async {
static Future<InlqBitmap> createDriverMarker() async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
const double size = 100.0;
@@ -137,6 +137,6 @@ class MarkerGenerator {
.toImage(size.toInt(), size.toInt());
final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(data!.buffer.asUint8List());
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/controller/home/captin/order_request_controller.dart';
@@ -63,20 +64,21 @@ class OrderRequestPage extends StatelessWidget {
// 1. الخارطة
Positioned.fill(
bottom: 300,
child: GoogleMap(
mapType: MapType.normal,
child: IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition(
target: LatLng(
controller.latPassenger, controller.lngPassenger),
zoom: 13.0,
),
markers: controller.markers,
mapType: Get.isDarkMode
? IntaleqMapType.normal
: IntaleqMapType.light,
polylines: controller.polylines,
zoomControlsEnabled: false,
myLocationButtonEnabled: false,
compassEnabled: false,
padding: const EdgeInsets.only(
top: 80, bottom: 20, left: 20, right: 20),
onMapCreated: (c) {
controller.onMapCreated(c);
controller.update();
@@ -124,15 +126,16 @@ class OrderRequestPage extends StatelessWidget {
alignment: Alignment.bottomCenter,
child: Container(
height: 360,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black12,
color:
Theme.of(context).shadowColor.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 5)
],
@@ -146,8 +149,9 @@ class OrderRequestPage extends StatelessWidget {
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
color: Theme.of(context).dividerColor,
borderRadius: BorderRadius.circular(2)))),
const SizedBox(height: 15),
// الصف الأول: الراكب والسعر
@@ -156,11 +160,14 @@ class OrderRequestPage extends StatelessWidget {
children: [
Row(
children: [
const CircleAvatar(
CircleAvatar(
radius: 24,
backgroundColor: Color(0xFFF5F5F5),
backgroundColor: Theme.of(context)
.colorScheme
.surfaceVariant,
child: Icon(Icons.person,
color: Colors.grey, size: 28),
color: Theme.of(context).hintColor,
size: 28),
),
const SizedBox(width: 10),
Column(
@@ -233,27 +240,45 @@ class OrderRequestPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
border: Border.all(
color: Theme.of(context).dividerColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoItem(
carIcon, carTypeLabel, carTypeColor),
context, carIcon, carTypeLabel, carTypeColor),
Container(
height: 20,
width: 1,
color: Colors.grey.shade300),
_buildInfoItem(Icons.route,
controller.totalTripDistance, Colors.black87),
color: Theme.of(context).dividerColor),
_buildInfoItem(
context,
Icons.route,
controller.totalTripDistance,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
Container(
height: 20,
width: 1,
color: Colors.grey.shade300),
_buildInfoItem(Icons.access_time_filled,
controller.totalTripDuration, Colors.black87),
color: Theme.of(context).dividerColor),
_buildInfoItem(
context,
Icons.access_time_filled,
controller.totalTripDuration,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
],
),
),
@@ -397,7 +422,8 @@ class OrderRequestPage extends StatelessWidget {
);
}
Widget _buildInfoItem(IconData icon, String text, Color color) {
Widget _buildInfoItem(
BuildContext context, IconData icon, String text, Color color) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -2,7 +2,7 @@
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:google_maps_flutter/google_maps_flutter.dart';
// import 'package:intaleq_maps/intaleq_maps.dart';
// import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
// import 'package:sefer_driver/constant/box_name.dart';

View File

@@ -1,7 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'dart:math' as math;
import '../../../../constant/colors.dart';
@@ -60,7 +61,8 @@ class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
children: [
SizedBox(
height: Get.height * .33,
child: Obx(() => GoogleMap(
child: Obx(() => IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition(
zoom: 12,
target:
@@ -69,13 +71,13 @@ class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
cameraTargetBounds: CameraTargetBounds(
_orderRequestController.mapBounds.value),
myLocationButtonEnabled: true,
trafficEnabled: false,
buildingsEnabled: false,
mapToolbarEnabled: true,
// trafficEnabled: false,
// buildingsEnabled: false,
// mapToolbarEnabled: true,
myLocationEnabled: true,
markers: _orderRequestController.markers.value,
polylines: _orderRequestController.polylines.value,
onMapCreated: (GoogleMapController controller) {
onMapCreated: (IntaleqMapController controller) {
_orderRequestController.mapController.value =
controller;
},
@@ -104,11 +106,11 @@ class OrderRequestController extends GetxController {
Rx<Set<Marker>> markers = Rx<Set<Marker>>({});
Rx<Set<Polyline>> polylines = Rx<Set<Polyline>>({});
Rx<GoogleMapController?> mapController = Rx<GoogleMapController?>(null);
Rx<IntaleqMapController?> mapController = Rx<IntaleqMapController?>(null);
// Icons for start and end markers
late BitmapDescriptor startIcon;
late BitmapDescriptor endIcon;
late InlqBitmap startIcon;
late InlqBitmap endIcon;
// Coordinates for passenger location and destination
Rx<LatLng?> passengerLocation = Rx<LatLng?>(null);
@@ -123,12 +125,8 @@ class OrderRequestController extends GetxController {
void _initializeMarkerIcons() async {
// Load custom marker icons
startIcon = await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(48, 48)),
'assets/start_marker.png');
endIcon = await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(48, 48)), 'assets/end_marker.png');
startIcon = InlqBitmap.fromAsset('assets/start_marker.png');
endIcon = InlqBitmap.fromAsset('assets/end_marker.png');
}
void parseCoordinates(List myList) {
@@ -184,10 +182,10 @@ class OrderRequestController extends GetxController {
polylines.value = {
Polyline(
zIndex: 1,
consumeTapEvents: true,
// consumeTapEvents: true,
geodesic: true,
endCap: Cap.buttCap,
startCap: Cap.buttCap,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
visible: true,
polylineId: const PolylineId('routeOrder'),
points: [passengerLocation.value!, passengerDestination.value!],

View File

@@ -2,30 +2,35 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart'; // Assuming this has your text styles
import 'package:sefer_driver/views/widgets/mycircular.dart'; // Assuming this is your loading widget
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class PaymentHistoryDriverPage extends StatelessWidget {
const PaymentHistoryDriverPage({super.key});
@override
Widget build(BuildContext context) {
// Initialize your controller
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Payment History'.tr),
backgroundColor: Colors.white,
elevation: 1,
title: Text('Payment History'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
// Using your custom loading indicator
return const Center(child: MyCircularProgressIndicator());
}
@@ -34,16 +39,10 @@ class PaymentHistoryDriverPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.account_balance_wallet_outlined,
size: 80, color: Colors.grey[400]),
Icon(Icons.history_rounded, size: 80, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text(
'No transactions yet'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
Text('No transactions yet'.tr,
style: TextStyle(color: Colors.grey.shade400, fontWeight: FontWeight.bold)),
],
),
);
@@ -51,18 +50,26 @@ class PaymentHistoryDriverPage extends StatelessWidget {
return AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemCount: controller.archive.length,
itemBuilder: (BuildContext context, int index) {
var transaction = controller.archive[index];
final tx = controller.archive[index];
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _TransactionCard(transaction: transaction),
child: TransactionPreviewItem(
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['created_at'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['created_at']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
onTap: () {},
),
),
),
);
@@ -74,71 +81,3 @@ class PaymentHistoryDriverPage extends StatelessWidget {
);
}
}
// A dedicated widget for displaying a single transaction with a modern UI.
class _TransactionCard extends StatelessWidget {
final Map<String, dynamic> transaction;
const _TransactionCard({required this.transaction});
@override
Widget build(BuildContext context) {
// Safely parse the amount to avoid errors
final double amount =
double.tryParse(transaction['amount']?.toString() ?? '0') ?? 0;
final bool isCredit = amount >= 0;
final Color indicatorColor =
isCredit ? AppColor.greenColor : AppColor.redColor;
final IconData iconData =
isCredit ? Icons.arrow_upward_rounded : Icons.arrow_downward_rounded;
final String transactionType = (isCredit ? 'Credit'.tr : 'Debit'.tr).tr;
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
margin: const EdgeInsets.only(bottom: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
clipBehavior: Clip.antiAlias, // Ensures the color bar is clipped neatly
child: IntrinsicHeight(
// Ensures the color bar and content have the same height
child: Row(
children: [
// Left-side color indicator bar
Container(width: 6, color: indicatorColor),
Expanded(
child: ListTile(
leading: Icon(iconData, color: indicatorColor, size: 30),
title: Text(
// Use .abs() to remove the negative sign from the display
'${amount.abs().toStringAsFixed(2)} ${'SYP'.tr}',
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
subtitle: Text(
transaction['created_at'] ?? 'No date',
style: AppStyle.title.copyWith(
fontSize: 12,
color: Colors.grey[600],
),
),
trailing: Text(
transactionType,
style: AppStyle.title.copyWith(
fontSize: 14,
color: indicatorColor,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
}
}

View File

@@ -11,6 +11,7 @@ import 'package:sefer_driver/controller/payment/smsPaymnet/payment_services.dart
import 'package:webview_flutter/webview_flutter.dart';
import '../../../constant/box_name.dart';
import '../../../constant/finance_design_system.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart';
@@ -21,8 +22,8 @@ import '../../widgets/my_textField.dart';
import 'ecash.dart';
class PointsCaptain extends StatelessWidget {
PaymentController paymentController = Get.put(PaymentController());
CaptainWalletController captainWalletController =
final PaymentController paymentController = Get.put(PaymentController());
final CaptainWalletController captainWalletController =
Get.put(CaptainWalletController());
PointsCaptain({
@@ -31,263 +32,67 @@ class PointsCaptain extends StatelessWidget {
required this.countPoint,
required this.pricePoint,
});
final Color kolor;
final String countPoint;
double pricePoint;
final double pricePoint;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
Get.defaultDialog(
title: 'Which method you will pay'.tr,
titleStyle: AppStyle.title,
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
return Container(
margin: const EdgeInsets.only(right: 12, bottom: 4),
child: Material(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
elevation: 4,
shadowColor: kolor.withOpacity(0.3),
child: InkWell(
onTap: () => _showPaymentOptions(context),
borderRadius: BorderRadius.circular(20),
child: Container(
width: 130,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
kolor.withOpacity(0.05),
Colors.white,
],
),
border: Border.all(color: kolor.withOpacity(0.2), width: 1.5),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}',
style: AppStyle.title,
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: kolor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.account_balance_wallet_rounded,
color: kolor, size: 24),
),
// Add some spacing between buttons
GestureDetector(
onTap: () async {
Get.back();
payWithEcashDriver(context, pricePoint.toString());
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay with Debit Card'.tr),
const SizedBox(width: 10),
Icon(Icons.credit_card_sharp,
color: AppColor.blueColor, size: 70),
],
)),
// GestureDetector(
// onTap: () async {
// Get.back();
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Insert Wallet phone number'.tr,
// content: Form(
// key: paymentController.formKey,
// child: MyTextForm(
// controller:
// paymentController.walletphoneController,
// label: 'Insert Wallet phone number'.tr,
// hint: '963941234567',
// type: TextInputType.phone)),
// confirm: MyElevatedButton(
// title: 'OK'.tr,
// onPressed: () async {
// Get.back();
// if (paymentController.formKey.currentState!
// .validate()) {
// box.write(
// BoxName.phoneWallet,
// paymentController
// .walletphoneController.text);
// await payWithMTNWallet(
// context, pricePoint.toString(), 'SYP');
// }
// }));
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Pay by MTN Wallet'.tr),
// const SizedBox(width: 10),
// Image.asset(
// 'assets/images/cashMTN.png',
// width: 70,
// height: 70,
// fit: BoxFit.fill,
// ),
// ],
// )),
GestureDetector(
onTap: () async {
Get.back();
Get.defaultDialog(
barrierDismissible: false,
title: 'Insert Wallet phone number'.tr,
content: Form(
key: paymentController.formKey,
child: MyTextForm(
controller:
paymentController.walletphoneController,
label: 'Insert Wallet phone number'.tr,
hint: '963991234567',
type: TextInputType.phone)),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () async {
Get.back();
if (paymentController.formKey.currentState!
.validate()) {
box.write(
BoxName.phoneWallet,
paymentController
.walletphoneController.text);
await payWithSyriaTelWallet(
context, pricePoint.toString(), 'SYP');
}
}));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay by Syriatel Wallet'.tr),
const SizedBox(width: 10),
Image.asset(
'assets/images/syriatel.jpeg',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
)),
GestureDetector(
onTap: () async {
// التحقق بالبصمة قبل أي شيء
bool isAuthSupported =
await LocalAuthentication().isDeviceSupported();
if (isAuthSupported) {
bool didAuthenticate =
await LocalAuthentication().authenticate(
localizedReason:
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
);
if (!didAuthenticate) {
print("❌ User did not authenticate with biometrics");
return;
}
}
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
Get.to(
() => PaymentScreenSmsProvider(amount: pricePoint));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay by Sham Cash'.tr),
const SizedBox(width: 10),
Image.asset(
'assets/images/shamCash.png',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
)),
// GestureDetector(
// onTap: () async {
// Get.back();
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Insert Wallet phone number'.tr,
// content: Form(
// key: paymentController.formKey,
// child: MyTextForm(
// controller:
// paymentController.walletphoneController,
// label: 'Insert Wallet phone number'.tr,
// hint: '963941234567',
// type: TextInputType.phone)),
// confirm: MyElevatedButton(
// title: 'OK'.tr,
// onPressed: () async {
// Get.back();
// if (paymentController.formKey.currentState!
// .validate()) {
// box.write(
// BoxName.phoneWallet,
// paymentController
// .walletphoneController.text);
// // await payWithSyriaTelWallet(
// // context, pricePoint.toString(), 'SYP');
// bool isAuthSupported =
// await LocalAuthentication()
// .isDeviceSupported();
// if (isAuthSupported) {
// bool didAuthenticate =
// await LocalAuthentication()
// .authenticate(
// localizedReason:
// 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
// );
// if (!didAuthenticate) {
// if (Get.isDialogOpen ?? false) Get.back();
// print(
// "❌ User did not authenticate with biometrics");
// return;
// }
// }
// Get.to(() => PaymentScreenMtn(
// amount: pricePoint,
// userType: 'Driver',
// ));
// }
// }));
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Pay by MTN Wallet'.tr),
// const SizedBox(width: 10),
// Image.asset(
// 'assets/images/cashMTN.png',
// width: 70,
// height: 70,
// fit: BoxFit.fill,
// ),
// ],
// )),
],
));
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
child: Container(
width: Get.width * .22,
height: Get.width * .22,
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
kolor.withOpacity(0.3),
kolor,
kolor.withOpacity(0.7),
kolor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(color: AppColor.accentColor),
borderRadius: BorderRadius.circular(12),
shape: BoxShape.rectangle,
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(height: 10),
Text(
'$countPoint ${'L.S'.tr}',
style: AppStyle.subtitle
.copyWith(color: AppColor.secondaryColor),
'$countPoint ${'SYP'.tr}',
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
),
const SizedBox(height: 4),
Text(
'$pricePoint ${'L.S'.tr}',
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
textAlign: TextAlign.center,
'${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${'SYP'.tr}',
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade600,
fontWeight: FontWeight.w600,
),
),
],
),
@@ -296,6 +101,183 @@ class PointsCaptain extends StatelessWidget {
),
);
}
void _showPaymentOptions(BuildContext context) {
Get.bottomSheet(
isScrollControlled: true,
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
padding: const EdgeInsets.fromLTRB(24, 24, 24, 32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Select Payment Method".tr,
style: FinanceDesignSystem.headingStyle),
IconButton(
icon: const Icon(Icons.close_rounded, color: Colors.grey),
onPressed: () => Get.back(),
),
],
),
const SizedBox(height: 8),
Text(
"${'Amount to charge:'.tr} $countPoint ${'SYP'.tr}",
style: FinanceDesignSystem.subHeadingStyle),
const SizedBox(height: 24),
_buildPaymentMethodTile(
icon: Icons.credit_card_rounded,
title: 'Debit Card'.tr,
subtitle: 'E-Cash payment gateway'.tr,
color: Colors.blue,
onTap: () {
Get.back();
payWithEcashDriver(context, pricePoint.toString());
},
),
const SizedBox(height: 16),
_buildPaymentMethodTile(
image: 'assets/images/syriatel.jpeg',
title: 'Syriatel Cash'.tr,
subtitle: 'Pay using Syriatel mobile wallet'.tr,
color: Colors.red,
onTap: () => _showPhoneInputDialog(context, 'Syriatel'),
),
const SizedBox(height: 16),
_buildPaymentMethodTile(
image: 'assets/images/shamCash.png',
title: 'Sham Cash'.tr,
subtitle: 'Pay using Sham Cash wallet'.tr,
color: Colors.orange,
onTap: () async {
Get.back();
bool isAuthSupported =
await LocalAuthentication().isDeviceSupported();
if (isAuthSupported) {
bool didAuthenticate =
await LocalAuthentication().authenticate(
localizedReason: 'Confirm payment with biometrics'.tr,
);
if (!didAuthenticate) return;
}
Get.to(() => PaymentScreenSmsProvider(amount: pricePoint));
},
),
],
),
),
),
);
}
Widget _buildPaymentMethodTile({
IconData? icon,
String? image,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return Material(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(20),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.shade200, width: 1),
),
child: Row(
children: [
Container(
width: 56,
height: 56,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: image != null
? Image.asset(image, fit: BoxFit.contain)
: Icon(icon, color: color, size: 30),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: FinanceDesignSystem.primaryDark)),
const SizedBox(height: 2),
Text(subtitle,
style: TextStyle(
color: Colors.grey.shade600, fontSize: 12)),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: Colors.grey.shade400),
],
),
),
),
);
}
void _showPhoneInputDialog(BuildContext context, String provider) {
Get.back();
Get.defaultDialog(
title: 'Wallet Phone Number'.tr,
content: Form(
key: paymentController.formKey,
child: MyTextForm(
controller: paymentController.walletphoneController,
label: 'Phone Number'.tr,
hint: provider == 'Syriatel' ? '963991234567' : '963941234567',
type: TextInputType.phone,
),
),
confirm: MyElevatedButton(
title: 'Confirm'.tr,
onPressed: () async {
if (paymentController.formKey.currentState!.validate()) {
Get.back();
box.write(BoxName.phoneWallet,
paymentController.walletphoneController.text);
if (provider == 'Syriatel') {
await payWithSyriaTelWallet(
context, pricePoint.toString(), 'SYP');
} else {
await payWithMTNWallet(context, pricePoint.toString(), 'SYP');
}
}
},
),
);
}
}
class PaymentScreen extends StatefulWidget {

View File

@@ -1,521 +1,370 @@
import 'package:local_auth/local_auth.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/controller/functions/tts.dart';
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/info.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
import 'package:sefer_driver/controller/payment/driver_payment_controller.dart';
import '../../../controller/payment/driver_payment_controller.dart';
import '../../widgets/my_scafold.dart';
import 'card_wallet_widget.dart';
// Import new widgets
import 'points_captain.dart';
import 'transfer_budget_page.dart';
import 'weekly_payment_page.dart';
import 'widgets/balance_card.dart';
import 'widgets/quick_actions.dart';
import 'widgets/financial_summary_card.dart';
import 'widgets/promo_gamification_card.dart';
import 'widgets/transaction_preview_item.dart';
class WalletCaptainRefactored extends StatelessWidget {
WalletCaptainRefactored({super.key});
final CaptainWalletController captainWalletController =
Get.put(CaptainWalletController());
// دالة مساعدة لتحديد لون خلفية النقاط
Color _getPointsColor(String pointsStr) {
final points = double.tryParse(pointsStr) ?? 0.0;
if (points < -30000) {
return AppColor.redColor;
} else if (points < 0 && points >= -30000) {
return AppColor.yellowColor;
} else {
return AppColor.greenColor;
}
}
final CaptainWalletController controller = Get.put(CaptainWalletController());
@override
Widget build(BuildContext context) {
captainWalletController.refreshCaptainWallet();
return MyScafolld(
title: 'Driver Balance'.tr,
isleading: true,
action: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => captainWalletController.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
controller.refreshCaptainWallet();
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Driver Balance'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded,
color: FinanceDesignSystem.primaryDark),
onPressed: () => controller.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
),
],
),
body: [
GetBuilder<CaptainWalletController>(
builder: (controller) {
if (controller.isLoading) {
return const MyCircularProgressIndicator();
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildTotalPointsSection(context, controller),
const SizedBox(height: 16),
const CardSeferWalletDriver(), // This can be redesigned if needed
const SizedBox(height: 16),
_buildWalletDetailsCard(context, controller),
const SizedBox(height: 24),
_buildPromoSection(controller),
const SizedBox(height: 24),
_buildNavigationButtons(),
],
),
);
},
)
],
);
}
/// القسم العلوي لعرض النقاط الإجمالية
Widget _buildTotalPointsSection(
BuildContext context, CaptainWalletController controller) {
return Card(
elevation: 4,
color: _getPointsColor(controller.totalPoints.toString()),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text('Info'.tr),
content: Text(
'The 30000 points equal 30000 S.P for you \nSo go and gain your money'
.tr),
actions: [
CupertinoDialogAction(
child: Text("OK".tr),
onPressed: () => Navigator.of(context).pop(),
body: GetBuilder<CaptainWalletController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: FinanceDesignSystem.horizontalPadding,
vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Header / Balance
BalanceCard(
balance: controller.totalPoints.toString(),
isNegative:
double.tryParse(controller.totalPoints.toString()) !=
null &&
double.parse(controller.totalPoints.toString()) <
-30000,
lastUpdated: "Just now".tr,
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 2. Quick Actions
QuickActionsGrid(
onAddBalance: () =>
_showAddBalanceOptions(context, controller),
onWithdraw: () => addSyrianPaymentMethod(controller),
onTransfer: () => Get.to(() => TransferBudgetPage()),
onHistory: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage());
},
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 3. Earnings Summary
FinancialSummaryCard(
title: 'Earnings Summary'.tr,
subtitle: 'ملخص الأرباح'.tr,
items: [
SummaryItem(
icon: Icons.money_rounded,
label: 'Cash Earnings'.tr,
amount: controller.totalAmount,
color: FinanceDesignSystem.successGreen,
),
SummaryItem(
icon: Icons.credit_card_rounded,
label: 'Card Earnings'.tr,
amount: controller.totalAmountVisa,
color: FinanceDesignSystem.accentBlue,
),
],
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 3. Recharge Balance Packages
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Recharge Balance'.tr,
style: FinanceDesignSystem.headingStyle),
Icon(Icons.info_outline_rounded,
size: 18, color: Colors.grey.shade400),
],
),
const SizedBox(height: 12),
SizedBox(
height: 140, // Increased height for modern cards
child: ListView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
children: [
PointsCaptain(
kolor: Colors.blueGrey,
pricePoint: 100,
countPoint: '100'),
PointsCaptain(
kolor: Colors.brown,
pricePoint: 200,
countPoint: '210'),
PointsCaptain(
kolor: Colors.amber,
pricePoint: 400,
countPoint: '450'),
PointsCaptain(
kolor: Colors.orange,
pricePoint: 1000,
countPoint: '1100'),
],
),
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 5. Promotions
Text('Promotions'.tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 12),
PromoGamificationCard(
title: 'Morning Promo'.tr,
subtitle: "from 7:00am to 10:00am".tr,
currentProgress: controller.walletDate['message']?[0]
?['morning_count'] ??
0,
targetProgress: 5,
reward: "+50 SYP",
onTap: () =>
controller.addDriverWalletFromPromo('Morning Promo', 50),
),
const SizedBox(height: 16),
PromoGamificationCard(
title: 'Afternoon Promo'.tr,
subtitle: "from 3:00pm to 6:00 pm".tr,
currentProgress: controller.walletDate['message']?[0]
?['afternoon_count'] ??
0,
targetProgress: 5,
reward: "+50 SYP",
onTap: () => controller.addDriverWalletFromPromo(
'Afternoon Promo', 50),
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 6. Transactions Preview
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Recent Transactions'.tr,
style: FinanceDesignSystem.headingStyle),
TextButton(
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage());
},
child: Text('View All'.tr,
style: const TextStyle(
color: FinanceDesignSystem.accentBlue,
fontWeight: FontWeight.bold)),
),
],
),
GetBuilder<DriverWalletHistoryController>(
init: DriverWalletHistoryController(),
builder: (historyController) {
if (historyController.archive.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Center(
child: Text("No transactions yet".tr,
style: TextStyle(color: Colors.grey.shade400))),
);
}
// Show only last 3
final lastThree =
historyController.archive.take(3).toList();
return Column(
children: lastThree.map((tx) {
final double amount =
double.tryParse(tx['amount']?.toString() ?? '0') ??
0;
return TransactionPreviewItem(
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['created_at'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['created_at']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
onTap: () {},
);
}).toList(),
);
},
),
const SizedBox(height: 40),
],
),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: Column(
children: [
Text(
'رصيد التشغيل 💎',
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
controller.totalPoints.toString(),
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w900),
textAlign: TextAlign.center,
),
if (double.parse(controller.totalPoints.toString()) < -30000)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: CupertinoButton(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20),
onPressed: () {
// Add your charge account logic here
},
child: Text(
'Charge your Account'.tr,
style: TextStyle(
color: AppColor.redColor,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
),
);
}
/// بطاقة لعرض تفاصيل المحفظة وخيارات الشراء
Widget _buildWalletDetailsCard(
void _showAddBalanceOptions(
BuildContext context, CaptainWalletController controller) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_BudgetInfoRow(
title: 'Total Budget from trips is '.tr,
amount: controller.totalAmount,
onTap: () {
Get.snackbar(
icon: InkWell(
onTap: () async => await Get.put(TextToSpeechController())
.speakText(
'This amount for all trip I get from Passengers'
.tr),
child: const Icon(Icons.headphones)),
'${'Total Amount:'.tr} ${controller.totalAmount} ${'SYP'.tr}',
'This amount for all trip I get from Passengers'.tr,
duration: const Duration(seconds: 6),
backgroundColor: AppColor.yellowColor,
snackPosition: SnackPosition.BOTTOM,
);
},
),
const Divider(height: 32),
_BudgetInfoRow(
title: 'Total Budget from trips by\nCredit card is '.tr,
amount: controller.totalAmountVisa,
onTap: () {
Get.snackbar(
icon: InkWell(
onTap: () async => await Get.find<TextToSpeechController>()
.speakText(
'This amount for all trip I get from Passengers and Collected For me in'
.tr +
' Intaleq Wallet'.tr),
child: const Icon(Icons.headphones),
),
'${'Total Amount:'.tr} ${controller.totalAmountVisa} ${'SYP'.tr}',
'This amount for all trip I get from Passengers and Collected For me in'
.tr +
' ${AppInformation.appName} Wallet'.tr,
duration: const Duration(seconds: 6),
backgroundColor: AppColor.redColor,
snackPosition: SnackPosition.BOTTOM,
);
},
),
const SizedBox(height: 24),
_buildBuyPointsButton(controller),
const SizedBox(height: 16),
// _buildTransferBudgetButton(controller), // Uncomment if needed
_buildPurchaseInstructions(),
Text("Add Balance".tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 8),
_buildPointsOptions(),
],
),
),
);
}
/// قسم العروض الترويجية
Widget _buildPromoSection(CaptainWalletController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Today's Promo".tr, style: AppStyle.headTitle),
const SizedBox(height: 10),
_PromoProgressCard(
title: 'Morning Promo'.tr,
timePromo: 'Morning Promo',
count: (controller.walletDate['message'][0]['morning_count']),
maxCount: 5,
description:
"this is count of your all trips in the morning promo today from 7:00am to 10:00am"
.tr,
controller: controller,
),
const SizedBox(height: 16),
_PromoProgressCard(
title: 'Afternoon Promo'.tr,
timePromo: 'Afternoon Promo',
count: (controller.walletDate['message'][0]['afternoon_count']),
maxCount: 5,
description:
"this is count of your all trips in the Afternoon promo today from 3:00pm to 6:00 pm"
.tr,
controller: controller,
),
],
);
}
/// أزرار التنقل السفلية
Widget _buildNavigationButtons() {
return Row(
children: [
Expanded(
child: MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Payment History'.tr,
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage(),
transition: Transition.size);
},
),
),
const SizedBox(width: 16),
Expanded(
child: MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Weekly Budget'.tr,
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getWeekllyArchivePayment();
Get.to(() => const WeeklyPaymentPage(),
transition: Transition.size);
},
),
),
],
);
}
// --- حافظت على هذه الدوال كما هي لأنها تحتوي على منطق مهم ---
Widget _buildBuyPointsButton(CaptainWalletController controller) {
return MyElevatedButton(
title: 'You can buy points from your budget'.tr,
onPressed: () {
Get.defaultDialog(
title: 'Pay from my budget'.tr,
content: Form(
key: controller.formKey,
child: MyTextForm(
controller: controller.amountFromBudgetController,
label:
'${'You have in account'.tr} ${controller.totalAmountVisa}',
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
type: TextInputType.number,
),
),
confirm: MyElevatedButton(
title: 'Pay'.tr,
onPressed: () async {
bool isAvailable =
await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason:
'Use Touch ID or Face ID to confirm payment'.tr,
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (double.parse(controller.amountFromBudgetController.text) <
double.parse(controller.totalAmountVisa)) {
await controller.payFromBudget();
} else {
Get.back();
mySnackeBarError('Your Budget less than needed'.tr);
}
} else {
// Authentication failed, handle accordingly
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
Get.back();
});
}
} else {
MyDialog().getDialog('Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() {
Get.back();
});
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
},
);
}
Widget _buildPurchaseInstructions() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.accentColor.withOpacity(0.3)),
),
child: Column(
children: [
Text(
"You can purchase a budget to enable online access through the options listed below"
.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(fontSize: 14),
),
],
),
),
);
}
Widget _buildPointsOptions() {
return SizedBox(
height: Get.height * 0.19,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
PointsCaptain(
kolor: AppColor.greyColor, pricePoint: 100, countPoint: '100'),
PointsCaptain(
kolor: AppColor.bronze, pricePoint: 200, countPoint: '210'),
PointsCaptain(
kolor: AppColor.goldenBronze, pricePoint: 400, countPoint: '450'),
PointsCaptain(
kolor: AppColor.gold, pricePoint: 1000, countPoint: '1100'),
],
),
);
}
}
/// ويدجت مُحسّن لعرض صف معلومات الرصيد
class _BudgetInfoRow extends StatelessWidget {
final String title;
final String amount;
final VoidCallback onTap;
const _BudgetInfoRow({
required this.title,
required this.amount,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: AppStyle.title.copyWith(fontSize: 16),
Text("Select how you want to charge your account".tr,
style: FinanceDesignSystem.subHeadingStyle),
const SizedBox(height: 24),
ListTile(
leading: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: FinanceDesignSystem.accentBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12)),
child: const Icon(Icons.account_balance_wallet_rounded,
color: FinanceDesignSystem.accentBlue),
),
title: Text("Pay from my budget".tr),
subtitle: Text(
"${'You have in account'.tr} ${controller.totalAmountVisa}"),
onTap: () {
Get.back();
_showPayFromBudgetDialog(controller);
},
),
const SizedBox(width: 10),
Container(
decoration: BoxDecoration(
color: AppColor.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
'$amount ${'S.P'.tr}',
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
),
),
);
}
}
/// ويدجت مُحسّن لعرض بطاقة العرض الترويجي
class _PromoProgressCard extends StatelessWidget {
final String title;
final String timePromo;
final int count;
final int maxCount;
final String description;
final CaptainWalletController controller;
const _PromoProgressCard({
required this.title,
required this.timePromo,
required this.count,
required this.maxCount,
required this.description,
required this.controller,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
MyDialog().getDialog(title, description, () async {
if (count >= maxCount) {
controller.addDriverWalletFromPromo(timePromo, 50);
}
Get.back();
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
const Divider(),
const SizedBox(height: 16),
Text("Recharge Balance Packages".tr,
style: FinanceDesignSystem.subHeadingStyle
.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
SizedBox(
height: 140,
child: ListView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
children: [
Text(title,
style:
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
Text(
'$count / $maxCount',
style: AppStyle.title.copyWith(color: AppColor.blueColor),
),
PointsCaptain(
kolor: Colors.blueGrey,
pricePoint: 100,
countPoint: '100'),
PointsCaptain(
kolor: Colors.brown, pricePoint: 200, countPoint: '210'),
PointsCaptain(
kolor: Colors.amber, pricePoint: 400, countPoint: '450'),
PointsCaptain(
kolor: Colors.orange,
pricePoint: 1000,
countPoint: '1100'),
],
),
const SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
minHeight: 12,
value: count / maxCount,
backgroundColor: AppColor.accentColor.withOpacity(0.2),
color: count >= maxCount
? AppColor.greenColor
: AppColor.blueColor,
),
),
],
),
),
],
),
),
);
}
void _showPayFromBudgetDialog(CaptainWalletController controller) {
Get.defaultDialog(
title: 'Pay from my budget'.tr,
content: Form(
key: controller.formKey,
child: MyTextForm(
controller: controller.amountFromBudgetController,
label: '${'You have in account'.tr} ${controller.totalAmountVisa}',
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
type: TextInputType.number,
),
),
confirm: MyElevatedButton(
title: 'Pay'.tr,
onPressed: () async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr,
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (double.parse(controller.amountFromBudgetController.text) <
double.parse(controller.totalAmountVisa)) {
await controller.payFromBudget();
} else {
Get.back();
mySnackeBarError('Your Budget less than needed'.tr);
}
} else {
MyDialog().getDialog(
'Authentication failed'.tr, ''.tr, () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
),
cancel: MyElevatedButton(title: 'Cancel'.tr, onPressed: () => Get.back()),
);
}
}
// --- الدوال والويدجتس الخاصة بالدفع في سوريا تبقى كما هي ---
// This function is a placeholder for adding Syrian payment methods.
// You would implement the UI and logic for mobile wallets or other local options here.
Future<dynamic> addSyrianPaymentMethod(
CaptainWalletController captainWalletController) {
return Get.defaultDialog(
@@ -528,15 +377,12 @@ Future<dynamic> addSyrianPaymentMethod(
"Insert your mobile wallet details to receive your money weekly"
.tr),
MyTextForm(
controller: captainWalletController
.cardBank, // Re-using for mobile number
controller: captainWalletController.cardBank,
label: "Insert mobile wallet number".tr,
hint: '0912 345 678',
type: TextInputType.phone),
const SizedBox(
height: 10,
),
MyDropDownSyria() // Dropdown for Syrian providers
const SizedBox(height: 10),
MyDropDownSyria()
],
)),
confirm: MyElevatedButton(
@@ -545,7 +391,6 @@ Future<dynamic> addSyrianPaymentMethod(
if (captainWalletController.formKeyAccount.currentState!
.validate()) {
Get.back();
// Replace with your actual API endpoint and payload for Syria
var res =
await CRUD().post(link: AppLink.updateAccountBank, payload: {
"paymentProvider":
@@ -554,7 +399,6 @@ Future<dynamic> addSyrianPaymentMethod(
captainWalletController.cardBank.text.toString(),
"id": box.read(BoxName.driverID).toString()
});
print('res: $res');
if (res != 'failure') {
mySnackbarSuccess('Payment details added successfully'.tr);
}
@@ -562,10 +406,8 @@ Future<dynamic> addSyrianPaymentMethod(
}));
}
// A new GetX controller for the Syrian payout dropdown
class SyrianPayoutController extends GetxController {
String dropdownValue = 'syriatel';
void changeValue(String? newValue) {
if (newValue != null) {
dropdownValue = newValue;
@@ -574,33 +416,25 @@ class SyrianPayoutController extends GetxController {
}
}
// A new Dropdown widget for Syrian mobile wallet providers
class MyDropDownSyria extends StatelessWidget {
const MyDropDownSyria({super.key});
@override
Widget build(BuildContext context) {
Get.put(SyrianPayoutController());
final theme = Theme.of(context);
return GetBuilder<SyrianPayoutController>(builder: (controller) {
return DropdownButton<String>(
value: controller.dropdownValue,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
isExpanded: true,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
controller.changeValue(newValue);
},
dropdownColor: theme.cardColor,
style: TextStyle(color: theme.textTheme.bodyLarge?.color),
underline: Container(height: 2, color: theme.primaryColor),
onChanged: (String? newValue) => controller.changeValue(newValue),
items: <String>['syriatel', 'mtn']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
return DropdownMenuItem<String>(value: value, child: Text(value.tr));
}).toList(),
);
});

View File

@@ -2,11 +2,12 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class WeeklyPaymentPage extends StatelessWidget {
const WeeklyPaymentPage({super.key});
@@ -16,12 +17,18 @@ class WeeklyPaymentPage extends StatelessWidget {
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Weekly Summary'.tr),
backgroundColor: Colors.white,
elevation: 1,
title: Text('Weekly Summary'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
@@ -30,45 +37,46 @@ class WeeklyPaymentPage extends StatelessWidget {
return Column(
children: [
// 1. Prominent Summary Card at the top
_buildSummaryCard(controller),
// 2. A title for the transactions list
_buildWeeklyStatsHeader(controller),
const SizedBox(height: 16),
_buildWeekSelector(),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Icon(Icons.list_alt, color: Colors.grey[600]),
const SizedBox(width: 8),
Text(
'Transactions this week'.tr,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
Text('Transactions this week'.tr, style: FinanceDesignSystem.headingStyle),
const Spacer(),
Icon(Icons.list_rounded, color: Colors.grey.shade400, size: 20),
],
),
),
// 3. The animated list of transactions
const SizedBox(height: 8),
Expanded(
child: controller.weeklyList.isEmpty
? _buildEmptyState(context)
: AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
itemCount: controller.weeklyList.length,
itemBuilder: (BuildContext context, int index) {
var transaction = controller.weeklyList[index];
final tx = controller.weeklyList[index];
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
duration: const Duration(milliseconds: 250),
child: SlideAnimation(
verticalOffset: 50.0,
verticalOffset: 25,
child: FadeInAnimation(
child: _TransactionListItem(
transaction: transaction),
child: TransactionPreviewItem(
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['dateUpdated'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['dateUpdated']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
method: tx['paymentMethod'],
onTap: () {},
),
),
),
);
@@ -83,92 +91,87 @@ class WeeklyPaymentPage extends StatelessWidget {
);
}
// A widget for the top summary card.
Widget _buildSummaryCard(DriverWalletHistoryController controller) {
final String totalAmount = controller.weeklyList.isEmpty
Widget _buildWeeklyStatsHeader(DriverWalletHistoryController controller) {
final totalAmount = controller.weeklyList.isEmpty
? '0.00'
: controller.weeklyList[0]['totalAmount']?.toString() ?? '0.00';
final double earnings = double.tryParse(totalAmount) ?? 0;
final int trips = controller.weeklyList.length;
final double commission = earnings * 0.15; // Example 15%
final double netProfit = earnings - commission;
return Card(
margin: const EdgeInsets.all(16.0),
elevation: 4,
shadowColor: AppColor.primaryColor.withOpacity(0.2),
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Container(
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.primaryColor, AppColor.greenColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: FinanceDesignSystem.primaryDark.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 8),
),
),
child: Row(
children: [
const Icon(Icons.account_balance_wallet,
color: Colors.white, size: 40),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Weekly Earnings'.tr,
style: AppStyle.title
.copyWith(color: Colors.white70, fontSize: 16),
),
const SizedBox(height: 4),
Text(
'$totalAmount ${'SYP'.tr}',
style: AppStyle.number
.copyWith(color: Colors.white, fontSize: 32),
),
],
),
),
],
),
],
),
child: Column(
children: [
Text('Total Weekly Earnings'.tr,
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
const SizedBox(height: 8),
Text('${earnings.toStringAsFixed(0)} SYP',
style: const TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildStatItem('Total Trips'.tr, trips.toString(), Icons.directions_car_rounded),
_buildStatItem('Commission'.tr, '${commission.toStringAsFixed(0)}', Icons.percent_rounded),
_buildStatItem('Net Profit'.tr, '${netProfit.toStringAsFixed(0)}', Icons.account_balance_rounded),
],
),
],
),
);
}
// A dedicated widget for the list item.
Widget _TransactionListItem({required Map<String, dynamic> transaction}) {
final String paymentMethod = transaction['paymentMethod'] ?? 'Unknown';
final String amount = transaction['amount']?.toString() ?? '0';
final DateTime? date = DateTime.tryParse(transaction['dateUpdated'] ?? '');
Widget _buildStatItem(String label, String value, IconData icon) {
return Column(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(10)),
child: Icon(icon, color: Colors.white, size: 18),
),
const SizedBox(height: 8),
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)),
Text(label, style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 10)),
],
);
}
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
margin: const EdgeInsets.only(bottom: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
child: Icon(_getPaymentIcon(paymentMethod),
color: AppColor.primaryColor, size: 22),
Widget _buildWeekSelector() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10)],
),
title: Text(
'$amount ${'SYP'.tr}',
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
subtitle: Text(
date != null
? DateFormat('EEEE, hh:mm a', Get.locale?.toString())
.format(date) // e.g., Tuesday, 10:11 AM
: 'Invalid Date',
style: AppStyle.title.copyWith(fontSize: 12, color: Colors.grey[600]),
),
trailing: Chip(
label: Text(
_getTranslatedPaymentMethod(paymentMethod),
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
backgroundColor: Colors.grey.shade200,
padding: const EdgeInsets.symmetric(horizontal: 6),
side: BorderSide.none,
child: Row(
children: [
IconButton(icon: const Icon(Icons.chevron_left_rounded), onPressed: () {}),
Expanded(
child: Center(
child: Text('Dec 15 - Dec 21, 2024'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: FinanceDesignSystem.primaryDark)),
),
),
IconButton(icon: const Icon(Icons.chevron_right_rounded), onPressed: () {}),
],
),
),
);
@@ -179,45 +182,11 @@ class WeeklyPaymentPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long_outlined, size: 80, color: Colors.grey[400]),
Icon(Icons.bar_chart_rounded, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text(
'No transactions this week'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
Text('No transactions this week'.tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)),
],
),
);
}
// Helper to get a specific icon for each payment method.
IconData _getPaymentIcon(String paymentMethod) {
switch (paymentMethod.toLowerCase()) {
case 'visa':
return Icons.credit_card;
case 'frombudget':
return Icons.account_balance_wallet_outlined;
case 'remainder':
return Icons.receipt_long;
case 'cash':
return Icons.money;
default:
return Icons.payment;
}
}
// Helper to get translated or formatted payment method names.
String _getTranslatedPaymentMethod(String paymentMethod) {
switch (paymentMethod) {
case 'Remainder':
return 'Remainder'.tr;
case 'fromBudget':
return 'From Budget'.tr;
default:
return paymentMethod;
}
}
}

View File

@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class BalanceCard extends StatelessWidget {
final String balance;
final bool isNegative;
final String lastUpdated;
const BalanceCard({
super.key,
required this.balance,
required this.isNegative,
this.lastUpdated = "Just now",
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
gradient: isNegative
? FinanceDesignSystem.dangerGradient
: FinanceDesignSystem.balanceGradient,
boxShadow: [
BoxShadow(
color: (isNegative
? FinanceDesignSystem.dangerRed
: FinanceDesignSystem.primaryDark)
.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Available Balance".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text(
"الرصيد المتاح".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
isNegative
? Icons.warning_rounded
: Icons.account_balance_wallet_rounded,
color: Colors.white,
size: 20,
),
),
],
),
const SizedBox(height: 20),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
balance,
style: FinanceDesignSystem.balanceStyle,
),
const SizedBox(width: 8),
Text(
"SYP".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 16),
Divider(color: Colors.white.withOpacity(0.15)),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.history_rounded,
color: Colors.white.withOpacity(0.5),
size: 14,
),
const SizedBox(width: 6),
Text(
"${'Last updated:'.tr} $lastUpdated",
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class FinancialSummaryCard extends StatelessWidget {
final String title;
final String? subtitle;
final List<SummaryItem> items;
const FinancialSummaryCard({
super.key,
required this.title,
this.subtitle,
required this.items,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: FinanceDesignSystem.headingStyle),
if (subtitle != null)
Text(subtitle!, style: FinanceDesignSystem.subHeadingStyle),
],
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: items.length,
separatorBuilder: (context, index) =>
Divider(color: Colors.grey.withOpacity(0.1), height: 24),
itemBuilder: (context, index) {
final item = items[index];
return Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: item.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(item.icon, color: item.color, size: 20),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: FinanceDesignSystem.primaryDark,
),
),
if (item.subLabel != null)
Text(
item.subLabel!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${item.amount} ${'SYP'.tr}",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
if (item.trend != null)
Text(
item.trend!,
style: TextStyle(
fontSize: 11,
color: item.trendColor ??
FinanceDesignSystem.successGreen,
fontWeight: FontWeight.w500,
),
),
],
),
],
);
},
),
),
],
);
}
}
class SummaryItem {
final IconData icon;
final String label;
final String? subLabel;
final String amount;
final Color color;
final String? trend;
final Color? trendColor;
SummaryItem({
required this.icon,
required this.label,
this.subLabel,
required this.amount,
required this.color,
this.trend,
this.trendColor,
});
}

View File

@@ -0,0 +1,163 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class PromoGamificationCard extends StatelessWidget {
final String title;
final String subtitle;
final int currentProgress;
final int targetProgress;
final String reward;
final VoidCallback? onTap;
const PromoGamificationCard({
super.key,
required this.title,
required this.subtitle,
required this.currentProgress,
required this.targetProgress,
required this.reward,
this.onTap,
});
@override
Widget build(BuildContext context) {
final double progress = (currentProgress / targetProgress).clamp(0.0, 1.0);
final bool isCompleted = progress >= 1.0;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: (isCompleted
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.accentBlue)
.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
reward,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isCompleted
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.accentBlue,
),
),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${'Progress:'.tr} $currentProgress / $targetProgress",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
if (isCompleted)
Icon(Icons.check_circle_rounded,
color: FinanceDesignSystem.successGreen, size: 16),
],
),
const SizedBox(height: 8),
Stack(
children: [
Container(
height: 10,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(5),
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: 10,
width: MediaQuery.of(context).size.width *
progress, // Simplified for now
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isCompleted
? [
FinanceDesignSystem.successGreen,
Colors.lightGreenAccent
]
: [FinanceDesignSystem.accentBlue, Colors.blueAccent],
),
borderRadius: BorderRadius.circular(5),
),
),
],
),
if (isCompleted && onTap != null) ...[
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
backgroundColor: FinanceDesignSystem.successGreen,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
),
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text("Claim Reward".tr),
),
),
],
],
),
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class QuickActionsGrid extends StatelessWidget {
final VoidCallback onAddBalance;
final VoidCallback onWithdraw;
final VoidCallback onTransfer;
final VoidCallback onHistory;
const QuickActionsGrid({
super.key,
required this.onAddBalance,
required this.onWithdraw,
required this.onTransfer,
required this.onHistory,
});
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.5,
children: [
_ActionItem(
icon: Icons.add_circle_outline_rounded,
label: "Add Balance".tr,
subLabel: "شحن",
color: FinanceDesignSystem.accentBlue,
onTap: onAddBalance,
),
_ActionItem(
icon: Icons.file_download_outlined,
label: "Withdraw".tr,
subLabel: "سحب",
color: FinanceDesignSystem.successGreen,
onTap: onWithdraw,
),
_ActionItem(
icon: Icons.swap_horiz_rounded,
label: "Transfer".tr,
subLabel: "تحويل",
color: Colors.orange,
onTap: onTransfer,
),
_ActionItem(
icon: Icons.history_rounded,
label: "History".tr,
subLabel: "السجل",
color: FinanceDesignSystem.primaryDark,
onTap: onHistory,
),
],
);
}
}
class _ActionItem extends StatelessWidget {
final IconData icon;
final String label;
final String subLabel;
final Color color;
final VoidCallback onTap;
const _ActionItem({
required this.icon,
required this.label,
required this.subLabel,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.withOpacity(0.1)),
borderRadius:
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
textAlign: TextAlign.center,
),
Text(
subLabel,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class TransactionPreviewItem extends StatelessWidget {
final String title;
final String subtitle;
final String amount;
final String date;
final String type; // 'credit' or 'debit'
final String? method;
final VoidCallback? onTap;
const TransactionPreviewItem({
super.key,
required this.title,
required this.subtitle,
required this.amount,
required this.date,
required this.type,
this.method,
this.onTap,
});
@override
Widget build(BuildContext context) {
final bool isCredit = type.toLowerCase() == 'credit';
final Color statusColor = isCredit
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.dangerRed;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.mainRadius),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
isCredit
? Icons.arrow_downward_rounded
: Icons.arrow_upward_rounded,
color: statusColor,
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
date,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
if (method != null) ...[
const SizedBox(width: 8),
Container(
width: 3,
height: 3,
decoration: BoxDecoration(
color: Colors.grey.shade400,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(
method!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
],
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${isCredit ? '+' : '-'}$amount ${'SYP'.tr}",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade400,
),
),
],
),
],
),
),
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/captin/behavior_controller.dart';
class BehaviorPage extends StatelessWidget {
@@ -12,6 +13,7 @@ class BehaviorPage extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.put(DriverBehaviorController());
controller.fetchDriverBehavior();
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
@@ -37,20 +39,23 @@ class BehaviorPage extends StatelessWidget {
child: Column(
children: [
Text("Overall Behavior Score".tr,
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Text(
"${controller.overallScore.value.toStringAsFixed(1)} / 100",
style: const TextStyle(
fontSize: 28, color: Colors.blue)),
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor)),
],
),
),
),
const SizedBox(height: 20),
Text("Last 10 Trips".tr,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
@@ -62,7 +67,7 @@ class BehaviorPage extends StatelessWidget {
elevation: 3,
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
backgroundColor: AppColor.primaryColor,
child: Text("${index + 1}",
style: const TextStyle(color: Colors.white)),
),

View File

@@ -41,50 +41,91 @@ class CaptainsCars extends StatelessWidget {
itemBuilder: (context, index) {
final car = controller.cars[index];
return Padding(
padding: const EdgeInsets.all(4.0),
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
child: Card(
color: car['isDefault'].toString() == '0'
? AppColor.accentColor
: AppColor.blueColor,
elevation: 2,
color: car['isDefault'].toString() == '1'
? AppColor.primaryColor
: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: car['isDefault'].toString() == '1'
? BorderSide.none
: BorderSide(color: Theme.of(context).dividerColor)),
child: ListTile(
leading: Icon(
Fontisto.car,
size: 50,
color: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
contentPadding: const EdgeInsets.all(12),
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white12,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Fontisto.car,
size: 32,
color: car['isDefault'].toString() == '1'
? Colors.white
: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
),
),
title: Text(
car['make'],
style: AppStyle.title,
), // Assuming `make` is a field in each car item
subtitle: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
car['model'],
style: AppStyle.title,
),
Container(
decoration: BoxDecoration(
border: Border.all(
color: AppColor.blueColor)),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4),
child: Text(
(car['car_plate']),
style: AppStyle.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: car['isDefault'].toString() == '1'
? Colors.white
: Theme.of(context).textTheme.bodyLarge?.color,
),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Text(
car['model'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
),
Text(
car['year'],
style: AppStyle.title,
),
],
), // Assuming `model` is a field in each car item
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: car['isDefault'].toString() == '1'
? Colors.white54
: AppColor.primaryColor,
),
),
child: Text(
car['car_plate'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: car['isDefault'].toString() == '1'
? Colors.white
: AppColor.primaryColor,
),
),
),
const Spacer(),
Text(
car['year'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
],
),
),
// trailing: IconButton(
// icon: const Icon(
// Icons.delete,

View File

@@ -99,29 +99,30 @@ class ProfileHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
CircleAvatar(
radius: 50,
backgroundColor: Get.theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: Get.theme.primaryColor),
backgroundColor: theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: theme.primaryColor),
),
const SizedBox(height: 12),
Text(
name,
style: Get.textTheme.headlineSmall
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
const Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text(
'${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)',
style: Get.textTheme.titleMedium
?.copyWith(color: Colors.grey.shade600),
style: theme.textTheme.titleMedium
?.copyWith(color: theme.hintColor),
),
],
),
@@ -130,6 +131,7 @@ class ProfileHeader extends StatelessWidget {
}
}
/// 2. ويدجت شبكة الأزرار
class ActionsGrid extends StatelessWidget {
const ActionsGrid({super.key});
@@ -187,20 +189,23 @@ void showShamCashInput() {
TextEditingController(text: existingCode);
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(25),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: Colors.black26, blurRadius: 10, offset: Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
Builder(builder: (context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- 1. المقبض العلوي ---
Center(
@@ -208,12 +213,13 @@ void showShamCashInput() {
height: 5,
width: 50,
decoration: BoxDecoration(
color: Colors.grey[300],
color: theme.dividerColor,
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.only(bottom: 20),
),
),
// --- 2. العنوان والأيقونة ---
Image.asset(
'assets/images/shamCash.png',
@@ -386,11 +392,13 @@ void showShamCashInput() {
],
),
),
),
isScrollControlled: true, // ضروري لرفع النافذة عند فتح الكيبورد
);
}),
isScrollControlled: true,
);
}
/// ويدجت داخلية لزر في الشبكة
class _ActionTile extends StatelessWidget {
final String title;
@@ -402,8 +410,9 @@ class _ActionTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: Colors.grey.shade100,
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
@@ -413,12 +422,12 @@ class _ActionTile extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Get.theme.primaryColor, size: 20),
Icon(icon, color: theme.primaryColor, size: 20),
const SizedBox(width: 8),
Flexible(
child: Text(
title,
style: Get.textTheme.labelLarge,
style: theme.textTheme.labelLarge,
textAlign: TextAlign.center,
)),
],
@@ -429,6 +438,7 @@ class _ActionTile extends StatelessWidget {
}
}
/// 3. بطاقة المعلومات الشخصية
class PersonalInfoCard extends StatelessWidget {
final Map<String, dynamic> data;
@@ -551,19 +561,21 @@ class _InfoRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(icon, color: Colors.grey.shade500, size: 20),
Icon(icon, color: theme.hintColor.withOpacity(0.6), size: 20),
const SizedBox(width: 16),
Text(label, style: Get.textTheme.bodyLarge),
Text(label, style: theme.textTheme.bodyLarge),
const Spacer(),
Flexible(
child: Text(
value,
style: Get.textTheme.bodyLarge?.copyWith(
color: Colors.grey.shade700, fontWeight: FontWeight.w500),
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
fontWeight: FontWeight.w500),
textAlign: TextAlign.end,
),
),
@@ -572,3 +584,4 @@ class _InfoRow extends StatelessWidget {
);
}
}

View File

@@ -26,77 +26,91 @@ class PromosPassengerPage extends StatelessWidget {
itemBuilder: (BuildContext context, int index) {
final rides = orderHistoryController.promoList[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: AppColor.secondaryColor,
boxShadow: [
BoxShadow(
color: AppColor.accentColor,
offset: Offset(-3, -3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer),
BoxShadow(
color: AppColor.accentColor,
offset: Offset(3, 3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer)
]),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Card(
elevation: 4,
shadowColor:
Theme.of(context).shadowColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
FlickerAnimatedText(
rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
],
isRepeatingAnimation: true,
onTap: () {},
),
Text(
rides['description'],
style: AppStyle.title,
),
],
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 30,
child: AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
WavyAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
],
repeatForever: true,
),
),
const SizedBox(height: 8),
Text(
rides['description'],
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
rides['validity_start_date'],
style: AppStyle.title,
),
Text(
rides['validity_end_date'],
style: AppStyle.title,
),
_buildDateBadge(context,
rides['validity_start_date'], true),
const SizedBox(height: 4),
_buildDateBadge(context,
rides['validity_end_date'], false),
],
),
],
),
const Divider(height: 24),
Text(
'Copy this Promo to use it in your Ride!'.tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle2
.copyWith(color: AppColor.accentColor),
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
)
],
),
@@ -109,4 +123,24 @@ class PromosPassengerPage extends StatelessWidget {
],
);
}
Widget _buildDateBadge(BuildContext context, String date, bool isStart) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isStart
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
date,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isStart ? Colors.green : Colors.red,
),
),
);
}
}

View File

@@ -5,6 +5,8 @@ import 'package:get/get.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
class TaarifPage extends StatelessWidget {
const TaarifPage({super.key});
@@ -18,47 +20,35 @@ class TaarifPage extends StatelessWidget {
// crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge,
children: [
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.symmetric(),
textBaseline: TextBaseline.alphabetic,
children: [
TableRow(
// decoration: AppStyle.boxDecoration,
children: [
Text('Minimum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title)
: Text('20 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Maximum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('200 ${'JOD'.tr}', style: AppStyle.title)
: Text('15000 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Flag-down fee'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title)
: Text('15 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km',
style: AppStyle.title)
: Text('1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km',
style: AppStyle.title),
Text('Including Tax'.tr, style: AppStyle.title),
],
),
],
_buildTariffItem(
context,
'Minimum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '1 ${'JOD'.tr}'
: '20 ${'LE'.tr}'),
_buildTariffItem(
context,
'Maximum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '200 ${'JOD'.tr}'
: '15000 ${'LE'.tr}'),
_buildTariffItem(
context,
'Flag-down fee'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.47 ${'JOD'.tr}'
: '15 ${'LE'.tr}'),
_buildTariffItem(
context,
'Rate'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km'
: '1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text('Including Tax'.tr,
style: AppStyle.subtitle
.copyWith(color: Theme.of(context).hintColor)),
),
const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2),
@@ -85,4 +75,21 @@ class TaarifPage extends StatelessWidget {
),
]);
}
Widget _buildTariffItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(label, style: AppStyle.title),
),
Text(value,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
],
),
);
}
}