25-12-1/1

This commit is contained in:
Hamza-Ayed
2025-12-01 07:52:54 +03:00
parent b1b8efdd7d
commit 9b1008a0bf
40 changed files with 2471 additions and 2039 deletions

View File

@@ -39,18 +39,18 @@ class SettingsCaptain extends StatelessWidget {
subtitle: 'Change the app language'.tr,
onTap: () => Get.to(() => const Language()),
),
_buildListTile(
icon: Icons.flag_outlined,
title: 'Change Country'.tr,
subtitle: 'Get features for your country'.tr,
onTap: () => Get.to(
() => MyScafolld(
title: 'Change Country'.tr,
body: [CountryPickerFromSetting()],
isleading: true,
),
),
),
// _buildListTile(
// icon: Icons.flag_outlined,
// title: 'Change Country'.tr,
// subtitle: 'Get features for your country'.tr,
// onTap: () => Get.to(
// () => MyScafolld(
// title: 'Change Country'.tr,
// body: [CountryPickerFromSetting()],
// isleading: true,
// ),
// ),
// ),
],
),
const SizedBox(height: 20),

View File

@@ -1,3 +1,4 @@
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:flutter/material.dart';
@@ -6,6 +7,7 @@ import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import '../../../constant/colors.dart';
import '../../../controller/functions/location_controller.dart';
import '../../../main.dart';
import '../../Rate/rate_passenger.dart';
import '../../widgets/my_textField.dart';
import 'mapDriverWidgets/driver_end_ride_bar.dart';
@@ -14,114 +16,169 @@ import 'mapDriverWidgets/google_map_app.dart';
import 'mapDriverWidgets/passenger_info_window.dart';
import 'mapDriverWidgets/sos_connect.dart';
// Changed: تم إعادة بناء الصفحة بالكامل لتكون أكثر تنظيمًا
class PassengerLocationMapPage extends StatelessWidget {
PassengerLocationMapPage({super.key});
final LocationController locationController = Get.put(LocationController());
final MapDriverController mapDriverController =
Get.put(MapDriverController());
// Helper function to show exit confirmation dialog
Future<bool> showExitDialog() async {
bool? result = await Get.defaultDialog(
title: "Warning".tr,
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
middleText:
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?"
.tr,
middleTextStyle: AppStyle.title,
barrierDismissible: false,
confirm: MyElevatedButton(
title: 'Stay'.tr,
kolor: AppColor.greenColor,
onPressed: () => Get.back(result: false), // Return false (Don't pop)
),
cancel: MyElevatedButton(
title: 'Exit'.tr,
kolor: AppColor.redColor,
onPressed: () => Get.back(result: true), // Return true (Allow pop)
),
);
return result ?? false;
}
@override
Widget build(BuildContext context) {
// New: استخدام addPostFrameCallback لضمان أن تحميل البيانات يتم بعد بناء الواجهة
WidgetsBinding.instance.addPostFrameCallback((_) {
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
mapDriverController.argumentLoading();
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
} else {
// في حال عدم وجود arguments، يتم التعامل مع هذا الخطأ
Get.snackbar("Error", "No order data found.");
Get.back();
}
});
return Scaffold(
body: SafeArea(
child: Stack(
children: [
// 1. الخريطة في الخلفية
GoogleDriverMap(locationController: locationController),
// ✅ Added PopScope to intercept back button
return PopScope(
canPop: false, // Prevents immediate popping
onPopInvokedWithResult: (didPop, result) async {
if (didPop) {
return;
}
// Show dialog
final shouldExit = await showExitDialog();
if (shouldExit) {
Get.back(); // Manually pop if confirmed
}
},
child: Scaffold(
body: SafeArea(
child: Stack(
children: [
// 1. Map
GoogleDriverMap(locationController: locationController),
// 2. شريط تعليمات الطريق في الأعلى
const InstructionsOfRoads(),
// 2. Instructions
const InstructionsOfRoads(),
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
// 3. Passenger Info
Positioned(
top: 0,
left: 0,
right: 0,
child: PassengerInfoWindow(),
),
PassengerInfoWindow(),
// 3. زر إلغاء الرحلة في الأعلى يسارًا
// 4. Cancel Widget
CancelWidget(mapDriverController: mapDriverController),
CancelWidget(mapDriverController: mapDriverController),
// Changed: تم تعديل تصميم زر الإلغاء ليكون أيقونة بسيطة في الأعلى
// 5. شريط معلومات وإنهاء الرحلة (يظهر بعد بدء الرحلة)
driverEndRideBar(),
// 5. End Ride Bar
driverEndRideBar(),
// 6. أزرار الطوارئ والاتصال
SosConnect(),
// 6. SOS
SosConnect(),
// 7. دائرة عرض السرعة
speedCircle(),
GoogleMapApp(),
// 8. نافذة عرض السعر النهائي (تظهر بعد انتهاء الرحلة)
const PricesWindow(),
],
),
));
// 7. Speed
speedCircle(),
// 8. External Map
Positioned(
bottom: 100,
right: 10,
child: GoogleMapApp(),
),
// 9. Prices Window
const PricesWindow(),
],
),
)),
);
}
}
// New: تصميم جديد لشريط تعليمات الطريق في أعلى الشاشة
// ... The rest of your widgets (InstructionsOfRoads, CancelWidget, etc.) remain unchanged ...
// ... Keep the code below exactly as you had it in the previous snippet ...
class InstructionsOfRoads extends StatelessWidget {
const InstructionsOfRoads({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapDriverController>(
builder: (controller) =>
// يتم إظهار التعليمات فقط إذا كانت متوفرة
controller.currentInstruction.isNotEmpty
? Positioned(
bottom: 10,
left: MediaQuery.of(context).size.width * 0.15,
right: MediaQuery.of(context).size.width * 0.15,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
return Positioned(
bottom: 10,
left: MediaQuery.of(context).size.width * 0.15,
right: MediaQuery.of(context).size.width * 0.15,
child: GetBuilder<MapDriverController>(
builder: (controller) => controller.currentInstruction.isNotEmpty
? AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.directions,
color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(
controller.currentInstruction,
style: AppStyle.title.copyWith(fontSize: 16),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
],
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.directions, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(
controller.currentInstruction,
style: AppStyle.title.copyWith(fontSize: 16),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
),
)
: const SizedBox(), // في حالة عدم وجود تعليمات، لا يظهر شيء
const SizedBox(width: 10),
InkWell(
onTap: () {
controller.toggleTts();
},
child: Icon(
controller.isTtsEnabled
? Icons.volume_up
: Icons.volume_off,
color: controller.isTtsEnabled
? AppColor.greenColor
: Colors.grey,
),
),
],
),
)
: const SizedBox(),
),
);
}
}
// Changed: تم تعديل تصميم وموضع زر الإلغاء ليكون أيقونة بسيطة في الأعلى
class CancelWidget extends StatelessWidget {
const CancelWidget({
super.key,
@@ -137,7 +194,6 @@ class CancelWidget extends StatelessWidget {
left: 10,
child: GetBuilder<MapDriverController>(
builder: (controller) {
// يظهر زر الإلغاء فقط قبل انتهاء الرحلة
if (controller.isRideFinished) return const SizedBox.shrink();
return GestureDetector(
@@ -199,7 +255,6 @@ class CancelWidget extends StatelessWidget {
}
}
// Changed: تم تعديل تصميم نافذة السعر لتكون أكثر وضوحًا
class PricesWindow extends StatelessWidget {
const PricesWindow({
super.key,

View File

@@ -621,45 +621,95 @@ class FloatingActionButtons extends StatelessWidget {
const SizedBox(
height: 5,
),
// هذا الكود يوضع داخل الـ Stack في ملف الواجهة (HomeCaptain View)
box.read(BoxName.rideStatus) == 'Applied' ||
box.read(BoxName.rideStatus) == 'Begin'
? Positioned(
bottom: Get.height * .2,
right: 6,
child: AnimatedContainer(
duration: const Duration(microseconds: 200),
width: homeCaptainController.widthMapTypeAndTraffic,
decoration: BoxDecoration(
border: Border.all(color: AppColor.blueColor),
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(15)),
child: GestureDetector(
onLongPress: () {
box.write(BoxName.rideStatus, 'delete');
homeCaptainController.update();
},
child: IconButton(
onPressed: () {
box.read(BoxName.rideStatus) == 'Applied'
? {
Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArguments)),
Get.put(MapDriverController())
.changeRideToBeginToPassenger()
}
: {
Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArguments)),
Get.put(MapDriverController())
.startRideFromStartApp()
};
},
icon: const Icon(
Icons.directions_rounded,
size: 29,
color: AppColor.blueColor,
// جعلنا الزر يظهر في المنتصف أو يمتد ليكون واضحاً جداً
right: 20,
left: 20,
child: Center(
child: AnimatedContainer(
duration: const Duration(
milliseconds:
200), // تم تصحيح microseconds إلى milliseconds لحركة أنعم
// أزلنا العرض الثابت homeCaptainController.widthMapTypeAndTraffic لكي يتسع للنص
// width: homeCaptainController.widthMapTypeAndTraffic,
decoration: BoxDecoration(
border: Border.all(
color: AppColor.blueColor,
width: 2), // تعريض الإطار قليلاً
color: AppColor.secondaryColor, // لون الخلفية
borderRadius: BorderRadius.circular(
30), // تدوير الحواف ليشبه الأزرار الحديثة
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
)
]),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onLongPress: () {
// وظيفة الحذف عند الضغط الطويل (للطوارئ)
box.write(BoxName.rideStatus, 'delete');
homeCaptainController.update();
},
onTap: () {
// نفس منطقك الأصلي للانتقال
if (box.read(BoxName.rideStatus) == 'Applied') {
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Get.put(MapDriverController())
.changeRideToBeginToPassenger();
} else {
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Get.put(MapDriverController())
.startRideFromStartApp();
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
child: Row(
mainAxisSize:
MainAxisSize.min, // حجم الزر على قد المحتوى
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons
.directions_car_filled_rounded, // تغيير الأيقونة لسيارة أو اتجاهات لتكون معبرة أكثر
size: 24,
color: AppColor.blueColor,
),
const SizedBox(
width: 10), // مسافة بين الأيقونة والنص
Text(
"متابعة الرحلة", // النص الواضح للسائق
style: const TextStyle(
color: AppColor.blueColor,
fontSize: 16,
fontWeight: FontWeight.bold,
fontFamily:
'Cairo', // تأكد من نوع الخط المستخدم عندك
),
),
if (box.read(BoxName.rideStatus) ==
'Begin') ...[
const SizedBox(width: 5),
// إضافة مؤشر صغير (نقطة حمراء) إذا كانت الرحلة قد بدأت بالفعل (اختياري)
const Icon(Icons.circle,
size: 8, color: Colors.green)
]
],
),
),
),
),
),

View File

@@ -219,9 +219,10 @@ Future<void> checkForPendingOrderFromServer() async {
link: AppLink.getArgumentAfterAppliedFromBackground,
payload: {'driver_id': driverId},
);
Log.print('response: ${response}');
// Assuming the server returns order data if found, or 'failure'/'none' if not
if (response != 'failure') {
if (response['status'] == 'success') {
final Map<String, dynamic> orderInfoFromServer = response['message'];
final Map<String, dynamic> rideArguments =
_transformServerDataToAppArguments(orderInfoFromServer);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:slide_to_act/slide_to_act.dart';
import 'package:vibration/vibration.dart';
import 'dart:io';
@@ -11,82 +12,88 @@ import '../../../../controller/home/captin/map_driver_controller.dart';
import '../../../widgets/elevated_btn.dart';
// Changed: إعادة تصميم كاملة للشريط ليصبح شريطًا علويًا عند بدء الرحلة
GetBuilder<MapDriverController> driverEndRideBar() {
return GetBuilder<MapDriverController>(
builder: (controller) => AnimatedPositioned(
duration: const Duration(milliseconds: 300),
// New: يظهر الشريط من الأعلى عندما تبدأ الرحلة
top: controller.isRideStarted ? 0 : -200,
left: 0,
right: 0,
child: Card(
margin: EdgeInsets.zero,
elevation: 10,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Column(
children: [
// -- معلومات الرحلة --
if (controller.carType != 'Mishwar Vip')
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildInfoColumn(
icon: Icons.social_distance,
text: '${controller.distance} ${'KM'.tr}',
label: 'Distance'.tr,
),
_buildInfoColumn(
icon: Icons.timelapse,
text: controller.hours > 1
? '${controller.hours}h ${controller.minutes}m'
: '${controller.minutes}m',
label: 'Time'.tr,
),
_buildInfoColumn(
icon: Icons.money_sharp,
text: '${controller.paymentAmount} ${'SYP'.tr}',
label: 'Price'.tr,
),
],
),
if (controller.carType != 'Mishwar Vip')
const Divider(height: 20),
// ملف: driver_end_ride_bar.dart
// -- مؤقت الرحلة المتبقي (إن وجد) --
_builtTimerAndCarType(),
Widget driverEndRideBar() {
// 1. Positioned هي الوالد المباشر (لأنها داخل Stack في الصفحة الرئيسية)
return Positioned(
top: 0,
left: 0,
right: 0,
// 2. GetBuilder يكون في الداخل
child: GetBuilder<MapDriverController>(
builder: (controller) => AnimatedContainer(
duration: const Duration(milliseconds: 300),
// 3. نستخدم التحريك (Translation) لإخفاء الشريط وإظهاره بدلاً من تغيير الـ top
transform: Matrix4.translationValues(
0, controller.isRideStarted ? 0 : -250, 0),
child: Card(
margin: EdgeInsets.zero,
elevation: 10,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Column(
children: [
if (controller.carType != 'Mishwar Vip')
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildInfoColumn(
icon: Icons.social_distance,
text: '${controller.distance} ${'KM'.tr}',
label: 'Distance'.tr,
),
_buildInfoColumn(
icon: Icons.timelapse,
text: controller.hours > 1
? '${controller.hours}h ${controller.minutes}m'
: '${controller.minutes}m',
label: 'Time'.tr,
),
_buildInfoColumn(
icon: Icons.money_sharp,
text:
'${NumberFormat('#,##0').format(double.tryParse(controller.paymentAmount.toString()) ?? 0)} ${'SYP'.tr}',
label: 'Price'.tr,
),
],
),
const SizedBox(height: 12),
// -- زر إنهاء الرحلة المنزلق --
SlideAction(
height: 55,
borderRadius: 15,
elevation: 4,
text: 'Slide to End Trip'.tr,
textStyle: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
// ... بقية الكود كما هو (الأزرار والمؤقت)
if (controller.carType != 'Mishwar Vip')
const Divider(height: 20),
const _builtTimerAndCarType(),
const SizedBox(height: 12),
SlideAction(
height: 55,
borderRadius: 15,
elevation: 4,
text: 'Slide to End Trip'.tr,
textStyle: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
outerColor: AppColor.redColor,
innerColor: Colors.white,
sliderButtonIcon: const Icon(
Icons.arrow_forward_ios,
color: AppColor.redColor,
size: 24,
),
sliderRotate: false,
onSubmit: () {
HapticFeedback.mediumImpact();
controller.finishRideFromDriver();
return null;
},
),
outerColor: AppColor.redColor,
innerColor: Colors.white,
sliderButtonIcon: const Icon(
Icons.arrow_forward_ios,
color: AppColor.redColor,
size: 24,
),
sliderRotate: false,
onSubmit: () {
HapticFeedback.mediumImpact();
controller.finishRideFromDriver();
return null; // New: onSubmit now returns null
},
),
],
],
),
),
),
),
@@ -116,100 +123,119 @@ class _builtTimerAndCarType extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Get.find<MapDriverController>();
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// -- نوع السيارة --
Container(
decoration: AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
controller.carType,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
),
// -- مؤقت الرحلة --
if (controller.carType != 'Comfort' &&
controller.carType != 'Mishwar Vip' &&
controller.carType != 'Lady') ...[
const SizedBox(width: 10),
Expanded(
child: Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor.withOpacity(0.8)
: AppColor.greenColor.withOpacity(0.8),
controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor
: AppColor.greenColor,
],
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
alignment: Alignment.center,
children: [
LinearProgressIndicator(
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white.withOpacity(0.2)),
minHeight: 40,
value: controller.progressTimerRideBegin.toDouble(),
),
Text(
controller.stringRemainingTimeRideBegin,
style: AppStyle.title.copyWith(
color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
),
// نستخدم GetBuilder هنا لضمان تحديث العداد في كل ثانية
return GetBuilder<MapDriverController>(builder: (controller) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// -- نوع السيارة --
Container(
decoration:
AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
controller.carType.tr,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
)
),
// -- مؤقت الرحلة --
if (controller.carType != 'Comfort' &&
controller.carType != 'Mishwar Vip' &&
controller.carType != 'Lady') ...[
const SizedBox(width: 10),
Expanded(
child: Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor.withOpacity(0.8)
: AppColor.greenColor.withOpacity(0.8),
controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor
: AppColor.greenColor,
],
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
alignment: Alignment.center,
children: [
LinearProgressIndicator(
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white.withOpacity(0.2)),
minHeight: 40,
// تأكد من أن هذه القيمة بين 0.0 و 1.0 في الكونترولر
value: controller.progressTimerRideBegin.toDouble(),
),
Text(
controller.stringRemainingTimeRideBegin,
style: AppStyle.title.copyWith(
color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
),
),
)
],
],
],
);
);
});
}
}
// Changed: تم تعديل مكان ومظهر دائرة السرعة
GetBuilder<MapDriverController> speedCircle() {
if (Get.find<MapDriverController>().speed > 100) {
if (Platform.isIOS) {
HapticFeedback.selectionClick();
} else {
Vibration.vibrate(duration: 1000);
}
Get.defaultDialog(
barrierDismissible: false,
titleStyle: AppStyle.title,
title: 'Speed Over'.tr,
middleText: 'Please slow down'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'I will slow down'.tr,
onPressed: () => Get.back(),
),
);
}
return GetBuilder<MapDriverController>(
builder: (controller) {
return controller.isRideStarted
? Positioned(
// New: تم وضع دائرة السرعة في الأسفل يمينًا
bottom: 25,
left: 3,
child: Container(
// غيرنا نوع الإرجاع إلى Widget بدلاً من GetBuilder
Widget speedCircle() {
// التحقق من السرعة يمكن أن يبقى هنا أو داخل الـ builder
// لكن التنبيهات (Vibration/Dialog) يفضل أن تكون داخل الـ builder لتجنب تكرارها أثناء إعادة البناء الخارجية
return Positioned(
// New: Positioned الآن هي الوالد المباشر (يجب وضع هذه الدالة داخل Stack في الصفحة الرئيسية)
bottom: 25,
left: 3,
child: GetBuilder<MapDriverController>(
builder: (controller) {
// التحقق من التنبيهات هنا
if (controller.speed > 100) {
// نستخدم addPostFrameCallback لضمان عدم استدعاء الـ Dialog أثناء عملية البناء
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!Get.isDialogOpen!) {
// تجنب فتح أكثر من نافذة
if (Platform.isIOS) {
HapticFeedback.selectionClick();
} else {
Vibration.vibrate(duration: 1000);
}
Get.defaultDialog(
barrierDismissible: false,
titleStyle: AppStyle.title,
title: 'Speed Over'.tr,
middleText: 'Please slow down'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'I will slow down'.tr,
onPressed: () => Get.back(),
),
);
}
});
}
return controller.isRideStarted
? Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)],
boxShadow: const [
BoxShadow(blurRadius: 5, color: Colors.black26)
],
border: Border.all(
width: 4,
color: controller.speed > 100
@@ -227,13 +253,13 @@ GetBuilder<MapDriverController> speedCircle() {
controller.speed.toStringAsFixed(0),
style: AppStyle.number.copyWith(fontSize: 24),
),
Text("km/h", style: TextStyle(fontSize: 10)),
const Text("km/h", style: TextStyle(fontSize: 10)),
],
),
),
),
)
: const SizedBox();
},
)
: const SizedBox(); // إذا لم تبدأ الرحلة نخفي العنصر وهو داخل الـ Positioned
},
),
);
}

View File

@@ -1,37 +1,43 @@
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/info.dart';
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/style.dart';
import '../../../../controller/firebase/notification_service.dart';
import '../../../../main.dart';
import '../../../../print.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
class PassengerInfoWindow extends StatelessWidget {
PassengerInfoWindow({super.key});
final fcm = Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
// Optimization: defining static styles here avoids rebuilding them every frame
final TextStyle _labelStyle =
AppStyle.title.copyWith(color: Colors.grey[600], fontSize: 13);
final TextStyle _valueStyle =
AppStyle.title.copyWith(fontWeight: FontWeight.bold, fontSize: 18);
@override
Widget build(BuildContext context) {
// Get safe area top padding (for Notches/Status bars)
final double topPadding = MediaQuery.of(context).padding.top;
final double topMargin = topPadding + 10; // Safe area + 10px spacing
return GetBuilder<MapDriverController>(
builder: (controller) => AnimatedPositioned(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
// Changed: تم تغيير الموضع من الأسفل إلى الأعلى
top: controller.isPassengerInfoWindow ? 15.0 : -200.0,
// FIX: Use calculated top margin to avoid hiding behind status bar
top: controller.isPassengerInfoWindow ? topMargin : -250.0,
left: 15.0,
right: 15.0,
child: Card(
elevation: 8,
shadowColor: Colors.black.withOpacity(0.3),
// Optimization: Lower elevation slightly for smoother animation on cheap phones
elevation: 4,
shadowColor: Colors.black.withOpacity(0.2),
color: Colors.white,
surfaceTintColor: Colors.white, // Fix for Material 3 tinting
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
@@ -41,14 +47,12 @@ class PassengerInfoWindow extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// New: صف علوي للمعلومات الأساسية
_buildTopInfoRow(controller),
const Divider(height: 16),
// Changed: الأزرار الآن في صف أفقي ومدمج
if (!controller.isRideBegin) _buildActionButtons(controller),
// New: مؤشر انتظار الراكب المدمج
// Optimization: Only render linear indicator if needed
if (controller.remainingTimeInPassengerLocatioWait < 300 &&
controller.remainingTimeInPassengerLocatioWait != 0 &&
!controller.isRideBegin) ...[
@@ -56,7 +60,6 @@ class PassengerInfoWindow extends StatelessWidget {
_buildWaitingIndicator(controller),
],
// زر الإلغاء بعد انتهاء وقت الانتظار
if (controller.isdriverWaitTimeEnd &&
!controller.isRideBegin) ...[
const SizedBox(height: 10),
@@ -70,35 +73,33 @@ class PassengerInfoWindow extends StatelessWidget {
);
}
// New: ودجت لعرض المعلومات العلوية بشكل مدمج
Widget _buildTopInfoRow(MapDriverController controller) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, // Align top
children: [
// معلومات الراكب
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Go to passenger:'.tr, style: _labelStyle),
const SizedBox(height: 2),
Text(
'Go to passenger:'.tr,
style: AppStyle.title
.copyWith(color: Colors.grey[600], fontSize: 13),
),
Text(
controller.passengerName,
style: AppStyle.title
.copyWith(fontWeight: FontWeight.bold, fontSize: 18),
controller.passengerName ?? 'loading...',
style: _valueStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
// معلومات المسافة والزمن
Row(
const SizedBox(width: 10), // Spacing between name and chips
Column(
// Changed to Column for better layout on small screens
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_buildInfoChip(Icons.map_outlined, '${controller.distance} km'),
const SizedBox(width: 8),
const SizedBox(height: 6), // Vertical spacing
_buildInfoChip(
Icons.timer_outlined,
controller.hours > 1
@@ -111,10 +112,9 @@ class PassengerInfoWindow extends StatelessWidget {
);
}
// New: ودجت مخصص لعرض المعلومات بشكل أنيق
Widget _buildInfoChip(IconData icon, String text) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
@@ -122,144 +122,164 @@ class PassengerInfoWindow extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: AppColor.primaryColor, size: 16),
const SizedBox(width: 4),
Text(text,
style: TextStyle(
color: AppColor.primaryColor, fontWeight: FontWeight.bold)),
Icon(icon, color: AppColor.primaryColor, size: 14), // Smaller icon
const SizedBox(width: 6),
Text(
text,
style: TextStyle(
color: AppColor.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 12 // Slightly smaller font for chips
),
),
],
),
);
}
// Changed: إعادة تصميم أزرار الإجراءات لتكون أكثر دمجًا
Widget _buildActionButtons(MapDriverController controller) {
return Row(
children: [
if (controller.isArrivedSend)
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.location_on, size: 18),
label: Text('I Arrive'.tr),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.yellowColor,
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
onPressed: () async {
controller.getRoute(
origin: controller.latLngPassengerLocation,
destination: controller.latLngPassengerDestination,
routeColor: Colors.blue // أو أي لون
);
if (await controller
.calculateDistanceBetweenDriverAndPassengerLocation() <
140) {
// fcm.sendNotificationToDriverMAP(
// 'Hi ,I Arrive your site',
// 'I Arrive at your site'.tr,
// controller.tokenPassenger,
// [],
// 'ding.wav',
// );
Log.print(
'controller.tokenPassenger: ${controller.tokenPassenger}');
flex: 1,
child: SizedBox(
height: 45, // Fixed height for consistency
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.yellowColor,
foregroundColor: Colors.black,
padding: EdgeInsets.zero, // Reduce padding to fit text
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
onPressed: () async {
// LOGIC FIX: Check distance FIRST
double distance = await controller
.calculateDistanceBetweenDriverAndPassengerLocation();
NotificationService.sendNotification(
target: controller.tokenPassenger.toString(),
title: 'Hi ,I Arrive your site'.tr,
body: 'I Arrive at your site'.tr,
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [], category: 'Hi ,I Arrive your site',
);
controller.startTimerToShowDriverWaitPassengerDuration();
controller.isArrivedSend = false;
} else {
MyDialog().getDialog(
'You are not near the passenger location'.tr,
'Please go to the pickup location exactly'.tr,
() => Get.back());
}
},
if (distance < 140) {
// Only draw route and send notif if close enough
controller.getRoute(
origin: controller.latLngPassengerLocation,
destination: controller.latLngPassengerDestination,
routeColor: Colors.blue);
NotificationService.sendNotification(
target: controller.tokenPassenger.toString(),
title: 'Hi ,I Arrive your site'.tr,
body: 'I Arrive at your site'.tr,
isTopic: false,
tone: 'ding',
driverList: [],
category: 'Hi ,I Arrive your site',
);
controller.startTimerToShowDriverWaitPassengerDuration();
controller.isArrivedSend = false;
} else {
MyDialog().getDialog(
'You are not near'.tr, // Shortened title
'Please go to the pickup location exactly'.tr,
() => Get.back());
}
},
// Using Row instead of .icon constructor for better control
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.location_on, size: 16),
const SizedBox(width: 4),
Flexible(
child: Text('I Arrive'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12))),
],
),
),
),
),
if (controller.isArrivedSend) const SizedBox(width: 8),
Expanded(
flex: 2,
child: ElevatedButton.icon(
icon: const Icon(Icons.play_arrow_rounded, size: 20),
label: Text('Start the Ride'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
flex: 2, // Give "Start" button more space
child: SizedBox(
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
onPressed: () {
MyDialog().getDialog(
"Is the Passenger in your Car?".tr,
"Don't start trip if passenger not in your car".tr,
() async {
await controller.startRideFromDriver();
Get.back();
},
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.play_arrow_rounded, size: 22),
const SizedBox(width: 6),
Flexible(
child: Text('Start the Ride'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.bold))),
],
),
),
onPressed: () {
MyDialog().getDialog(
"Is the Passenger in your Car?".tr,
"Don't start trip if passenger not in your car".tr,
() async {
await controller.startRideFromDriver();
Get.back();
},
);
},
),
),
],
);
}
// Changed: مؤشر الانتظار الآن أكثر دمجًا
Widget _buildWaitingIndicator(MapDriverController controller) {
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Stack(
alignment: Alignment.center,
children: [
LinearProgressIndicator(
backgroundColor: AppColor.greyColor.withOpacity(0.3),
return Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: AppColor.greyColor.withOpacity(0.2),
// Ternary for color is fine
color: controller.remainingTimeInPassengerLocatioWait < 60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 25,
minHeight: 8, // Thinner looks more modern
value: controller.progressInPassengerLocationFromDriver.toDouble(),
),
Text(
controller.stringRemainingTimeWaitingPassenger,
style: AppStyle.title.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13,
shadows: [
Shadow(color: Colors.black.withOpacity(0.5), blurRadius: 2)
]),
),
const SizedBox(height: 4),
Text(
"${'Waiting'.tr}: ${controller.stringRemainingTimeWaitingPassenger}",
style: AppStyle.title.copyWith(
color: Colors.grey[700],
fontWeight: FontWeight.bold,
fontSize: 12,
),
],
),
),
],
);
}
// New: زر الإلغاء بعد انتهاء الانتظار
Widget _buildCancelAfterWaitButton(MapDriverController controller) {
return MyElevatedButton(
title: 'You Can Cancel the Trip and get Cost From '.tr +
AppInformation.appName.tr,
title: 'Cancel Trip & Get Cost'.tr, // Shortened text
kolor: AppColor.gold,
onPressed: () {
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
NotificationService.sendNotification(
target: controller.tokenPassenger.toString(),
title: 'Driver Cancelled Your Trip'.tr,
body:
'You will need to pay the cost to the driver, or it will be deducted from your next trip',
isTopic: false, // Important: this is a token
body: 'You will need to pay the cost...',
isTopic: false,
tone: 'cancel',
driverList: [], category: 'Driver Cancelled Your Trip',
driverList: [],
category: 'Driver Cancelled Your Trip',
);
box.write(BoxName.rideStatus, 'Cancel');
await controller.addWaitingTimeCostFromPassengerToDriverWallet();

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart';
import 'package:sefer_driver/constant/api_key.dart';
import '../../../../constant/box_name.dart';
@@ -213,13 +214,14 @@ class _OrderOverlayState extends State<OrderOverlay>
await _closeOverlay();
return;
}
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
'id': orderData!.orderId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
var res = await CRUD().post(
link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
payload: {
'id': orderData!.orderId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
List<String> bodyToPassenger = [
_getData(6).toString(),
_getData(8).toString(),
@@ -340,16 +342,12 @@ class _OrderOverlayState extends State<OrderOverlay>
_log("Driver ID is null, cannot refuse order");
return;
}
await _crud.post(link: AppLink.addDriverOrder, payload: {
_crud.post(link: AppLink.addDriverOrder, payload: {
'driver_id': driverId,
'order_id': orderID,
'status': 'Refused'
});
await _crud.post(link: AppLink.updateRides, payload: {
'id': orderID,
'status': 'Refused',
'driver_id': driverId,
});
_log("Order $orderID refused successfully");
} catch (e) {
_log("Error in _apiRefuseOrder for $orderID: $e");
@@ -509,9 +507,16 @@ class _OrderOverlayState extends State<OrderOverlay>
children: [
Expanded(
flex: 3,
child: _buildHighlightInfo("\$${order.price}", "السعر".tr,
Icons.monetization_on_rounded, AppColors.priceHighlight,
isLarge: true),
child: _buildHighlightInfo(
// التعديل هنا 👇
"${NumberFormat('#,##0').format(order.price)} ل.س",
// أو يمكنك استخدام "SYP" بدلاً من "ل.س"
"السعر".tr,
Icons.monetization_on_rounded,
AppColors.priceHighlight,
isLarge: true,
),
),
const SizedBox(width: 12),
Expanded(

View File

@@ -248,38 +248,31 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
kolor: AppColor.greenColor,
title: 'Accept Order'.tr,
onPressed: () async {
Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
controller.endTimer();
controller.changeApplied();
var res = await CRUD().post(
link: AppLink.updateStausFromSpeed,
payload: {
'id': (controller.myList[16]),
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
CRUD().post(
link:
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
"${AppLink.ride}/rides/updateStausFromSpeed.php",
payload: {
'id': (controller.myList[16]),
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
});
if (res == 'failure') {
MyDialog().getDialog(
"This ride is already applied by another driver."
.tr,
'', () {
Get.back();
// Get.back();
Get.back();
});
} else {
await CRUD().postFromDialogue(
Get.put(HomeCaptainController()).changeRideId();
box.write(BoxName.statusDriverLocation, 'on');
controller.endTimer();
controller.changeApplied();
CRUD().postFromDialogue(
link: AppLink.addDriverOrder,
payload: {
'driver_id':
@@ -386,33 +379,26 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
title: 'Refuse Order'.tr,
onPressed: () async {
controller.endTimer();
List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
];
// List<String> bodyToPassenger = [
// box.read(BoxName.driverID).toString(),
// box.read(BoxName.nameDriver).toString(),
// box.read(BoxName.tokenDriver).toString(),
// ];
// FirebaseMessagesController()
// .sendNotificationToPassengerToken(
// 'Order Under Review'.tr,
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
// controller.myList[9].toString(),
// bodyToPassenger,
// 'notification');
NotificationService.sendNotification(
target: controller.myList[9].toString(),
title: 'Order Under Review'.tr,
body:
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
isTopic: false, // Important: this is a token
tone: 'start',
driverList: [], category: 'Order Under Review',
);
// NotificationService.sendNotification(
// target: controller.myList[9].toString(),
// title: 'Order Under Review'.tr,
// body:
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
// isTopic: false, // Important: this is a token
// tone: 'start',
// driverList: bodyToPassenger,
// category: 'Order Under Review',
// );
controller.refuseOrder(
EncryptionHelper.instance.encryptData(
controller.myList[16].toString()),
);
// controller.refuseOrder(
// (controller.myList[16].toString()),
// );
controller.addRideToNotificationDriverString(
controller.myList[16].toString(),
controller.myList[29].toString(),

File diff suppressed because it is too large Load Diff

View File

@@ -66,48 +66,48 @@ class PointsCaptain extends StatelessWidget {
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: '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 {
@@ -153,51 +153,26 @@ class PointsCaptain extends StatelessWidget {
)),
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(() => PaymentScreenSmsProvider(
amount: pricePoint));
}
}));
// التحقق بالبصمة قبل أي شيء
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,

View File

@@ -113,9 +113,11 @@ class WalletCaptainRefactored extends StatelessWidget {
child: Column(
children: [
Text(
'${'Total Points is'.tr} 💎',
style: AppStyle.headTitle2
.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
'رصيد التشغيل 💎',
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),