feat: refactor financial wallet UI components and add offline map service support
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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});
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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!],
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
124
lib/views/home/my_wallet/widgets/balance_card.dart
Normal file
124
lib/views/home/my_wallet/widgets/balance_card.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/views/home/my_wallet/widgets/financial_summary_card.dart
Normal file
142
lib/views/home/my_wallet/widgets/financial_summary_card.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
163
lib/views/home/my_wallet/widgets/promo_gamification_card.dart
Normal file
163
lib/views/home/my_wallet/widgets/promo_gamification_card.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
120
lib/views/home/my_wallet/widgets/quick_actions.dart
Normal file
120
lib/views/home/my_wallet/widgets/quick_actions.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
128
lib/views/home/my_wallet/widgets/transaction_preview_item.dart
Normal file
128
lib/views/home/my_wallet/widgets/transaction_preview_item.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user