first commit
This commit is contained in:
101
siro_driver/lib/views/home/Captin/About Us/about_us.dart
Executable file
101
siro_driver/lib/views/home/Captin/About Us/about_us.dart
Executable file
@@ -0,0 +1,101 @@
|
||||
import 'package:siro_driver/constant/box_name.dart';
|
||||
import 'package:siro_driver/constant/style.dart';
|
||||
import 'package:siro_driver/main.dart';
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScafolld(
|
||||
title: 'About Us'.tr,
|
||||
body: [
|
||||
// Company Logo (consider adding an image asset)
|
||||
ListView(
|
||||
children: [
|
||||
Center(
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png', // Replace with your logo image asset path
|
||||
height: 100.0,
|
||||
width: 100.0,
|
||||
),
|
||||
), // Company Name and Location
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Intaleq LLC\n${'Syria'.tr}',
|
||||
style: AppStyle.headTitle2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
'Intaleq is a ride-sharing app designed with your safety and affordability in mind. We connect you with reliable drivers in your area, ensuring a convenient and stress-free travel experience.\n\nHere are some of the key features that set us apart:'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
), // Security Features List
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.lock, color: Colors.blue),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'Most Secure Methods'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.phone, color: Colors.blue),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'In-App VOIP Calls'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.videocam, color: Colors.blue),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'Recorded Trips for Safety'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
), // Affordability Highlight
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
'\nWe also prioritize affordability, offering competitive pricing to make your rides accessible.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// About Us Text
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
145
siro_driver/lib/views/home/Captin/About Us/frequantly_question.dart
Executable file
145
siro_driver/lib/views/home/Captin/About Us/frequantly_question.dart
Executable file
@@ -0,0 +1,145 @@
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:siro_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../constant/style.dart';
|
||||
|
||||
class FrequentlyQuestionsPage extends StatelessWidget {
|
||||
const FrequentlyQuestionsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String selectedPayment = 'cash'; // Replace with your initial selection
|
||||
bool canCancelRide = false;
|
||||
return MyScafolld(
|
||||
title: 'Frequently Questions'.tr,
|
||||
body: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
// Question 1: How do I request a ride?
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'How do I request a ride?'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Step-by-step instructions on how to request a ride through the Intaleq app.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 2: What types of vehicles are available?
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'What types of vehicles are available?'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Intaleq offers a variety of vehicle options to suit your needs, including economy, comfort, and luxury. Choose the option that best fits your budget and passenger count.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 3: How can I pay for my ride?
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'How can I pay for my ride?'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Intaleq offers multiple payment methods for your convenience. Choose between cash payment or credit/debit card payment during ride confirmation.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 4: Can I cancel my ride? (if applicable)
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'Can I cancel my ride?'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Yes, you can cancel your ride under certain conditions (e.g., before driver is assigned). See the Intaleq cancellation policy for details.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 5 & 6: Driver-specific questions
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'Driver Registration & Requirements'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'${'How can I register as a driver?'.tr}\n${'What are the requirements to become a driver?'.tr}',
|
||||
style: AppStyle.title,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
MyDialog().getDialog('title', 'midTitle', () {
|
||||
; //todo add in this dialog papers for driver
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Visit our website or contact Intaleq support for information on driver registration and requirements.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 7: How do I communicate with the other party?
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'How do I communicate with the other party (passenger/driver)?'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Intaleq provides in-app chat functionality to allow you to communicate with your driver or passenger during your ride.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Question 8: What safety measures does Intaleq offer?
|
||||
ExpansionTile(
|
||||
title: Text(
|
||||
'What safety measures does Intaleq offer?'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
children: [
|
||||
Text(
|
||||
'Intaleq prioritizes your safety. We offer features like driver verification, in-app trip tracking, and emergency contact options.'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
222
siro_driver/lib/views/home/Captin/About Us/settings_captain.dart
Executable file
222
siro_driver/lib/views/home/Captin/About Us/settings_captain.dart
Executable file
@@ -0,0 +1,222 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
import 'package:siro_driver/controller/profile/setting_controller.dart';
|
||||
import 'package:siro_driver/views/lang/languages.dart';
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:siro_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
// تأكد من صحة مسارات الاستيراد هذه
|
||||
import '../../../../controller/functions/vibrate.dart'; // Controller with isVibrate
|
||||
import '../../../auth/country_widget.dart';
|
||||
import 'about_us.dart';
|
||||
import 'frequantly_question.dart';
|
||||
import 'using_app_page.dart';
|
||||
|
||||
class SettingsCaptain extends StatelessWidget {
|
||||
const SettingsCaptain({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// تحميل الـ Controllers المطلوبة
|
||||
final settingsController = Get.put(SettingController());
|
||||
final homeController = Get.put(HomePageController());
|
||||
|
||||
return MyScafolld(
|
||||
title: 'Settings'.tr,
|
||||
isleading: true,
|
||||
body: [
|
||||
ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||
children: <Widget>[
|
||||
// --- القسم الأول: عام ---
|
||||
_buildSectionHeader('General'.tr, context),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
_buildListTile(
|
||||
icon: Icons.language_outlined,
|
||||
title: 'Language'.tr,
|
||||
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,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- القسم الثاني: تفضيلات التطبيق ---
|
||||
_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,
|
||||
title: 'Google Map App'.tr,
|
||||
subtitle: 'Run Google Maps directly'.tr,
|
||||
controller: settingsController,
|
||||
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,
|
||||
subtitle: 'Vibration feedback for buttons'.tr,
|
||||
controller: homeController,
|
||||
valueGetter: (ctrl) => (ctrl).isVibrate,
|
||||
onChanged: (ctrl) => (ctrl)
|
||||
.changeVibrateOption(true), // قد تحتاج لتعديل الدالة
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- القسم الثالث: المساعدة والدعم ---
|
||||
_buildSectionHeader('Help & Support'.tr, context),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
_buildListTile(
|
||||
icon: Icons.quiz_outlined,
|
||||
title: 'Frequently Questions'.tr,
|
||||
onTap: () => Get.to(() => const FrequentlyQuestionsPage()),
|
||||
),
|
||||
_buildListTile(
|
||||
icon: Icons.support_agent,
|
||||
title: "How to use App".tr,
|
||||
onTap: () => Get.to(() => const UsingAppPage()),
|
||||
),
|
||||
_buildListTile(
|
||||
icon: Icons.info_outline,
|
||||
title: 'About Us'.tr,
|
||||
onTap: () => Get.to(() => const AboutPage()),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- القسم الرابع: تسجيل الخروج ---
|
||||
_buildSectionHeader('Account'.tr, context),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout, color: Colors.red),
|
||||
title: Text(
|
||||
'Logout'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.red, fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () {
|
||||
MyDialog().getDialog(
|
||||
'Logout'.tr,
|
||||
'Are you sure you want to logout?'.tr,
|
||||
() {
|
||||
// أضف دالة تسجيل الخروج هنا
|
||||
Get.back(); // لإغلاق مربع الحوار
|
||||
},
|
||||
// isConfirmation: true,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ويدجت لبناء عنوان كل قسم
|
||||
Widget _buildSectionHeader(String title, BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, bottom: 12.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ويدجت لبناء بطاقة الإعدادات
|
||||
Widget _buildSettingsCard({required List<Widget> children}) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
clipBehavior: Clip.antiAlias, // مهم لجعل splash effect داخل حدود البطاقة
|
||||
child: Column(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
// ويدجت لبناء عنصر قابل للضغط (مثل اللغة، عن التطبيق)
|
||||
Widget _buildListTile({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
String? subtitle,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
leading: Icon(icon, color: Colors.grey.shade700),
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
// ويدجت لبناء عنصر يحتوي على مفتاح تفعيل/إلغاء (Switch)
|
||||
Widget _buildSwitchTile<T extends GetxController>({
|
||||
required IconData icon,
|
||||
Color? color,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required T controller,
|
||||
required bool Function(T) valueGetter,
|
||||
required Function(T) onChanged,
|
||||
}) {
|
||||
return GetBuilder<T>(
|
||||
init: controller,
|
||||
builder: (ctrl) {
|
||||
return SwitchListTile(
|
||||
secondary: Icon(icon, color: color ?? Colors.grey.shade700),
|
||||
title:
|
||||
Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: Text(subtitle),
|
||||
value: valueGetter(ctrl),
|
||||
onChanged: (value) => onChanged(ctrl),
|
||||
activeColor: AppColor.primaryColor,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
112
siro_driver/lib/views/home/Captin/About Us/using_app_page.dart
Executable file
112
siro_driver/lib/views/home/Captin/About Us/using_app_page.dart
Executable file
@@ -0,0 +1,112 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
|
||||
// 1. إنشاء Class لتمثيل بيانات كل سؤال وجواب
|
||||
class FaqItem {
|
||||
final String question;
|
||||
final Widget answer; // استخدام Widget يسمح بوضع نصوص أو صور
|
||||
final IconData icon;
|
||||
|
||||
FaqItem({required this.question, required this.answer, required this.icon});
|
||||
}
|
||||
|
||||
class UsingAppPage extends StatelessWidget {
|
||||
const UsingAppPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 2. تجهيز قائمة البيانات بشكل منظم
|
||||
final List<FaqItem> faqItems = [
|
||||
FaqItem(
|
||||
question: "What are the order details we provide to you?".tr,
|
||||
icon: Icons.receipt_long_outlined,
|
||||
answer: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image.network(
|
||||
'https://api.tripz-egypt.com/tripz/imageForUsingApp/order_page.jpg',
|
||||
fit: BoxFit.cover,
|
||||
// يمكنك إضافة مؤشر تحميل هنا
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Center(
|
||||
child:
|
||||
Icon(Icons.error_outline, color: Colors.red, size: 40));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
FaqItem(
|
||||
question: "What is the feature of our wallet?".tr,
|
||||
icon: Icons.account_balance_wallet_outlined,
|
||||
answer: Text(
|
||||
'''Intaleq Wallet Features:
|
||||
|
||||
- Transfer money multiple times.
|
||||
- Transfer to anyone.
|
||||
- Make purchases.
|
||||
- Charge your account.
|
||||
- Charge a friend's Intaleq account.
|
||||
- Store your money with us and receive it in your bank as a monthly salary.'''
|
||||
.tr,
|
||||
style:
|
||||
TextStyle(fontSize: 15, height: 1.5, color: Colors.grey.shade700),
|
||||
),
|
||||
),
|
||||
FaqItem(
|
||||
question: "What is Types of Trips in Intaleq?".tr,
|
||||
icon: Icons.map_outlined,
|
||||
answer: Text(
|
||||
'''Types of Trips in Intaleq:
|
||||
|
||||
- Comfort: For cars newer than 2017 with air conditioning.
|
||||
- Lady: For girl drivers.
|
||||
- Speed: For fixed salary and endpoints.
|
||||
- Mashwari: For flexible trips where passengers choose the car and driver with prior arrangements.
|
||||
- Raih Gai: For same-day return trips longer than 50km.'''
|
||||
.tr,
|
||||
style:
|
||||
TextStyle(fontSize: 15, height: 1.5, color: Colors.grey.shade700),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
// 3. بناء الواجهة الرسومية باستخدام البيانات
|
||||
return MyScafolld(
|
||||
title: "How to use App".tr, // تم تغيير العنوان ليكون أعم
|
||||
isleading: true,
|
||||
body: [
|
||||
ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||
itemCount: faqItems.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final item = faqItems[index];
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ExpansionTile(
|
||||
leading: Icon(item.icon, color: Theme.of(context).primaryColor),
|
||||
title: Text(item.question,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 12),
|
||||
item.answer,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
193
siro_driver/lib/views/home/Captin/About Us/video_page.dart
Executable file
193
siro_driver/lib/views/home/Captin/About Us/video_page.dart
Executable file
@@ -0,0 +1,193 @@
|
||||
import 'package:siro_driver/constant/info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||
|
||||
import '../../../../controller/home/captin/help/video_controller.dart';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class VideoListPage extends StatelessWidget {
|
||||
final VideoController videoController = Get.put(VideoController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text('Videos Tutorials'.tr),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 10.0), // Outer padding around the list
|
||||
child: GetBuilder<VideoController>(
|
||||
builder: (videoController) {
|
||||
return ListView.builder(
|
||||
itemCount: videoController.videos.length,
|
||||
itemBuilder: (context, index) {
|
||||
final video = videoController.videos[index];
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8.0), // Spacing between each card
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoColors.white,
|
||||
borderRadius:
|
||||
BorderRadius.circular(12.0), // Rounded corners
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: CupertinoColors.systemGrey.withOpacity(0.3),
|
||||
offset: const Offset(
|
||||
0, 4), // Offset for shadow to appear below
|
||||
blurRadius: 10.0, // Blur for softer shadow effect
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: CupertinoColors.systemGrey4,
|
||||
width: 0.5, // Subtle border for a refined iOS-like look
|
||||
),
|
||||
),
|
||||
child: CupertinoListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
video['title'],
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
video['description'],
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.systemGrey,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
// Navigate to video player page (iOS-style)
|
||||
Get.to(() => VideoPlayerPage1(videoUrl: video['url']));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayerPage extends StatelessWidget {
|
||||
final String videoUrl;
|
||||
|
||||
VideoPlayerPage({required this.videoUrl});
|
||||
|
||||
final VideoController videoController = Get.put(VideoController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Initialize the video when the page is loaded
|
||||
videoController.initializeVideo(videoUrl);
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
middle: Text(AppInformation.appName),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: GetBuilder<VideoController>(
|
||||
builder: (controller) {
|
||||
if (!controller.videoPlayerController.value.isInitialized) {
|
||||
return const CircularProgressIndicator(); // Show loading indicator while initializing
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Video player widget
|
||||
AspectRatio(
|
||||
aspectRatio:
|
||||
controller.videoPlayerController.value.aspectRatio,
|
||||
// child: VideoPlayer(controller.videoPlayerController),
|
||||
child: VideoPlayer(controller.videoPlayerController),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Play/pause button
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
if (controller.videoPlayerController.value.isPlaying) {
|
||||
controller.pause();
|
||||
} else {
|
||||
controller.play();
|
||||
}
|
||||
},
|
||||
child: Icon(
|
||||
controller.videoPlayerController.value.isPlaying
|
||||
? CupertinoIcons.pause
|
||||
: CupertinoIcons.play_arrow,
|
||||
color: CupertinoColors.activeBlue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayerPage1 extends StatelessWidget {
|
||||
final String videoUrl;
|
||||
|
||||
const VideoPlayerPage1({required this.videoUrl});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Extract the video ID from the URL
|
||||
String videoId = YoutubePlayer.convertUrlToId(videoUrl)!;
|
||||
|
||||
// Create a YoutubePlayerController
|
||||
final YoutubePlayerController _controller = YoutubePlayerController(
|
||||
initialVideoId: videoId,
|
||||
flags: const YoutubePlayerFlags(
|
||||
autoPlay: true,
|
||||
loop: true,
|
||||
mute: false,
|
||||
captionLanguage: 'ar',
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Full-screen YouTube player
|
||||
Positioned.fill(
|
||||
child: YoutubePlayer(
|
||||
controller: _controller,
|
||||
showVideoProgressIndicator: true,
|
||||
),
|
||||
),
|
||||
// Overlay back button in the top left corner for exit
|
||||
Positioned(
|
||||
top: 40.0,
|
||||
left: 16.0,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
191
siro_driver/lib/views/home/Captin/assurance_health_page.dart
Executable file
191
siro_driver/lib/views/home/Captin/assurance_health_page.dart
Executable file
@@ -0,0 +1,191 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
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});
|
||||
final AssuranceHealthController assuranceHealthController =
|
||||
Get.put(AssuranceHealthController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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,
|
||||
),
|
||||
),
|
||||
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: theme.textTheme.bodyLarge
|
||||
?.copyWith(fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF42A5F5), Color(0xFF1976D2)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
count,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
111
siro_driver/lib/views/home/Captin/bottom_bar.dart
Executable file
111
siro_driver/lib/views/home/Captin/bottom_bar.dart
Executable file
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
|
||||
class BottomBarController extends GetxController {
|
||||
var currentIndex = 0.obs;
|
||||
|
||||
void changePage(int index) {
|
||||
currentIndex.value = index;
|
||||
}
|
||||
}
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
final BottomBarController controller = Get.put(BottomBarController());
|
||||
|
||||
HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Bottom Bar Example'.tr),
|
||||
),
|
||||
body: Obx(() => IndexedStack(
|
||||
index: controller.currentIndex.value,
|
||||
children: const [
|
||||
HomeView(),
|
||||
ProfileView(),
|
||||
StatisticsView(),
|
||||
WalletView(),
|
||||
],
|
||||
)),
|
||||
bottomNavigationBar: Obx(() => BottomNavigationBar(
|
||||
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),
|
||||
label: 'Home'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.person),
|
||||
label: 'Profile'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.bar_chart),
|
||||
label: 'Statistics'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.account_balance_wallet),
|
||||
label: 'Wallet'.tr,
|
||||
),
|
||||
],
|
||||
)),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeView extends StatelessWidget {
|
||||
const HomeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Map<String, dynamic> data;
|
||||
return const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Home View'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileView extends StatelessWidget {
|
||||
const ProfileView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Profile View'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StatisticsView extends StatelessWidget {
|
||||
const StatisticsView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Statistics View'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WalletView extends StatelessWidget {
|
||||
const WalletView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Wallet View'),
|
||||
);
|
||||
}
|
||||
}
|
||||
302
siro_driver/lib/views/home/Captin/camer_widget.dart
Executable file
302
siro_driver/lib/views/home/Captin/camer_widget.dart
Executable file
@@ -0,0 +1,302 @@
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:siro_driver/constant/colors.dart';
|
||||
// import 'package:siro_driver/constant/style.dart';
|
||||
// import 'package:siro_driver/controller/functions/camer_controller.dart';
|
||||
// import 'package:siro_driver/views/widgets/elevated_btn.dart';
|
||||
// import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
|
||||
// class CameraWidgetCardId extends StatelessWidget {
|
||||
// final CameraClassController cameraClassController =
|
||||
// Get.put(CameraClassController());
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
|
||||
// class CameraWidgetPassPort extends StatelessWidget {
|
||||
// final CameraClassController cameraClassController =
|
||||
// Get.put(CameraClassController());
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
448
siro_driver/lib/views/home/Captin/driver_map_page.dart
Executable file
448
siro_driver/lib/views/home/Captin/driver_map_page.dart
Executable file
@@ -0,0 +1,448 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/constant/box_name.dart';
|
||||
import 'package:siro_driver/constant/style.dart';
|
||||
import 'package:siro_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_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';
|
||||
import 'mapDriverWidgets/google_driver_map_page.dart';
|
||||
import 'mapDriverWidgets/google_map_app.dart';
|
||||
import 'mapDriverWidgets/passenger_info_window.dart';
|
||||
import 'mapDriverWidgets/sos_connect.dart';
|
||||
import 'mapDriverWidgets/sped_circle.dart';
|
||||
|
||||
class PassengerLocationMapPage extends StatelessWidget {
|
||||
PassengerLocationMapPage({super.key});
|
||||
final LocationController locationController = Get.put(LocationController());
|
||||
final MapDriverController mapDriverController =
|
||||
Get.put(MapDriverController());
|
||||
|
||||
// دالة ديالوج الخروج
|
||||
Future<bool> showExitDialog() async {
|
||||
bool? result = await Get.defaultDialog(
|
||||
title: "Warning".tr,
|
||||
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
||||
middleText:
|
||||
"Active ride in progress. Leaving might stop tracking. Exit?".tr,
|
||||
barrierDismissible: false,
|
||||
radius: 15,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Stay'.tr,
|
||||
kolor: AppColor.greenColor,
|
||||
onPressed: () => Get.back(result: false)),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Exit'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () => Get.back(result: true)),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
|
||||
// 🔥 [Fix] argumentLoading ضرورية هنا للعودة للرحلة من صفحة الهوم
|
||||
// (عند العودة لا يُستدعى onInit() لأن الكنترولر موجود مسبقاً)
|
||||
// الحماية من التكرار موجودة داخل argumentLoading بواسطة _isRouteRequested flag
|
||||
mapDriverController.argumentLoading();
|
||||
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
|
||||
mapDriverController
|
||||
.update(['PassengerInfo', 'DriverEndBar', 'SosConnect']);
|
||||
}
|
||||
});
|
||||
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) return;
|
||||
final shouldExit = await showExitDialog();
|
||||
if (shouldExit) Get.back();
|
||||
},
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
children: [
|
||||
// 1. الخريطة (الخلفية)
|
||||
Positioned.fill(
|
||||
child: GoogleDriverMap(locationController: locationController)),
|
||||
|
||||
// 2. واجهة المستخدم (فوق الخريطة)
|
||||
SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// أ) زر الإلغاء (أعلى اليسار)
|
||||
CancelWidget(mapDriverController: mapDriverController),
|
||||
|
||||
// ب) شريط إنهاء الرحلة (أعلى الوسط)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SafeArea(child: driverEndRideBar())),
|
||||
|
||||
// ج) شريط التعليمات الملاحية (الأسفل)
|
||||
const InstructionsOfRoads(),
|
||||
|
||||
// د) نافذة معلومات الراكب (تعلو التعليمات ديناميكياً)
|
||||
const PassengerInfoWindow(),
|
||||
// SpeedCircle(),
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 20, // أو أي مسافة تناسبك
|
||||
child: GetBuilder<MapDriverController>(
|
||||
// id: 'SosConnect', // لتحديث الزر عند بدء الرحلة
|
||||
builder: (controller) {
|
||||
// حساب الهوامش ديناميكياً لرفع الأزرار فوق النوافذ السفلية
|
||||
double bottomPadding = 0;
|
||||
if (controller.currentInstruction.isNotEmpty)
|
||||
bottomPadding += 120;
|
||||
if (controller.isPassengerInfoWindow)
|
||||
bottomPadding += 220;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: EdgeInsets.only(bottom: bottomPadding),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
SosConnect(), // ويدجت نظيفة
|
||||
const SizedBox(height: 12),
|
||||
const GoogleMapApp(), // ويدجت نظيفة
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 3. النوافذ المنبثقة (Overlay)
|
||||
const PricesWindow(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. ويدجت شريط التعليمات (InstructionsOfRoads)
|
||||
// ---------------------------------------------------------------------------
|
||||
class InstructionsOfRoads extends StatelessWidget {
|
||||
const InstructionsOfRoads({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
bottom: 20,
|
||||
left: 15,
|
||||
right: 15,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// إخفاء الشريط إذا لم يكن هناك تعليمات
|
||||
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(
|
||||
opacity: value,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.cardColor
|
||||
.withOpacity(0.95), // Adaptive background
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 5)),
|
||||
],
|
||||
border: Border.all(
|
||||
color: theme.dividerColor.withOpacity(0.1)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// أيقونة الاتجاه
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(controller.currentManeuverIcon,
|
||||
color: Colors.white, size: 24),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
// نص التعليمات
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${"NEXT STEP".tr} (${controller.distanceToNextStep})",
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
controller.currentInstruction,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600, height: 1.2),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// فاصل عمودي
|
||||
Container(
|
||||
width: 1,
|
||||
height: 30,
|
||||
color: Colors.white12,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10)),
|
||||
|
||||
// زر التحكم بالصوت
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => controller.toggleTts(),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
controller.isTtsEnabled
|
||||
? Icons.volume_up_rounded
|
||||
: Icons.volume_off_rounded,
|
||||
color: controller.isTtsEnabled
|
||||
? AppColor.greenColor
|
||||
: Colors.grey,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. ويدجت زر الإلغاء (CancelWidget) - كامل
|
||||
// ---------------------------------------------------------------------------
|
||||
class CancelWidget extends StatelessWidget {
|
||||
const CancelWidget({super.key, required this.mapDriverController});
|
||||
final MapDriverController mapDriverController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
top: 10,
|
||||
left: 15,
|
||||
child: GetBuilder<MapDriverController>(builder: (controller) {
|
||||
// نخفي الزر إذا انتهت الرحلة
|
||||
if (controller.isRideFinished) return const SizedBox();
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).shadowColor.withOpacity(0.1),
|
||||
blurRadius: 8)
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onTap: () => _showCancelDialog(context, controller),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(Icons.close_rounded,
|
||||
color: AppColor.redColor, size: 26),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCancelDialog(BuildContext context, MapDriverController controller) {
|
||||
Get.defaultDialog(
|
||||
title: "Cancel Trip?".tr,
|
||||
titleStyle: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
radius: 16,
|
||||
content: Column(
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_rounded,
|
||||
size: 50, color: Colors.orangeAccent),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
"Please tell us why you want to cancel.".tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Form(
|
||||
key: controller.formKeyCancel,
|
||||
child: MyTextForm(
|
||||
controller: controller.cancelTripCotroller,
|
||||
label: "Reason".tr,
|
||||
hint: "Write your reason...".tr,
|
||||
type: TextInputType.text,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
confirm: SizedBox(
|
||||
width: 100,
|
||||
child: MyElevatedButton(
|
||||
title: 'Confirm'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () async {
|
||||
// استدعاء دالة الإلغاء من الكنترولر
|
||||
await controller.cancelTripFromDriverAfterApplied();
|
||||
// Get.back(); // عادة موجودة داخل الدالة في الكنترولر
|
||||
},
|
||||
),
|
||||
),
|
||||
cancel: SizedBox(
|
||||
width: 100,
|
||||
child: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text('Back'.tr, style: const TextStyle(color: Colors.grey)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. ويدجت نافذة الأسعار (PricesWindow) - كامل
|
||||
// ---------------------------------------------------------------------------
|
||||
class PricesWindow extends StatelessWidget {
|
||||
const PricesWindow({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(builder: (controller) {
|
||||
// إخفاء إذا لم تكن مفعلة
|
||||
if (!controller.isPriceWindow) return const SizedBox();
|
||||
|
||||
return Container(
|
||||
color: Colors.black.withOpacity(0.6), // خلفية معتمة
|
||||
child: Center(
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.8, end: 1.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.elasticOut,
|
||||
builder: (context, scale, child) {
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: Container(
|
||||
width: Get.width * 0.85,
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).shadowColor.withOpacity(0.2),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primaryColor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.check_circle_rounded,
|
||||
color: AppColor.primaryColor, size: 50),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Total Price'.tr,
|
||||
style: AppStyle.headTitle2
|
||||
.copyWith(fontSize: 18, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'${controller.totalCost} ${'\$'.tr}',
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
fontSize: 42,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 55,
|
||||
child: MyElevatedButton(
|
||||
title: 'Collect Payment'.tr,
|
||||
kolor: AppColor.primaryColor,
|
||||
onPressed: () {
|
||||
// الذهاب لصفحة التقييم
|
||||
Get.to(() => RatePassenger(), arguments: {
|
||||
'rideId': controller.rideId,
|
||||
'passengerId': controller.passengerId,
|
||||
'driverId': controller.driverId,
|
||||
'price': controller.paymentAmount,
|
||||
'walletChecked': controller.walletChecked
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
411
siro_driver/lib/views/home/Captin/history/history_captain.dart
Executable file
411
siro_driver/lib/views/home/Captin/history/history_captain.dart
Executable file
@@ -0,0 +1,411 @@
|
||||
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:siro_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
class HistoryCaptain extends StatelessWidget {
|
||||
const HistoryCaptain({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(HistoryCaptainController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: GetBuilder<HistoryCaptainController>(
|
||||
builder: (controller) {
|
||||
if (controller.isloading) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: FinanceDesignSystem.primaryDark),
|
||||
);
|
||||
}
|
||||
|
||||
final List trips = controller.historyData['message'] ?? [];
|
||||
|
||||
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: BoxDecoration(
|
||||
gradient: FinanceDesignSystem.balanceGradient,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: -50,
|
||||
top: -20,
|
||||
child: Icon(
|
||||
Icons.history_rounded,
|
||||
size: 200,
|
||||
color: Colors.white.withValues(alpha: 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.withValues(alpha: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TripHistoryCard extends StatelessWidget {
|
||||
final Map trip;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _TripHistoryCard({required this.trip, required this.onTap});
|
||||
|
||||
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 Container(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 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(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: FinanceDesignSystem.primaryDark
|
||||
.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: 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: 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: [
|
||||
Icon(Icons.circle,
|
||||
size: 12, color: FinanceDesignSystem.accentBlue),
|
||||
Container(
|
||||
width: 2,
|
||||
height: 20,
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
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: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 16,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.arrow_forward_ios_rounded,
|
||||
size: 12, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip(String? status) {
|
||||
Color color;
|
||||
IconData icon;
|
||||
String label = status ?? 'Unknown';
|
||||
List<Color> gradientColors;
|
||||
|
||||
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.withValues(alpha: 0.3), width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
371
siro_driver/lib/views/home/Captin/history/history_details_page.dart
Executable file
371
siro_driver/lib/views/home/Captin/history/history_details_page.dart
Executable file
@@ -0,0 +1,371 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_driver/constant/api_key.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
|
||||
import 'package:siro_driver/controller/auth/captin/history_captain.dart';
|
||||
import 'package:siro_driver/controller/functions/launch.dart';
|
||||
|
||||
class HistoryDetailsPage extends StatefulWidget {
|
||||
const HistoryDetailsPage({super.key});
|
||||
|
||||
@override
|
||||
State<HistoryDetailsPage> createState() => _HistoryDetailsPageState();
|
||||
}
|
||||
|
||||
class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
|
||||
// Get the controller instance
|
||||
final HistoryCaptainController controller =
|
||||
Get.find<HistoryCaptainController>();
|
||||
|
||||
// Helper method to safely parse LatLng from a string 'lat,lng'
|
||||
LatLng? _parseLatLng(String? latLngString) {
|
||||
if (latLngString == null) return null;
|
||||
final parts = latLngString.split(',');
|
||||
if (parts.length != 2) return null;
|
||||
final lat = double.tryParse(parts[0]);
|
||||
final lng = double.tryParse(parts[1]);
|
||||
if (lat == null || lng == null) return null;
|
||||
return LatLng(lat, lng);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(
|
||||
title: Text('Trip Details'.tr),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
),
|
||||
body: GetBuilder<HistoryCaptainController>(
|
||||
builder: (controller) {
|
||||
if (controller.isloading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final res = controller.historyDetailsData['data'];
|
||||
if (res == null) {
|
||||
return Center(child: Text('Could not load trip details.'.tr));
|
||||
}
|
||||
|
||||
final startLocation = _parseLatLng(res['start_location']);
|
||||
final endLocation = _parseLatLng(res['end_location']);
|
||||
|
||||
// Create markers for the map
|
||||
final Set<Marker> markers = {};
|
||||
if (startLocation != null) {
|
||||
markers.add(Marker(
|
||||
markerId: const MarkerId('start'),
|
||||
position: startLocation,
|
||||
infoWindow: InfoWindow(title: 'Start'.tr)));
|
||||
}
|
||||
if (endLocation != null) {
|
||||
markers.add(Marker(
|
||||
markerId: const MarkerId('end'),
|
||||
position: endLocation,
|
||||
infoWindow: InfoWindow(title: 'End'.tr)));
|
||||
}
|
||||
|
||||
return AnimationLimiter(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: AnimationConfiguration.toStaggeredList(
|
||||
duration: const Duration(milliseconds: 375),
|
||||
childAnimationBuilder: (widget) => SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(child: widget),
|
||||
),
|
||||
children: [
|
||||
// --- Map Card ---
|
||||
_buildMapCard(context, startLocation, endLocation, markers),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --- Trip Info Card ---
|
||||
_DetailCard(
|
||||
icon: Icons.receipt_long,
|
||||
title: 'Trip Info'.tr,
|
||||
child: Column(
|
||||
children: [
|
||||
_InfoTile(
|
||||
label: 'Order ID'.tr,
|
||||
value: res['id']?.toString() ?? 'N/A'),
|
||||
_InfoTile(
|
||||
label: 'Date'.tr,
|
||||
value: res['date']?.toString() ?? 'N/A'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// --- Earnings Card ---
|
||||
_DetailCard(
|
||||
icon: Icons.account_balance_wallet,
|
||||
title: 'Earnings & Distance'.tr,
|
||||
child: Column(
|
||||
children: [
|
||||
_InfoTile(
|
||||
label: 'Your Earnings'.tr,
|
||||
value: '${res['price_for_driver']}'),
|
||||
_InfoTile(
|
||||
label: 'Distance'.tr,
|
||||
value: '${res['distance']} KM'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// --- Timeline Card ---
|
||||
_DetailCard(
|
||||
icon: Icons.timeline,
|
||||
title: 'Trip Timeline'.tr,
|
||||
child: Column(
|
||||
children: [
|
||||
_InfoTile(
|
||||
label: 'Time to Passenger'.tr,
|
||||
value: res['DriverIsGoingToPassenger'] ?? 'N/A'),
|
||||
_InfoTile(
|
||||
label: 'Trip Started'.tr,
|
||||
value: res['rideTimeStart'] ?? 'N/A'),
|
||||
_InfoTile(
|
||||
label: 'Trip Finished'.tr,
|
||||
value: res['rideTimeFinish'] ?? 'N/A'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// --- Passenger & Status Card ---
|
||||
_DetailCard(
|
||||
icon: Icons.person,
|
||||
title: 'Passenger & Status'.tr,
|
||||
child: Column(
|
||||
children: [
|
||||
_InfoTile(
|
||||
label: 'Passenger Name'.tr,
|
||||
value:
|
||||
'${res['passengerName']} ${res['last_name']}'),
|
||||
_InfoTile(
|
||||
label: 'Status'.tr,
|
||||
value: res['status'] ?? 'N/A',
|
||||
isStatus: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMapCard(BuildContext context, LatLng? startLocation,
|
||||
LatLng? endLocation, Set<Marker> markers) {
|
||||
// A fallback position if locations are not available
|
||||
final initialCameraPosition = (startLocation != null)
|
||||
? CameraPosition(target: startLocation, zoom: 14)
|
||||
: const CameraPosition(
|
||||
target: LatLng(31.96, 35.92), zoom: 12); // Fallback to Amman
|
||||
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.1),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
clipBehavior:
|
||||
Clip.antiAlias, // Ensures the map respects the border radius
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 250,
|
||||
child: IntaleqMap(
|
||||
apiKey: AK.mapAPIKEY,
|
||||
initialCameraPosition: initialCameraPosition,
|
||||
markers: markers,
|
||||
polylines: {
|
||||
if (startLocation != null && endLocation != null)
|
||||
Polyline(
|
||||
polylineId: const PolylineId('route'),
|
||||
points: [startLocation, endLocation],
|
||||
color: Colors.deepPurple,
|
||||
width: 5,
|
||||
),
|
||||
},
|
||||
onMapCreated: (IntaleqMapController mapController) {
|
||||
// Animate camera to fit the route
|
||||
if (startLocation != null && endLocation != null) {
|
||||
LatLngBounds bounds = LatLngBounds(
|
||||
southwest: LatLng(
|
||||
startLocation.latitude < endLocation.latitude
|
||||
? startLocation.latitude
|
||||
: endLocation.latitude,
|
||||
startLocation.longitude < endLocation.longitude
|
||||
? startLocation.longitude
|
||||
: endLocation.longitude,
|
||||
),
|
||||
northeast: LatLng(
|
||||
startLocation.latitude > endLocation.latitude
|
||||
? startLocation.latitude
|
||||
: endLocation.latitude,
|
||||
startLocation.longitude > endLocation.longitude
|
||||
? startLocation.longitude
|
||||
: endLocation.longitude,
|
||||
),
|
||||
);
|
||||
mapController.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(bounds, left: 60, top: 60, right: 60, bottom: 60));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: FloatingActionButton.small(
|
||||
heroTag: 'open_maps',
|
||||
onPressed: () {
|
||||
if (startLocation != null && endLocation != null) {
|
||||
String mapUrl =
|
||||
'https://www.google.com/maps/dir/${startLocation.latitude},${startLocation.longitude}/${endLocation.latitude},${endLocation.longitude}/';
|
||||
showInBrowser(mapUrl);
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.directions),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A reusable widget for the main detail cards
|
||||
class _DetailCard extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Widget child;
|
||||
|
||||
const _DetailCard({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.05),
|
||||
margin: const EdgeInsets.only(bottom: 16.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: Theme.of(context).primaryColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 24),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A reusable widget for a label-value pair inside a card
|
||||
class _InfoTile extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final bool isStatus;
|
||||
|
||||
const _InfoTile({
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.isStatus = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: Colors.grey[700])),
|
||||
if (isStatus)
|
||||
_buildStatusChip(value)
|
||||
else
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reusing the status chip from the previous page for consistency
|
||||
Widget _buildStatusChip(String status) {
|
||||
Color chipColor;
|
||||
Color textColor;
|
||||
IconData iconData;
|
||||
|
||||
switch (status.toLowerCase()) {
|
||||
case 'apply':
|
||||
case 'completed': // Assuming 'Apply' means completed
|
||||
chipColor = Colors.green.shade50;
|
||||
textColor = Colors.green.shade800;
|
||||
iconData = Icons.check_circle;
|
||||
status = 'Completed';
|
||||
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;
|
||||
status = 'Cancelled';
|
||||
break;
|
||||
default:
|
||||
chipColor = Colors.grey.shade200;
|
||||
textColor = Colors.grey.shade800;
|
||||
iconData = Icons.hourglass_empty;
|
||||
}
|
||||
|
||||
return Chip(
|
||||
avatar: Icon(iconData, color: textColor, size: 16),
|
||||
label: Text(
|
||||
status.tr,
|
||||
style: TextStyle(color: textColor, fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: chipColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
side: BorderSide.none,
|
||||
);
|
||||
}
|
||||
407
siro_driver/lib/views/home/Captin/home_captain/drawer_captain.dart
Executable file
407
siro_driver/lib/views/home/Captin/home_captain/drawer_captain.dart
Executable file
@@ -0,0 +1,407 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:siro_driver/constant/box_name.dart';
|
||||
import 'package:siro_driver/constant/links.dart';
|
||||
import 'package:siro_driver/controller/functions/upload_image.dart';
|
||||
import 'package:siro_driver/controller/home/captin/home_captain_controller.dart';
|
||||
import 'package:siro_driver/device_compatibility_page.dart';
|
||||
import 'package:siro_driver/main.dart';
|
||||
|
||||
// استيراد الصفحات الأخرى... تأكد من صحة المسارات
|
||||
import 'package:siro_driver/views/Rate/rate_app_page.dart';
|
||||
import 'package:siro_driver/views/auth/captin/contact_us_page.dart';
|
||||
import 'package:siro_driver/views/auth/captin/invite_driver_screen.dart';
|
||||
import 'package:siro_driver/views/home/statistics/statistics_dashboard.dart';
|
||||
import 'package:siro_driver/views/gamification/challenges_page.dart';
|
||||
import 'package:siro_driver/views/gamification/leaderboard_page.dart';
|
||||
import 'package:siro_driver/views/gamification/referral_center_page.dart';
|
||||
import 'package:siro_driver/views/home/journal/schedule_page.dart';
|
||||
import 'package:siro_driver/views/notification/available_rides_page.dart';
|
||||
import 'package:siro_driver/views/auth/captin/logout_captain.dart';
|
||||
import 'package:siro_driver/views/home/Captin/history/history_captain.dart';
|
||||
import 'package:siro_driver/views/home/Captin/home_captain/help_captain.dart';
|
||||
import 'package:siro_driver/views/home/Captin/About Us/settings_captain.dart';
|
||||
import 'package:siro_driver/views/home/my_wallet/walet_captain.dart';
|
||||
import 'package:siro_driver/views/home/profile/profile_captain.dart';
|
||||
import 'package:siro_driver/views/notification/notification_captain.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../About Us/video_page.dart';
|
||||
import '../assurance_health_page.dart';
|
||||
import '../maintain_center_page.dart';
|
||||
|
||||
// 1. إنشاء Class لتعريف بيانات كل عنصر في القائمة
|
||||
class DrawerItem {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
DrawerItem(
|
||||
{required this.title,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.onTap});
|
||||
}
|
||||
|
||||
// --- الويدجت الرئيسية للقائمة الجانبية ---
|
||||
class AppDrawer extends StatelessWidget {
|
||||
AppDrawer({super.key});
|
||||
|
||||
final ImageController imageController = Get.put(ImageController());
|
||||
|
||||
// 2. تعريف بيانات القائمة بشكل مركزي ومنظم
|
||||
final List<DrawerItem> drawerItems = [
|
||||
DrawerItem(
|
||||
title: 'Balance'.tr,
|
||||
icon: Icons.account_balance_wallet,
|
||||
color: Colors.green,
|
||||
onTap: () => Get.to(() => WalletCaptainRefactored())),
|
||||
DrawerItem(
|
||||
title: 'Statistics'.tr,
|
||||
icon: Icons.bar_chart_rounded,
|
||||
color: Colors.deepPurple,
|
||||
onTap: () => Get.to(() => StatisticsDashboard())),
|
||||
DrawerItem(
|
||||
title: 'Challenges'.tr,
|
||||
icon: Icons.bolt_rounded,
|
||||
color: Colors.amber,
|
||||
onTap: () => Get.to(() => ChallengesPage())),
|
||||
DrawerItem(
|
||||
title: 'My Schedule'.tr,
|
||||
icon: Icons.calendar_today_rounded,
|
||||
color: Colors.teal,
|
||||
onTap: () => Get.to(() => SchedulePage())),
|
||||
DrawerItem(
|
||||
title: 'Leaderboard'.tr,
|
||||
icon: Icons.leaderboard_rounded,
|
||||
color: Colors.red,
|
||||
onTap: () => Get.to(() => LeaderboardPage())),
|
||||
DrawerItem(
|
||||
title: 'Profile'.tr,
|
||||
icon: Icons.person,
|
||||
color: Colors.blue,
|
||||
onTap: () => Get.to(() => ProfileCaptain())),
|
||||
DrawerItem(
|
||||
title: 'History of Trip'.tr,
|
||||
icon: Icons.history,
|
||||
color: Colors.orange,
|
||||
onTap: () => Get.to(() => const HistoryCaptain())),
|
||||
DrawerItem(
|
||||
title: 'Available for rides'.tr,
|
||||
icon: Icons.drive_eta,
|
||||
color: Colors.teal,
|
||||
onTap: () => Get.to(() => const AvailableRidesPage())),
|
||||
DrawerItem(
|
||||
title: 'Notifications'.tr,
|
||||
icon: Icons.notifications,
|
||||
color: Colors.purple,
|
||||
onTap: () => Get.to(() => const NotificationCaptain())),
|
||||
DrawerItem(
|
||||
title: 'Helping Center'.tr,
|
||||
icon: Icons.help_center,
|
||||
color: Colors.cyan,
|
||||
onTap: () => Get.to(() => HelpCaptain())),
|
||||
DrawerItem(
|
||||
title: 'Invite Driver'.tr,
|
||||
icon: Icons.person_add_rounded,
|
||||
color: Colors.indigo,
|
||||
onTap: () => Get.to(() => InviteScreen())),
|
||||
// DrawerItem(
|
||||
// title: 'Maintenance Center'.tr,
|
||||
// icon: Icons.build,
|
||||
// color: Colors.brown,
|
||||
// onTap: () => Get.to(() => MaintainCenterPage())),
|
||||
// DrawerItem(
|
||||
// title: 'Health Insurance'.tr,
|
||||
// icon: Icons.favorite,
|
||||
// color: Colors.pink,
|
||||
// onTap: () => Get.to(() => AssuranceHealthPage())),
|
||||
DrawerItem(
|
||||
title: 'Contact Us'.tr,
|
||||
icon: Icons.email,
|
||||
color: Colors.blueGrey,
|
||||
onTap: () => Get.to(() => ContactUsPage())),
|
||||
DrawerItem(
|
||||
title: 'Videos Tutorials'.tr,
|
||||
icon: Icons.video_library,
|
||||
color: Colors.redAccent,
|
||||
onTap: () => Get.to(() => VideoListPage())),
|
||||
DrawerItem(
|
||||
title: 'Rate Our App'.tr,
|
||||
icon: Icons.star,
|
||||
color: Colors.amber,
|
||||
onTap: () => Get.to(() => RatingScreen())),
|
||||
DrawerItem(
|
||||
title: 'Is device compatible'.tr,
|
||||
icon: Icons.memory,
|
||||
color: Colors.greenAccent,
|
||||
onTap: () => Get.to(() => DeviceCompatibilityPage())),
|
||||
DrawerItem(
|
||||
title: 'Privacy Policy'.tr,
|
||||
icon: Icons.memory,
|
||||
color: Colors.greenAccent,
|
||||
onTap: () =>
|
||||
launchUrl(Uri.parse('${AppLink.server}/privacy_policy.php'))),
|
||||
DrawerItem(
|
||||
title: 'Settings'.tr,
|
||||
icon: Icons.settings,
|
||||
color: Colors.grey.shade600,
|
||||
onTap: () => Get.to(() => const SettingsCaptain())),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Drawer(
|
||||
child: Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Column(
|
||||
children: [
|
||||
// --- الجزء العلوي من القائمة (بيانات المستخدم) ---
|
||||
UserHeader(), // استخدمنا الويدجت المحسنة بالأسفل
|
||||
|
||||
// --- قائمة العناصر المتحركة ---
|
||||
Expanded(
|
||||
child: AnimationLimiter(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
itemCount: drawerItems.length + 1, // +1 لزر تسجيل الخروج
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
// --- زر تسجيل الخروج في النهاية ---
|
||||
if (index == drawerItems.length) {
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(
|
||||
child: _DrawerItemTile(
|
||||
item: DrawerItem(
|
||||
title: 'Sign Out'.tr,
|
||||
icon: Icons.logout,
|
||||
color: Colors.red,
|
||||
onTap: () =>
|
||||
Get.to(() => const LogoutCaptain())),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- بقية العناصر ---
|
||||
final item = drawerItems[index];
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(
|
||||
child: _DrawerItemTile(item: item),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- ويدجت خاصة بكل عنصر في القائمة ---
|
||||
class _DrawerItemTile extends StatelessWidget {
|
||||
final DrawerItem item;
|
||||
const _DrawerItemTile({required this.item});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: item.color.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(item.icon, color: item.color, size: 24),
|
||||
),
|
||||
title: Text(
|
||||
item.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(context); // لإغلاق القائمة عند الضغط بشكل آمن
|
||||
Future.delayed(const Duration(milliseconds: 250), () {
|
||||
item.onTap(); // الانتقال للصفحة بعد تأخير بسيط لإظهار الأنيميشن
|
||||
});
|
||||
},
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
splashColor: item.color.withValues(alpha: 0.2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- ويدجت محسنة للجزء العلوي من القائمة ---
|
||||
// ... (الاستيرادات السابقة تبقى كما هي)
|
||||
|
||||
// --- تم تعديل UserHeader لإضافة التحقق من الصورة ---
|
||||
class UserHeader extends StatelessWidget {
|
||||
UserHeader({super.key});
|
||||
final ImageController imageController = Get.find<ImageController>();
|
||||
final HomeCaptainController homeCaptainController =
|
||||
Get.find<HomeCaptainController>();
|
||||
|
||||
// دالة لإظهار التنبيه
|
||||
void _showUploadPhotoDialog(
|
||||
BuildContext context, ImageController controller) {
|
||||
// نستخدم addPostFrameCallback لضمان عدم ظهور الخطأ أثناء بناء الواجهة
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// نتأكد ألا يكون هناك dialog مفتوح بالفعل لتجنب التكرار
|
||||
if (Get.isDialogOpen == true) return;
|
||||
|
||||
Get.defaultDialog(
|
||||
title: "Profile Photo Required".tr,
|
||||
titleStyle:
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
middleText:
|
||||
"Please upload a clear photo of your face to be identified by passengers."
|
||||
.tr,
|
||||
barrierDismissible: false, // منع الإغلاق بالضغط خارج النافذة
|
||||
radius: 15,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
confirm: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Get.back(); // إغلاق النافذة الحالية
|
||||
// فتح الكاميرا فوراً
|
||||
controller.choosImagePicture(
|
||||
AppLink.uploadImagePortrate, 'portrait');
|
||||
},
|
||||
icon: const Icon(Icons.camera_alt, color: Colors.white),
|
||||
label: Text("Take Photo Now".tr,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor
|
||||
.primaryColor, // تأكد من وجود هذا اللون أو استبدله بـ Colors.blue
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
),
|
||||
),
|
||||
cancel: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text("Later".tr, style: const TextStyle(color: Colors.grey)),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UserAccountsDrawerHeader(
|
||||
accountName: Text(
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
shadows: [Shadow(blurRadius: 2, color: Colors.black26)]),
|
||||
),
|
||||
accountEmail:
|
||||
box.read(BoxName.emailDriver).toString().contains('intaleqapp')
|
||||
? Text('Your email not updated yet'.tr)
|
||||
: Text(box.read(BoxName.emailDriver)),
|
||||
currentAccountPicture: GetBuilder<ImageController>(
|
||||
builder: (controller) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withValues(alpha: 0.3), blurRadius: 5)
|
||||
],
|
||||
),
|
||||
child: controller.isloading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: CircleAvatar(
|
||||
backgroundImage: NetworkImage((box
|
||||
.read(BoxName.driverPhotoUrl)
|
||||
?.toString()
|
||||
.isNotEmpty ==
|
||||
true)
|
||||
? box.read(BoxName.driverPhotoUrl)
|
||||
: '${AppLink.server}/portrate_captain_image/${box.read(BoxName.driverID)}.jpg'),
|
||||
|
||||
// [تعديل هام]: في حال فشل تحميل الصورة (غير موجودة)
|
||||
onBackgroundImageError: (exception, stackTrace) {
|
||||
// طباعة الخطأ في الكونسول للتوضيح
|
||||
debugPrint(
|
||||
"Profile image not found or error loading: $exception");
|
||||
// استدعاء نافذة التنبيه
|
||||
_showUploadPhotoDialog(context, controller);
|
||||
},
|
||||
|
||||
// أيقونة بديلة تظهر في الخلفية إذا لم تكن الصورة موجودة
|
||||
backgroundColor: Colors.grey.shade300,
|
||||
child: const Icon(Icons.person,
|
||||
size: 40, color: Colors.white),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -5,
|
||||
right: -5,
|
||||
child: InkWell(
|
||||
onTap: () => controller.choosImagePicture(
|
||||
AppLink.uploadImagePortrate, 'portrait'),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.camera_alt,
|
||||
color: Theme.of(context).primaryColor, size: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
otherAccountsPictures: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
homeCaptainController.rating.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
const Icon(Icons.star, color: Colors.amber, size: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Theme.of(context).primaryColor, Colors.blue.shade700],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
189
siro_driver/lib/views/home/Captin/home_captain/driver_call_page.dart
Executable file
189
siro_driver/lib/views/home/Captin/home_captain/driver_call_page.dart
Executable file
@@ -0,0 +1,189 @@
|
||||
// import 'dart:async';
|
||||
// import 'package:SEFER/constant/box_name.dart';
|
||||
// import 'package:SEFER/main.dart';
|
||||
// import 'package:SEFER/views/widgets/my_scafold.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
// import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
|
||||
// import '../../../../constant/api_key.dart';
|
||||
// import '../../../../controller/functions/crud.dart';
|
||||
|
||||
// String appId = AK.agoraAppId;
|
||||
|
||||
// class DriverCallPage extends StatefulWidget {
|
||||
// const DriverCallPage({super.key});
|
||||
|
||||
// @override
|
||||
// State<DriverCallPage> createState() => _DriverCallPageState();
|
||||
// }
|
||||
|
||||
// class _DriverCallPageState extends State<DriverCallPage> {
|
||||
// String channelName = '';
|
||||
// String token = '';
|
||||
// // "00612994c6e707543e68d5638894d04f989IAAlydoFEC3ZeZkeUwl0dSswZTX8n+xyZR8PBWdwXFV6t6MLiA8AAAAAEACCHD/gn3TUZQEAAQAAAAAA";
|
||||
|
||||
// // int uid = int.parse(box.read(BoxName.phoneDriver)); // uid of the local user
|
||||
// int uid = 0;
|
||||
// int? _remoteUid; // uid of the remote user
|
||||
// bool _isJoined = false; // Indicates if the local user has joined the channel
|
||||
// late RtcEngine agoraEngine; // Agora engine instance
|
||||
|
||||
// final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
||||
// GlobalKey<ScaffoldMessengerState>(); // Global key to access the scaffold
|
||||
|
||||
// showMessage(String message) {
|
||||
// scaffoldMessengerKey.currentState?.showSnackBar(SnackBar(
|
||||
// content: Text(message),
|
||||
// ));
|
||||
// }
|
||||
|
||||
// initAgora() async {
|
||||
// await fetchToken();
|
||||
// await setupVoiceSDKEngine();
|
||||
// }
|
||||
|
||||
// fetchToken() async {
|
||||
// var res = await CRUD()
|
||||
// .getAgoraToken(channelName: channelName, uid: uid.toString());
|
||||
// setState(() {
|
||||
// token = res;
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _remoteUid = box.read(BoxName.phone) != null
|
||||
// ? int.parse(box.read(BoxName.phone))
|
||||
// : int.parse(box.read(BoxName.phoneDriver));
|
||||
// uid = box.read(BoxName.phoneDriver) != null
|
||||
// ? int.parse(box.read(BoxName.phoneDriver))
|
||||
// : int.parse(box.read(BoxName.phone));
|
||||
// // Set up an instance of Agora engine
|
||||
// initAgora();
|
||||
// }
|
||||
|
||||
// Future<void> setupVoiceSDKEngine() async {
|
||||
// // retrieve or request microphone permission
|
||||
// await [Permission.microphone].request();
|
||||
|
||||
// //create an instance of the Agora engine
|
||||
// agoraEngine = createAgoraRtcEngine();
|
||||
// await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
|
||||
// // Register the event handler
|
||||
// agoraEngine.registerEventHandler(
|
||||
// RtcEngineEventHandler(
|
||||
// onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||||
// showMessage(
|
||||
// "Local user uid:${connection.localUid} joined the channel");
|
||||
// setState(() {
|
||||
// _isJoined = true;
|
||||
// });
|
||||
// },
|
||||
// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
|
||||
// showMessage("Remote user uid:$remoteUid joined the channel");
|
||||
// setState(() {
|
||||
// _remoteUid = remoteUid;
|
||||
// });
|
||||
// },
|
||||
// onUserOffline: (RtcConnection connection, int remoteUid,
|
||||
// UserOfflineReasonType reason) {
|
||||
// showMessage("Remote user uid:$remoteUid left the channel");
|
||||
// setState(() {
|
||||
// _remoteUid = null;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// void join() async {
|
||||
// // Set channel options including the client role and channel profile
|
||||
// ChannelMediaOptions options = const ChannelMediaOptions(
|
||||
// clientRoleType: ClientRoleType.clientRoleBroadcaster,
|
||||
// channelProfile: ChannelProfileType.channelProfileCommunication,
|
||||
// );
|
||||
|
||||
// await agoraEngine.joinChannel(
|
||||
// token: token,
|
||||
// channelId: channelName,
|
||||
// options: options,
|
||||
// uid: uid,
|
||||
// );
|
||||
// }
|
||||
// //https://console.agora.io/invite?sign=5e9e22d06f22caeeada9954c9e908572%253A5ba8aed978a35eab5a5113742502ded2a41478b2a81cb19c71a30776e125b58a
|
||||
|
||||
// void leave() {
|
||||
// setState(() {
|
||||
// _isJoined = false;
|
||||
// _remoteUid = null;
|
||||
// });
|
||||
// agoraEngine.leaveChannel();
|
||||
// }
|
||||
|
||||
// // Clean up the resources when you leave
|
||||
// @override
|
||||
// void dispose() async {
|
||||
// await agoraEngine.leaveChannel();
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
// // Build UI
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return MaterialApp(
|
||||
// scaffoldMessengerKey: scaffoldMessengerKey,
|
||||
// home: MyScafolld(
|
||||
// // appBar: AppBar(
|
||||
// // title: const Text('Get started with Voice Calling'),
|
||||
// // ),
|
||||
// title: 'Voice Calling'.tr,
|
||||
// isleading: true,
|
||||
// body: [
|
||||
// ListView(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
// children: [
|
||||
// // Status text
|
||||
// Container(height: 40, child: Center(child: _status())),
|
||||
// // Button Row
|
||||
// Row(
|
||||
// children: <Widget>[
|
||||
// Expanded(
|
||||
// child: ElevatedButton(
|
||||
// child: const Text("Join"),
|
||||
// onPressed: () => {join()},
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 10),
|
||||
// Expanded(
|
||||
// child: ElevatedButton(
|
||||
// child: const Text("Leave"),
|
||||
// onPressed: () => {leave()},
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ]),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _status() {
|
||||
// String statusText;
|
||||
|
||||
// if (!_isJoined)
|
||||
// statusText = 'Join a channel';
|
||||
// else if (_remoteUid == null)
|
||||
// statusText = 'Waiting for a remote user to join...';
|
||||
// else
|
||||
// statusText = 'Connected to remote user, uid:$_remoteUid';
|
||||
|
||||
// return Text(
|
||||
// statusText,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
331
siro_driver/lib/views/home/Captin/home_captain/help_captain.dart
Executable file
331
siro_driver/lib/views/home/Captin/home_captain/help_captain.dart
Executable file
@@ -0,0 +1,331 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/controller/home/captin/help/help_controller.dart';
|
||||
import 'package:siro_driver/views/home/Captin/home_captain/help_details_replay_page.dart';
|
||||
|
||||
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});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(HelpController());
|
||||
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),
|
||||
decoration: BoxDecoration(
|
||||
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: theme.textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
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,
|
||||
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;
|
||||
},
|
||||
),
|
||||
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();
|
||||
}
|
||||
},
|
||||
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});
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// Get.put(HelpController());
|
||||
// return CupertinoPageScaffold(
|
||||
// navigationBar: CupertinoNavigationBar(
|
||||
// middle: Text('Helping Page'.tr),
|
||||
// leading: CupertinoButton(
|
||||
// padding: EdgeInsets.zero,
|
||||
// child: Icon(CupertinoIcons.back),
|
||||
// onPressed: () => Navigator.pop(context),
|
||||
// ),
|
||||
// ),
|
||||
// child: SafeArea(
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// border: Border.all(
|
||||
// color: CupertinoColors.systemGrey2,
|
||||
// ),
|
||||
// ),
|
||||
// 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,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Card(
|
||||
// elevation: 3,
|
||||
// color: CupertinoColors.systemGrey6,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: GetBuilder<HelpController>(
|
||||
// builder: (helpController) => Form(
|
||||
// key: helpController.formKey,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// CupertinoTextField(
|
||||
// controller: helpController.helpQuestionController,
|
||||
// placeholder: 'Enter your Question here'.tr,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// border: Border.all(
|
||||
// color: CupertinoColors.systemGrey,
|
||||
// ),
|
||||
// ),
|
||||
// padding: const EdgeInsets.all(16),
|
||||
// clearButtonMode: OverlayVisibilityMode.editing,
|
||||
// ),
|
||||
// const SizedBox(height: 20),
|
||||
// helpController.isLoading
|
||||
// ? const CupertinoActivityIndicator()
|
||||
// : CupertinoButton.filled(
|
||||
// onPressed: () {
|
||||
// if (helpController.formKey.currentState!
|
||||
// .validate()) {
|
||||
// helpController.addHelpQuestion();
|
||||
|
||||
// // Clear the feedback form
|
||||
// helpController.formKey.currentState!
|
||||
// .reset();
|
||||
// }
|
||||
// },
|
||||
// child: Text('Submit Question'.tr),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 20),
|
||||
// Expanded(
|
||||
// child: GetBuilder<HelpController>(
|
||||
// builder: (helpController) => Padding(
|
||||
// padding: const EdgeInsets.all(10),
|
||||
// child: Container(
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border.all(
|
||||
// color: CupertinoColors.systemGrey2,
|
||||
// ),
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
// child: ListView.builder(
|
||||
// itemCount: helpController.helpQuestionDate['message'] !=
|
||||
// null
|
||||
// ? helpController.helpQuestionDate['message'].length
|
||||
// : 0,
|
||||
// itemBuilder: (BuildContext context, int index) {
|
||||
// var list =
|
||||
// helpController.helpQuestionDate['message'][index];
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.all(3),
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// vertical: 12, horizontal: 16),
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border.all(
|
||||
// color: CupertinoColors.activeGreen,
|
||||
// width: 2,
|
||||
// ),
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// ),
|
||||
// child: GestureDetector(
|
||||
// onTap: () {
|
||||
// helpController.getIndex(
|
||||
// list['id'], list['helpQuestion']);
|
||||
// helpController
|
||||
// .getHelpRepley(list['id'].toString());
|
||||
// Get.to(() => const HelpDetailsReplayPage());
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment:
|
||||
// MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// list['helpQuestion'],
|
||||
// style: CupertinoTheme.of(context)
|
||||
// .textTheme
|
||||
// .textStyle,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// Text(
|
||||
// list['datecreated'],
|
||||
// style: CupertinoTheme.of(context)
|
||||
// .textTheme
|
||||
// .tabLabelTextStyle,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
110
siro_driver/lib/views/home/Captin/home_captain/help_details_replay_page.dart
Executable file
110
siro_driver/lib/views/home/Captin/home_captain/help_details_replay_page.dart
Executable file
@@ -0,0 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/constant/style.dart';
|
||||
import 'package:siro_driver/views/widgets/mycircular.dart';
|
||||
|
||||
import '../../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../../controller/home/captin/help/help_controller.dart';
|
||||
import '../../../widgets/my_scafold.dart';
|
||||
|
||||
class HelpDetailsReplayPage extends StatelessWidget {
|
||||
const HelpDetailsReplayPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.find<HelpController>();
|
||||
return GetBuilder<HelpController>(
|
||||
builder: (helpController) => MyScafolld(
|
||||
title: 'Help Details'.tr,
|
||||
body: [
|
||||
helpController.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: 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,
|
||||
));
|
||||
}
|
||||
}
|
||||
672
siro_driver/lib/views/home/Captin/home_captain/home_captin.dart
Executable file
672
siro_driver/lib/views/home/Captin/home_captain/home_captin.dart
Executable file
@@ -0,0 +1,672 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bubble_head/bubble.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:siro_driver/views/home/Captin/home_captain/drawer_captain.dart';
|
||||
import 'package:siro_driver/views/widgets/mycircular.dart';
|
||||
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../constant/info.dart';
|
||||
import '../../../../controller/functions/location_controller.dart';
|
||||
import '../../../../controller/functions/overlay_permisssion.dart';
|
||||
import '../../../../controller/functions/package_info.dart';
|
||||
import '../../../../controller/home/captin/home_captain_controller.dart';
|
||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../../controller/home/navigation/navigation_view.dart';
|
||||
import '../../../../controller/profile/setting_controller.dart';
|
||||
import '../../../../env/env.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../notification/available_rides_page.dart';
|
||||
import '../driver_map_page.dart';
|
||||
import 'widget/connect.dart';
|
||||
import 'widget/left_menu_map_captain.dart';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Design Tokens (Responsive to Theme)
|
||||
// ─────────────────────────────────────────────
|
||||
class _Token {
|
||||
static Color surface(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF1A1A2E)
|
||||
: Colors.white;
|
||||
|
||||
static Color surfaceCard(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF16213E)
|
||||
: const Color(0xFFF8F9FA);
|
||||
|
||||
static const Color accent = Color(0xFFF0A500);
|
||||
static Color accentSoft(BuildContext context) =>
|
||||
const Color(0xFFF0A500).withOpacity(0.12);
|
||||
static const Color danger = Color(0xFFE53935);
|
||||
static const Color success = Color(0xFF2ECC71);
|
||||
static const Color info = Color(0xFF3498DB);
|
||||
|
||||
static Color border(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF2A2A4A)
|
||||
: Colors.grey.withOpacity(0.2);
|
||||
|
||||
static Color text(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: const Color(0xFF2D3436);
|
||||
|
||||
static Color textDim(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white38
|
||||
: Colors.black45;
|
||||
|
||||
static const double radius = 16.0;
|
||||
static const double radiusSm = 10.0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Root Widget
|
||||
// ─────────────────────────────────────────────
|
||||
class HomeCaptain extends StatelessWidget {
|
||||
HomeCaptain({super.key});
|
||||
|
||||
final LocationController locationController =
|
||||
Get.put(LocationController(), permanent: true);
|
||||
final HomeCaptainController homeCaptainController =
|
||||
Get.put(HomeCaptainController(), permanent: true);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await closeOverlayIfFound();
|
||||
if (!context.mounted) return;
|
||||
await checkForUpdate(context);
|
||||
if (!context.mounted) return;
|
||||
await getPermissionOverlay();
|
||||
if (!context.mounted) return;
|
||||
await showDriverGiftClaim(context);
|
||||
if (!context.mounted) return;
|
||||
await checkForAppliedRide(context);
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor: _Token.surface(context),
|
||||
appBar: const _HomeAppBar(),
|
||||
drawer: AppDrawer(),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
const _MapView(),
|
||||
leftMainMenuCaptainIcons(),
|
||||
const _BottomStatusBar(),
|
||||
const _FloatingControls(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// AppBar — no blur, solid gradient
|
||||
// ─────────────────────────────────────────────
|
||||
class _HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
const _HomeAppBar();
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ctrl = Get.find<HomeCaptainController>();
|
||||
|
||||
return AppBar(
|
||||
backgroundColor: _Token.surface(context),
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||
titleSpacing: 0,
|
||||
// ── Logo + App Name ──────────────────────
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
_LogoBadge(),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
AppInformation.appName.split(' ')[0].tr,
|
||||
style: const TextStyle(
|
||||
color: _Token.accent,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.8,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
// ── Refuse Counter ───────────────────
|
||||
GetBuilder<HomeCaptainController>(
|
||||
builder: (c) => _PillBadge(
|
||||
icon: Icons.block_rounded,
|
||||
label: c.countRefuse.toString(),
|
||||
color: _Token.danger,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
// ── Map Controls Row ─────────────────
|
||||
_AppBarControls(ctrl: ctrl),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LogoBadge extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: _Token.surfaceCard(context),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: _Token.accent, width: 1.5),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset('assets/images/logo.gif', fit: BoxFit.cover),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _PillBadge extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final Color color;
|
||||
const _PillBadge(
|
||||
{required this.icon, required this.label, required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color.withOpacity(0.35)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 13),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: color, fontWeight: FontWeight.bold, fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _AppBarControls extends StatelessWidget {
|
||||
final HomeCaptainController ctrl;
|
||||
const _AppBarControls({required this.ctrl});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _Token.surfaceCard(context),
|
||||
borderRadius: BorderRadius.circular(_Token.radiusSm),
|
||||
border: Border.all(color: _Token.border(context)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Navigation
|
||||
_IconBtn(
|
||||
icon: Icons.map_rounded,
|
||||
color: _Token.success,
|
||||
tooltip: 'Navigation'.tr,
|
||||
onTap: () => Get.to(() => const NavigationView()),
|
||||
),
|
||||
// Heatmap
|
||||
GetBuilder<HomeCaptainController>(
|
||||
builder: (c) => _IconBtn(
|
||||
icon: Icons.local_fire_department_rounded,
|
||||
color:
|
||||
c.isHeatmapVisible ? Colors.orange : Colors.grey.shade600,
|
||||
tooltip: 'Heatmap'.tr,
|
||||
onTap: c.toggleHeatmap,
|
||||
),
|
||||
),
|
||||
// Center on me
|
||||
_IconBtn(
|
||||
icon: Icons.my_location_rounded,
|
||||
color: _Token.accent,
|
||||
tooltip: 'My Location'.tr,
|
||||
onTap: () {
|
||||
ctrl.mapHomeCaptainController?.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(
|
||||
Get.find<LocationController>().myLocation,
|
||||
17.5,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _IconBtn extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final String tooltip;
|
||||
final VoidCallback onTap;
|
||||
const _IconBtn(
|
||||
{required this.icon,
|
||||
required this.color,
|
||||
required this.tooltip,
|
||||
required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Tooltip(
|
||||
message: tooltip,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 6),
|
||||
child: Icon(icon, color: color, size: 20),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Map View
|
||||
// ─────────────────────────────────────────────
|
||||
class _MapView extends StatelessWidget {
|
||||
const _MapView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<HomeCaptainController>(
|
||||
builder: (ctrl) {
|
||||
if (ctrl.isLoading) return const MyCircularProgressIndicator();
|
||||
|
||||
return GetBuilder<SettingController>(
|
||||
builder: (s) => GetBuilder<LocationController>(
|
||||
builder: (loc) => IntaleqMap(
|
||||
apiKey: Env.mapSaasKey,
|
||||
onMapCreated: ctrl.onMapCreated,
|
||||
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: (loc.myLocation.latitude == 0 ||
|
||||
loc.myLocation.latitude.isNaN)
|
||||
? ctrl.myLocation
|
||||
: loc.myLocation,
|
||||
zoom: 15,
|
||||
),
|
||||
mapType:
|
||||
s.isMapDarkMode ? IntaleqMapType.normal : IntaleqMapType.light,
|
||||
polygons: ctrl.heatmapPolygons,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: MarkerId('MyLocation'.tr),
|
||||
position: loc.myLocation,
|
||||
rotation: loc.heading,
|
||||
flat: true,
|
||||
anchor: const Offset(0.5, 0.5),
|
||||
icon: ctrl.carIcon,
|
||||
)
|
||||
},
|
||||
myLocationButtonEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
compassEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Bottom Status Bar — no blur, solid card
|
||||
// ─────────────────────────────────────────────
|
||||
class _BottomStatusBar extends StatelessWidget {
|
||||
const _BottomStatusBar();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showDetailsSheet(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: _Token.surfaceCard(context),
|
||||
borderRadius: BorderRadius.circular(_Token.radius),
|
||||
border: Border.all(color: _Token.border(context)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.35),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Online toggle
|
||||
const ConnectWidget(),
|
||||
const Spacer(),
|
||||
// Ride count
|
||||
GetBuilder<HomeCaptainController>(
|
||||
builder: (c) => _StatChip(
|
||||
icon: Icons.directions_car_rounded,
|
||||
value: c.countRideToday,
|
||||
label: 'Rides'.tr,
|
||||
color: _Token.info,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
// Today earnings
|
||||
GetBuilder<HomeCaptainController>(
|
||||
builder: (c) => _StatChip(
|
||||
icon: Entypo.wallet,
|
||||
value: c.totalMoneyToday.toString(),
|
||||
label: 'Today'.tr,
|
||||
color: _Token.success,
|
||||
),
|
||||
),
|
||||
// Chevron hint
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.keyboard_arrow_up_rounded,
|
||||
color: Colors.grey, size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDetailsSheet(BuildContext context) {
|
||||
final ctrl = Get.find<HomeCaptainController>();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => _DetailsSheet(ctrl),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String value;
|
||||
final String label;
|
||||
final Color color;
|
||||
const _StatChip(
|
||||
{required this.icon,
|
||||
required this.value,
|
||||
required this.label,
|
||||
required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(label,
|
||||
style: const TextStyle(color: Colors.white38, fontSize: 11)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Details Bottom Sheet — replaces AlertDialog
|
||||
// ─────────────────────────────────────────────
|
||||
class _DetailsSheet extends StatelessWidget {
|
||||
final HomeCaptainController ctrl;
|
||||
const _DetailsSheet(this.ctrl);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _Token.surfaceCard(context),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 28),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Drag handle
|
||||
Container(
|
||||
width: 36,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: _Token.border(context),
|
||||
borderRadius: BorderRadius.circular(2)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Your Activity'.tr,
|
||||
style: TextStyle(
|
||||
color: _Token.text(context),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Divider(color: _Token.border(context), height: 1),
|
||||
const SizedBox(height: 16),
|
||||
_SheetRow(
|
||||
icon: Entypo.wallet,
|
||||
color: _Token.success,
|
||||
label: 'Today'.tr,
|
||||
value: ctrl.totalMoneyToday.toString(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_SheetRow(
|
||||
icon: Entypo.wallet,
|
||||
color: _Token.accent,
|
||||
label: AppInformation.appName,
|
||||
value: ctrl.totalMoneyInSEFER.toString(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Divider(color: _Token.border(context), height: 1),
|
||||
const SizedBox(height: 12),
|
||||
_SheetRow(
|
||||
icon: Icons.timer_outlined,
|
||||
color: _Token.success,
|
||||
label: 'Active Duration'.tr,
|
||||
value: ctrl.stringActiveDuration,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_SheetRow(
|
||||
icon: Icons.access_time_rounded,
|
||||
color: _Token.info,
|
||||
label: 'Total Connection'.tr,
|
||||
value: ctrl.totalDurationToday,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Divider(color: _Token.border(context), height: 1),
|
||||
const SizedBox(height: 12),
|
||||
_SheetRow(
|
||||
icon: Icons.star_rounded,
|
||||
color: _Token.accent,
|
||||
label: 'Total Points'.tr,
|
||||
value: ctrl.totalPoints.toString(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: _Token.accentSoft(context),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_Token.radiusSm)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: Text('Close'.tr,
|
||||
style: const TextStyle(
|
||||
color: _Token.accent, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SheetRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final String label;
|
||||
final String value;
|
||||
const _SheetRow(
|
||||
{required this.icon,
|
||||
required this.color,
|
||||
required this.label,
|
||||
required this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Row(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 20),
|
||||
const SizedBox(width: 12),
|
||||
Text(label,
|
||||
style: TextStyle(color: _Token.textDim(context), fontSize: 14)),
|
||||
const Spacer(),
|
||||
Text(value,
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Floating Action Buttons (right side)
|
||||
// ─────────────────────────────────────────────
|
||||
class _FloatingControls extends StatelessWidget {
|
||||
const _FloatingControls({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
bottom: 88,
|
||||
right: 12,
|
||||
child: GetBuilder<HomeCaptainController>(
|
||||
builder: (ctrl) => Column(
|
||||
children: [
|
||||
// Bubble/overlay mode (Android only)
|
||||
if (Platform.isAndroid) ...[
|
||||
_Fab(
|
||||
onTap: () =>
|
||||
Bubble().startBubbleHead(sendAppToBackground: true),
|
||||
tooltip: 'Overlay'.tr,
|
||||
child: Image.asset('assets/images/logo1.png',
|
||||
width: 26, height: 26),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
// Available rides
|
||||
_Fab(
|
||||
onTap: () => Get.to(() => const AvailableRidesPage()),
|
||||
icon: Icons.list_alt_rounded,
|
||||
color: AppColor.primaryColor,
|
||||
tooltip: 'Available Rides'.tr,
|
||||
),
|
||||
// Continue active ride
|
||||
if (box.read(BoxName.rideStatus) == 'Applied' ||
|
||||
box.read(BoxName.rideStatus) == 'Begin') ...[
|
||||
const SizedBox(height: 10),
|
||||
_Fab(
|
||||
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();
|
||||
}
|
||||
},
|
||||
icon: Icons.navigation_rounded,
|
||||
color: _Token.info,
|
||||
tooltip: 'Continue Ride'.tr,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Fab extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final IconData? icon;
|
||||
final Widget? child;
|
||||
final Color? color;
|
||||
final String? tooltip;
|
||||
const _Fab(
|
||||
{required this.onTap, this.icon, this.child, this.color, this.tooltip});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Tooltip(
|
||||
message: tooltip ?? '',
|
||||
child: Material(
|
||||
color: color ?? _Token.surfaceCard(context),
|
||||
shape: const CircleBorder(),
|
||||
elevation: 6,
|
||||
shadowColor: (color ?? Colors.black).withOpacity(0.4),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
customBorder: const CircleBorder(),
|
||||
child: SizedBox(
|
||||
width: 52,
|
||||
height: 52,
|
||||
child: Center(
|
||||
child: child ??
|
||||
Icon(icon,
|
||||
color: color != null ? Colors.white : _Token.accent,
|
||||
size: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper
|
||||
// ─────────────────────────────────────────────
|
||||
Future<void> checkForAppliedRide(BuildContext context) async {
|
||||
checkForPendingOrderFromServer();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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';
|
||||
|
||||
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>();
|
||||
|
||||
return Obx(() {
|
||||
final LatLng currentLocation = LatLng(
|
||||
locationController.myLocation.latitude,
|
||||
locationController.myLocation.longitude);
|
||||
final double currentHeading = locationController.heading;
|
||||
|
||||
return IntaleqMap(
|
||||
apiKey: AK.mapSaasKey,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: currentLocation,
|
||||
zoom: 15,
|
||||
bearing: currentHeading,
|
||||
),
|
||||
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
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
84
siro_driver/lib/views/home/Captin/home_captain/widget/call_page.dart
Executable file
84
siro_driver/lib/views/home/Captin/home_captain/widget/call_page.dart
Executable file
@@ -0,0 +1,84 @@
|
||||
// import 'package:SEFER/constant/colors.dart';
|
||||
// import 'package:SEFER/constant/style.dart';
|
||||
// import 'package:SEFER/controller/firebase/firbase_messge.dart';
|
||||
// import 'package:SEFER/controller/home/captin/map_driver_controller.dart';
|
||||
// import 'package:SEFER/views/widgets/my_scafold.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:SEFER/controller/home/captin/home_captain_controller.dart';
|
||||
|
||||
// import '../../../../../controller/functions/call_controller.dart';
|
||||
|
||||
// class CallPage extends StatelessWidget {
|
||||
// const CallPage({super.key});
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return MyScafolld(
|
||||
// title: 'Call Page'.tr, isleading: true, body: [callPage()]);
|
||||
// }
|
||||
// }
|
||||
|
||||
// GetBuilder<HomeCaptainController> callPage() {
|
||||
// CallController callController = Get.put(CallController());
|
||||
// Get.put(MapDriverController());
|
||||
// // callController.join();
|
||||
// return GetBuilder<HomeCaptainController>(
|
||||
// builder: (controller) => Positioned(
|
||||
// top: Get.height * .2,
|
||||
// child: Container(
|
||||
// height: 100, width: Get.width,
|
||||
// decoration: AppStyle.boxDecoration,
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
// children: [
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// callController.join();
|
||||
// },
|
||||
// child: Container(
|
||||
// width: 50,
|
||||
// height: 50,
|
||||
// decoration: const BoxDecoration(
|
||||
// shape: BoxShape.circle, color: AppColor.greenColor),
|
||||
// child: const Icon(
|
||||
// Icons.phone,
|
||||
// size: 35,
|
||||
// color: AppColor.secondaryColor,
|
||||
// )),
|
||||
// ),
|
||||
// Column(
|
||||
// children: [
|
||||
// Text(callController.status),
|
||||
// Text(Get.find<MapDriverController>().passengerName.toString()),
|
||||
// ],
|
||||
// ),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// FirebaseMessagesController().sendNotificationToPassengerToken(
|
||||
// 'Call End'.tr,
|
||||
// 'Call End',
|
||||
// Get.find<MapDriverController>().tokenPassenger,
|
||||
// [],
|
||||
// 'iphone_ringtone.wav');
|
||||
// callController.leave();
|
||||
// Get.back();
|
||||
// },
|
||||
// child: Container(
|
||||
// width: 50,
|
||||
// height: 50,
|
||||
// decoration: const BoxDecoration(
|
||||
// shape: BoxShape.circle, color: AppColor.redColor),
|
||||
// child: const Icon(
|
||||
// Icons.phone_disabled_sharp,
|
||||
// size: 35,
|
||||
// color: AppColor.secondaryColor,
|
||||
// )),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// // ignore: prefer_const_constructors
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
151
siro_driver/lib/views/home/Captin/home_captain/widget/connect.dart
Executable file
151
siro_driver/lib/views/home/Captin/home_captain/widget/connect.dart
Executable file
@@ -0,0 +1,151 @@
|
||||
import 'package:siro_driver/controller/functions/tts.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/controller/home/payment/captain_wallet_controller.dart';
|
||||
|
||||
import '../../../../../constant/style.dart';
|
||||
import '../../../../widgets/elevated_btn.dart';
|
||||
import '../../../../../controller/home/captin/home_captain_controller.dart';
|
||||
|
||||
class ConnectWidget extends StatelessWidget {
|
||||
const ConnectWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final OrderRequestController orderRequestController =
|
||||
// Get.put(OrderRequestController());
|
||||
CaptainWalletController captainWalletController =
|
||||
Get.put(CaptainWalletController());
|
||||
int refusedRidesToday = 0;
|
||||
captainWalletController.getCaptainWalletFromBuyPoints();
|
||||
return Center(
|
||||
child: GetBuilder<HomeCaptainController>(
|
||||
builder: (homeCaptainController) => double.parse(
|
||||
(captainWalletController.totalPoints)) <
|
||||
-200
|
||||
? CupertinoButton(
|
||||
onPressed: () {
|
||||
Get.defaultDialog(
|
||||
// backgroundColor: CupertinoColors.destructiveRed,
|
||||
barrierDismissible: false,
|
||||
title: double.parse(
|
||||
(captainWalletController.totalPoints)) <
|
||||
-200
|
||||
? 'You dont have Points'.tr
|
||||
: 'You Are Stopped For this Day !'.tr,
|
||||
titleStyle: AppStyle.title,
|
||||
content: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
double.parse((captainWalletController
|
||||
.totalPoints)) <
|
||||
-200
|
||||
? await Get.find<TextToSpeechController>()
|
||||
.speakText(
|
||||
'You must be recharge your Account'
|
||||
.tr)
|
||||
: await Get.find<TextToSpeechController>()
|
||||
.speakText(
|
||||
'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
|
||||
.tr);
|
||||
},
|
||||
icon: const Icon(Icons.headphones),
|
||||
),
|
||||
Text(
|
||||
double.parse((captainWalletController
|
||||
.totalPoints)) <
|
||||
-200
|
||||
? 'You must be recharge your Account'.tr
|
||||
: 'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
|
||||
.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
confirm: double.parse(
|
||||
(captainWalletController.totalPoints)) <
|
||||
-200
|
||||
? MyElevatedButton(
|
||||
title: 'Recharge my Account'.tr,
|
||||
onPressed: () {
|
||||
homeCaptainController.goToWalletFromConnect();
|
||||
})
|
||||
: MyElevatedButton(
|
||||
title: 'Ok , See you Tomorrow'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
}));
|
||||
},
|
||||
color: CupertinoColors.destructiveRed,
|
||||
child: Text(
|
||||
'You are Stopped'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: homeCaptainController.isActive
|
||||
? [const Color(0xFF00C853), const Color(0xFF00E676)]
|
||||
: [Colors.grey.shade600, Colors.grey.shade400],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
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: 20, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
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: 10),
|
||||
Text(
|
||||
homeCaptainController.isActive
|
||||
? 'Online'.tr
|
||||
: 'Offline'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
323
siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart
Executable file
323
siro_driver/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart
Executable file
@@ -0,0 +1,323 @@
|
||||
import 'package:siro_driver/constant/box_name.dart';
|
||||
import 'package:siro_driver/controller/firebase/local_notification.dart';
|
||||
import 'package:siro_driver/main.dart';
|
||||
import 'package:siro_driver/views/home/Captin/driver_map_page.dart';
|
||||
import 'package:siro_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
|
||||
import 'package:siro_driver/views/auth/syria/registration_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/controller/home/captin/home_captain_controller.dart';
|
||||
import 'package:siro_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
import '../../../../../constant/links.dart';
|
||||
import '../../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../../controller/functions/crud.dart';
|
||||
import '../../../../../controller/home/captin/order_request_controller.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() {
|
||||
return GetBuilder<HomeCaptainController>(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
);
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'Ride info'.tr,
|
||||
'you dont have accepted ride'.tr,
|
||||
() => Get.back(),
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
||||
box.write(BoxName.statusDriverLocation, 'off');
|
||||
box.write(BoxName.rideStatus, 'no_ride');
|
||||
Log.print(box.read(BoxName.statusDriverLocation));
|
||||
ctrl.update();
|
||||
},
|
||||
),
|
||||
|
||||
_Divider(context),
|
||||
|
||||
// ── 2. Earnings Chart ────────────────
|
||||
_MenuIcon(
|
||||
icon: FontAwesome5.chart_bar,
|
||||
color: _T.blue,
|
||||
tooltip: 'Earnings'.tr,
|
||||
onTap: () {
|
||||
final now = DateTime.now();
|
||||
final lastTimeRaw = box.read(BoxName.lastTimeStaticThrottle);
|
||||
DateTime? lastTime;
|
||||
|
||||
if (lastTimeRaw != null) {
|
||||
try {
|
||||
lastTime = DateTime.parse(lastTimeRaw.toString());
|
||||
} catch (_) {
|
||||
lastTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTime == null ||
|
||||
now.difference(lastTime).inMinutes >= 2) {
|
||||
box.write(
|
||||
BoxName.lastTimeStaticThrottle, now.toIso8601String());
|
||||
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()),
|
||||
),
|
||||
],
|
||||
|
||||
// _Divider(context),
|
||||
|
||||
// // ── 4. Driver Registration ──────────
|
||||
// _MenuIcon(
|
||||
// icon: Icons.person_add_alt_1_rounded,
|
||||
// color: Colors.purple.shade400,
|
||||
// tooltip: 'Registration'.tr,
|
||||
// onTap: () => Get.to(() => const RegistrationView()),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 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 isProcessing = false;
|
||||
if (isProcessing) return;
|
||||
|
||||
final driverId = box.read(BoxName.driverID)?.toString();
|
||||
if (driverId == null) return;
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
try {
|
||||
var response = await CRUD().post(
|
||||
link: AppLink.getArgumentAfterAppliedFromBackground,
|
||||
payload: {'driver_id': driverId},
|
||||
);
|
||||
Log.print('response: $response');
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
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, rideArgs);
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
box.write(BoxName.rideStatus, 'Apply');
|
||||
Get.put(OrderRequestController()).changeApplied();
|
||||
|
||||
Get.to(
|
||||
() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArgumentsFromBackground),
|
||||
);
|
||||
} else {
|
||||
box.write(BoxName.rideArgumentsFromBackground, 'failure');
|
||||
}
|
||||
} catch (_) {
|
||||
// silent
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _transformServerDataToAppArguments(
|
||||
Map<String, dynamic> d) {
|
||||
String s(String key, [String def = 'unknown']) => d[key]?.toString() ?? def;
|
||||
|
||||
return {
|
||||
'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, dynamic rideId) {
|
||||
if (customerToken == null || customerToken.isEmpty) return;
|
||||
|
||||
List<String> body = [
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
box.read(BoxName.tokenDriver).toString(),
|
||||
rideId.toString(),
|
||||
];
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: customerToken,
|
||||
title: 'Accepted Ride'.tr,
|
||||
body: 'your ride is Accepted'.tr,
|
||||
isTopic: false,
|
||||
tone: 'start',
|
||||
driverList: body,
|
||||
category: 'Accepted Ride',
|
||||
);
|
||||
}
|
||||
67
siro_driver/lib/views/home/Captin/home_captain/widget/zones_controller.dart
Executable file
67
siro_driver/lib/views/home/Captin/home_captain/widget/zones_controller.dart
Executable file
@@ -0,0 +1,67 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
|
||||
class ZonesController extends GetxController {
|
||||
Map<String, List<LatLng>> generateZoneMap(
|
||||
LatLng southwest, LatLng southEast, LatLng northeast) {
|
||||
const double desiredZoneArea = 4; // in square kilometers
|
||||
|
||||
final double width = (southEast.longitude - southwest.longitude) * 100;
|
||||
final double height = (northeast.latitude - southEast.latitude) * 100;
|
||||
final double totalArea = width * height;
|
||||
|
||||
// final int numZones = (totalArea / desiredZoneArea).ceil();
|
||||
|
||||
final double zoneWidth = width / sqrt(desiredZoneArea);
|
||||
final double zoneHeight = height / sqrt(desiredZoneArea);
|
||||
final numRows =
|
||||
((northeast.latitude - southwest.latitude) / zoneHeight).ceil();
|
||||
final numCols =
|
||||
((southEast.longitude - southwest.longitude) / zoneWidth).ceil();
|
||||
List<String> zoneNames = [];
|
||||
List<LatLng> zoneCoordinates = [];
|
||||
|
||||
for (int row = 0; row < numRows; row++) {
|
||||
for (int col = 0; col < numCols; col++) {
|
||||
final double zoneSouthwestLat =
|
||||
southwest.latitude + (row * zoneHeight / 100);
|
||||
final double zoneSouthwestLng =
|
||||
southwest.longitude + (col * zoneWidth / 100);
|
||||
final double zoneNortheastLat = zoneSouthwestLat + zoneHeight / 100;
|
||||
final double zoneNortheastLng = zoneSouthwestLng + zoneWidth / 100;
|
||||
|
||||
LatLng zoneSouthwest = LatLng(zoneSouthwestLat, zoneSouthwestLng);
|
||||
LatLng zoneNortheast = LatLng(zoneNortheastLat, zoneNortheastLng);
|
||||
|
||||
String zoneName =
|
||||
'Zone${row + col}'; // Assign a unique name to each zone
|
||||
|
||||
zoneNames.add(zoneName);
|
||||
zoneCoordinates.add(zoneSouthwest);
|
||||
zoneCoordinates.add(zoneNortheast);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<LatLng>> zoneMap = {};
|
||||
for (int i = 0; i < zoneNames.length; i++) {
|
||||
zoneMap[zoneNames[i]] = [
|
||||
zoneCoordinates[i], // Southwest LatLng
|
||||
zoneCoordinates[i + 1], // Northeast LatLng
|
||||
];
|
||||
}
|
||||
|
||||
return zoneMap;
|
||||
}
|
||||
|
||||
void getJsonOfZones() {
|
||||
LatLng southwest = const LatLng(32.111107, 36.062222);
|
||||
LatLng southEast = const LatLng(32.108333, 36.101667);
|
||||
LatLng northeast = const LatLng(32.143889, 36.058889);
|
||||
Map<String, List<LatLng>> zoneMap =
|
||||
generateZoneMap(southwest, southEast, northeast);
|
||||
String jsonMap = json.encode(zoneMap);
|
||||
}
|
||||
}
|
||||
273
siro_driver/lib/views/home/Captin/maintain_center_page.dart
Executable file
273
siro_driver/lib/views/home/Captin/maintain_center_page.dart
Executable file
@@ -0,0 +1,273 @@
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
import 'package:siro_driver/constant/style.dart';
|
||||
import 'package:siro_driver/controller/home/captin/help/maintain_center_controller.dart';
|
||||
import 'package:siro_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
// class MaintainCenterPage extends StatelessWidget {
|
||||
// MaintainCenterPage({super.key});
|
||||
// MaintainCenterController maintainCenterController =
|
||||
// Get.put(MaintainCenterController());
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return MyScafolld(
|
||||
// title: "Maintenance Center".tr,
|
||||
// body: [
|
||||
// GetBuilder<MaintainCenterController>(
|
||||
// builder: (maintainCenterController) {
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Text(
|
||||
// "When you complete 600 trips, you will be eligible to receive offers for maintenance of your car."
|
||||
// .tr),
|
||||
// const SizedBox(
|
||||
// height: 10,
|
||||
// ),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
// children: [
|
||||
// MyElevatedButton(
|
||||
// title: "Show My Trip Count".tr,
|
||||
// onPressed: () async {
|
||||
// maintainCenterController.getTripCountByCaptain();
|
||||
// }),
|
||||
// _buildPriceAvatar(
|
||||
// maintainCenterController.tripCount['count'] == null
|
||||
// ? '0'
|
||||
// : maintainCenterController.tripCount['count']
|
||||
// .toString())
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 10,
|
||||
// ),
|
||||
// Container(
|
||||
// decoration: AppStyle.boxDecoration,
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(14),
|
||||
// child: Text(
|
||||
// "We have maintenance offers for your car. You can use them after completing 600 trips to get a 20% discount on car repairs. Enjoy using our Tripz app and be part of our Tripz family."
|
||||
// .tr,
|
||||
// style: AppStyle.title,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 10,
|
||||
// ),
|
||||
// MyElevatedButton(
|
||||
// title: 'Show maintenance center near my location'.tr,
|
||||
// onPressed: () {
|
||||
// if (maintainCenterController.tripCount['count'] > 600) {
|
||||
// } else {
|
||||
// Get.snackbar("You should complete 600 trips".tr, '',
|
||||
// backgroundColor: AppColor.yellowColor);
|
||||
// }
|
||||
// })
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// })
|
||||
// ],
|
||||
// isleading: true);
|
||||
// }
|
||||
|
||||
// Widget _buildPriceAvatar(String count) {
|
||||
// return Container(
|
||||
// width: 80,
|
||||
// height: 80,
|
||||
// decoration: BoxDecoration(
|
||||
// shape: BoxShape.circle,
|
||||
// gradient: const RadialGradient(
|
||||
// colors: [Color(0xFF4CAF50), Color(0xFF2E7D32)],
|
||||
// center: Alignment.center,
|
||||
// radius: 0.8,
|
||||
// ),
|
||||
// boxShadow: [
|
||||
// BoxShadow(
|
||||
// color: Colors.black.withOpacity(0.2),
|
||||
// blurRadius: 8,
|
||||
// offset: const Offset(0, 4),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// child: Center(
|
||||
// child: Text(
|
||||
// count,
|
||||
// style: const TextStyle(
|
||||
// fontSize: 22,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class MaintainCenterPage extends StatelessWidget {
|
||||
MaintainCenterPage({super.key});
|
||||
final MaintainCenterController maintainCenterController =
|
||||
Get.put(MaintainCenterController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScafolld(
|
||||
title: "Maintenance Center".tr,
|
||||
body: [
|
||||
GetBuilder<MaintainCenterController>(
|
||||
builder: (controller) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Introduction Text with better styling
|
||||
Text(
|
||||
"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: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.8),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Trip Count Section in a Card
|
||||
Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyElevatedButton(
|
||||
title: "Show My Trip Count".tr,
|
||||
onPressed: () async {
|
||||
controller.getTripCountByCaptain();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildPriceAvatar(
|
||||
controller.tripCount['count'] == null
|
||||
? '0'
|
||||
: controller.tripCount['count'].toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Maintenance Offer Information in a Card
|
||||
Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Maintenance Offer".tr,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"We have maintenance offers for your car. You can use them after completing 600 trips to get a 20% discount on car repairs. Enjoy using our Tripz app and be part of our Tripz family."
|
||||
.tr,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.9),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Show Maintenance Center Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: MyElevatedButton(
|
||||
title: 'Show maintenance center near my location'.tr,
|
||||
onPressed: () {
|
||||
if (controller.tripCount['count'] != null &&
|
||||
controller.tripCount['count'] >= 600) {
|
||||
// Implement navigation or action to show maintenance centers
|
||||
// For now, let's print a message
|
||||
print("Navigating to maintenance centers...");
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Ineligible for Offer".tr,
|
||||
"You should complete 500 trips to unlock this feature."
|
||||
.tr,
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
colorText: Colors.black,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
isleading: true,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPriceAvatar(String count) {
|
||||
return Container(
|
||||
width: 70, // Slightly reduced size
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
// Changed to LinearGradient
|
||||
colors: [Color(0xFF4CAF50), Color(0xFF2E7D32)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15), // Reduced opacity
|
||||
blurRadius: 6, // Slightly reduced blur
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
count,
|
||||
style: const TextStyle(
|
||||
fontSize: 20, // Slightly reduced size
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
218
siro_driver/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart
Executable file
218
siro_driver/lib/views/home/Captin/mapDriverWidgets/driver_end_ride_bar.dart
Executable file
@@ -0,0 +1,218 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slide_to_act/slide_to_act.dart';
|
||||
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
|
||||
Widget driverEndRideBar() {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// 🔥 فحص هل السعر ثابت للعرض
|
||||
final String carType = controller.carType;
|
||||
final bool isFixed = (carType == 'Speed' ||
|
||||
carType == 'Awfar' ||
|
||||
carType == 'Fixed Price');
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeOutBack,
|
||||
transform: Matrix4.translationValues(
|
||||
0,
|
||||
controller.isRideStarted ? 0 : -300,
|
||||
0,
|
||||
), // Matrix4.translationValues مستخدمة للإزاحة [web:28]
|
||||
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// لوحة العدادات
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8F9FA),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// 1) المسافة (تتغير دائماً)
|
||||
_buildLiveMetric(
|
||||
icon: Icons.alt_route_rounded,
|
||||
iconColor: Colors.blueAccent,
|
||||
value: controller.currentRideDistanceKm.toStringAsFixed(2),
|
||||
unit: 'KM'.tr,
|
||||
label: 'Traveled'.tr,
|
||||
),
|
||||
|
||||
_buildVerticalDivider(),
|
||||
|
||||
// 2) السعر (ثابت أو متغير)
|
||||
_buildLiveMetric(
|
||||
icon: isFixed
|
||||
? Icons.lock_outline_rounded
|
||||
: Icons.attach_money_rounded,
|
||||
iconColor: isFixed ? Colors.grey : AppColor.primaryColor,
|
||||
value: NumberFormat('#,##0').format(
|
||||
double.tryParse(controller.price.toString()) ?? 0,
|
||||
),
|
||||
unit: 'SYP'.tr,
|
||||
label: isFixed ? 'Fixed Price'.tr : 'Meter Fare'.tr,
|
||||
isHighlight: true,
|
||||
isFixedStyle: isFixed,
|
||||
),
|
||||
|
||||
_buildVerticalDivider(),
|
||||
|
||||
// 3) الوقت (تصغير الخط + إخفاء الساعات إذا 0)
|
||||
_buildLiveMetric(
|
||||
icon: Icons.timer_outlined,
|
||||
iconColor: Colors.orange,
|
||||
value: _formatDurationFromStart(controller),
|
||||
unit: '',
|
||||
label: 'Duration'.tr,
|
||||
valueFontSize: 14, // ✅ تصغير خط “المدة”
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// زر الإنهاء
|
||||
SlideAction(
|
||||
key: ValueKey(controller.isRideFinished),
|
||||
height: 60,
|
||||
borderRadius: 18,
|
||||
elevation: 0,
|
||||
text: 'Swipe to End Trip'.tr,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
outerColor: AppColor.redColor,
|
||||
innerColor: Colors.white,
|
||||
sliderButtonIcon: const Icon(
|
||||
Icons.stop_circle_outlined,
|
||||
color: AppColor.redColor,
|
||||
size: 32,
|
||||
),
|
||||
onSubmit: () async {
|
||||
await controller.finishRideFromDriver(isFromSlider: true);
|
||||
return null;
|
||||
},
|
||||
), // SlideAction مثال الاستخدام موجود في صفحة الحزمة [web:19]
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
); // GetBuilder يعيد البناء عند update() من الكنترولر [web:21]
|
||||
}
|
||||
|
||||
/// دالة تنسيق المدة:
|
||||
/// - أقل من ساعة: mm:ss
|
||||
/// - ساعة فأكثر: h:mm:ss (خانة واحدة للساعات بدون leading zero)
|
||||
String _formatDurationFromStart(MapDriverController controller) {
|
||||
if (controller.rideStartTime == null) return "00:00";
|
||||
|
||||
final d = DateTime.now().difference(controller.rideStartTime!);
|
||||
|
||||
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
||||
|
||||
final hours = d.inHours;
|
||||
final minutes = d.inMinutes.remainder(60);
|
||||
final seconds = d.inSeconds.remainder(60);
|
||||
|
||||
if (hours == 0) {
|
||||
// mm:ss
|
||||
final totalMinutes = d.inMinutes;
|
||||
return "${twoDigits(totalMinutes)}:${twoDigits(seconds)}";
|
||||
}
|
||||
|
||||
// h:mm:ss
|
||||
return "$hours:${twoDigits(minutes)}:${twoDigits(seconds)}";
|
||||
} // Duration وتفكيكه (inHours/inMinutes/inSeconds) من أساسيات Dart [web:11]
|
||||
|
||||
Widget _buildLiveMetric({
|
||||
required IconData icon,
|
||||
required Color iconColor,
|
||||
required String value,
|
||||
required String unit,
|
||||
required String label,
|
||||
bool isHighlight = false,
|
||||
bool isFixedStyle = false,
|
||||
double? valueFontSize, // ✅ جديد: حجم خط القيمة فقط
|
||||
}) {
|
||||
final effectiveFontSize = valueFontSize ?? (isHighlight ? 20 : 18);
|
||||
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 14, color: iconColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: effectiveFontSize, // ✅ هنا صار التحكم
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isFixedStyle
|
||||
? Colors.grey[800]
|
||||
: (isHighlight ? AppColor.primaryColor : Colors.black87),
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
if (unit.isNotEmpty) ...[
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
unit,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVerticalDivider() {
|
||||
return Container(height: 35, width: 1, color: Colors.grey.shade300);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_driver/constant/api_key.dart';
|
||||
|
||||
import '../../../../controller/functions/location_controller.dart';
|
||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../../controller/profile/setting_controller.dart';
|
||||
|
||||
class GoogleDriverMap extends StatelessWidget {
|
||||
const GoogleDriverMap({
|
||||
super.key,
|
||||
required this.locationController,
|
||||
});
|
||||
|
||||
final LocationController locationController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MapDriverController controller = Get.put(MapDriverController());
|
||||
|
||||
// New: تحديد قيمة الـ padding لتحريك مركز الخريطة للأعلى
|
||||
final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3;
|
||||
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) => Listener(
|
||||
onPointerDown: (_) => controller.onUserMapInteraction(),
|
||||
child: IntaleqMap(
|
||||
apiKey: AK.mapAPIKEY,
|
||||
onMapCreated: (mapController) {
|
||||
controller.onMapCreated(mapController);
|
||||
},
|
||||
mapType: Get.isRegistered<SettingController>()
|
||||
? (Get.find<SettingController>().isMapDarkMode
|
||||
? IntaleqMapType.normal
|
||||
: IntaleqMapType.light)
|
||||
: IntaleqMapType.light,
|
||||
zoomControlsEnabled: false,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: controller.smoothedLocation ?? locationController.myLocation,
|
||||
zoom: 17,
|
||||
bearing: controller.smoothedHeading,
|
||||
tilt: 60,
|
||||
),
|
||||
// padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
|
||||
// minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
|
||||
myLocationEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
compassEnabled: false,
|
||||
polylines: controller.polyLines.toSet(),
|
||||
markers: {
|
||||
// 🔥 Car icon — always visible, moves with GPS location on map.
|
||||
// MarkerId matches exactly with updateMarker() in controller.
|
||||
Marker(
|
||||
markerId: const MarkerId('MyLocation'),
|
||||
position: controller.smoothedLocation ?? controller.myLocation,
|
||||
rotation: controller.smoothedHeading,
|
||||
flat: true,
|
||||
anchor: const Offset(0.5, 0.5),
|
||||
icon: controller.carIcon,
|
||||
zIndex: 100,
|
||||
),
|
||||
if (!controller.isRideStarted &&
|
||||
controller.latLngPassengerLocation.latitude != 0)
|
||||
Marker(
|
||||
markerId: const MarkerId('start'),
|
||||
position: controller.latLngPassengerLocation,
|
||||
icon: controller.startIcon,
|
||||
),
|
||||
if (controller.latLngPassengerDestination.latitude != 0 ||
|
||||
controller.latLngPassengerDestination.longitude != 0)
|
||||
Marker(
|
||||
markerId: const MarkerId('end'),
|
||||
position: controller.latLngPassengerDestination,
|
||||
icon: controller.endIcon,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
62
siro_driver/lib/views/home/Captin/mapDriverWidgets/google_map_app.dart
Executable file
62
siro_driver/lib/views/home/Captin/mapDriverWidgets/google_map_app.dart
Executable file
@@ -0,0 +1,62 @@
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
import 'package:siro_driver/constant/style.dart';
|
||||
import 'package:siro_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class GoogleMapApp extends StatelessWidget {
|
||||
const GoogleMapApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (mapDriverController) {
|
||||
if (!mapDriverController.isRideStarted) return const SizedBox();
|
||||
|
||||
// REMOVED: Positioned wrapper
|
||||
return Material(
|
||||
elevation: 8,
|
||||
shadowColor: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Colors.white,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onTap: () async {
|
||||
var endLat =
|
||||
mapDriverController.latLngPassengerDestination.latitude;
|
||||
var endLng =
|
||||
mapDriverController.latLngPassengerDestination.longitude;
|
||||
String url = 'google.navigation:q=$endLat,$endLng';
|
||||
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
} else {
|
||||
String webUrl =
|
||||
'https://www.google.com/maps/dir/?api=1&destination=$endLat,$endLng';
|
||||
if (await canLaunchUrl(Uri.parse(webUrl))) {
|
||||
await launchUrl(Uri.parse(webUrl));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueAccent,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: AppColor.blueColor.withOpacity(0.2), width: 1),
|
||||
),
|
||||
child: Icon(
|
||||
MaterialCommunityIcons.google_maps,
|
||||
size: 28,
|
||||
color: AppColor.secondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
617
siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart
Executable file
617
siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart
Executable file
@@ -0,0 +1,617 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
import 'package:siro_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import 'package:siro_driver/views/widgets/mydialoug.dart';
|
||||
import '../../../../controller/voice_call_controller.dart';
|
||||
import '../../../../controller/functions/launch.dart';
|
||||
import '../../../../controller/functions/location_controller.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../widgets/error_snakbar.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import '../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../controller/functions/crud.dart';
|
||||
import '../../../../constant/links.dart';
|
||||
import '../../../widgets/my_textField.dart';
|
||||
|
||||
class PassengerInfoWindow extends StatelessWidget {
|
||||
const PassengerInfoWindow({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 1. حساب الهوامش الآمنة (SafeArea) من الأسفل
|
||||
final double safeBottomPadding = MediaQuery.of(context).padding.bottom;
|
||||
|
||||
return GetBuilder<MapDriverController>(
|
||||
// id: 'PassengerInfo',
|
||||
builder: (controller) {
|
||||
// --- 1. تجهيز بيانات العرض ---
|
||||
String displayName = controller.passengerName ?? "Unknown";
|
||||
String avatarText = "";
|
||||
|
||||
// التحقق من اللغة (عربي/إنجليزي) للاسم المختصر
|
||||
bool isArabic = RegExp(r'[\u0600-\u06FF]').hasMatch(displayName);
|
||||
|
||||
if (displayName.isNotEmpty) {
|
||||
if (isArabic) {
|
||||
avatarText = displayName.split(' ').first;
|
||||
if (avatarText.length > 4) {
|
||||
avatarText = avatarText.substring(0, 4);
|
||||
}
|
||||
} else {
|
||||
avatarText = displayName[0].toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. المنطق الذكي للموقع (Smart Positioning) ---
|
||||
// نرفع النافذة إذا ظهر شريط التعليمات في الأسفل لتجنب التغطية
|
||||
bool hasInstructions = controller.currentInstruction.isNotEmpty;
|
||||
double instructionsHeight = hasInstructions ? 110.0 : 0.0;
|
||||
|
||||
// الموقع النهائي: إذا كانت مفعلة تظهر، وإلا تختفي للأسفل
|
||||
double finalBottomPosition = controller.isPassengerInfoWindow
|
||||
? (safeBottomPadding + 10 + instructionsHeight)
|
||||
: -450.0;
|
||||
|
||||
return AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
bottom: finalBottomPosition,
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 25,
|
||||
offset: const Offset(0, 8),
|
||||
spreadRadius: 2,
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// --- مقبض السحب (Visual Handle) ---
|
||||
Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 8, bottom: 4),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
||||
child: Column(
|
||||
children: [
|
||||
// --- الصف العلوي: معلومات الراكب ---
|
||||
Row(
|
||||
children: [
|
||||
// الصورة الرمزية
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.primaryColor.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color:
|
||||
AppColor.primaryColor.withOpacity(0.2)),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
avatarText,
|
||||
style: TextStyle(
|
||||
color: AppColor.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: isArabic ? 14 : 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// النصوص (الاسم والمسافة)
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
displayName,
|
||||
style: AppStyle.title.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 16),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.location_on,
|
||||
size: 14, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
// 🔥 [Fix Overflow] Flexible لمنع الـ overflow + تحويل المسافة
|
||||
// السيرفر يُرجع المسافة بالأمتار (5864.022)
|
||||
Flexible(
|
||||
child: Text(
|
||||
_formatDistanceDisplay(
|
||||
controller.distance),
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(Icons.access_time_filled,
|
||||
size: 14, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
controller.hours > 0
|
||||
? '${controller.hours}h ${controller.minutes}m'
|
||||
: '${controller.minutes} min',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// أزرار جانبية (سرعة + اتصال)
|
||||
Row(
|
||||
children: [
|
||||
_buildSpeedCircle(),
|
||||
const SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
controller.isSocialPressed = true;
|
||||
|
||||
// نفحص النتيجة: هل مسموح له يتصل؟
|
||||
bool canCall =
|
||||
await controller.driverCallPassenger();
|
||||
|
||||
if (canCall) {
|
||||
_showCallSelectionDialog(
|
||||
context, controller);
|
||||
} else {
|
||||
// هنا ممكن تظهر رسالة: تم منع الاتصال بسبب كثرة الإلغاءات
|
||||
mySnackeBarError(
|
||||
"You cannot call the passenger due to policy violations"
|
||||
.tr);
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.green.withOpacity(0.2)),
|
||||
),
|
||||
child: const Icon(Icons.phone,
|
||||
color: Colors.green, size: 22),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
_showMessageOptions(context, controller),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
shape: BoxShape.circle,
|
||||
border:
|
||||
Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Icon(
|
||||
MaterialCommunityIcons
|
||||
.message_text_outline,
|
||||
color: AppColor.primaryColor,
|
||||
size: 22),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// خط فاصل
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Divider(height: 1, color: Colors.grey.shade100),
|
||||
),
|
||||
|
||||
// --- مؤشر الانتظار (يظهر عند الوصول) ---
|
||||
if (controller.remainingTimeInPassengerLocatioWait <
|
||||
300 &&
|
||||
controller.remainingTimeInPassengerLocatioWait != 0 &&
|
||||
!controller.isRideBegin) ...[
|
||||
_buildWaitingIndicator(controller),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// --- الأزرار الرئيسية (وصلت / ابدأ) ---
|
||||
if (!controller.isRideBegin)
|
||||
_buildActionButtons(controller),
|
||||
|
||||
// --- زر الإلغاء المدفوع (بعد انتهاء وقت الانتظار) ---
|
||||
if (controller.isdriverWaitTimeEnd &&
|
||||
!controller.isRideBegin)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFFF0F0),
|
||||
foregroundColor: Colors.red,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFFFFCDCD)),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
MyDialog().getDialog(
|
||||
'Confirm Cancellation'.tr,
|
||||
'Are you sure you want to cancel and collect the fee?'
|
||||
.tr, () async {
|
||||
// كود الإلغاء
|
||||
Get.back();
|
||||
controller
|
||||
.addWaitingTimeCostFromPassengerToDriverWallet();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.money_off, size: 20),
|
||||
label: Text('Cancel & Collect Fee'.tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Widgets مساعدة ---
|
||||
|
||||
Widget _buildSpeedCircle() {
|
||||
return GetBuilder<LocationController>(builder: (locController) {
|
||||
int speedKmh = (locController.speed * 3.6).round();
|
||||
Color color = speedKmh > 100 ? Colors.red : const Color(0xFF0D47A1);
|
||||
|
||||
return Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: color.withOpacity(0.3), width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('$speedKmh',
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 13,
|
||||
height: 1)),
|
||||
Text('km/h',
|
||||
style: TextStyle(
|
||||
color: color.withOpacity(0.7), fontSize: 8, height: 1)),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildWaitingIndicator(MapDriverController controller) {
|
||||
bool isUrgent = controller.remainingTimeInPassengerLocatioWait < 60;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.timer_outlined,
|
||||
size: 16, color: isUrgent ? Colors.red : Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
controller.progressInPassengerLocationFromDriver.toDouble(),
|
||||
backgroundColor: Colors.grey[200],
|
||||
color: isUrgent ? Colors.red : Colors.green,
|
||||
minHeight: 6,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
controller.stringRemainingTimeWaitingPassenger,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isUrgent ? Colors.red : Colors.green,
|
||||
fontFamily: 'monospace'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons(MapDriverController controller) {
|
||||
if (controller.isArrivedSend) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFF1C40F),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () async {
|
||||
await controller.markDriverAsArrived();
|
||||
},
|
||||
icon: const Icon(Icons.near_me_rounded),
|
||||
label: Text('I Have Arrived'.tr,
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF27AE60),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
// 🔥 [Fix Start-Ride] استخدام Get.defaultDialog بدلاً من MyDialog
|
||||
// لأن MyDialog يستخدم Navigator.of(context, rootNavigator: true).pop()
|
||||
// الذي يتعارض مع Get.dialog() المستخدم في startRideFromDriver()
|
||||
// وقد يُسبب Get.back() اللاحق إغلاق صفحة الماب بدلاً من الـ loading dialog
|
||||
Get.defaultDialog(
|
||||
title: "Start Trip?".tr,
|
||||
titleStyle: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
middleText: "Ensure the passenger is in the car.".tr,
|
||||
barrierDismissible: true,
|
||||
radius: 14,
|
||||
confirm: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF27AE60),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () async {
|
||||
// نُغلق الديالوج بـ Get.back() لضمان أن GetX يعرف أنه أُغلق
|
||||
Get.back();
|
||||
// ثم نُنفذ startRideFromDriver الذي يستخدم Get.dialog و Get.back بأمان
|
||||
await controller.startRideFromDriver();
|
||||
},
|
||||
child: Text('Start'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
cancel: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text('Cancel'.tr,
|
||||
style: const TextStyle(color: Colors.grey)),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.play_circle_fill_rounded),
|
||||
label: Text('Start Ride'.tr,
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showCallSelectionDialog(
|
||||
BuildContext context, MapDriverController controller) {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Call Options'.tr,
|
||||
style: AppStyle.title
|
||||
.copyWith(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Choose how you want to call the passenger'.tr,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Colors.green.withOpacity(0.1),
|
||||
child: const Icon(Icons.phone_android_rounded,
|
||||
color: Colors.green),
|
||||
),
|
||||
title: Text('Standard Call'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Text('Uses cellular network'.tr,
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
makePhoneCall(controller.passengerPhone.toString());
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
|
||||
child: Icon(Icons.wifi_calling_3_rounded,
|
||||
color: AppColor.primaryColor),
|
||||
),
|
||||
title: Text('Free Call'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Text('Voice call over internet'.tr,
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
final voiceCtrl = Get.find<VoiceCallController>();
|
||||
final driverId = box.read(BoxName.driverID).toString();
|
||||
voiceCtrl.startCall(
|
||||
rideIdVal: controller.rideId,
|
||||
driverId: driverId,
|
||||
passengerId: controller.passengerId,
|
||||
remoteNameVal: controller.passengerName ?? "Passenger",
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMessageOptions(
|
||||
BuildContext context, MapDriverController controller) {
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(25)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Quick Messages'.tr,
|
||||
style:
|
||||
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 15),
|
||||
_buildQuickMessageItem("Where are you, sir?".tr, controller),
|
||||
_buildQuickMessageItem("I've arrived.".tr, controller),
|
||||
const Divider(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller.messageToPassenger,
|
||||
decoration:
|
||||
InputDecoration(hintText: 'Type a message...'.tr),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.send),
|
||||
onPressed: () {
|
||||
_sendMessage(controller, controller.messageToPassenger.text,
|
||||
'cancel');
|
||||
controller.messageToPassenger.clear();
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickMessageItem(String text, MapDriverController controller) {
|
||||
return ListTile(
|
||||
title: Text(text),
|
||||
onTap: () {
|
||||
_sendMessage(controller, text, 'ding');
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _sendMessage(
|
||||
MapDriverController controller, String body, String tone) async {
|
||||
try {
|
||||
await CRUD().post(
|
||||
link: AppLink.sendChatMessage,
|
||||
payload: {
|
||||
'ride_id': controller.rideId.toString(),
|
||||
'sender_id': box.read(BoxName.driverID).toString(),
|
||||
'receiver_id': controller.passengerId.toString(),
|
||||
'sender_type': 'driver',
|
||||
'message_content': body,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// Ignore or log error
|
||||
}
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Driver Message'.tr,
|
||||
body: body,
|
||||
isTopic: false,
|
||||
tone: tone,
|
||||
driverList: [],
|
||||
category: 'message From Driver',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// تحويل المسافة من الأمتار إلى عرض مقروء
|
||||
/// السيرفر يُرجع المسافة بالأمتار (مثال: 5864.022)
|
||||
/// النتيجة: "5.9 km" أو "250 م"
|
||||
String _formatDistanceDisplay(String rawDistance) {
|
||||
final meters = double.tryParse(rawDistance) ?? 0.0;
|
||||
if (meters >= 1000) {
|
||||
return '${(meters / 1000).toStringAsFixed(1)} km';
|
||||
} else if (meters > 0) {
|
||||
return '${meters.toStringAsFixed(0)} م';
|
||||
}
|
||||
return rawDistance; // fallback للقيمة الأصلية
|
||||
}
|
||||
131
siro_driver/lib/views/home/Captin/mapDriverWidgets/sos_connect.dart
Executable file
131
siro_driver/lib/views/home/Captin/mapDriverWidgets/sos_connect.dart
Executable file
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:siro_driver/views/widgets/my_textField.dart';
|
||||
import 'package:siro_driver/views/widgets/elevated_btn.dart'; // Checked import
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import '../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../controller/functions/launch.dart';
|
||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../../main.dart';
|
||||
|
||||
class SosConnect extends StatelessWidget {
|
||||
SosConnect({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(
|
||||
id: 'SosConnect', // Keep ID for updates
|
||||
builder: (controller) {
|
||||
bool showSos = controller.isRideStarted;
|
||||
|
||||
if (!showSos) return const SizedBox();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// === SOS Button ===
|
||||
_buildModernActionButton(
|
||||
icon: MaterialIcons.warning,
|
||||
color: Colors.white,
|
||||
bgColor: AppColor.redColor,
|
||||
tooltip: 'EMERGENCY SOS',
|
||||
isPulsing: true,
|
||||
onTap: () => _handleSosCall(controller),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModernActionButton({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required Color bgColor,
|
||||
required String tooltip,
|
||||
required VoidCallback onTap,
|
||||
bool isPulsing = false,
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: isPulsing
|
||||
? [
|
||||
BoxShadow(
|
||||
color: bgColor.withOpacity(0.4),
|
||||
blurRadius: 12,
|
||||
spreadRadius: 2,
|
||||
)
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: Icon(icon, color: color, size: 24),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- Logic Functions ---
|
||||
void _handleSosCall(MapDriverController mapDriverController) {
|
||||
if (box.read(BoxName.sosPhoneDriver) == null) {
|
||||
Get.defaultDialog(
|
||||
title: 'Emergency Contact'.tr,
|
||||
content: Column(
|
||||
children: [
|
||||
Text('Please enter the emergency number.'.tr),
|
||||
Form(
|
||||
key: mapDriverController.formKey1,
|
||||
child: MyTextForm(
|
||||
controller: mapDriverController.sosEmergincyNumberCotroller,
|
||||
label: 'Phone Number'.tr,
|
||||
hint: '0923456789',
|
||||
type: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Save & Call'.tr,
|
||||
onPressed: () {
|
||||
if (mapDriverController.formKey1.currentState!.validate()) {
|
||||
box.write(BoxName.sosPhoneDriver,
|
||||
mapDriverController.sosEmergincyNumberCotroller.text);
|
||||
Get.back();
|
||||
launchCommunication(
|
||||
'phone', box.read(BoxName.sosPhoneDriver), '');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
launchCommunication('phone', box.read(BoxName.sosPhoneDriver), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
|
||||
// ويدجت للعرض فقط (بدون منطق فتح نوافذ)
|
||||
class SpeedCircle extends StatelessWidget {
|
||||
const SpeedCircle({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(
|
||||
id: 'SpeedCircle', // نحدد ID للتحديث الخفيف
|
||||
builder: (controller) {
|
||||
// إذا السرعة 0 أو أقل، نخفي الدائرة
|
||||
if (controller.speed <= 0) return const SizedBox();
|
||||
|
||||
bool isSpeeding = controller.speed > 100;
|
||||
|
||||
return Positioned(
|
||||
left: 20,
|
||||
top: 100, // مكانها المناسب
|
||||
child: Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
],
|
||||
border: Border.all(
|
||||
color: _getSpeedColor(controller.speed),
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
controller.speed.toStringAsFixed(0),
|
||||
style: TextStyle(
|
||||
fontFamily: AppStyle.title.fontFamily,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"km/h",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Color _getSpeedColor(double speed) {
|
||||
if (speed < 60) return AppColor.greenColor;
|
||||
if (speed < 100) return Colors.orange;
|
||||
return Colors.red;
|
||||
}
|
||||
}
|
||||
25
siro_driver/lib/views/home/Captin/orderCaptin/call.dart
Executable file
25
siro_driver/lib/views/home/Captin/orderCaptin/call.dart
Executable file
@@ -0,0 +1,25 @@
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:ride/constant/api_key.dart';
|
||||
// import 'package:ride/constant/box_name.dart';
|
||||
// import 'package:ride/main.dart';
|
||||
// import 'package:zego_uikit_prebuilt_call/zego_uikit_prebuilt_call.dart';
|
||||
|
||||
// class CallPage extends StatelessWidget {
|
||||
// const CallPage({Key? key, required this.callID}) : super(key: key);
|
||||
// final String callID;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return ZegoUIKitPrebuiltCall(
|
||||
// appID: AK
|
||||
// .zegoCloudAppID, // Fill in the appID that you get from ZEGOCLOUD Admin Console.
|
||||
// appSign: AK
|
||||
// .zegoCloudAppSIGN, // Fill in the appSign that you get from ZEGOCLOUD Admin Console.
|
||||
// userID: box.read(BoxName.passengerID) ?? box.read(BoxName.driverID),
|
||||
// userName: box.read(BoxName.name) ?? box.read(BoxName.nameDriver),
|
||||
// callID: callID,
|
||||
// // You can also use groupVideo/groupVoice/oneOnOneVoice to make more types of calls.
|
||||
// config: ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,142 @@
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class MarkerGenerator {
|
||||
// دالة لرسم ماركر يحتوي على نص (للوقت والمسافة)
|
||||
static Future<InlqBitmap> createCustomMarkerBitmap({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required Color color,
|
||||
required IconData iconData,
|
||||
}) async {
|
||||
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
|
||||
final Canvas canvas = Canvas(pictureRecorder);
|
||||
|
||||
// إعدادات القياسات
|
||||
const double width = 220.0;
|
||||
const double height = 110.0;
|
||||
const double circleRadius = 25.0;
|
||||
|
||||
// 1. رسم المربع (Info Box) مع تدرج لوني بسيط
|
||||
final Paint paint = Paint()..color = color;
|
||||
final RRect rRect = RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(0, 0, width, 65),
|
||||
const Radius.circular(20), // زوايا أكثر استدارة لشكل عصري
|
||||
);
|
||||
|
||||
// ظل أقوى لمظهر بارز (Premium Feel)
|
||||
canvas.drawShadow(Path()..addRRect(rRect), Colors.black54, 10.0, true);
|
||||
canvas.drawRRect(rRect, paint);
|
||||
|
||||
// 2. رسم مثلث صغير أسفل المربع (Arrow Tail)
|
||||
final Path path = Path();
|
||||
path.moveTo(width / 2 - 10, 60);
|
||||
path.lineTo(width / 2, 75);
|
||||
path.lineTo(width / 2 + 10, 60);
|
||||
path.close();
|
||||
canvas.drawPath(path, paint);
|
||||
|
||||
// 3. رسم الدائرة (مكان الأيقونة)
|
||||
canvas.drawCircle(const Offset(width / 2, 85), circleRadius, paint);
|
||||
|
||||
// 4. رسم الأيقونة داخل الدائرة
|
||||
TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||
iconPainter.text = TextSpan(
|
||||
text: String.fromCharCode(iconData.codePoint),
|
||||
style: TextStyle(
|
||||
fontSize: 30.0,
|
||||
fontFamily: iconData.fontFamily,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
iconPainter.layout();
|
||||
iconPainter.paint(
|
||||
canvas,
|
||||
Offset((width - iconPainter.width) / 2, 85 - (iconPainter.height / 2)),
|
||||
);
|
||||
|
||||
// 5. رسم النصوص (العنوان والوصف) داخل المربع
|
||||
// العنوان (مثلاً: المدة)
|
||||
TextPainter titlePainter = TextPainter(
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
titlePainter.text = TextSpan(
|
||||
text: title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
titlePainter.layout(minWidth: width);
|
||||
titlePainter.paint(canvas, const Offset(0, 8));
|
||||
|
||||
// الوصف (مثلاً: المسافة)
|
||||
TextPainter subTitlePainter = TextPainter(
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
subTitlePainter.text = TextSpan(
|
||||
text: subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Colors.white70,
|
||||
),
|
||||
);
|
||||
subTitlePainter.layout(minWidth: width);
|
||||
subTitlePainter.paint(canvas, const Offset(0, 32));
|
||||
|
||||
// تحويل الرسم إلى صورة
|
||||
final ui.Image image = await pictureRecorder.endRecording().toImage(
|
||||
width.toInt(),
|
||||
(height + 20).toInt(), // مساحة إضافية
|
||||
);
|
||||
final ByteData? data =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
|
||||
}
|
||||
|
||||
// دالة خاصة لرسم ماركر السائق (دائرة وخلفها سهم)
|
||||
static Future<InlqBitmap> createDriverMarker() async {
|
||||
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
|
||||
final Canvas canvas = Canvas(pictureRecorder);
|
||||
const double size = 100.0;
|
||||
|
||||
final Paint paint = Paint()..color = const Color(0xFF2E7D32); // أخضر غامق
|
||||
final Paint borderPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4.0;
|
||||
|
||||
// الدائرة
|
||||
canvas.drawCircle(const Offset(size / 2, size / 2), size / 2.5, paint);
|
||||
canvas.drawCircle(
|
||||
const Offset(size / 2, size / 2), size / 2.5, borderPaint);
|
||||
|
||||
// رسم السهم (Arrow Up)
|
||||
TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||
iconPainter.text = TextSpan(
|
||||
text: String.fromCharCode(Icons.navigation.codePoint), // سهم ملاحة
|
||||
style: TextStyle(
|
||||
fontSize: 40.0,
|
||||
fontFamily: Icons.navigation.fontFamily,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
iconPainter.layout();
|
||||
iconPainter.paint(
|
||||
canvas,
|
||||
Offset((size - iconPainter.width) / 2, (size - iconPainter.height) / 2),
|
||||
);
|
||||
|
||||
final ui.Image image = await pictureRecorder
|
||||
.endRecording()
|
||||
.toImage(size.toInt(), size.toInt());
|
||||
final ByteData? data =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
|
||||
}
|
||||
}
|
||||
814
siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart
Executable file
814
siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart
Executable file
@@ -0,0 +1,814 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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:siro_driver/constant/api_key.dart';
|
||||
import 'package:siro_driver/models/overlay_service.dart';
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/links.dart';
|
||||
import '../../../../controller/firebase/firbase_messge.dart';
|
||||
import '../../../../controller/firebase/local_notification.dart';
|
||||
import '../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../controller/functions/crud.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../../models/model/order_data.dart';
|
||||
import '../../../../print.dart';
|
||||
|
||||
// === Enhanced Colors for Better Readability ===
|
||||
class AppColors {
|
||||
static const primary = Color(0xFF1A252F);
|
||||
static const card = Color(0xFF2C3E50);
|
||||
static const white = Colors.white;
|
||||
static const gray = Color(0xFFBDC3C7);
|
||||
static const lightGray = Color(0xFFECF0F1);
|
||||
static const accent = Color(0xFF00BCD4);
|
||||
static const accept = Color(0xFF4CAF50);
|
||||
static const reject = Color(0xFFFF5722);
|
||||
static const highlight = Color(0xFFFFC107);
|
||||
static const priceHighlight = Color(0xFF00E676);
|
||||
static const urgentRed = Color(0xFFD32F2F);
|
||||
}
|
||||
|
||||
class OrderOverlay extends StatefulWidget {
|
||||
const OrderOverlay({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OrderOverlay> createState() => _OrderOverlayState();
|
||||
}
|
||||
|
||||
class _OrderOverlayState extends State<OrderOverlay>
|
||||
with WidgetsBindingObserver {
|
||||
// === State Variables ===
|
||||
OrderData? orderData;
|
||||
Timer? timer;
|
||||
int remainingSeconds = 10;
|
||||
final AudioPlayer audioPlayer = AudioPlayer();
|
||||
bool buttonsEnabled = true;
|
||||
final String mapApiKey = AK.mapAPIKEY;
|
||||
final CRUD _crud = CRUD();
|
||||
|
||||
final NotificationController notificationController =
|
||||
Get.put(NotificationController());
|
||||
// === Getters ===
|
||||
bool get canShowMap {
|
||||
if (orderData == null || mapApiKey.isEmpty) return false;
|
||||
final start = orderData!.startCoordinates;
|
||||
final end = orderData!.endCoordinates;
|
||||
return start?['lat'] != null &&
|
||||
start?['lng'] != null &&
|
||||
end?['lat'] != null &&
|
||||
end?['lng'] != null;
|
||||
}
|
||||
|
||||
String get staticMapUrl {
|
||||
if (!canShowMap) return "";
|
||||
final start = orderData!.startCoordinates!;
|
||||
final end = orderData!.endCoordinates!;
|
||||
final startMarker = Uri.encodeComponent("${start['lat']},${start['lng']}");
|
||||
final endMarker = Uri.encodeComponent("${end['lat']},${end['lng']}");
|
||||
|
||||
return "https://maps.googleapis.com/maps/api/staticmap?"
|
||||
"size=600x150&maptype=roadmap"
|
||||
"&markers=color:green%7Clabel:S%7C$startMarker"
|
||||
"&markers=color:red%7Clabel:D%7C$endMarker"
|
||||
"&path=color:0x007bff%7Cweight:5%7C$startMarker%7C$endMarker"
|
||||
"&key=$mapApiKey";
|
||||
}
|
||||
|
||||
// === Lifecycle ===
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
FlutterOverlayWindow.overlayListener.listen((event) {
|
||||
if (mounted) _processEventData(event);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
_stopAudio();
|
||||
audioPlayer.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_checkOverlayStatus();
|
||||
}
|
||||
}
|
||||
|
||||
List myList = [];
|
||||
// === Setup & Listeners ===
|
||||
void _setupOverlayListener() {
|
||||
FlutterOverlayWindow.overlayListener.listen((event) {
|
||||
if (mounted) _processEventData(event);
|
||||
});
|
||||
}
|
||||
|
||||
void _processEventData(dynamic event) {
|
||||
_log("Received event: $event");
|
||||
if (event is List<dynamic>) {
|
||||
try {
|
||||
myList = event;
|
||||
final newOrder = OrderData.fromList(event);
|
||||
_log("Parsed OrderData: ${newOrder.toMap()}");
|
||||
setState(() {
|
||||
orderData = newOrder;
|
||||
});
|
||||
_resetAndStartTimer();
|
||||
} catch (e, s) {
|
||||
_log("Error parsing OrderData: $e\nStackTrace: $s");
|
||||
}
|
||||
} else {
|
||||
_log("Unexpected data format: $event");
|
||||
}
|
||||
}
|
||||
|
||||
void _checkOverlayStatus() async {
|
||||
bool isActive = await FlutterOverlayWindow.isActive();
|
||||
if (isActive && mounted && orderData != null) {
|
||||
if (remainingSeconds > 0 && (timer == null || !timer!.isActive)) {
|
||||
_resetAndStartTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Timer Management ===
|
||||
void _resetAndStartTimer() {
|
||||
timer?.cancel();
|
||||
audioPlayer.stop();
|
||||
setState(() {
|
||||
buttonsEnabled = true;
|
||||
remainingSeconds = _calculateTimerDuration();
|
||||
});
|
||||
_playAudio();
|
||||
_startTimer();
|
||||
}
|
||||
|
||||
int _calculateTimerDuration() {
|
||||
if (orderData?.durationToPassengerMinutes != null &&
|
||||
orderData!.durationToPassengerMinutes > 0) {
|
||||
int duration = orderData!.durationToPassengerMinutes * 60;
|
||||
return duration > 10 ? 10 : duration;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
if (orderData == null) return;
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
_stopAudio();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
if (remainingSeconds > 0) {
|
||||
remainingSeconds--;
|
||||
} else {
|
||||
timer.cancel();
|
||||
_stopAudio();
|
||||
if (buttonsEnabled) _handleOrderTimeout();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// === Audio Management ===
|
||||
void _playAudio() async {
|
||||
try {
|
||||
await audioPlayer.setAsset('assets/order.mp3', preload: true);
|
||||
await audioPlayer.setLoopMode(LoopMode.one);
|
||||
await audioPlayer.play();
|
||||
} catch (e) {
|
||||
_log('Error playing audio: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _stopAudio() {
|
||||
audioPlayer.stop();
|
||||
}
|
||||
|
||||
String _getData(int index, {String defaultValue = ''}) {
|
||||
if (myList.length > index && myList[index] != null) {
|
||||
return myList[index].toString();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// === Order Actions ===
|
||||
Future<void> _acceptOrder() async {
|
||||
if (!buttonsEnabled || orderData == null) return;
|
||||
_disableButtonsAndProcess();
|
||||
_log("Order ACCEPTED: ${orderData!.orderId}");
|
||||
|
||||
try {
|
||||
final driverId = box.read(BoxName.driverID)?.toString();
|
||||
if (driverId == null) {
|
||||
_log("Error: Driver ID is null. Closing overlay.");
|
||||
await _closeOverlay();
|
||||
return;
|
||||
}
|
||||
var res = await CRUD()
|
||||
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
'passengerToken': _getData(9),
|
||||
});
|
||||
|
||||
final payload = {
|
||||
// بيانات أساسية
|
||||
'driver_id': driverId,
|
||||
'status': 'Apply',
|
||||
'passengerLocation': '${_getData(0)},${_getData(1)}',
|
||||
'passengerDestination': '${_getData(3)},${_getData(4)}',
|
||||
'Duration': _getData(4),
|
||||
'totalCost': _getData(26),
|
||||
'Distance': _getData(5),
|
||||
'name': _getData(8),
|
||||
'phone': _getData(10),
|
||||
'email': _getData(28),
|
||||
'WalletChecked': _getData(13),
|
||||
'tokenPassenger': _getData(9),
|
||||
'direction': staticMapUrl.toString(),
|
||||
'DurationToPassenger': _getData(15),
|
||||
'rideId': orderData!.orderId,
|
||||
'passengerId': _getData(7),
|
||||
'durationOfRideValue': _getData(19),
|
||||
'paymentAmount': _getData(2),
|
||||
'paymentMethod': _getData(13) == 'true' ? 'visa' : 'cash',
|
||||
'isHaveSteps': _getData(20),
|
||||
'step0': myList[21].toString(),
|
||||
'step1': myList[22].toString(),
|
||||
'step2': myList[23].toString(),
|
||||
'step3': myList[24].toString(),
|
||||
'step4': myList[25].toString(),
|
||||
'passengerWalletBurc': myList[26].toString(),
|
||||
'carType': myList[31].toString(),
|
||||
'kazan': myList[32].toString(),
|
||||
'startNameLocation': myList[29].toString(),
|
||||
'endNameLocation': myList[30].toString(),
|
||||
// الحقول الإضافية التي يجب تضمينها
|
||||
'timeOfOrder': DateTime.now().toIso8601String(),
|
||||
'totalPassenger': _getData(2),
|
||||
};
|
||||
Log.print('payload: ${payload}');
|
||||
CRUD().post(
|
||||
link: AppLink.addOverLayStatus,
|
||||
payload: payload,
|
||||
);
|
||||
if (res != "failure") {
|
||||
// Using rideId (_getData(16)) for order_id consistently
|
||||
|
||||
_log("Server update successful. Writing to storage.");
|
||||
notificationController.showNotification(
|
||||
"Order Accepted".tr,
|
||||
"Open app and go to passenger".tr,
|
||||
'ding',
|
||||
'',
|
||||
);
|
||||
|
||||
// 3. الخطوة الأهم: فتح التطبيق وإغلاق النافذة
|
||||
try {
|
||||
// استدعاء الميثود التي تم تحديثها في الخطوة 1
|
||||
await OverlayMethodChannel.bringToForeground();
|
||||
} catch (e) {
|
||||
_log("Failed to bring app to foreground: $e");
|
||||
}
|
||||
await _closeOverlay();
|
||||
} else {
|
||||
_log("Failed to update order status on server: $res");
|
||||
notificationController.showNotification(
|
||||
"Order Accepted by another driver".tr,
|
||||
"Open app and go to passenger".tr,
|
||||
'ding',
|
||||
'',
|
||||
);
|
||||
await _closeOverlay();
|
||||
}
|
||||
} catch (e, s) {
|
||||
_log(
|
||||
"A critical error occurred during server update: $e\nStackTrace: $s");
|
||||
if (mounted) setState(() => buttonsEnabled = true);
|
||||
|
||||
_log("Error in accept order: $e");
|
||||
await _closeOverlay();
|
||||
// حتى في حال الخطأ، نحاول فتح التطبيق ليرى السائق ما حدث
|
||||
await OverlayMethodChannel.bringToForeground();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Your list parsing for 'customerToken' should be something like:
|
||||
// customerToken: list.length > a_certain_index ? list[a_certain_index].toString() : null,
|
||||
Future<void> _rejectOrder() async {
|
||||
if (!buttonsEnabled || orderData == null) return;
|
||||
_disableButtonsAndProcess();
|
||||
_log("Order REJECTED: ${orderData!.orderId}");
|
||||
box.write(BoxName.rideStatus, 'reject');
|
||||
Log.print('rideStatus from overlay 303 : ${box.read(BoxName.rideStatus)}');
|
||||
await _apiRefuseOrder(orderData!.orderId);
|
||||
await _closeOverlay();
|
||||
}
|
||||
|
||||
void _handleOrderTimeout() {
|
||||
if (orderData == null) return;
|
||||
_log("Order TIMED OUT: ${orderData!.orderId}");
|
||||
_rejectOrder();
|
||||
}
|
||||
|
||||
Future<void> _apiRefuseOrder(String orderID) async {
|
||||
if (orderID == "N/A") {
|
||||
_log("Cannot refuse order with N/A ID");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final driverId = box.read(BoxName.driverID)?.toString();
|
||||
if (driverId == null) {
|
||||
_log("Driver ID is null, cannot refuse order");
|
||||
return;
|
||||
}
|
||||
CRUD().post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderID,
|
||||
'status': 'Refused'
|
||||
});
|
||||
|
||||
_log("Order $orderID refused successfully");
|
||||
} catch (e) {
|
||||
_log("Error in _apiRefuseOrder for $orderID: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// === Helper Methods ===
|
||||
void _disableButtonsAndProcess() {
|
||||
setState(() => buttonsEnabled = false);
|
||||
timer?.cancel();
|
||||
_stopAudio();
|
||||
}
|
||||
|
||||
Future<void> _closeOverlay() async {
|
||||
_stopAudio();
|
||||
timer?.cancel();
|
||||
if (await FlutterOverlayWindow.isActive()) {
|
||||
await FlutterOverlayWindow.closeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
// A simple logger to distinguish overlay logs
|
||||
print("OVERLAY_LOG: $message");
|
||||
}
|
||||
|
||||
// === UI Build Methods ===
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// ... (Your entire UI build method remains unchanged) ...
|
||||
// The UI code is excellent and doesn't need modification.
|
||||
if (orderData == null) {
|
||||
return const Material(
|
||||
color: Colors.transparent,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(color: AppColors.accent)));
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
child: Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
border: Border.all(
|
||||
color: AppColors.accent.withOpacity(0.3), width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
blurRadius: 15,
|
||||
spreadRadius: 2,
|
||||
)
|
||||
],
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildQuickHeader(),
|
||||
const SizedBox(height: 12),
|
||||
_buildPrimaryInfo(),
|
||||
const SizedBox(height: 12),
|
||||
if (canShowMap) _buildCompactMap(),
|
||||
if (canShowMap) const SizedBox(height: 12),
|
||||
_buildSecondaryInfo(),
|
||||
const SizedBox(height: 16),
|
||||
_buildEnhancedActionButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// All your _build... widget methods (_buildQuickHeader, _buildPrimaryInfo, etc.)
|
||||
// are perfectly fine and do not need to be changed.
|
||||
// ... Paste all your existing _build... methods here ...
|
||||
Widget _buildQuickHeader() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
remainingSeconds <= 3
|
||||
? AppColors.urgentRed
|
||||
: remainingSeconds <= 5
|
||||
? AppColors.highlight
|
||||
: AppColors.accent,
|
||||
remainingSeconds <= 3
|
||||
? AppColors.urgentRed.withOpacity(0.7)
|
||||
: remainingSeconds <= 5
|
||||
? AppColors.highlight.withOpacity(0.7)
|
||||
: AppColors.accent.withOpacity(0.7),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.drive_eta_rounded, color: AppColors.white, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"طلب جديد".tr,
|
||||
style: const TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
"$remainingSeconds ث",
|
||||
style: TextStyle(
|
||||
color: remainingSeconds <= 3
|
||||
? AppColors.urgentRed
|
||||
: remainingSeconds <= 5
|
||||
? AppColors.highlight
|
||||
: AppColors.accent,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrimaryInfo() {
|
||||
final order = orderData!;
|
||||
|
||||
// FIX: Parse the price to a number safely before formatting
|
||||
// This handles cases where order.price is a String like "173"
|
||||
final num priceValue = num.tryParse(order.price.toString()) ?? 0;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppColors.priceHighlight.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Price and Distance - Most Important Info
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _buildHighlightInfo(
|
||||
// FIX: Use the parsed priceValue here
|
||||
"${NumberFormat('#,##0').format(priceValue)} ل.س",
|
||||
"السعر".tr,
|
||||
Icons.monetization_on_rounded,
|
||||
AppColors.priceHighlight,
|
||||
isLarge: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildHighlightInfo(
|
||||
// Ensure tripDistanceKm is treated safely too
|
||||
"${(num.tryParse(order.tripDistanceKm.toString()) ?? 0).toStringAsFixed(1)} كم",
|
||||
"المسافة".tr,
|
||||
Icons.straighten_rounded,
|
||||
AppColors.accent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Divider(color: AppColors.gray.withOpacity(0.2), thickness: 1),
|
||||
const SizedBox(height: 12),
|
||||
// Passenger Info and ETA
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildPassengerQuickInfo(),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildHighlightInfo(
|
||||
"${order.durationToPassengerMinutes} د",
|
||||
"للوصول".tr,
|
||||
Icons.access_time_filled_rounded,
|
||||
order.durationToPassengerMinutes <= 3
|
||||
? AppColors.priceHighlight
|
||||
: AppColors.gray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHighlightInfo(
|
||||
String value, String label, IconData icon, Color color,
|
||||
{bool isLarge = false}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: isLarge ? 24 : 20),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: isLarge ? 20 : 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: AppColors.lightGray.withOpacity(0.7),
|
||||
fontSize: 11,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPassengerQuickInfo() {
|
||||
final order = orderData!;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.highlight.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border:
|
||||
Border.all(color: AppColors.highlight.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.person_rounded, color: AppColors.highlight, size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.customerName,
|
||||
style: const TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
order.rideType,
|
||||
style: TextStyle(
|
||||
color: AppColors.lightGray.withOpacity(0.7),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactMap() {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Image.network(
|
||||
staticMapUrl,
|
||||
height: 100, // Reduced from 110
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Center(
|
||||
child:
|
||||
Icon(Icons.map_outlined, color: AppColors.gray, size: 32)),
|
||||
);
|
||||
},
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
color: AppColors.accent,
|
||||
strokeWidth: 2.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSecondaryInfo() {
|
||||
final order = orderData!;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildLocationRow(
|
||||
Icons.trip_origin_rounded,
|
||||
"من".tr,
|
||||
order.startLocationAddress,
|
||||
Colors.green.shade300,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildLocationRow(
|
||||
Icons.flag_rounded,
|
||||
"إلى".tr,
|
||||
order.endLocationAddress,
|
||||
Colors.red.shade300,
|
||||
),
|
||||
if (order.tripDurationMinutes > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Divider(color: AppColors.gray.withOpacity(0.2), thickness: 0.5),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.timer_outlined, color: AppColors.accent, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"مدة الرحلة: ${order.tripDurationMinutes} دقيقة".tr,
|
||||
style: const TextStyle(
|
||||
color: AppColors.lightGray,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationRow(
|
||||
IconData icon, String label, String address, Color iconColor) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, color: iconColor, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"$label: ",
|
||||
style: TextStyle(
|
||||
color: AppColors.lightGray.withOpacity(0.8),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
address,
|
||||
style: const TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEnhancedActionButtons() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: buttonsEnabled ? _rejectOrder : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.reject,
|
||||
foregroundColor: AppColors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
disabledBackgroundColor: AppColors.reject.withOpacity(0.3),
|
||||
elevation: 3,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.close_rounded, size: 20),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"رفض".tr,
|
||||
style: const TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: buttonsEnabled ? _acceptOrder : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.accept,
|
||||
foregroundColor: AppColors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
disabledBackgroundColor: AppColors.accept.withOpacity(0.3),
|
||||
elevation: 3,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.check_circle_rounded, size: 20),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"قبول".tr,
|
||||
style: const TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
438
siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart
Executable file
438
siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart
Executable file
@@ -0,0 +1,438 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_driver/constant/api_key.dart';
|
||||
import 'package:siro_driver/constant/colors.dart';
|
||||
import 'package:siro_driver/controller/home/captin/order_request_controller.dart';
|
||||
|
||||
class OrderRequestPage extends StatelessWidget {
|
||||
const OrderRequestPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// حقن الكنترولر
|
||||
final OrderRequestController controller = Get.put(OrderRequestController());
|
||||
|
||||
return Scaffold(
|
||||
body: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: GetBuilder<OrderRequestController>(
|
||||
builder: (controller) {
|
||||
// 🔥 التعديل الأهم: التحقق من وجود أي بيانات (List أو Map)
|
||||
if (controller.myList == null && controller.myMapData == null) {
|
||||
return const Center(
|
||||
child:
|
||||
CircularProgressIndicator()); // شاشة تحميل بدلاً من فراغ
|
||||
}
|
||||
|
||||
// 🔥 استخدام دوال الكنترولر الآمنة لجلب البيانات بدلاً من الوصول المباشر
|
||||
// قمت بتحويل _safeGet إلى دالة عامة safeGet في الكنترولر (تأكد من جعلها public)
|
||||
// أو سأقوم بكتابة المنطق هنا مباشرة لضمان العمل:
|
||||
|
||||
String getValue(int index) {
|
||||
if (controller.myList != null &&
|
||||
index < controller.myList!.length) {
|
||||
return controller.myList![index].toString();
|
||||
}
|
||||
if (controller.myMapData != null &&
|
||||
controller.myMapData!.containsKey(index.toString())) {
|
||||
return controller.myMapData![index.toString()].toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
final String passengerName =
|
||||
getValue(8).isEmpty ? "عميل" : getValue(8);
|
||||
final String startAddr =
|
||||
getValue(29).isEmpty ? "موقع الانطلاق" : getValue(29);
|
||||
final String endAddr =
|
||||
getValue(30).isEmpty ? "الوجهة" : getValue(30);
|
||||
final bool isVisa = (getValue(13) == 'true');
|
||||
|
||||
// منطق Speed = سعر ثابت
|
||||
final bool isSpeed =
|
||||
controller.tripType.toLowerCase().contains('speed');
|
||||
final String carTypeLabel =
|
||||
isSpeed ? "سعر ثابت" : controller.tripType;
|
||||
final Color carTypeColor =
|
||||
isSpeed ? Colors.red.shade700 : Colors.blue.shade700;
|
||||
final IconData carIcon =
|
||||
isSpeed ? Icons.local_offer : Icons.directions_car;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// 1. الخارطة
|
||||
Positioned.fill(
|
||||
bottom: 300,
|
||||
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,
|
||||
onMapCreated: (c) {
|
||||
controller.onMapCreated(c);
|
||||
controller.update();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 2. كبسولة الوصول للراكب
|
||||
Positioned(
|
||||
top: 50,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black87,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 8)
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.near_me,
|
||||
color: Colors.amber, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"الوصول للراكب: ${controller.timeToPassenger}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 3. البطاقة السفلية
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
height: 360,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
Theme.of(context).shadowColor.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 5)
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dividerColor,
|
||||
borderRadius: BorderRadius.circular(2)))),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// الصف الأول: الراكب والسعر
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
child: Icon(Icons.person,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: 28),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(passengerName,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold)),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.star,
|
||||
color: Colors.amber, size: 14),
|
||||
Text(controller.passengerRating,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text("${controller.tripPrice} ل.س",
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.primaryColor)),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: isVisa
|
||||
? Colors.purple.withOpacity(0.1)
|
||||
: Colors.green.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(isVisa ? "فيزا" : "كاش",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isVisa
|
||||
? Colors.purple
|
||||
: Colors.green)),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
isVisa
|
||||
? Icons.credit_card
|
||||
: Icons.money,
|
||||
size: 14,
|
||||
color: isVisa
|
||||
? Colors.purple
|
||||
: Colors.green),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// الصف الثاني: شريط المعلومات
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10, horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
context, carIcon, carTypeLabel, carTypeColor),
|
||||
Container(
|
||||
height: 20,
|
||||
width: 1,
|
||||
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: Theme.of(context).dividerColor),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
Icons.access_time_filled,
|
||||
controller.totalTripDuration,
|
||||
Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.color ??
|
||||
Colors.black87),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// الصف الثالث: العناوين
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
const Icon(Icons.my_location,
|
||||
size: 18, color: Colors.green),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: 2,
|
||||
color: Colors.grey.shade300,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 2))),
|
||||
const Icon(Icons.location_on,
|
||||
size: 18, color: Colors.red),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text("موقع الانطلاق",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey)),
|
||||
Text(startAddr,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text("الوجهة",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey)),
|
||||
Text(endAddr,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// الصف الرابع: الأزرار
|
||||
Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
shape: BoxShape.circle,
|
||||
border:
|
||||
Border.all(color: Colors.red.shade100)),
|
||||
child: const Icon(Icons.close,
|
||||
color: Colors.red, size: 24),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => controller.acceptOrder(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15)),
|
||||
elevation: 2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("قبول الرحلة",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold)),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
value: controller.progress,
|
||||
color: Colors.white,
|
||||
strokeWidth: 2.5,
|
||||
backgroundColor: Colors.white24),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text("${controller.remainingTime}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItem(
|
||||
BuildContext context, IconData icon, String text, Color color) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 6),
|
||||
Text(text,
|
||||
style: TextStyle(
|
||||
fontSize: 13, fontWeight: FontWeight.bold, color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
558
siro_driver/lib/views/home/Captin/orderCaptin/order_speed_request.dart
Executable file
558
siro_driver/lib/views/home/Captin/orderCaptin/order_speed_request.dart
Executable file
@@ -0,0 +1,558 @@
|
||||
// import 'dart:convert';
|
||||
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
|
||||
// import 'package:siro_driver/controller/home/captin/home_captain_controller.dart';
|
||||
// import 'package:siro_driver/constant/box_name.dart';
|
||||
// import 'package:siro_driver/controller/firebase/firbase_messge.dart';
|
||||
// import 'package:siro_driver/main.dart';
|
||||
// import 'package:siro_driver/print.dart';
|
||||
// import 'package:siro_driver/views/home/Captin/driver_map_page.dart';
|
||||
|
||||
// import '../../../../constant/colors.dart';
|
||||
// import '../../../../constant/links.dart';
|
||||
// import '../../../../constant/style.dart';
|
||||
// import '../../../../controller/firebase/notification_service.dart';
|
||||
// import '../../../../controller/functions/crud.dart';
|
||||
// import '../../../../controller/functions/launch.dart';
|
||||
// import '../../../../controller/home/captin/order_request_controller.dart';
|
||||
// import '../../../widgets/elevated_btn.dart';
|
||||
// import '../../../widgets/mydialoug.dart';
|
||||
|
||||
// class OrderSpeedRequest extends StatelessWidget {
|
||||
// OrderSpeedRequest({super.key});
|
||||
|
||||
// final OrderRequestController orderRequestController =
|
||||
// Get.put(OrderRequestController());
|
||||
|
||||
// // دالة مساعدة لاستخراج البيانات بأمان (Null Safety)
|
||||
// String _getData(int index, {String defaultValue = ''}) {
|
||||
// // if (orderRequestController.myList.length > index &&
|
||||
// // orderRequestController.myList[index] != null) {
|
||||
// // return orderRequestController.myList[index].toString();
|
||||
// // }
|
||||
// return defaultValue;
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return GetBuilder<OrderRequestController>(
|
||||
// builder: (controller) {
|
||||
// // --- استخراج البيانات بشكل نظيف ---
|
||||
// final String price =
|
||||
// double.tryParse(_getData(2))?.toStringAsFixed(2) ?? 'N/A';
|
||||
// final bool isComfortTrip = _getData(31) == 'Comfort';
|
||||
// final String carType = _getData(31).tr;
|
||||
|
||||
// final String pickupName = _getData(12);
|
||||
// final String pickupDetails = '(${_getData(11)})';
|
||||
// final String pickupFullAddress = _getData(29);
|
||||
// final String dropoffName = _getData(5);
|
||||
// final String dropoffDetails = '(${_getData(4)})';
|
||||
// final String dropoffFullAddress = _getData(30);
|
||||
|
||||
// final String passengerName = _getData(8);
|
||||
// final String passengerRating = _getData(33);
|
||||
// final bool isVisaPayment = _getData(13) == 'true';
|
||||
// final bool hasSteps = _getData(20) == 'haveSteps';
|
||||
|
||||
// final String mapUrl =
|
||||
// 'https://www.google.com/maps/dir/${_getData(0)}/${_getData(1)}/';
|
||||
// final String rideId = _getData(16);
|
||||
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: Text('Speed Order'.tr),
|
||||
// leading: IconButton(
|
||||
// icon: const Icon(Icons.arrow_back),
|
||||
// onPressed: () => Get.back(),
|
||||
// ),
|
||||
// backgroundColor: AppColor.primaryColor,
|
||||
// elevation: 2.0,
|
||||
// ),
|
||||
// backgroundColor: AppColor.secondaryColor ?? Colors.grey[100],
|
||||
// body: SafeArea(
|
||||
// child: Padding(
|
||||
// padding:
|
||||
// const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0),
|
||||
// child: Column(
|
||||
// children: [
|
||||
// // 1. قسم الخريطة (ارتفاع ثابت)
|
||||
// SizedBox(
|
||||
// height: Get.height * 0.28,
|
||||
// child: ClipRRect(
|
||||
// borderRadius: BorderRadius.circular(15.0),
|
||||
// child: GoogleMap(
|
||||
// initialCameraPosition: CameraPosition(
|
||||
// zoom: 12,
|
||||
// target: Get.find<HomeCaptainController>().myLocation,
|
||||
// ),
|
||||
// cameraTargetBounds:
|
||||
// CameraTargetBounds(controller.bounds),
|
||||
// myLocationButtonEnabled: false,
|
||||
// trafficEnabled: false,
|
||||
// buildingsEnabled: false,
|
||||
// mapToolbarEnabled: false,
|
||||
// myLocationEnabled: true,
|
||||
// markers: {
|
||||
// Marker(
|
||||
// markerId: MarkerId('MyLocation'.tr),
|
||||
// position: LatLng(controller.latPassengerLocation,
|
||||
// controller.lngPassengerLocation),
|
||||
// icon: controller.startIcon),
|
||||
// Marker(
|
||||
// markerId: MarkerId('Destination'.tr),
|
||||
// position: LatLng(
|
||||
// controller.latPassengerDestination,
|
||||
// controller.lngPassengerDestination),
|
||||
// icon: controller.endIcon),
|
||||
// },
|
||||
// polylines: {
|
||||
// Polyline(
|
||||
// zIndex: 1,
|
||||
// consumeTapEvents: true,
|
||||
// geodesic: true,
|
||||
// endCap: Cap.buttCap,
|
||||
// startCap: Cap.buttCap,
|
||||
// visible: true,
|
||||
// polylineId: const PolylineId('routeOrder'),
|
||||
// points: controller.pointsDirection,
|
||||
// color: AppColor.primaryColor,
|
||||
// width: 3,
|
||||
// ),
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// const SizedBox(height: 8),
|
||||
|
||||
// // 2. بطاقة السعر
|
||||
// _buildPriceCard(price, carType, isComfortTrip),
|
||||
|
||||
// const SizedBox(height: 8),
|
||||
|
||||
// // 3. التفاصيل القابلة للتمرير (تأخذ المساحة المتبقية)
|
||||
// Expanded(
|
||||
// child: SingleChildScrollView(
|
||||
// physics: const BouncingScrollPhysics(),
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// _buildLocationCard(
|
||||
// icon: Icons.arrow_circle_up,
|
||||
// iconColor: AppColor.greenColor,
|
||||
// title: pickupName,
|
||||
// subtitle: pickupDetails,
|
||||
// fullAddress: pickupFullAddress,
|
||||
// ),
|
||||
// const SizedBox(height: 8),
|
||||
// _buildLocationCard(
|
||||
// icon: Icons.arrow_circle_down,
|
||||
// iconColor: AppColor.redColor,
|
||||
// title: dropoffName,
|
||||
// subtitle: dropoffDetails,
|
||||
// fullAddress: dropoffFullAddress,
|
||||
// ),
|
||||
// const SizedBox(height: 8),
|
||||
// _buildInfoCard(isVisaPayment, hasSteps, mapUrl),
|
||||
// const SizedBox(height: 8),
|
||||
// _buildPassengerCard(passengerName, passengerRating),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // 4. الأزرار والمؤقت (مثبتة في الأسفل)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 8.0),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// // زر القبول
|
||||
// Expanded(
|
||||
// child: MyElevatedButton(
|
||||
// kolor: AppColor.greenColor,
|
||||
// title: 'Accept Order'.tr,
|
||||
// onPressed: () => _handleAcceptOrder(controller),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 10),
|
||||
|
||||
// // المؤقت
|
||||
// GetBuilder<OrderRequestController>(
|
||||
// id: 'timerUpdate',
|
||||
// builder: (timerCtrl) {
|
||||
// final isNearEnd = timerCtrl.remainingTimeSpeed <= 5;
|
||||
// return SizedBox(
|
||||
// width: 60,
|
||||
// height: 60,
|
||||
// child: Stack(
|
||||
// alignment: Alignment.center,
|
||||
// children: [
|
||||
// CircularProgressIndicator(
|
||||
// value: timerCtrl.progressSpeed,
|
||||
// color: isNearEnd
|
||||
// ? Colors.redAccent
|
||||
// : AppColor.primaryColor,
|
||||
// strokeWidth: 5,
|
||||
// backgroundColor: Colors.grey.shade300,
|
||||
// ),
|
||||
// Text(
|
||||
// '${timerCtrl.remainingTimeSpeed}',
|
||||
// style: AppStyle.headTitle2.copyWith(
|
||||
// color: isNearEnd
|
||||
// ? Colors.redAccent
|
||||
// : AppColor.writeColor ?? Colors.black,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
|
||||
// const SizedBox(width: 10),
|
||||
// // زر الرفض
|
||||
// Expanded(
|
||||
// child: MyElevatedButton(
|
||||
// title: 'Refuse Order'.tr,
|
||||
// onPressed: () =>
|
||||
// _handleRefuseOrder(controller, rideId),
|
||||
// kolor: AppColor.redColor,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
// // --- WIDGET BUILDERS (لبناء الواجهة بشكل نظيف) ---
|
||||
|
||||
// // Widget _buildPriceCard(String price, String carType, bool isComfortTrip) {
|
||||
// // return Card(
|
||||
// // elevation: 3,
|
||||
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
// // child: Padding(
|
||||
// // padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
||||
// // child: Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// // children: [
|
||||
// // Text(
|
||||
// // price,
|
||||
// // style: AppStyle.headTitle.copyWith(
|
||||
// // color: AppColor.primaryColor,
|
||||
// // fontWeight: FontWeight.bold,
|
||||
// // fontSize: 28),
|
||||
// // ),
|
||||
// // Column(
|
||||
// // crossAxisAlignment: CrossAxisAlignment.end,
|
||||
// // children: [
|
||||
// // Text(
|
||||
// // carType,
|
||||
// // style: AppStyle.title.copyWith(
|
||||
// // color: AppColor.greenColor, fontWeight: FontWeight.bold),
|
||||
// // ),
|
||||
// // if (isComfortTrip)
|
||||
// // Row(
|
||||
// // children: [
|
||||
// // const Icon(Icons.ac_unit,
|
||||
// // color: AppColor.blueColor, size: 18),
|
||||
// // const SizedBox(width: 4),
|
||||
// // Text('Air condition Trip'.tr,
|
||||
// // style: AppStyle.subtitle.copyWith(fontSize: 13)),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // Widget _buildInfoCard(bool isVisaPayment, bool hasSteps, String mapUrl) {
|
||||
// // return Card(
|
||||
// // elevation: 2,
|
||||
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
// // child: Padding(
|
||||
// // padding: const EdgeInsets.all(10.0),
|
||||
// // child: Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// // children: [
|
||||
// // Row(
|
||||
// // children: [
|
||||
// // Icon(
|
||||
// // isVisaPayment ? Icons.credit_card : Icons.payments_outlined,
|
||||
// // color: isVisaPayment
|
||||
// // ? AppColor.deepPurpleAccent
|
||||
// // : AppColor.greenColor,
|
||||
// // size: 24,
|
||||
// // ),
|
||||
// // const SizedBox(width: 8),
|
||||
// // Text(
|
||||
// // isVisaPayment ? 'Visa'.tr : 'Cash'.tr,
|
||||
// // style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // if (hasSteps)
|
||||
// // Expanded(
|
||||
// // child: Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.center,
|
||||
// // children: [
|
||||
// // const Icon(Icons.format_list_numbered_rtl_outlined,
|
||||
// // color: AppColor.bronze, size: 24),
|
||||
// // const SizedBox(width: 4),
|
||||
// // Flexible(
|
||||
// // child: Text(
|
||||
// // 'Trip has Steps'.tr,
|
||||
// // style: AppStyle.title
|
||||
// // .copyWith(color: AppColor.bronze, fontSize: 13),
|
||||
// // overflow: TextOverflow.ellipsis,
|
||||
// // )),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // TextButton.icon(
|
||||
// // style: TextButton.styleFrom(
|
||||
// // padding: EdgeInsets.zero,
|
||||
// // tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
// // alignment: Alignment.centerRight,
|
||||
// // ),
|
||||
// // onPressed: () => showInBrowser(mapUrl),
|
||||
// // icon: const Icon(Icons.directions_outlined,
|
||||
// // color: AppColor.blueColor, size: 20),
|
||||
// // label: Text("Directions".tr,
|
||||
// // style: AppStyle.subtitle
|
||||
// // .copyWith(color: AppColor.blueColor, fontSize: 13)),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // Widget _buildPassengerCard(String name, String rating) {
|
||||
// // return Card(
|
||||
// // elevation: 2,
|
||||
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
// // child: Padding(
|
||||
// // padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
|
||||
// // child: Row(
|
||||
// // children: [
|
||||
// // const Icon(Icons.person_outline,
|
||||
// // color: AppColor.greyColor, size: 22),
|
||||
// // const SizedBox(width: 10),
|
||||
// // Expanded(
|
||||
// // child: Text(
|
||||
// // name,
|
||||
// // style: AppStyle.title,
|
||||
// // overflow: TextOverflow.ellipsis,
|
||||
// // ),
|
||||
// // ),
|
||||
// // const SizedBox(width: 10),
|
||||
// // const Icon(Icons.star_rounded, color: Colors.amber, size: 20),
|
||||
// // Text(
|
||||
// // rating,
|
||||
// // style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // Widget _buildLocationCard(
|
||||
// {required IconData icon,
|
||||
// required Color iconColor,
|
||||
// required String title,
|
||||
// required String subtitle,
|
||||
// required String fullAddress}) {
|
||||
// return Card(
|
||||
// elevation: 2,
|
||||
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
// margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(10.0),
|
||||
// child: Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Icon(icon, color: iconColor, size: 28),
|
||||
// const SizedBox(width: 12),
|
||||
// Expanded(
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text(
|
||||
// "$title $subtitle".trim(),
|
||||
// style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// if (fullAddress.isNotEmpty) ...[
|
||||
// const SizedBox(height: 3),
|
||||
// Text(
|
||||
// fullAddress,
|
||||
// style: AppStyle.subtitle
|
||||
// .copyWith(fontSize: 13, color: AppColor.greyColor),
|
||||
// maxLines: 2,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ]
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// // // --- منطق التعامل مع الطلبات (Logic Handlers) ---
|
||||
|
||||
// // Future<void> _handleAcceptOrder(OrderRequestController controller) async {
|
||||
// // // 1. محاولة تحديث الحالة في السيرفر
|
||||
// // // 1. إظهار لودينج وإيقاف التفاعل
|
||||
// // Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
// // barrierDismissible: false);
|
||||
// // // هذا الرابط يجب أن يكون لملف PHP الآمن الذي يحتوي على rowCount
|
||||
// // 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),
|
||||
// // });
|
||||
// // Log.print('oreder response update: ${res}');
|
||||
// // // 2. إغلاق اللودينج بمجرد وصول الرد
|
||||
// // Get.back(); // إغلاق اللودينج
|
||||
// // // 2. معالجة الفشل (Robust Error Handling)
|
||||
// // // نفحص إذا كانت النتيجة فشل سواء وصلت كنص أو كـ JSON
|
||||
// // bool isFailed = false;
|
||||
// // if (res == 'failure') isFailed = true;
|
||||
|
||||
// // if (res is Map && res['status'] == 'failure') isFailed = true;
|
||||
|
||||
// // if (isFailed) {
|
||||
// // MyDialog().getDialog(
|
||||
// // "This ride is already applied by another driver.".tr, '', () {
|
||||
// // Get.back(); // يغلق نافذة التنبيه (Dialog)
|
||||
// // Get.back(); // يغلق صفحة الطلب بالكامل (Screen) ويرجع للخريطة
|
||||
// // });
|
||||
// // return; // توقف تام للكود هنا، لن يتم تنفيذ أي سطر بالأسفل
|
||||
// // }
|
||||
|
||||
// // // 3. معالجة النجاح (Success Handling)
|
||||
|
||||
// // // إيقاف المؤقت وتحديث الواجهة
|
||||
// // controller.endTimer();
|
||||
// // controller.changeApplied();
|
||||
|
||||
// // // تحديث حالة السائق في التطبيق
|
||||
// // Get.put(HomeCaptainController()).changeRideId();
|
||||
// // box.write(BoxName.statusDriverLocation, 'on');
|
||||
|
||||
// // // *هام*: تم حذف استدعاء الـ API الثاني المكرر هنا لأنه غير ضروري وقد يسبب مشاكل
|
||||
|
||||
// // // تسجيل الطلب في سجل السائقين (Driver History)
|
||||
// // CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
|
||||
// // 'driver_id': (controller.myList[6].toString()),
|
||||
// // 'order_id': (controller.myList[16].toString()),
|
||||
// // 'status': 'Apply'
|
||||
// // });
|
||||
|
||||
// // // إرسال إشعار للراكب
|
||||
// // List<String> bodyToPassenger = [
|
||||
// // controller.myList[6].toString(),
|
||||
// // controller.myList[8].toString(),
|
||||
// // controller.myList[9].toString(),
|
||||
// // ];
|
||||
|
||||
// // NotificationService.sendNotification(
|
||||
// // target: controller.myList[9].toString(),
|
||||
// // title: "Accepted Ride".tr,
|
||||
// // body: 'your ride is Accepted'.tr,
|
||||
// // isTopic: false,
|
||||
// // tone: 'start',
|
||||
// // driverList: bodyToPassenger,
|
||||
// // category: 'Accepted Ride',
|
||||
// // );
|
||||
|
||||
// // // حفظ البيانات في الصندوق (Box) للانتقال للصفحة التالية
|
||||
// // box.write(BoxName.rideArguments, {
|
||||
// // 'passengerLocation': controller.myList[0].toString(),
|
||||
// // 'passengerDestination': controller.myList[1].toString(),
|
||||
// // 'Duration': controller.myList[4].toString(),
|
||||
// // 'totalCost': controller.myList[26].toString(),
|
||||
// // 'Distance': controller.myList[5].toString(),
|
||||
// // 'name': controller.myList[8].toString(),
|
||||
// // 'phone': controller.myList[10].toString(),
|
||||
// // 'email': controller.myList[28].toString(),
|
||||
// // 'WalletChecked': controller.myList[13].toString(),
|
||||
// // 'tokenPassenger': controller.myList[9].toString(),
|
||||
// // 'direction':
|
||||
// // 'https://www.google.com/maps/dir/${controller.myList[0]}/${controller.myList[1]}/',
|
||||
// // 'DurationToPassenger': controller.myList[15].toString(),
|
||||
// // 'rideId': (controller.myList[16].toString()),
|
||||
// // 'passengerId': (controller.myList[7].toString()),
|
||||
// // 'driverId': (controller.myList[18].toString()),
|
||||
// // 'durationOfRideValue': controller.myList[19].toString(),
|
||||
// // 'paymentAmount': controller.myList[2].toString(),
|
||||
// // 'paymentMethod':
|
||||
// // controller.myList[13].toString() == 'true' ? 'visa' : 'cash',
|
||||
// // 'isHaveSteps': controller.myList[20].toString(),
|
||||
// // 'step0': controller.myList[21].toString(),
|
||||
// // 'step1': controller.myList[22].toString(),
|
||||
// // 'step2': controller.myList[23].toString(),
|
||||
// // 'step3': controller.myList[24].toString(),
|
||||
// // 'step4': controller.myList[25].toString(),
|
||||
// // 'passengerWalletBurc': controller.myList[26].toString(),
|
||||
// // 'timeOfOrder': DateTime.now().toString(),
|
||||
// // 'totalPassenger': controller.myList[2].toString(),
|
||||
// // 'carType': controller.myList[31].toString(),
|
||||
// // 'kazan': controller.myList[32].toString(),
|
||||
// // 'startNameLocation': controller.myList[29].toString(),
|
||||
// // 'endNameLocation': controller.myList[30].toString(),
|
||||
// // });
|
||||
|
||||
// // // الانتقال لصفحة تتبع الراكب
|
||||
// // Get.back(); // يغلق صفحة الطلب الحالية
|
||||
// // Get.to(() => PassengerLocationMapPage(),
|
||||
// // arguments: box.read(BoxName.rideArguments));
|
||||
// // Log.print(
|
||||
// // 'box.read(BoxName.rideArguments): ${box.read(BoxName.rideArguments)}');
|
||||
// // }
|
||||
|
||||
// // void _handleRefuseOrder(OrderRequestController controller, String rideId) {
|
||||
// controller.endTimer();
|
||||
// // controller.refuseOrder(rideId);
|
||||
|
||||
// // تسجيل الرفض في الإشعارات المحلية للسائق
|
||||
// controller.addRideToNotificationDriverString(
|
||||
// rideId,
|
||||
// _getData(29),
|
||||
// _getData(30),
|
||||
// '${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}',
|
||||
// '${DateTime.now().hour}:${DateTime.now().minute}',
|
||||
// _getData(2),
|
||||
// _getData(7),
|
||||
// 'wait',
|
||||
// _getData(31),
|
||||
// _getData(33),
|
||||
// _getData(2),
|
||||
// _getData(5),
|
||||
// _getData(4));
|
||||
|
||||
// // الخروج من الصفحة بعد الرفض
|
||||
// Get.back();
|
||||
// }
|
||||
// // }
|
||||
228
siro_driver/lib/views/home/Captin/orderCaptin/test_order_page.dart
Executable file
228
siro_driver/lib/views/home/Captin/orderCaptin/test_order_page.dart
Executable file
@@ -0,0 +1,228 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_driver/constant/api_key.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../controller/home/captin/home_captain_controller.dart';
|
||||
|
||||
class OrderRequestPageTest extends StatefulWidget {
|
||||
const OrderRequestPageTest({super.key});
|
||||
|
||||
@override
|
||||
State<OrderRequestPageTest> createState() => _OrderRequestPageTestState();
|
||||
}
|
||||
|
||||
class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
|
||||
late OrderRequestController _orderRequestController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize the controller and process arguments
|
||||
_initializeController();
|
||||
}
|
||||
|
||||
void _initializeController() {
|
||||
// Get the controller or create a new one if not exists
|
||||
_orderRequestController = Get.put(OrderRequestController());
|
||||
|
||||
// Process arguments passed to the page
|
||||
final arguments = Get.arguments;
|
||||
final myListString = arguments['myListString'];
|
||||
var myList =
|
||||
arguments['DriverList'] == null || arguments['DriverList'].isEmpty
|
||||
? jsonDecode(myListString)
|
||||
: arguments['DriverList'];
|
||||
|
||||
// Parse coordinates and prepare map data
|
||||
_orderRequestController.parseCoordinates(myList);
|
||||
|
||||
// Start timer and calculate fuel consumption
|
||||
_orderRequestController.startTimer(
|
||||
myList[6].toString(),
|
||||
myList[16].toString(),
|
||||
);
|
||||
_orderRequestController.calculateConsumptionFuel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Container(
|
||||
color: const Color.fromARGB(255, 241, 238, 238),
|
||||
child: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Get.height * .33,
|
||||
child: Obx(() => IntaleqMap(
|
||||
apiKey: AK.mapAPIKEY,
|
||||
initialCameraPosition: CameraPosition(
|
||||
zoom: 12,
|
||||
target:
|
||||
Get.find<HomeCaptainController>().myLocation,
|
||||
),
|
||||
cameraTargetBounds: CameraTargetBounds(
|
||||
_orderRequestController.mapBounds.value),
|
||||
myLocationButtonEnabled: true,
|
||||
// trafficEnabled: false,
|
||||
// buildingsEnabled: false,
|
||||
// mapToolbarEnabled: true,
|
||||
myLocationEnabled: true,
|
||||
markers: _orderRequestController.markers.value,
|
||||
polylines: _orderRequestController.polylines.value,
|
||||
onMapCreated: (IntaleqMapController controller) {
|
||||
_orderRequestController.mapController.value =
|
||||
controller;
|
||||
},
|
||||
onCameraMove: (CameraPosition position) {
|
||||
_orderRequestController
|
||||
.updateCameraPosition(position);
|
||||
},
|
||||
)),
|
||||
),
|
||||
// Rest of your UI components
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OrderRequestController extends GetxController {
|
||||
// Reactive variables for map-related data
|
||||
Rx<LatLngBounds> mapBounds = Rx<LatLngBounds>(LatLngBounds(
|
||||
southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)));
|
||||
|
||||
Rx<Set<Marker>> markers = Rx<Set<Marker>>({});
|
||||
Rx<Set<Polyline>> polylines = Rx<Set<Polyline>>({});
|
||||
|
||||
Rx<IntaleqMapController?> mapController = Rx<IntaleqMapController?>(null);
|
||||
|
||||
// Icons for start and end markers
|
||||
late InlqBitmap startIcon;
|
||||
late InlqBitmap endIcon;
|
||||
|
||||
// Coordinates for passenger location and destination
|
||||
Rx<LatLng?> passengerLocation = Rx<LatLng?>(null);
|
||||
Rx<LatLng?> passengerDestination = Rx<LatLng?>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// Initialize marker icons
|
||||
_initializeMarkerIcons();
|
||||
}
|
||||
|
||||
void _initializeMarkerIcons() async {
|
||||
// Load custom marker icons
|
||||
startIcon = InlqBitmap.fromAsset('assets/start_marker.png');
|
||||
endIcon = InlqBitmap.fromAsset('assets/end_marker.png');
|
||||
}
|
||||
|
||||
void parseCoordinates(List myList) {
|
||||
// Parse coordinates from the input list
|
||||
var cords = myList[0].split(',');
|
||||
var cordDestination = myList[1].split(',');
|
||||
|
||||
double latPassengerLocation = double.parse(cords[0]);
|
||||
double lngPassengerLocation = double.parse(cords[1]);
|
||||
double latPassengerDestination = double.parse(cordDestination[0]);
|
||||
double lngPassengerDestination = double.parse(cordDestination[1]);
|
||||
|
||||
// Update passenger location and destination
|
||||
passengerLocation.value =
|
||||
LatLng(latPassengerLocation, lngPassengerLocation);
|
||||
passengerDestination.value =
|
||||
LatLng(latPassengerDestination, lngPassengerDestination);
|
||||
|
||||
// Create markers
|
||||
_createMarkers();
|
||||
|
||||
// Create polyline
|
||||
_createPolyline();
|
||||
|
||||
// Calculate map bounds
|
||||
_calculateMapBounds();
|
||||
}
|
||||
|
||||
void _createMarkers() {
|
||||
if (passengerLocation.value == null || passengerDestination.value == null)
|
||||
return;
|
||||
|
||||
markers.value = {
|
||||
Marker(
|
||||
markerId: MarkerId('MyLocation'.tr),
|
||||
position: passengerLocation.value!,
|
||||
draggable: true,
|
||||
icon: startIcon,
|
||||
),
|
||||
Marker(
|
||||
markerId: MarkerId('Destination'.tr),
|
||||
position: passengerDestination.value!,
|
||||
draggable: true,
|
||||
icon: endIcon,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
void _createPolyline() {
|
||||
if (passengerLocation.value == null || passengerDestination.value == null)
|
||||
return;
|
||||
|
||||
polylines.value = {
|
||||
Polyline(
|
||||
zIndex: 1,
|
||||
// consumeTapEvents: true,
|
||||
geodesic: true,
|
||||
// endCap: Cap.buttCap,
|
||||
// startCap: Cap.buttCap,
|
||||
visible: true,
|
||||
polylineId: const PolylineId('routeOrder'),
|
||||
points: [passengerLocation.value!, passengerDestination.value!],
|
||||
color: AppColor.primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
void _calculateMapBounds() {
|
||||
if (passengerLocation.value == null || passengerDestination.value == null)
|
||||
return;
|
||||
|
||||
double minLatitude = math.min(passengerLocation.value!.latitude,
|
||||
passengerDestination.value!.latitude);
|
||||
double maxLatitude = math.max(passengerLocation.value!.latitude,
|
||||
passengerDestination.value!.latitude);
|
||||
double minLongitude = math.min(passengerLocation.value!.longitude,
|
||||
passengerDestination.value!.longitude);
|
||||
double maxLongitude = math.max(passengerLocation.value!.longitude,
|
||||
passengerDestination.value!.longitude);
|
||||
|
||||
mapBounds.value = LatLngBounds(
|
||||
southwest: LatLng(minLatitude, minLongitude),
|
||||
northeast: LatLng(maxLatitude, maxLongitude),
|
||||
);
|
||||
}
|
||||
|
||||
void updateCameraPosition(CameraPosition position) {
|
||||
// Implement any specific logic for camera position updates
|
||||
}
|
||||
|
||||
void startTimer(String param1, String param2) {
|
||||
// Implement timer start logic
|
||||
}
|
||||
|
||||
void calculateConsumptionFuel() {
|
||||
// Implement fuel consumption calculation
|
||||
}
|
||||
}
|
||||
237
siro_driver/lib/views/home/Captin/orderCaptin/vip_order_page.dart
Executable file
237
siro_driver/lib/views/home/Captin/orderCaptin/vip_order_page.dart
Executable file
@@ -0,0 +1,237 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_driver/constant/box_name.dart';
|
||||
import 'package:siro_driver/constant/links.dart';
|
||||
import 'package:siro_driver/controller/functions/crud.dart';
|
||||
import 'package:siro_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../main.dart';
|
||||
import '../../../../print.dart';
|
||||
|
||||
class VipOrderPage extends StatelessWidget {
|
||||
const VipOrderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(VipOrderController());
|
||||
return MyScafolld(
|
||||
title: 'VIP Order'.tr,
|
||||
body: [
|
||||
GetBuilder<VipOrderController>(builder: (vipOrderController) {
|
||||
if (vipOrderController.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (vipOrderController.tripData.isEmpty) {
|
||||
return Center(
|
||||
child: Text('No orders available'.tr),
|
||||
);
|
||||
}
|
||||
final order = vipOrderController.tripData[0];
|
||||
Log.print('order: ${order}');
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Passenger Section
|
||||
_buildSectionTitle('Passenger Information'.tr),
|
||||
_buildInfoCard(
|
||||
children: [
|
||||
_buildDetailRow('Name'.tr,
|
||||
'${order['passengerName'] ?? 'Unknown'} ${order['passengerLastName'] ?? ''}'),
|
||||
_buildDetailRow(
|
||||
'Phone'.tr, order['passengerPhone'] ?? 'Unknown'),
|
||||
_buildDetailRow(
|
||||
'Gender'.tr, order['passengergender'] ?? 'Unknown'),
|
||||
_buildDetailRow('time Selected'.tr,
|
||||
order['timeSelected'] ?? 'Unknown'),
|
||||
_buildDetailRow(
|
||||
'Ride Status'.tr, order['status'] ?? 'Unknown'),
|
||||
// _buildDetailRow('Ride Status'.tr,
|
||||
// vipOrderController.myList[4] ?? 'Unknown'),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// print(vipOrderController.myList);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Action Buttons
|
||||
_buildActionButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
isleading: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSectionTitle(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoCard({required List<Widget> children}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _onReject(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Reject'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _onApply(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Apply'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onReject(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Ride Rejected'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onApply(BuildContext context) {
|
||||
// TODO: Implement application logic
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Ride Applied'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class VipOrderController extends GetxController {
|
||||
bool isLoading = false;
|
||||
List tripData = [];
|
||||
|
||||
fetchOrder() async {
|
||||
isLoading = true; // Set loading state
|
||||
update(); // Notify listeners
|
||||
var res = await CRUD().get(link: AppLink.getMishwariDriver, payload: {
|
||||
'driverId': box.read(BoxName.driverID).toString(),
|
||||
});
|
||||
if (res != 'failure') {
|
||||
tripData = jsonDecode(res)['message'];
|
||||
} else {
|
||||
tripData = [];
|
||||
}
|
||||
isLoading = false; // Loading complete
|
||||
update(); // Notify listeners
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
fetchOrder();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
46
siro_driver/lib/views/home/Captin/passportimage.dart
Executable file
46
siro_driver/lib/views/home/Captin/passportimage.dart
Executable file
@@ -0,0 +1,46 @@
|
||||
// import 'dart:io';
|
||||
//
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import '../../../controller/functions/ocr_controller.dart';
|
||||
//
|
||||
// class PassportDataExtractorWidget extends StatelessWidget {
|
||||
// final PassportDataExtractor passportDataExtractor =
|
||||
// Get.put(PassportDataExtractor());
|
||||
// final PassportDataController controller = Get.put(PassportDataController());
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: const Text('Passport Data Extractor'),
|
||||
// ),
|
||||
// body: Column(
|
||||
// children: [
|
||||
// ElevatedButton(
|
||||
// onPressed: controller.extractDataAndDrawBoundingBoxes,
|
||||
// child: const Text('Extract Data'),
|
||||
// ),
|
||||
// Expanded(
|
||||
// child: Center(
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// GetBuilder<PassportDataController>(
|
||||
// builder: (controller) => CustomPaint(
|
||||
// painter: BoundingBoxPainter(
|
||||
// controller.extractedTextWithCoordinates),
|
||||
// child: GetBuilder<PassportDataExtractor>(
|
||||
// builder: (controller) =>
|
||||
// Image.file(File(passportDataExtractor.image!.path)),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
175
siro_driver/lib/views/home/Captin/text_scanner.dart
Executable file
175
siro_driver/lib/views/home/Captin/text_scanner.dart
Executable file
@@ -0,0 +1,175 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_driver/views/widgets/mycircular.dart';
|
||||
|
||||
import '../../../controller/functions/ocr_controller.dart';
|
||||
|
||||
class TextRecognizerAPI extends StatelessWidget {
|
||||
const TextRecognizerAPI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(ScanDocumentsByApi());
|
||||
return GetBuilder<ScanDocumentsByApi>(
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Api'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
controller.matchFaceApi();
|
||||
},
|
||||
icon: const Icon(Icons.face),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: controller.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.memory(
|
||||
controller.imagePortrait,
|
||||
width: 60,
|
||||
),
|
||||
Image.memory(
|
||||
controller.imageSignature,
|
||||
width: 60,
|
||||
),
|
||||
Image.memory(
|
||||
controller.imageDocumentFrontSide,
|
||||
width: 250,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(controller.responseMap['authenticity_meta']
|
||||
.toString()),
|
||||
Text(
|
||||
'countryName: ${controller.responseMap['data']['countryName'].toString()}'),
|
||||
Text(
|
||||
'Expiry Date: ${controller.responseMap['data']['ocr']['dateOfExpiry'].toString()}'),
|
||||
// Add more Text widgets to display other record values
|
||||
Text(
|
||||
'Address: ${controller.responseMap['data']['ocr']['address'].toString()}'),
|
||||
Text(
|
||||
'City: ${controller.responseMap['data']['ocr']['addressCity'].toString()}'),
|
||||
Text(
|
||||
'Jurisdiction Code: ${controller.responseMap['data']['ocr']['addressJurisdictionCode'].toString()}'),
|
||||
Text(
|
||||
'Postal Code: ${controller.responseMap['data']['ocr']['addressPostalCode'].toString()}'),
|
||||
Text(
|
||||
'Street: ${controller.responseMap['data']['ocr']['addressStreet'].toString()}'),
|
||||
Text(
|
||||
'Date of Birth: ${controller.responseMap['data']['ocr']['dateOfBirth'].toString()}'),
|
||||
Text(
|
||||
'Date of Issue: ${controller.responseMap['data']['ocr']['dateOfIssue'].toString()}'),
|
||||
Text(
|
||||
'Drivers License Class: ${controller.responseMap['data']['ocr']['dlClass'].toString()}'),
|
||||
Text(
|
||||
'Document Number: ${controller.responseMap['data']['ocr']['documentNumber'].toString()}'),
|
||||
Text(
|
||||
'Eye Color: ${controller.responseMap['data']['ocr']['eyesColor'].toString()}'),
|
||||
Text(
|
||||
'Given Names: ${controller.responseMap['data']['ocr']['givenNames'].toString()}'),
|
||||
Text(
|
||||
'Height: ${controller.responseMap['data']['ocr']['height'].toString()}'),
|
||||
Text(
|
||||
'Issuing State Code: ${controller.responseMap['data']['ocr']['issuingStateCode'].toString()}'),
|
||||
Text(
|
||||
'Name: ${controller.responseMap['data']['ocr']['name'].toString()}'),
|
||||
Text(
|
||||
'Sex: ${controller.responseMap['data']['ocr']['sex'].toString()}'),
|
||||
Text(
|
||||
'Surname: ${controller.responseMap['data']['ocr']['surname'].toString()}'),
|
||||
Text(
|
||||
'Valid State: ${controller.responseMap['data']['ocr']['validState'].toString()}'),
|
||||
],
|
||||
),
|
||||
))));
|
||||
}
|
||||
}
|
||||
// class TextExtractionView extends StatelessWidget {
|
||||
// TextExtractionView({super.key});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// Get.put(TextExtractionController());
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: const Text('Text Extraction'),
|
||||
// ),
|
||||
// body: GetBuilder<TextExtractionController>(builder: (controller) {
|
||||
// return Center(
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// ElevatedButton(
|
||||
// onPressed: () {},
|
||||
// child: const Text('Pick Image and Extract Text'),
|
||||
// ),
|
||||
// const SizedBox(height: 20),
|
||||
// controller.isloading
|
||||
// ? const MyCircularProgressIndicator()
|
||||
// : Text(controller.extractedText),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class TextRecognizerWidget extends StatelessWidget {
|
||||
// const TextRecognizerWidget({super.key});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// Get.put(TextMLGoogleRecognizerController());
|
||||
// return GetBuilder<TextMLGoogleRecognizerController>(
|
||||
// builder: (controller) => Scaffold(
|
||||
// appBar: AppBar(),
|
||||
// body: Center(
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text('${controller.decode['DRIVER_LICENSE'].toString()}'),
|
||||
// Text('DL: ${controller.decode['dl_number'].toString()}'),
|
||||
// Text(
|
||||
// 'Expiry Date: ${controller.decode['expiry_date'].toString()}'),
|
||||
// Text('Last Name: ${controller.decode['lastName'].toString()}'),
|
||||
// Text(
|
||||
// 'First Name: ${controller.decode['firstName'].toString()}'),
|
||||
// Text('Address: ${controller.decode['address'].toString()}'),
|
||||
// Text('Date of Birth: ${controller.decode['dob'].toString()}'),
|
||||
// Text('RSTR: ${controller.decode['rstr'].toString()}'),
|
||||
// Text('Class: ${controller.decode['class'].toString()}'),
|
||||
// Text('End: ${controller.decode['end'].toString()}'),
|
||||
// Text('DD: ${controller.decode['dd'].toString()}'),
|
||||
// Text('Sex: ${controller.decode['sex'].toString()}'),
|
||||
// Text('Hair: ${controller.decode['hair'].toString()}'),
|
||||
// Text('Eyes: ${controller.decode['eyes'].toString()}'),
|
||||
// // and so on for other fields
|
||||
// ],
|
||||
// ))));
|
||||
// }
|
||||
// }
|
||||
|
||||
// class PassportPage extends StatelessWidget {
|
||||
// PassportPage({super.key});
|
||||
// PassportRecognizerController passportRecognizerController =
|
||||
// Get.put(PassportRecognizerController());
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: const Text('Driver License'),
|
||||
// ),
|
||||
// body: const Center(child: Text('data')));
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user