first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('About Us'.tr),
),
child: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Company Logo
Center(
child: Image.asset(
'assets/images/logo.gif', // Replace with your logo image asset path
height: 80.0,
),
),
const SizedBox(height: 20),
// Company Name and Introduction
Text(
'Intaleq LLC',
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
"Syria's pioneering ride-sharing service, proudly developed by Arabian and local owners. We prioritize being near you both our valued passengers and our dedicated captains."
.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
// Key Features Section
Text(
'Why Choose Intaleq?'.tr,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
textAlign: TextAlign.center,
),
const SizedBox(height: 15),
// Nearest Availability
Row(
children: [
const Icon(CupertinoIcons.location_solid,
color: CupertinoColors.activeBlue),
const SizedBox(width: 10),
Expanded(
child: Text(
'Closest to You'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Text(
'We connect you with the nearest drivers for faster pickups and quicker journeys.'
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(color: CupertinoColors.secondaryLabel),
),
const SizedBox(height: 20),
// High-Level Security
Row(
children: [
const Icon(CupertinoIcons.shield_fill,
color: CupertinoColors.activeGreen),
const SizedBox(width: 10),
Expanded(
child: Text(
'Uncompromising Security'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(CupertinoIcons.person_2_fill,
size: 18, color: CupertinoColors.activeGreen),
const SizedBox(width: 5),
Text(
'Lady Captains Available'.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(fontSize: 15),
),
],
),
const SizedBox(height: 5),
Row(
children: [
const Icon(CupertinoIcons.recordingtape,
size: 18, color: CupertinoColors.activeGreen),
const SizedBox(width: 5),
Text(
'Recorded Trips (Voice & AI Analysis)'.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(fontSize: 15),
),
],
),
],
),
const SizedBox(height: 20),
// Fast Support
Row(
children: [
const Icon(CupertinoIcons.bolt_horizontal_fill,
color: CupertinoColors.systemOrange),
const SizedBox(width: 10),
Expanded(
child: Text(
'Fastest Complaint Response'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Text(
'Our dedicated customer service team ensures swift resolution of any issues.'
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(color: CupertinoColors.secondaryLabel),
),
const SizedBox(height: 20),
// Affordable Pricing
Row(
children: [
const Icon(CupertinoIcons.money_dollar_circle_fill,
color: CupertinoColors.activeBlue),
const SizedBox(width: 10),
Expanded(
child: Text(
'Affordable for Everyone'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Text(
'Enjoy competitive prices across all trip options, making travel accessible.'
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(color: CupertinoColors.secondaryLabel),
),
const SizedBox(height: 20),
// Trip Options
Row(
children: [
const Icon(CupertinoIcons.car_detailed,
color: CupertinoColors.systemPurple),
const SizedBox(width: 10),
Expanded(
child: Text(
'Variety of Trip Choices'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Text(
'Choose the trip option that perfectly suits your needs and preferences.'
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(color: CupertinoColors.secondaryLabel),
),
const SizedBox(height: 20),
// Passenger Choice
Row(
children: [
Icon(CupertinoIcons.hand_draw_fill,
color: CupertinoColors.systemGreen),
const SizedBox(width: 10),
Expanded(
child: Text(
'Your Choice, Our Priority'.tr,
style: CupertinoTheme.of(context).textTheme.textStyle,
),
),
],
),
const SizedBox(height: 10),
Text(
'Because we are near, you have the flexibility to choose the ride that works best for you.'
.tr,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(color: CupertinoColors.secondaryLabel),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,360 @@
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../../controller/functions/tts.dart';
import '../../../controller/home/contact_us_controller.dart';
import '../../widgets/elevated_btn.dart';
class ContactUsPage extends StatelessWidget {
ContactUsPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(ContactUsController());
final isDark = Theme.of(context).brightness == Brightness.dark;
return MyScafolld(
title: "Contact Us".tr,
isleading: true,
body: [
// Background subtle gradient/shape
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF0D0D14), const Color(0xFF161622)]
: [const Color(0xFFF8F9FF), const Color(0xFFEFF1FB)],
),
),
),
),
SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Hero Section ──────────────────────────────────────────
_buildHeroSection(isDark),
const SizedBox(height: 24),
// ── Availability Status ────────────────────────────────────
_buildAvailabilityStatus(controller, isDark),
const SizedBox(height: 24),
// ── Support Actions ────────────────────────────────────────
Text(
"Reach out to us via".tr,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold,
fontSize: 16,
color: isDark ? Colors.white70 : Colors.black54,
),
),
const SizedBox(height: 12),
_buildContactCards(controller, isDark),
const SizedBox(height: 32),
// ── About Section ──────────────────────────────────────────
_buildAboutSection(isDark),
const SizedBox(height: 40),
],
),
),
),
],
);
}
Widget _buildHeroSection(bool isDark) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: isDark ? Colors.white12 : Colors.black.withOpacity(0.05),
),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black26 : Colors.black.withOpacity(0.03),
blurRadius: 20,
offset: const Offset(0, 10),
)
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isDark ? Colors.white10 : Colors.white,
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/logo.gif', height: 80),
),
),
const SizedBox(height: 16),
Text(
"Intaleq Support".tr,
style: AppStyle.headTitle2.copyWith(
fontSize: 22,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : AppColor.primaryColor,
),
),
const SizedBox(height: 4),
Text(
"We're here to help you 24/7".tr,
style: TextStyle(
color: isDark ? Colors.white54 : Colors.black45,
fontSize: 14,
),
),
],
),
);
}
Widget _buildAvailabilityStatus(ContactUsController controller, bool isDark) {
final isOpen = controller.isWorkTime;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: (isOpen ? AppColor.greenColor : Colors.orange).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: (isOpen ? AppColor.greenColor : Colors.orange).withOpacity(0.2),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isOpen ? AppColor.greenColor : Colors.orange,
),
child: Icon(
isOpen ? Icons.check_circle_outline : Icons.access_time,
color: Colors.white,
size: 18,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isOpen ? "Support is currently Online".tr : "Support is Away".tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isOpen ? AppColor.greenColor : Colors.orange,
),
),
Text(
"${"Working Hours:".tr} ${controller.workHoursString}",
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.white60 : Colors.black54,
),
),
],
),
),
],
),
);
}
Widget _buildContactCards(ContactUsController controller, bool isDark) {
return Column(
children: [
_ContactCard(
title: "Voice Call".tr,
subtitle: "Direct talk with our team".tr,
icon: Icons.phone_in_talk_outlined,
color: AppColor.primaryColor,
isDark: isDark,
enabled: controller.isWorkTime,
onTap: controller.makeCall,
trailing: controller.isWorkTime ? null : Icon(Icons.lock_clock_outlined, size: 20, color: isDark ? Colors.white24 : Colors.black26),
),
const SizedBox(height: 12),
_ContactCard(
title: "WhatsApp".tr,
subtitle: "Chat with us anytime".tr,
icon: FontAwesome.whatsapp,
color: AppColor.greenColor,
isDark: isDark,
onTap: controller.sendWhatsApp,
),
const SizedBox(height: 12),
_ContactCard(
title: "Email Support".tr,
subtitle: "For official inquiries".tr,
icon: Icons.alternate_email_outlined,
color: AppColor.redColor,
isDark: isDark,
onTap: controller.sendEmail,
),
],
);
}
Widget _buildAboutSection(bool isDark) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.02) : Colors.black.withOpacity(0.02),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: isDark ? Colors.white10 : Colors.black12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"About Intaleq".tr,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: isDark ? Colors.white : Colors.black87,
),
),
IconButton(
onPressed: () {
Get.find<TextToSpeechController>().speakText(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience.'
.tr,
);
},
icon: Icon(Icons.volume_up_outlined, color: AppColor.primaryColor),
),
],
),
const SizedBox(height: 12),
Text(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr,
style: TextStyle(
color: isDark ? Colors.white70 : Colors.black54,
fontSize: 14,
height: 1.6,
),
),
],
),
);
}
}
class _ContactCard extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final bool isDark;
final bool enabled;
final VoidCallback onTap;
final Widget? trailing;
const _ContactCard({
required this.title,
required this.subtitle,
required this.icon,
required this.color,
required this.isDark,
this.enabled = true,
required this.onTap,
this.trailing,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: enabled ? onTap : null,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: enabled
? (isDark ? Colors.white.withOpacity(0.05) : Colors.white)
: (isDark ? Colors.white.withOpacity(0.02) : Colors.grey.withOpacity(0.05)),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: enabled
? (isDark ? Colors.white12 : Colors.black.withOpacity(0.05))
: Colors.transparent,
),
boxShadow: enabled ? [
BoxShadow(
color: isDark ? Colors.black12 : Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
)
] : null,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: (enabled ? color : Colors.grey).withOpacity(0.12),
borderRadius: BorderRadius.circular(14),
),
child: Icon(icon, color: enabled ? color : Colors.grey, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: enabled
? (isDark ? Colors.white : Colors.black87)
: Colors.grey,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white38 : Colors.black38,
),
),
],
),
),
trailing ?? Icon(
Icons.arrow_forward_ios,
size: 14,
color: isDark ? Colors.white24 : Colors.black12,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,228 @@
import 'package:siro_rider/views/home/HomePage/contact_us.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class FrequentlyQuestionsPage extends StatelessWidget {
const FrequentlyQuestionsPage({super.key});
void _showAnswerDialog(BuildContext context, String question, String answer) {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(question,
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.indigo)),
content: Text(answer),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: Colors.indigo,
middle: Text(
'Frequently Asked Questions'.tr,
style: const TextStyle(color: Colors.white),
),
),
child: SafeArea(
child: ListView(
children: <Widget>[
CupertinoListSection.insetGrouped(
header: Text(
'Getting Started'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.car_detailed,
color: Colors.indigo,
),
title: Text('How do I request a ride?'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'How do I request a ride?'.tr,
'Simply open the Intaleq app, enter your destination, and tap "Request Ride". The app will connect you with a nearby driver.'
.tr,
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'Vehicle Options'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.car_fill,
color: Colors.blue,
),
title: Text('What types of vehicles are available?'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'What types of vehicles are available?'.tr,
'Intaleq offers a variety of options including Economy, Comfort, and Luxury to suit your needs and budget.'
.tr,
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'Payments'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.creditcard,
color: Colors.green,
),
title: Text('How can I pay for my ride?'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'How can I pay for my ride?'.tr,
'You can pay for your ride using cash or credit/debit card. You can select your preferred payment method before confirming your ride.'
.tr,
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'Ride Management'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.xmark_circle_fill,
color: Colors.red,
),
title: Text('Can I cancel my ride?'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'Can I cancel my ride?'.tr,
'Yes, you can cancel your ride, but please note that cancellation fees may apply depending on how far in advance you cancel.'
.tr,
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'For Drivers'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.person_crop_circle_fill,
color: Colors.orange,
),
title: Text('Driver Registration & Requirements'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text('Driver Registration'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.indigo)),
content: Text(
'To register as a driver or learn about the requirements, please visit our website or contact Intaleq support directly.'
.tr),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () {
Get.to(() => ContactUsPage());
// Optionally, you can open a URL here
},
child: Text('Visit Website/Contact Support'.tr),
),
CupertinoDialogAction(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Close'.tr),
),
],
),
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'Communication'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.chat_bubble_2_fill,
color: Colors.purple,
),
title: Text(
'How do I communicate with the other party (passenger/driver)?'
.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'How do I communicate with the other party (passenger/driver)?'
.tr,
'You can communicate with your driver or passenger through the in-app chat feature once a ride is confirmed.'
.tr,
),
),
],
),
CupertinoListSection.insetGrouped(
header: Text(
'Safety & Security'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: <CupertinoListTile>[
CupertinoListTile(
leading: const Icon(
CupertinoIcons.shield_fill,
color: Colors.teal,
),
title: Text('What safety measures does Intaleq offer?'.tr),
trailing: const CupertinoListTileChevron(),
onTap: () => _showAnswerDialog(
context,
'What safety measures does Intaleq offer?'.tr,
'Intaleq offers various safety features including driver verification, in-app trip tracking, emergency contact options, and the ability to share your trip status with trusted contacts.'
.tr,
),
),
],
),
const SizedBox(height: 20), // Add some bottom padding
],
),
),
);
}
}

View File

@@ -0,0 +1,577 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/profile/invit_controller.dart';
import '../../../print.dart';
class ShareAppPage extends StatelessWidget {
final InviteController controller = Get.put(InviteController());
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.secondaryColor,
appBar: AppBar(
backgroundColor: AppColor.secondaryColor,
elevation: 0,
title: Text(
'Invite'.tr,
style: AppStyle.headTitle2.copyWith(fontSize: 20),
),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: AppColor.cyanBlue),
onPressed: () => Get.back(),
),
),
body: SafeArea(
child: GetBuilder<InviteController>(
builder: (controller) {
return Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: _buildPassengerTab(context),
),
),
],
);
},
),
),
);
}
Widget _buildPassengerTab(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
"Share this code with your friends and earn rewards when they use it!"
.tr,
textAlign: TextAlign.center,
style: TextStyle(
color: AppColor.grayColor,
fontSize: 13,
),
),
],
),
),
const SizedBox(height: 20),
_buildPhoneInput(),
const SizedBox(height: 20),
_buildActionButtonsPassengers(),
const SizedBox(height: 20),
const SizedBox(height: 20),
_buildInvitationsListPassengers(context),
],
);
}
// Widget _buildPhoneInput() {
// return Container(
// decoration: BoxDecoration(
// color: CupertinoColors.systemGrey6,
// borderRadius: BorderRadius.circular(8),
// ),
// child: Row(
// children: [
// Expanded(
// child: CupertinoTextField.borderless(
// controller: controller.invitePhoneController,
// placeholder: 'Enter phone'.tr,
// padding: const EdgeInsets.all(12),
// keyboardType: TextInputType.phone,
// ),
// ),
// CupertinoButton(
// child: const Icon(CupertinoIcons.person_badge_plus,
// color: AppColor.blueColor),
// onPressed: () async {
// await controller.pickContacts();
// if (controller.contacts.isNotEmpty) {
// if (box.read(BoxName.isSavedPhones) == null) {
// controller.savePhoneToServer();
// box.write(BoxName.isSavedPhones, true);
// }
// _showContactsDialog(Get.context!);
// }
// },
// ),
// ],
// ),
// );
// }
Widget _buildPhoneInput() {
return Container(
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.grayColor.withOpacity(0.1)),
),
child: Row(
children: [
Expanded(
child: CupertinoTextField.borderless(
controller: controller.invitePhoneController,
placeholder: 'Enter phone'.tr,
placeholderStyle: TextStyle(
color: AppColor.grayColor.withOpacity(0.5), fontSize: 16),
style: TextStyle(color: AppColor.writeColor, fontSize: 16),
padding: const EdgeInsets.all(14),
keyboardType: TextInputType.phone,
),
),
CupertinoButton(
child: Icon(CupertinoIcons.person_badge_plus,
color: AppColor.cyanBlue),
onPressed: () async {
await controller.pickContacts();
Log.print('contacts: ${controller.contacts}');
if (controller.contacts.isNotEmpty) {
_showContactsDialog(Get
.context!); // Show contacts dialog after loading contacts
} else {
Get.snackbar(
'No contacts available'.tr,
'Please add contacts to your phone.'.tr,
);
}
},
),
],
),
);
}
Widget _buildActionButtonsPassengers() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.primaryColor,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
onPressed: controller.sendInviteToPassenger,
child: Text(
'Send Invite'.tr,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.secondaryColor,
),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.cyanBlue,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
child: Text(
'Show Invitations'.tr,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.secondaryColor,
),
),
onPressed: () async {
controller.fetchDriverStatsPassengers();
},
),
),
),
],
),
);
}
Widget _buildInvitationsListPassengers(BuildContext context) {
return SizedBox(
height: Get.height * .4,
child: controller.driverInvitationDataToPassengers.isEmpty
? Center(
child: Text(
"No invitation found yet!".tr,
style: TextStyle(
color: AppColor.grayColor,
fontSize: 17,
),
),
)
: ListView.builder(
itemCount: controller.driverInvitationDataToPassengers.length,
itemBuilder: (context, index) {
return _buildInvitationItemPassengers(context, index);
},
),
);
}
Widget _buildInvitationItemPassengers(BuildContext context, int index) {
// Extracting the data from the sample JSON-like structure
var invitation = controller.driverInvitationDataToPassengers[index];
int countOfInvitDriver =
int.tryParse(invitation['countOfInvitDriver']?.toString() ?? '0') ?? 0;
double progressValue = (countOfInvitDriver / 10.0).clamp(0.0, 1.0);
return GestureDetector(
onTap: () {
controller.onSelectPassengerInvitation(index);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.grayColor.withOpacity(0.05)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
invitation['passengerName']
.toString(), // Handle null or missing data
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColor.writeColor,
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: progressValue,
backgroundColor: AppColor.grayColor.withOpacity(0.1),
valueColor:
AlwaysStoppedAnimation<Color>(AppColor.primaryColor),
minHeight: 6,
),
),
const SizedBox(height: 4),
Text(
'$countOfInvitDriver / 2 ${'Trip'.tr}', // Show trips completed
style: TextStyle(
fontSize: 13,
color: AppColor.grayColor,
),
),
],
),
),
);
}
Widget _buildPassengerStats(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.grayColor.withOpacity(0.05)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Your Rewards".tr,
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColor.writeColor,
),
),
const SizedBox(height: 16),
_buildStatItem(
context,
"Total Invites".tr,
controller.driverInvitationDataToPassengers[0]['countOfInvitDriver']
.toString(),
),
_buildStatItem(
context,
"Active Users".tr,
controller.driverInvitationDataToPassengers[0]['passengerName']
.toString(),
),
],
),
);
}
Widget _buildStatItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
color: AppColor.writeColor,
fontSize: 15,
),
),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: AppColor.blueColor,
fontSize: 15,
),
),
],
),
);
}
void _showContactsDialog(BuildContext context) {
Get.defaultDialog(
backgroundColor: AppColor.secondaryColor,
title: 'Choose from contact'.tr,
titleStyle: TextStyle(color: AppColor.writeColor),
content: SizedBox(
height: 400,
width: 400,
child: Column(
children: [
// Header with cancel and title
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
// decoration: const BoxDecoration(
// borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
// color: CupertinoColors.systemGrey6,
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// CupertinoButton(
// padding: EdgeInsets.zero,
// child: Text(
// 'Cancel'.tr,
// style: const TextStyle(color: CupertinoColors.systemBlue),
// ),
// onPressed: () => Navigator.pop(context),
// ),
// Container(
// child: Text('Choose from contact'.tr,
// style: AppStyle.title)),
// const SizedBox(width: 60), // Balance for Cancel button
// ],
// ),
// ),
// Contact list
Expanded(
child: ListView.builder(
itemCount: controller.contactMaps.length,
itemBuilder: (context, index) {
final contact = controller.contactMaps[index];
return CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () {
controller.selectPhone(contact['phones'].toString());
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border(
bottom: BorderSide(
color: AppColor.grayColor.withOpacity(0.1),
),
),
),
child: Row(
children: [
// Display contact name and phone number
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
contact['name'],
style: TextStyle(
color: AppColor.writeColor,
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
Text(
(contact['phones'][0].toString()),
style: TextStyle(
color: AppColor.grayColor,
fontSize: 15,
),
),
],
),
),
// Chevron icon for selection
Icon(
CupertinoIcons.chevron_forward,
color: AppColor.grayColor.withOpacity(0.5),
),
],
),
),
);
},
),
),
],
),
),
);
// showCupertinoModalPopup(
// context: context,
// builder: (BuildContext context) => Container(
// height: 400,
// decoration: BoxDecoration(
// color: CupertinoColors.systemBackground,
// borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
// boxShadow: [
// BoxShadow(
// color: CupertinoColors.black.withOpacity(0.2),
// offset: const Offset(0, -4),
// blurRadius: 10,
// ),
// ],
// ),
// child: Column(
// children: [
// // Header with cancel and title
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
// decoration: const BoxDecoration(
// borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
// color: CupertinoColors.systemGrey6,
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// CupertinoButton(
// padding: EdgeInsets.zero,
// child: Text(
// 'Cancel'.tr,
// style: const TextStyle(color: CupertinoColors.systemBlue),
// ),
// onPressed: () => Navigator.pop(context),
// ),
// Container(
// child: Text('Choose from contact'.tr,
// style: AppStyle.title)),
// const SizedBox(width: 60), // Balance for Cancel button
// ],
// ),
// ),
// // Contact list
// Expanded(
// child: ListView.builder(
// itemCount: controller.contactMaps.length,
// itemBuilder: (context, index) {
// final contact = controller.contactMaps[index];
// return CupertinoButton(
// padding: EdgeInsets.zero,
// onPressed: () {
// controller.selectPhone(contact['phones'].toString());
// },
// child: Container(
// padding: const EdgeInsets.symmetric(
// horizontal: 16, vertical: 12),
// decoration: BoxDecoration(
// color: CupertinoColors.systemBackground,
// border: Border(
// bottom: BorderSide(
// color: CupertinoColors.separator.withOpacity(0.5),
// ),
// ),
// ),
// child: Row(
// children: [
// // Display contact name and phone number
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// contact['name'],
// style: const TextStyle(
// color: CupertinoColors.label,
// fontSize: 17,
// fontWeight: FontWeight.w500,
// ),
// ),
// Text(
// controller.formatPhoneNumber(
// contact['phones'][0].toString()),
// style: const TextStyle(
// color: CupertinoColors.secondaryLabel,
// fontSize: 15,
// ),
// ),
// ],
// ),
// ),
// // Chevron icon for selection
// const Icon(
// CupertinoIcons.chevron_forward,
// color: CupertinoColors.systemGrey2,
// ),
// ],
// ),
// ),
// );
// },
// ),
// ),
// ],
// ),
// ),
// );
}
}

View File

@@ -0,0 +1,90 @@
import 'dart:io';
import 'package:siro_rider/controller/home/trip_monitor_controller.dart';
import 'package:siro_rider/env/env.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:vibration/vibration.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../widgets/elevated_btn.dart';
class TripMonitor extends StatelessWidget {
const TripMonitor({super.key});
@override
Widget build(BuildContext context) {
final params = Get.parameters;
// Use params to initialize your controller or pass data
Get.put(TripMonitorController()).init();
return GetBuilder<TripMonitorController>(builder: (tripMonitorController) {
return MyScafolld(
title: 'Trip Monitor'.tr,
body: [
IntaleqMap(
apiKey: Env.mapSaasKey,
onMapCreated: tripMonitorController.onMapCreated,
onStyleLoaded: tripMonitorController.onStyleLoaded,
initialCameraPosition: CameraPosition(
target: tripMonitorController.parentLocation,
zoom: 16,
tilt: 40,
),
markers: tripMonitorController.markers,
),
speedCircle()
],
isleading: true,
);
});
}
}
GetBuilder<TripMonitorController> speedCircle() {
if (Get.find<TripMonitorController>().speed > 100) {
if (Platform.isIOS) {
HapticFeedback.selectionClick();
} else {
Vibration.vibrate(duration: 1000);
}
Get.defaultDialog(
barrierDismissible: false,
titleStyle: AppStyle.title,
title: 'Speed Over'.tr,
middleText: 'Please slow down'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'I will slow down'.tr,
onPressed: () => Get.back(),
),
);
}
return GetBuilder<TripMonitorController>(
builder: (tripMonitorController) {
return Positioned(
bottom: 25,
right: 100,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: tripMonitorController.speed > 100
? Colors.red
: AppColor.secondaryColor,
border: Border.all(width: 3, color: AppColor.redColor),
),
height: 60,
width: 60,
child: Center(
child: Text(
tripMonitorController.speed.toStringAsFixed(0),
style: AppStyle.number,
),
),
),
);
},
);
}

View File

@@ -0,0 +1,97 @@
import 'dart:io';
import 'package:siro_rider/controller/home/trip_monitor_controller.dart';
import 'package:siro_rider/env/env.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:vibration/vibration.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../widgets/elevated_btn.dart';
class TripMonitor extends StatelessWidget {
const TripMonitor({super.key});
@override
Widget build(BuildContext context) {
final params = Get.parameters;
final arguments = Get.arguments as Map<String, dynamic>?;
// Use params or arguments to initialize your controller
final controller = Get.put(TripMonitorController());
controller.init(
rideId: params['rideId'] ?? arguments?['rideId'],
driverId: params['driverId'] ?? arguments?['driverId'],
);
return GetBuilder<TripMonitorController>(builder: (tripMonitorController) {
return MyScafolld(
title: 'Trip Monitor'.tr,
body: [
IntaleqMap(
apiKey: Env.mapSaasKey,
onMapCreated: tripMonitorController.onMapCreated,
onStyleLoaded: controller.onStyleLoaded,
initialCameraPosition: CameraPosition(
target: tripMonitorController.parentLocation,
zoom: 16,
tilt: 40,
),
markers: tripMonitorController.markers,
),
speedCircle()
],
isleading: true,
);
});
}
}
GetBuilder<TripMonitorController> speedCircle() {
if (Get.find<TripMonitorController>().speed > 100) {
if (Platform.isIOS) {
HapticFeedback.selectionClick();
} else {
Vibration.vibrate(duration: 1000);
}
Get.defaultDialog(
barrierDismissible: false,
titleStyle: AppStyle.title,
title: 'Speed Over'.tr,
middleText: 'Please slow down'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'I will slow down'.tr,
onPressed: () => Get.back(),
),
);
}
return GetBuilder<TripMonitorController>(
builder: (tripMonitorController) {
return Positioned(
bottom: 25,
right: 100,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: tripMonitorController.speed > 100
? Colors.red
: AppColor.secondaryColor,
border: Border.all(width: 3, color: AppColor.redColor),
),
height: 60,
width: 60,
child: Center(
child: Text(
tripMonitorController.speed.toStringAsFixed(0),
style: AppStyle.number,
),
),
),
);
},
);
}

View File

@@ -0,0 +1,263 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as path;
import 'package:share_plus/share_plus.dart';
import '../../../controller/functions/audio_record1.dart';
class TripsRecordedPage extends StatelessWidget {
const TripsRecordedPage({super.key});
@override
Widget build(BuildContext context) {
// Ensure the controller is available.
// If it's not initialized elsewhere, you can use Get.put() or Get.lazyPut() here.
// Get.lazyPut(() => AudioRecorderController());
return Scaffold(
appBar: AppBar(
title: Text('Trips recorded'.tr),
backgroundColor: Colors.white,
elevation: 1,
actions: [
GetBuilder<AudioRecorderController>(
builder: (controller) => IconButton(
tooltip: 'Delete All'.tr,
icon: const Icon(Icons.delete_sweep_outlined),
onPressed: () {
_showDeleteConfirmation(context, controller, isDeleteAll: true);
},
),
)
],
),
body: GetBuilder<AudioRecorderController>(
builder: (controller) {
return Column(
children: [
Expanded(
child: _buildRecordingsList(controller),
),
// Show player controls only when a file is selected
if (controller.selectedFilePath != null)
_buildPlayerControls(context, controller),
],
);
},
),
);
}
/// Builds the list of recorded audio files.
Widget _buildRecordingsList(AudioRecorderController controller) {
return FutureBuilder<List<String>>(
future: controller.getRecordedFiles(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'.tr));
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.mic_off_outlined, size: 80, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'No Recordings Found'.tr,
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Text(
'Record your trips to see them here.'.tr,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey[500]),
),
),
],
),
);
}
final recordedFiles = snapshot.data!;
return ListView.builder(
padding: const EdgeInsets.only(top: 8, bottom: 8),
itemCount: recordedFiles.length,
itemBuilder: (context, index) {
final file = recordedFiles[index];
final fileName = path.basename(file);
final isSelected = controller.selectedFilePath == file;
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
elevation: isSelected ? 4 : 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: ListTile(
leading: Icon(
isSelected && controller.isPlaying
? Icons.pause_circle_filled
: Icons.play_circle_fill,
color:
isSelected ? Theme.of(context).primaryColor : Colors.grey,
size: 40,
),
title: Text(fileName,
style: const TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text("Audio Recording".tr),
onTap: () {
if (isSelected) {
controller.isPlaying
? controller.pausePlayback()
: controller.resumePlayback();
} else {
controller.playRecordedFile(file);
}
},
selected: isSelected,
selectedTileColor:
Theme.of(context).primaryColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
);
},
);
},
);
}
/// Builds the player UI at the bottom of the screen.
Widget _buildPlayerControls(
BuildContext context, AudioRecorderController controller) {
final fileName = path.basename(controller.selectedFilePath!);
final positionText = Duration(seconds: controller.currentPosition.toInt())
.toString()
.split('.')
.first
.padLeft(8, '0')
.substring(3);
final durationText = Duration(seconds: controller.totalDuration.toInt())
.toString()
.split('.')
.first
.padLeft(8, '0')
.substring(3);
return Material(
elevation: 10,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(fileName,
style:
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
textAlign: TextAlign.center),
const SizedBox(height: 8),
Row(
children: [
Text(positionText),
Expanded(
child: Slider(
value: (controller.totalDuration > 0)
? controller.currentPosition / controller.totalDuration
: 0.0,
onChanged: (value) {
final newPosition = value * controller.totalDuration;
controller.audioPlayer
.seek(Duration(seconds: newPosition.toInt()));
},
),
),
Text(durationText),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.share_outlined),
tooltip: 'Share'.tr,
onPressed: () {
Share.shareXFiles([XFile(controller.selectedFilePath!)]);
},
iconSize: 28,
),
IconButton(
icon: Icon(controller.isPlaying
? Icons.pause_circle_filled
: Icons.play_circle_filled),
onPressed: () {
controller.isPlaying
? controller.pausePlayback()
: controller.resumePlayback();
},
iconSize: 56,
color: Theme.of(context).primaryColor,
),
IconButton(
icon:
const Icon(Icons.delete_outline, color: Colors.redAccent),
tooltip: 'Delete'.tr,
onPressed: () {
_showDeleteConfirmation(context, controller,
isDeleteAll: false);
},
iconSize: 28,
),
],
)
],
),
),
);
}
/// Shows a confirmation dialog for deleting one or all files.
void _showDeleteConfirmation(
BuildContext context,
AudioRecorderController controller, {
required bool isDeleteAll,
}) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(isDeleteAll
? 'Delete All Recordings?'.tr
: 'Delete Recording?'.tr),
content: Text(isDeleteAll
? 'This action cannot be undone.'.tr
: 'Are you sure you want to delete this file?'.tr),
actions: [
TextButton(
child: Text('Cancel'.tr),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child:
Text('Delete'.tr, style: const TextStyle(color: Colors.red)),
onPressed: () async {
if (isDeleteAll) {
await controller.deleteAllRecordedFiles();
} else {
// NOTE: You may need to add this method to your controller
// if it doesn't exist.
// await controller.deleteFile(controller.selectedFilePath!);
}
Navigator.of(context).pop();
},
),
],
);
},
);
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:siro_rider/constant/colors.dart';
class DrawerMenuPage extends StatelessWidget {
const DrawerMenuPage({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 500,
color: AppColor.secondaryColor.withOpacity(.5),
child: Column(
children: [
Container(
height: 100,
color: AppColor.secondaryColor,
),
Container(
color: Colors.transparent,
)
],
),
);
}
}

View File

@@ -0,0 +1,204 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../controller/functions/crud.dart';
import '../../controller/home/map/map_socket_controller.dart';
import '../../controller/home/map/map_engine_controller.dart';
import '../../controller/home/map/location_search_controller.dart';
import '../../controller/home/map/nearby_drivers_controller.dart';
import '../../controller/home/map/ride_lifecycle_controller.dart';
import '../../controller/home/map/ui_interactions_controller.dart';
import '../../controller/home/map/ride_state.dart';
import '../../main.dart';
import '../../views/home/map_widget.dart/ride_begin_passenger.dart';
import '../../controller/home/menu_controller.dart';
import 'map_widget.dart/apply_order_widget.dart';
import 'map_widget.dart/buttom_sheet_map_show.dart';
import 'map_widget.dart/car_details_widget_to_go.dart';
import 'map_widget.dart/cash_confirm_bottom_page.dart';
import 'map_widget.dart/google_map_passenger_widget.dart';
import 'map_widget.dart/left_main_menu_icons.dart';
import 'map_widget.dart/main_bottom_menu_map.dart';
import 'map_widget.dart/map_menu_widget.dart';
import '../../controller/functions/package_info.dart';
import 'map_widget.dart/passengerRideLoctionWidget.dart';
import 'map_widget.dart/payment_method.page.dart';
import 'map_widget.dart/points_page_for_rider.dart';
import 'map_widget.dart/ride_from_start_app.dart';
import 'map_widget.dart/searching_captain_window.dart';
import 'map_widget.dart/vip_begin.dart';
class MapPagePassenger extends StatelessWidget {
const MapPagePassenger({super.key});
@override
Widget build(BuildContext context) {
Get.find<MapSocketController>();
Get.find<MapEngineController>();
Get.find<LocationSearchController>();
Get.find<NearbyDriversController>();
Get.find<RideLifecycleController>();
Get.find<UiInteractionsController>();
Get.find<MyMenuController>();
Get.find<CRUD>();
WidgetsBinding.instance.addPostFrameCallback((_) {
checkForUpdate(context);
});
return Scaffold(
body: SafeArea(
bottom: true,
child: Stack(
children: [
GoogleMapPassengerWidget(),
// OsmMapPassengerWidget(),
leftMainMenuIcons(),
// PaymobPackage(),
const PickerIconOnMap(),
// PickerAnimtionContainerFormPlaces(),
const MainBottomMenuMap(),
// NewMainBottomSheet(),
buttomSheetMapPage(),
CarDetailsTypeToChoose(),
// const HeaderDestination(),
const BurcMoney(),
// const PromoCode(),
ApplyOrderWidget(),
const MapMenuWidget(),
// hexagonClipper(),
const CancelRidePageShow(),
CashConfirmPageShown(),
const PaymentMethodPage(),
const SearchingCaptainWindow(),
AttributionMap(),
// timerForCancelTripFromPassenger(),
// const DriverTimeArrivePassengerPage(),
// const TimerToPassengerFromDriver(),
const PassengerRideLocationWidget(),
const RideBeginPassenger(),
const VipRideBeginPassenger(),
const RideFromStartApp(),
// cancelRidePage(),
// const MenuIconMapPageWidget(),
PointsPageForRider()
],
),
),
);
}
}
class AttributionMap extends StatelessWidget {
const AttributionMap({
super.key,
});
@override
Widget build(BuildContext context) {
return Positioned(
left: 4,
bottom: 20,
child: RotatedBox(
quarterTurns: 0, // يخلي النص عمودي (من تحت لفوق)
child: Opacity(
opacity: 0.7,
child: Text(
"Intaleq Maps",
// "Intaleq Maps • © OpenStreetMap contributors",
style: TextStyle(
fontSize: 10,
color: Colors.grey[700],
fontWeight: FontWeight.w400,
),
),
),
),
);
}
}
class CancelRidePageShow extends StatelessWidget {
const CancelRidePageShow({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(
builder: (controller) {
// نستخدم RideState Enum لأنه أدق، أو نصلح المنطق النصي
// الشرط:
// 1. يوجد خط مسار
// 2. الحالة ليست "بدأت"
// 3. الحالة ليست "انتهت"
// 4. الحالة ليست "قيد التنفيذ" (لزيادة التأكيد)
// bool showCancelButton = controller.polyLines.isNotEmpty &&
// controller.statusRide != 'Begin' && // استخدمنا &&
// controller.statusRide != 'inProgress' &&
// controller.statusRide != 'Finished';
// يمكنك أيضاً استخدام RideState ليكون أدق:
bool showCancelButton = controller.polyLines.isNotEmpty &&
controller.currentRideState.value != RideState.inProgress &&
controller.currentRideState.value != RideState.finished;
return showCancelButton
? Positioned(
right: box.read(BoxName.lang) != 'ar' ? 10 : null,
left: box.read(BoxName.lang) == 'ar' ? 10 : null,
top: Get.height * .013,
child: GestureDetector(
onTap: () {
// استدعاء دالة الإلغاء
controller.changeCancelRidePageShow();
// ملاحظة: تأكد أن الدالة تظهر ديالوج للتأكيد أولاً ولا تلغي فوراً
},
child: Container(
decoration: BoxDecoration(
color: AppColor.redColor,
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(3),
child: Icon(
Icons.clear,
size: 40,
color: AppColor.secondaryColor,
),
),
),
))
: const SizedBox();
},
);
}
}
class PickerIconOnMap extends StatelessWidget {
const PickerIconOnMap({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(
builder: (controller) => controller.isPickerShown
? Positioned(
bottom: Get.height * .2,
top: 0,
left: 0,
right: 0,
child: controller.isPickerShown
? const Icon(
Icons.add_location,
color: Colors.purple,
)
: const SizedBox(),
)
: const SizedBox());
}
}

View File

@@ -0,0 +1,731 @@
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/links.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:siro_rider/controller/home/map/ride_state.dart';
import 'package:siro_rider/controller/voice_call_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
class ApplyOrderWidget extends StatelessWidget {
const ApplyOrderWidget({super.key});
@override
Widget build(BuildContext context) {
Color parseColor(String colorHex) {
if (colorHex.isEmpty) return Colors.grey;
try {
String processedHex = colorHex.replaceFirst('#', '').trim();
if (processedHex.length == 6) processedHex = 'FF$processedHex';
return Color(int.parse('0x$processedHex'));
} catch (e) {
return Colors.grey;
}
}
return Obx(() {
final controller = Get.find<RideLifecycleController>();
final bool isVisible =
controller.currentRideState.value == RideState.driverApplied ||
controller.currentRideState.value == RideState.driverArrived;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.elasticOut,
// تغيير: جعلنا الإخفاء للأسفل أقل حدة ليكون التحريك أسرع
bottom: isVisible ? 0 : -400,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
boxShadow: [
BoxShadow(
blurRadius: 20,
spreadRadius: 1,
color: Colors.black.withOpacity(0.1),
offset: const Offset(0, -3),
)
],
),
// تغيير: تقليل الحواف الخارجية بشكل كبير
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: GetBuilder<RideLifecycleController>(
builder: (c) {
return Column(
mainAxisSize:
MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة
children: [
// مقبض صغير
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 10), // تقليل المسافة
// 1. [تغيير جوهري] دمج السعر مع الحالة في صف واحد لتوفير المساحة
_buildCompactHeaderRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة
// 2. كرت المعلومات المضغوط
_buildCompactInfoCard(context, c, parseColor),
const SizedBox(height: 10), // مسافة مضغوطة
// 3. أزرار الاتصال (Slim)
_buildCompactButtonsRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة
// 4. شريط الوقت
c.currentRideState.value == RideState.driverArrived
? const DriverArrivePassengerAndWaitMinute()
: const TimeDriverToPassenger(),
],
);
},
),
),
);
});
}
// ---------------------------------------------------------------------------
// [NEW] 1. صف الرأس المضغوط (يحتوي الحالة + الإحصائيات + السعر)
// ---------------------------------------------------------------------------
Widget _buildCompactHeaderRow(
BuildContext context, RideLifecycleController controller) {
// تنسيق السعر
final formatter = NumberFormat("#,###");
String formattedPrice = formatter.format(controller.totalPassenger);
// حساب الدقائق من الوقت المتبقي الحي، وليس ETA الأصلي فقط.
final int secondsToPassenger =
controller.remainingTimeToPassengerFromDriverAfterApplied > 0
? controller.remainingTimeToPassengerFromDriverAfterApplied
: controller.timeToPassengerFromDriverAfterApplied;
final int minutes =
secondsToPassenger <= 0 ? 0 : (secondsToPassenger / 60).ceil();
// تنسيق المسافة
String distanceDisplay = "";
try {
double distMeters = double.parse(controller.distanceByPassenger);
if (distMeters >= 1000) {
distanceDisplay = "${(distMeters / 1000).toStringAsFixed(1)} km";
} else {
distanceDisplay = "${distMeters.toInt()} m";
}
} catch (e) {
distanceDisplay = controller.distanceByPassenger;
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// القسم الأيسر: الحالة + Chips
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Driver is on the way'.tr,
style: AppStyle.subtitle.copyWith(
color: Colors.grey[600],
fontWeight: FontWeight.w600,
fontSize: 13, // تصغير الخط
),
),
const SizedBox(height: 6),
Row(
children: [
_buildMiniStatChip(
icon: Icons.access_time_filled_rounded,
text: minutes > 0 ? "$minutes ${'min'.tr}" : "--",
color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1),
),
const SizedBox(width: 8),
_buildMiniStatChip(
icon: Icons.near_me_rounded,
text: distanceDisplay,
color: Colors.orange[800]!,
bgColor: Colors.orange.withOpacity(0.1),
),
],
),
],
),
),
// القسم الأيمن: السعر (كبير وواضح في الزاوية)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
formattedPrice,
style: AppStyle.title.copyWith(
fontSize: 24, // تصغير من 32 إلى 24
fontWeight: FontWeight.w900,
color: AppColor.primaryColor,
height: 1.0,
),
),
Text(
'SYP'.tr,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
],
),
],
);
}
Widget _buildMiniStatChip({
required IconData icon,
required String text,
required Color color,
required Color bgColor,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 12, color: color), // تصغير الأيقونة
const SizedBox(width: 4),
Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12, // تصغير الخط
),
),
],
),
);
}
// ---------------------------------------------------------------------------
// [MODIFIED] 2. كرت المعلومات المضغوط جداً
// ---------------------------------------------------------------------------
Widget _buildCompactInfoCard(BuildContext context,
RideLifecycleController controller, Color Function(String) parseColor) {
return Container(
// تقليل الحواف الداخلية للكرت
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Column(
children: [
// الصف العلوي: سائق + سيارة
Row(
children: [
// صورة السائق (أصغر)
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2), width: 2),
),
child: CircleAvatar(
radius: 22, // تصغير من 28 إلى 22
backgroundColor: Colors.grey[200],
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) =>
const Icon(Icons.person, color: Colors.grey, size: 20),
),
),
const SizedBox(width: 10),
// معلومات نصية
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName,
style: const TextStyle(
fontSize: 15, // تصغير الخط
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Row(
children: [
const Icon(Icons.star_rounded,
color: Colors.amber, size: 14),
Text(
" ${controller.driverRate}${controller.model}",
style: TextStyle(
fontSize: 12,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
],
),
),
// أيقونة السيارة (أصغر)
_buildMicroCarIcon(controller, parseColor),
],
),
const SizedBox(height: 8),
// لوحة السيارة (شريط نحيف جداً)
_buildSlimLicensePlate(controller.licensePlate),
],
),
);
}
Widget _buildMicroCarIcon(
RideLifecycleController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
return Container(
height: 40, // تصغير من 50
width: 40,
decoration: BoxDecoration(
color: carColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(4),
child: ColorFiltered(
colorFilter: ColorFilter.mode(carColor, BlendMode.srcIn),
child: Image.asset(
box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
fit: BoxFit.contain,
),
),
);
}
Widget _buildSlimLicensePlate(String plateNumber) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8),
decoration: BoxDecoration(
color: Get.isDarkMode ? Colors.grey[850] : const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color:
Get.isDarkMode ? Colors.white10 : Colors.grey.withOpacity(0.3)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
plateNumber,
style: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 18,
fontWeight: FontWeight.w900,
color: AppColor.writeColor,
letterSpacing: 1.5,
),
),
Text("SYR",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: AppColor.writeColor.withOpacity(0.6))),
],
),
);
}
// ---------------------------------------------------------------------------
// [MODIFIED] 3. أزرار الاتصال (Slim Buttons)
// ---------------------------------------------------------------------------
Widget _buildCompactButtonsRow(
BuildContext context, RideLifecycleController controller) {
return SizedBox(
height: 40, // تحديد ارتفاع ثابت وصغير للأزرار
child: Row(
children: [
Expanded(
child: _buildSlimButton(
label: 'Message'.tr, // اختصار الكلمة
icon: Icons.chat_bubble_outline_rounded,
color: AppColor.blueColor,
bgColor: AppColor.blueColor.withOpacity(0.08),
onTap: () => _showContactOptionsDialog(context, controller),
),
),
const SizedBox(width: 10), // تقليل المسافة
Expanded(
child: _buildSlimButton(
label: 'Call'.tr, // اختصار الكلمة
icon: Icons.phone_rounded,
color: Colors.white,
bgColor: AppColor.greenColor,
onTap: () {
HapticFeedback.heavyImpact();
_showCallSelectionDialog(context, controller);
},
isPrimary: true,
),
),
],
),
);
}
void _showCallSelectionDialog(
BuildContext context, RideLifecycleController 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 driver'.tr,
style: const TextStyle(color: Colors.grey, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.greenColor.withOpacity(0.1),
child: Icon(Icons.phone_android_rounded,
color: AppColor.greenColor),
),
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.driverPhone);
},
),
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 passengerId = box.read(BoxName.passengerID).toString();
voiceCtrl.startCall(
rideIdVal: controller.rideId,
driverId: controller.driverId,
passengerId: passengerId,
remoteNameVal: controller.driverName,
);
},
),
],
),
),
),
);
}
Widget _buildSlimButton({
required String label,
required IconData icon,
required Color color,
required Color bgColor,
required VoidCallback onTap,
bool isPrimary = false,
}) {
return ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
backgroundColor: bgColor,
foregroundColor: color,
elevation: isPrimary ? 2 : 0,
padding: EdgeInsets.zero, // إزالة الحواشي الداخلية
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 18, color: color), // تصغير الأيقونة
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14, // تصغير الخط
color: color,
),
),
],
),
);
}
// --- النوافذ المنبثقة للرسائل (نفس الكود السابق مع تحسين بسيط) ---
void _showContactOptionsDialog(
BuildContext context, RideLifecycleController controller) {
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quick Message'.tr,
style: AppStyle.title.copyWith(fontSize: 16)),
const SizedBox(height: 15),
..._buildPredefinedMessages(controller),
const Divider(height: 20),
_buildCustomMessageInput(controller, context),
SizedBox(height: MediaQuery.of(context).viewInsets.bottom),
],
),
),
isScrollControlled: true,
);
}
List<Widget> _buildPredefinedMessages(RideLifecycleController controller) {
const messages = [
'Hello, I\'m at the agreed-upon location',
'I\'m waiting for you',
"How much longer will you be?",
];
return messages
.map((message) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: InkWell(
onTap: () {
_sendMessage(controller, message.tr);
Get.back();
},
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Row(
children: [
Icon(Icons.chat_bubble_outline,
size: 16, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(message.tr,
style: AppStyle.subtitle.copyWith(fontSize: 13))),
],
),
),
),
))
.toList();
}
Widget _buildCustomMessageInput(
RideLifecycleController controller, BuildContext context) {
return Row(
children: [
Expanded(
child: Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
),
child: Form(
key: controller.messagesFormKey,
child: TextFormField(
controller: controller.messageToDriver,
decoration: InputDecoration(
hintText: 'Type your message...'.tr,
hintStyle: TextStyle(color: Colors.grey[500], fontSize: 13),
border: InputBorder.none,
contentPadding: const EdgeInsets.only(bottom: 10),
),
),
),
),
),
const SizedBox(width: 8),
InkWell(
onTap: () {
if (controller.messagesFormKey.currentState!.validate()) {
_sendMessage(controller, controller.messageToDriver.text);
controller.messageToDriver.clear();
Get.back();
}
},
child: CircleAvatar(
backgroundColor: AppColor.primaryColor,
radius: 20,
child:
const Icon(Icons.send_rounded, color: Colors.white, size: 16),
),
),
],
);
}
void _sendMessage(RideLifecycleController controller, String text) async {
try {
await CRUD().post(
link: AppLink.sendChatMessage,
payload: {
'ride_id': controller.rideId.toString(),
'sender_id': box.read(BoxName.passengerID).toString(),
'receiver_id': controller.driverId.toString(),
'sender_type': 'passenger',
'message_content': text.tr,
},
);
} catch (e) {
// Ignore or log error
}
NotificationService.sendNotification(
category: 'MSG_FROM_PASSENGER',
target: controller.driverToken.toString(),
title: text.tr,
body: text.tr,
isTopic: false,
tone: 'ding',
driverList: [],
);
}
}
// -----------------------------------------------------------------------------
// مؤشرات الانتظار والوقت (مضغوطة)
// -----------------------------------------------------------------------------
class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
const DriverArrivePassengerAndWaitMinute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Waiting...'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.orange)),
Text(
controller.stringRemainingTimeDriverWaitPassenger5Minute,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.orange,
fontSize: 12),
),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
backgroundColor: Colors.orange.withOpacity(0.2),
color: Colors.orange,
minHeight: 4,
value:
controller.progressTimerDriverWaitPassenger5Minute.toDouble(),
),
),
],
);
});
}
}
class TimeDriverToPassenger extends StatelessWidget {
const TimeDriverToPassenger({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.timeToPassengerFromDriverAfterApplied <= 0) {
return const SizedBox();
}
return Column(
children: [
// شريط التقدم فقط لأن الوقت والمسافة موجودان بالأعلى
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
color: AppColor.primaryColor,
minHeight: 4,
value: controller.progressTimerToPassengerFromDriverAfterApplied
.toDouble()
.clamp(0.0, 1.0),
),
),
],
);
});
}
}

View File

@@ -0,0 +1,560 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/controller/payment/payment_controller.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
GetBuilder<RideLifecycleController> buttomSheetMapPage() {
Get.put(PaymentController());
return GetBuilder<RideLifecycleController>(
builder: (controller) =>
controller.isBottomSheetShown && controller.rideConfirm == false
? const Positioned(
left: 5,
bottom: 0,
right: 5,
child: Column(
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// double.parse(box.read(BoxName.passengerWalletTotal)) <
// 0 &&
// controller.data.isNotEmpty
// ? Container(
// decoration: AppStyle.boxDecoration
// .copyWith(color: AppColor.redColor),
// height: 50,
// width: Get.width * .94,
// child: Padding(
// padding:
// const EdgeInsets.symmetric(horizontal: 8),
// child: Text(
// 'Your trip cost is'.tr +
// ' ${controller.totalCostPassenger.toStringAsFixed(2)} '
// 'But you have a negative salary of'
// .tr +
// '${double.parse(box.read(BoxName.passengerWalletTotal)).toStringAsFixed(2)}'
// ' in your'
// .tr +
// ' ${AppInformation.appName}'
// ' wallet due to a previous trip.'
// .tr,
// style: AppStyle.subtitle,
// ),
// ))
// : const SizedBox(),
// ],
// ),
// const SizedBox(
// height: 5,
// ),
// AnimatedContainer(
// // clipBehavior: Clip.antiAliasWithSaveLayer,
// curve: Curves.easeInCirc,
// onEnd: () {
// controller.height = 250;
// },
// height: controller.heightBottomSheetShown,
// duration: const Duration(seconds: 2),
// child: Column(
// children: [
// controller.data.isEmpty
// ? const SizedBox()
// : Container(
// // width: Get.width * .9,
// height: 100,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// boxShadow: [
// const BoxShadow(
// color: AppColor.accentColor,
// offset: Offset(2, 2)),
// BoxShadow(
// color: AppColor.accentColor
// .withOpacity(.4),
// offset: const Offset(-2, -2))
// ],
// borderRadius: const BorderRadius.all(
// Radius.circular(15))),
// child: ListView.builder(
// scrollDirection: Axis.horizontal,
// itemCount: controller
// .dataCarsLocationByPassenger.length -
// 1,
// itemBuilder:
// (BuildContext context, int index) {
// return Container(
// color: controller.gender == 'Female'
// ? const Color.fromARGB(
// 255, 246, 52, 181)
// : AppColor.secondaryColor,
// width: Get.width,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// SizedBox(
// width: Get.width * .15,
// child: Padding(
// padding:
// const EdgeInsets.all(8.0),
// child: Image.asset(
// 'assets/images/jeep.png',
// width: 50,
// fit: BoxFit.fill,
// repeat: ImageRepeat.repeatX,
// ),
// ),
// ),
// SizedBox(
// width: Get.width * .55,
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// mainAxisAlignment:
// MainAxisAlignment.spaceEvenly,
// children: [
// Text(
// controller.hours > 0
// ? '${'Your Ride Duration is '.tr}${controller.hours} ${'H and'.tr} ${controller.minutes} ${'m'.tr}'
// : '${'Your Ride Duration is '.tr} ${controller.minutes} m',
// style: AppStyle.subtitle,
// ),
// // Text(
// // '${'You will be thier in'.tr} ${DateFormat('h:mm a').format(controller.newTime)}',
// // style: AppStyle.subtitle,
// // ),
// Text(
// '${'Your trip distance is'.tr} ${controller.distance.toStringAsFixed(2)} ${'KM'.tr}',
// style: AppStyle.subtitle,
// )
// ],
// ),
// ),
// SizedBox(
// width: Get.width * .2,
// child: Padding(
// padding: const EdgeInsets.only(
// right: 5, left: 5),
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.center,
// children: [
// Container(
// width: Get.width * .14,
// height: Get.height * .06,
// decoration: BoxDecoration(
// color: AppColor
// .secondaryColor,
// shape:
// BoxShape.rectangle,
// border: Border.all(
// width: 2,
// color: AppColor
// .greenColor)),
// child: Center(
// child: Text(
// '${'Fee is'.tr} \n${controller.totalPassenger.toStringAsFixed(2)}',
// style:
// AppStyle.subtitle,
// ),
// ),
// ),
// controller.promoTaken
// ? const Icon(
// Icons
// .filter_vintage_rounded,
// color:
// AppColor.redColor,
// )
// : const SizedBox(
// height: 0,
// )
// ],
// ),
// ),
// ),
// ],
// ),
// );
// },
// ),
// ),
// const SizedBox(
// height: 5,
// ),
// Container(
// // height: 130,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// boxShadow: [
// const BoxShadow(
// color: AppColor.accentColor,
// offset: Offset(2, 2)),
// BoxShadow(
// color: AppColor.accentColor.withOpacity(.4),
// offset: const Offset(-2, -2))
// ],
// borderRadius:
// const BorderRadius.all(Radius.circular(15))),
// child: controller.data.isEmpty
// ? const SizedBox()
// : Center(
// child: Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 5),
// child: Column(
// children: [
// Row(
// children: [
// const Icon(
// Icons.location_on,
// color: AppColor.redColor,
// ),
// const SizedBox(
// width: 10,
// ),
// Text(
// 'From : '.tr,
// style: AppStyle.subtitle,
// ),
// Text(
// controller.data[0]
// ['start_address']
// .toString(),
// style: AppStyle.subtitle,
// )
// ],
// ),
// Row(
// children: [
// const Icon(Icons
// .location_searching_rounded),
// const SizedBox(
// width: 10,
// ),
// Text(
// 'To : '.tr,
// style: AppStyle.subtitle,
// ),
// Text(
// controller.data[0]['end_address'],
// style: AppStyle.subtitle,
// ),
// ],
// ),
// const Divider(
// color: AppColor.accentColor,
// thickness: 1,
// height: 2,
// indent: 1,
// ),
// SizedBox(
// height: 40,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// Container(
// decoration: BoxDecoration(
// color:
// AppColor.secondaryColor,
// borderRadius:
// BorderRadius.circular(12),
// // border: Border.all(),
// ),
// child: Row(
// children: [
// Icon(
// Icons.monetization_on,
// color: Colors.green[400],
// ),
// InkWell(
// onTap: () async {
// controller
// .changeCashConfirmPageShown();
// Get.find<
// PaymentController>()
// .getPassengerWallet();
// },
// child: GetBuilder<
// PaymentController>(
// builder: (paymentController) =>
// paymentController
// .isCashChecked
// ? Text(
// 'CASH',
// style: AppStyle
// .title,
// )
// : Text(
// '${AppInformation.appName} Wallet',
// style: AppStyle
// .title,
// ),
// ),
// ),
// ],
// ),
// ),
// const SizedBox(
// width: 40,
// ),
// GetBuilder<PaymentController>(
// builder:
// (paymentController) =>
// Container(
// decoration:
// BoxDecoration(
// color: AppColor
// .secondaryColor,
// borderRadius:
// BorderRadius
// .circular(
// 12),
// ),
// child: Row(
// children: [
// Icon(
// Icons
// .qr_code_2_rounded,
// color: Colors
// .green[
// 400],
// ),
// InkWell(
// onTap: () {
// if (controller
// .promoTaken ==
// false) {
// Get.defaultDialog(
// title: 'Add Promo'.tr,
// titleStyle: AppStyle.title,
// content: Column(
// children: [
// SizedBox(
// width: Get.width * .7,
// child: TextFormField(
// controller: controller.promo,
// decoration: InputDecoration(
// labelText: 'Promo Code'.tr,
// hintText: 'Enter promo code'.tr,
// labelStyle: AppStyle.subtitle,
// hintStyle: AppStyle.subtitle,
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(10),
// ),
// filled: true,
// fillColor: Colors.grey[200],
// focusedBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: AppColor.primaryColor,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// errorBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: Colors.red,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// enabledBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: Colors.grey,
// width: 1.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// ),
// ),
// ),
// MyElevatedButton(
// title: 'Add Promo'.tr,
// onPressed: () async {
// controller.applyPromoCodeToPassenger();
// },
// )
// ],
// ));
// } else {
// Get.snackbar(
// 'You have promo!'
// .tr,
// '',
// backgroundColor:
// AppColor.redColor);
// }
// },
// child: Text(
// 'Add Promo'
// .tr,
// style: AppStyle
// .title,
// ),
// ),
// ],
// ),
// )),
// ],
// ),
// ),
// SizedBox(
// width: Get.width * .95,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// controller.isCashSelectedBeforeConfirmRide ==
// false
// ? MyElevatedButton(
// title: 'Next'.tr,
// onPressed: () {
// controller
// .changeCashConfirmPageShown();
// },
// )
// :
// // controller.isPassengerChosen ==
// // false
// // ? MyElevatedButton(
// // title: 'Next'.tr,
// // onPressed: () {
// // controller
// // .onChangedPassengersChoose();
// // Get.defaultDialog(
// // barrierDismissible:
// // false,
// // title:
// // 'How Many Passengers?'
// // .tr,
// // titleStyle:
// // AppStyle
// // .title,
// // content:
// // Column(
// // children: [
// // Text(
// // 'Allowed up to 4 Passengers.'
// // .tr,
// // style: AppStyle
// // .title,
// // ),
// // SizedBox(
// // height:
// // 200, // Set the desired height here
// // child:
// // CupertinoPicker(
// // itemExtent:
// // 32,
// // onSelectedItemChanged:
// // (index) {
// // controller.onChangedPassengerCount(index +
// // 1);
// // },
// // children: [
// // Text('1 Passenger'.tr),
// // Text('2 Passengers'.tr),
// // Text('3 Passengers'.tr),
// // Text('4 Passengers'.tr),
// // ],
// // ),
// // ),
// // MyElevatedButton(
// // title:
// // 'Back',
// // onPressed:
// // () =>
// // Get.back(),
// // )
// // ],
// // ),
// // );
// // },
// // )
// // :
// MyElevatedButton(
// title: 'Confirm Selection'
// .tr,
// onPressed: () {
// controller
// .confirmRideForFirstDriver();
// },
// ),
// ],
// ),
// )
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// ],
),
)
: const SizedBox());
}
class Details extends StatelessWidget {
const Details({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(
builder: (controller) => Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${'Distance is'.tr} ${controller.distance.toStringAsFixed(2)} KM',
style: AppStyle.title,
),
Text(
'${'Duration is'.tr} ${controller.data[0]['duration']['text']}',
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'Cost for .21/km ${controller.costDistance.toStringAsFixed(2)} ',
style: AppStyle.title,
),
Text(
'${'Cost Duration'.tr} ${controller.averageDuration.toStringAsFixed(2)} is ${controller.costDuration.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'Total Driver ${controller.totalDriver.toStringAsFixed(2)}',
style: AppStyle.title,
),
Text(
'totaME ${controller.totalME.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
),
Text(
'Cost for passenger ${controller.totalPassenger.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
));
}
}

View File

@@ -0,0 +1,258 @@
// import 'dart:async';
// import 'package:SEFER/constant/box_name.dart';
// import 'package:SEFER/controller/home/map_passenger_controller.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 '../../../constant/colors.dart';
// import '../../../constant/style.dart';
// import '../../../controller/firebase/firbase_messge.dart';
// String appId = AK.agoraAppId;
// class PassengerCallPage extends StatefulWidget {
// const PassengerCallPage({
// super.key,
// required this.channelName,
// required this.token,
// required this.remoteID,
// });
// final String channelName, token, remoteID;
// @override
// State<PassengerCallPage> createState() => _PassengerCallPageState();
// }
// class _PassengerCallPageState extends State<PassengerCallPage> {
// int uid = 0;
// int? _remoteUid = 0; // uid of the remote user
// bool _isJoined = false; // Indicates if the local user has joined the channel
// late RtcEngine agoraEngine; // Agora engine instance
// String status = '';
// 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 setupVoiceSDKEngine();
// }
// @override
// void initState() {
// super.initState();
// _remoteUid = int.parse(widget.remoteID);
// uid = 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;
// status = 'joined'.tr;
// });
// },
// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
// showMessage("Driver joined the channel".tr);
// setState(() {
// status = "Driver joined the channel".tr;
// _remoteUid = remoteUid;
// });
// },
// onUserOffline: (RtcConnection connection, int? remoteUid,
// UserOfflineReasonType reason) {
// showMessage("Driver left the channel".tr);
// setState(() {
// status = "Driver left the channel".tr;
// _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: widget.token,
// channelId: widget.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: 'Call Page'.tr,
// isleading: true,
// body: [
// Positioned(
// top: Get.height * .2,
// child: Container(
// height: 100, width: Get.width,
// decoration: AppStyle.boxDecoration,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// GestureDetector(
// onTap: () async {
// // await callController.initAgoraFull();
// // callController.join();
// // FirebaseMessagesController().sendNotificationToPassengerToken(
// // 'Call Income',
// // '${'You have call from driver'.tr} ${box.read(BoxName.nameDriver)}',
// // Get.find<MapDriverController>().tokenPassenger,
// // [
// // callController.token,
// // callController.channelName,
// // callController.uid.toString(),
// // callController.remoteUid.toString(),
// // ],
// // );
// join();
// // callController.fetchToken();
// },
// 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(
// status,
// style: AppStyle.title,
// ),
// Text('Driver Name'),
// ],
// ),
// GestureDetector(
// onTap: () async {
// FirebaseMessagesController()
// .sendNotificationToPassengerToken(
// 'Call End'.tr,
// 'Call End',
// Get.find<MapPassengerController>().driverToken,
// [],
// 'iphone_ringtone.wav',
// );
// 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
// ),
// ),
// // 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: Text("Join".tr),
// // onPressed: () => {join()},
// // ),
// // ),
// // const SizedBox(width: 10),
// // Expanded(
// // child: ElevatedButton(
// // child: Text("Leave".tr),
// // onPressed: () => {leave()},
// // ),
// // ),
// // ],
// // ),
// // ],
// // ),
// ]),
// );
// }
// // Widget _status() {
// // String statusText;
// //
// // if (!_isJoined) {
// // statusText = 'Join a channel'.tr;
// // } else if (_remoteUid == null)
// // statusText = 'Waiting for a remote user to join...';
// // else
// // statusText = 'Connected to remote user, uid:$_remoteUid';
// //
// // return Text(
// // statusText,
// // );
// // }
// }

View File

@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import '../../widgets/elevated_btn.dart';
// دالة لإظهار الشيت
void showCancelRideBottomSheet() {
Get.bottomSheet(
const CancelRidePageWidget(),
backgroundColor: Colors.transparent,
isScrollControlled: true,
);
}
// الويدجت مفصولة لترتيب الكود
class CancelRidePageWidget extends StatelessWidget {
const CancelRidePageWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// تأكد من وجود الكنترولر
final controller = Get.find<RideLifecycleController>();
final List<String> reasons = [
"Changed my mind".tr,
"Found another transport".tr,
"Driver is taking too long".tr,
"Driver asked me to cancel".tr,
"Wrong pickup location".tr,
"Other".tr,
];
return Container(
height: Get.height * 0.7, // ارتفاع مناسب
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
),
child: GetBuilder<RideLifecycleController>(
builder: (controller) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// مؤشر السحب
Center(
child: Container(
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 20),
Text(
'Why do you want to cancel?'.tr,
style: AppStyle.title
.copyWith(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(
child: ListView.separated(
itemCount: reasons.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
bool isSelected = controller.selectedReasonIndex == index;
return Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
reasons[index],
style: TextStyle(
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
color: isSelected
? AppColor.primaryColor
: AppColor.writeColor,
fontSize: 15),
),
trailing: isSelected
? Icon(Icons.radio_button_checked,
color: AppColor.primaryColor)
: Icon(Icons.radio_button_off, color: Colors.grey),
onTap: () {
controller.selectReason(index, reasons[index]);
},
),
// إظهار حقل النص فقط عند اختيار "أخرى"
if (isSelected && reasons[index] == "Other".tr)
Padding(
padding: const EdgeInsets.only(
bottom: 10, left: 10, right: 10),
child: TextField(
controller: controller.otherReasonController,
decoration: InputDecoration(
hintText: "Please write the reason...".tr,
filled: true,
fillColor: Get.isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
),
maxLines: 2,
),
)
],
);
},
),
),
const SizedBox(height: 20),
// زر التأكيد
SizedBox(
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.redColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
onPressed: () => controller.cancelRide(),
child: Text(
'Confirm Cancellation'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16),
),
),
),
const SizedBox(height: 10),
// زر التراجع
Center(
child: TextButton(
onPressed: () => Get.back(),
child: Text("Don't Cancel".tr,
style: TextStyle(color: AppColor.grayColor)),
),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/home/my_wallet/passenger_wallet.dart';
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/payment/payment_controller.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
class CashConfirmPageShown extends StatelessWidget {
CashConfirmPageShown({super.key});
final PaymentController paymentController = Get.put(PaymentController());
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
// شرط الإظهار الرئيسي لم يتغير
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
// التحكم في ظهور اللوحة لم يتغير
transform: Matrix4.translationValues(
0, controller.isCashConfirmPageShown ? 0 : Get.height, 0),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
),
],
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- 1. رأس الصفحة ---
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Payment Method'.tr,
style: AppStyle.headTitle.copyWith(fontSize: 24),
),
// زر الإغلاق (كان معلقاً في الكود القديم، تم تفعيله هنا)
IconButton(
onPressed: () => controller.changeCashConfirmPageShown(),
icon: Icon(Icons.close, color: AppColor.writeColor),
),
],
),
const SizedBox(height: 16),
// --- 2. بطاقات اختيار الدفع ---
GetBuilder<PaymentController>(builder: (paymentCtrl) {
// نفس منطق تغيير اللون للسيارات النسائية
final bool isLadyRide = box.read(BoxName.carType) == 'Lady' ||
box.read(BoxName.carType) == 'Pink Bike';
final Color selectedColor =
isLadyRide ? Colors.pink.shade300 : AppColor.primaryColor;
return Column(
children: [
// بطاقة المحفظة
_buildPaymentOptionCard(
icon: Icons.account_balance_wallet_outlined,
title: '${AppInformation.appName} Balance'.tr,
subtitle:
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
isSelected: paymentCtrl.isWalletChecked,
selectedColor: selectedColor,
onTap: () =>
paymentCtrl.onChangedPaymentMethodWallet(true),
),
const SizedBox(height: 12),
// بطاقة الكاش
_buildPaymentOptionCard(
icon: Icons.money_rounded,
title: 'Cash'.tr,
subtitle: 'Pay directly to the captain'.tr,
isSelected: paymentCtrl.isCashChecked,
selectedColor: selectedColor,
onTap: () =>
paymentCtrl.onChangedPaymentMethodCash(true),
),
],
);
}),
const SizedBox(height: 24),
// --- 3. أزرار التأكيد (بنفس منطقك القديم تماماً) ---
GetBuilder<PaymentController>(builder: (paymentCtrl) {
final bool isWalletSufficient = (double.tryParse(
box.read(BoxName.passengerWalletTotal) ?? '0') ??
0) >=
controller.totalPassenger;
// إذا تم اختيار المحفظة والرصيد غير كافٍ
if (paymentCtrl.isWalletChecked && !isWalletSufficient) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MyElevatedButton(
title: 'Top up Balance to continue'.tr,
onPressed: () =>
Get.to(() => const PassengerWallet()),
kolor: AppColor.redColor,
),
const SizedBox(height: 8),
TextButton(
onPressed: () =>
paymentCtrl.onChangedPaymentMethodCash(true),
child: Text("Or pay with Cash instead".tr,
style: TextStyle(color: AppColor.primaryColor)),
)
],
);
}
// في كل الحالات الأخرى (كاش، أو محفظة برصيد كافٍ)
else {
return MyElevatedButton(
title: 'Confirm & Find a Ride'.tr,
onPressed: () {
// --- نفس منطقك القديم بالضبط ---
controller.changeCashConfirmPageShown();
// controller.isSearchingWindow = true;
controller.startSearchingForDriver();
// controller.update();
},
);
}
}),
],
),
),
),
);
});
}
// --- ويدجت مساعدة لبناء بطاقة الدفع ---
Widget _buildPaymentOptionCard({
required IconData icon,
required String title,
required String subtitle,
required bool isSelected,
required VoidCallback onTap,
required Color selectedColor,
}) {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? selectedColor.withOpacity(0.1)
: AppColor.writeColor.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? selectedColor
: AppColor.writeColor.withOpacity(0.2),
width: isSelected ? 2.0 : 1.0,
),
),
child: Row(
children: [
Icon(icon,
color: isSelected ? selectedColor : AppColor.writeColor,
size: 28),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style:
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
Text(subtitle,
style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withOpacity(0.7))),
],
),
),
if (isSelected)
Icon(Icons.check_circle_rounded, color: selectedColor, size: 24),
],
),
),
);
}
}

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import 'hexegone_clipper.dart';
GetBuilder<RideLifecycleController> hexagonClipper() {
return GetBuilder<RideLifecycleController>(
builder: ((controller) => controller.rideConfirm
? Positioned(
top: Get.height * .1,
left: Get.width * .1,
right: Get.width * .1,
child: ClipPath(
clipper:
HexagonClipper(), // CustomClipper to create a hexagon shape
child: AnimatedContainer(
duration: const Duration(microseconds: 300),
height: 250,
width: 250,
// decoration: AppStyle.boxDecoration,
// gradient: const LinearGradient(
// colors: [AppColor.greenColor, AppColor.secondaryColor],
// begin: Alignment.topLeft,
// end: Alignment.bottomCenter,
// ),
// border: Border.all(),
// color: AppColor.secondaryColor,
// borderRadius: BorderRadius.circular(15)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Waiting for Driver ...'.tr,
style: AppStyle.title,
),
// IconButton(
// onPressed: () {
// },
// icon: const Icon(Icons.add),
// ),
// Text(
// controller.dataCarsLocationByPassenger['message']
// [controller.carsOrder]['phone']
// .toString(),
// style: AppStyle.title,
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['first_name']} ${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['last_name']}',
style: AppStyle.title,
),
Text(
'Age is '.tr +
controller
.dataCarsLocationByPassenger['message']
[controller.carsOrder]['age']
.toString(),
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['make']
.toString(),
style: AppStyle.title,
),
Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['model']
.toString(),
style: AppStyle.title,
),
],
),
Text(
'Rating is '.tr +
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['ratingDriver']
.toString(),
style: AppStyle.title,
),
Container(
decoration: BoxDecoration(border: Border.all(width: 2)),
child: Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['car_plate']
.toString(),
style: AppStyle.title,
),
),
],
),
),
),
)
: const SizedBox()));
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// import 'package:intl/intl.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
class DriverTimeArrivePassengerPage extends StatelessWidget {
const DriverTimeArrivePassengerPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(
builder: (controller) {
return controller.remainingTime == 0
? Positioned(
bottom: Get.height * .35,
right: Get.width * .05,
child: Stack(
alignment: Alignment.center,
children: [
Container(
decoration: AppStyle.boxDecoration,
// width: 50,
// height: 50,
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 5, end: 5),
child: Column(
children: [
Text(
controller.durationByPassenger.toString() +
' to arrive you.'.tr,
style: AppStyle.title,
),
Text(
" ${DateFormat('h:mm a').format(controller.newTime)}",
style: AppStyle.title,
),
],
),
))
],
),
)
: const SizedBox();
},
);
}
}

View File

@@ -0,0 +1,496 @@
import 'package:siro_rider/print.dart';
import 'package:siro_rider/views/widgets/mydialoug.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/table_names.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../main.dart';
// ---------------------------------------------------
// -- Widget for Destination Point Search (Optimized) --
// ---------------------------------------------------
GetBuilder<LocationSearchController> formSearchPlacesDestenation() {
final String addWorkValue =
box.read(BoxName.addWork)?.toString() ?? 'addWork';
final String addHomeValue =
box.read(BoxName.addHome)?.toString() ?? 'addHome';
if (addWorkValue.isEmpty || addHomeValue.isEmpty) {
box.write(BoxName.addWork, 'addWork');
box.write(BoxName.addHome, 'addHome');
}
return GetBuilder<LocationSearchController>(
id: 'destination_form',
builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return Column(
children: [
_SearchField(
controller: controller,
mapEngine: mapEngine,
rideLifecycle: rideLifecycle,
),
_QuickActions(
controller: controller,
mapEngine: mapEngine,
rideLifecycle: rideLifecycle,
addWorkValue: addWorkValue,
addHomeValue: addHomeValue,
),
_SearchResults(
controller: controller,
mapEngine: mapEngine,
rideLifecycle: rideLifecycle,
),
],
);
},
);
}
// ---------------------------------------------------
// -- Private Helper Widgets for Cleaner Code --
// ---------------------------------------------------
class _SearchField extends StatefulWidget {
final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
const _SearchField({
required this.controller,
required this.mapEngine,
required this.rideLifecycle,
});
@override
State<_SearchField> createState() => _SearchFieldState();
}
class _SearchFieldState extends State<_SearchField> {
Timer? _debounce;
void _onTextChanged() {
if (mounted) {
setState(() {});
}
}
@override
void initState() {
super.initState();
widget.controller.placeDestinationController.addListener(_onTextChanged);
}
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
if (query.length > 2) {
widget.controller.getPlaces();
widget.mapEngine.changeHeightPlaces();
} else if (query.isEmpty) {
widget.controller.clearPlacesDestination();
widget.mapEngine.changeHeightPlaces();
}
});
}
@override
void dispose() {
_debounce?.cancel();
widget.controller.placeDestinationController.removeListener(_onTextChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: widget.controller.placeDestinationController,
onChanged: _onSearchChanged,
decoration: InputDecoration(
hintText: widget.controller.hintTextDestinationPoint,
hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon: Icon(Icons.search, color: AppColor.primaryColor),
suffixIcon: widget
.controller.placeDestinationController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, color: Colors.grey[400]),
onPressed: () {
widget.controller.placeDestinationController.clear();
},
)
: null,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
const SizedBox(width: 8.0),
IconButton(
onPressed: () {
widget.mapEngine.changeMainBottomMenuMap();
widget.mapEngine.changePickerShown();
},
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: widget.rideLifecycle.isAnotherOreder
? 'Pick destination on map'.tr
: 'Pick on map'.tr,
),
],
),
);
}
}
class _QuickActions extends StatelessWidget {
final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
final String addWorkValue;
final String addHomeValue;
const _QuickActions({
required this.controller,
required this.mapEngine,
required this.rideLifecycle,
required this.addWorkValue,
required this.addHomeValue,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildQuickActionButton(
icon: Icons.work_outline,
text: addWorkValue == 'addWork' ? 'Add Work'.tr : 'To Work'.tr,
onTap: () {
if (addWorkValue == 'addWork') {
controller.workLocationFromMap = true;
mapEngine.changeMainBottomMenuMap();
mapEngine.changePickerShown();
} else {
_handleQuickAction(
controller,
mapEngine,
rideLifecycle,
BoxName.addWork,
'To Work',
);
}
},
onLongPress: () =>
_showChangeLocationDialog(controller, mapEngine, 'Work'),
),
_buildQuickActionButton(
icon: Icons.home_outlined,
text: addHomeValue == 'addHome' ? 'Add Home'.tr : 'To Home'.tr,
onTap: () {
if (addHomeValue == 'addHome') {
controller.homeLocationFromMap = true;
mapEngine.changeMainBottomMenuMap();
mapEngine.changePickerShown();
} else {
_handleQuickAction(
controller,
mapEngine,
rideLifecycle,
BoxName.addHome,
'To Home',
);
}
},
onLongPress: () =>
_showChangeLocationDialog(controller, mapEngine, 'Home'),
),
],
),
);
}
}
class _SearchResults extends StatelessWidget {
final LocationSearchController controller;
final MapEngineController mapEngine;
final RideLifecycleController rideLifecycle;
const _SearchResults({
required this.controller,
required this.mapEngine,
required this.rideLifecycle,
});
@override
Widget build(BuildContext context) {
return GetBuilder<LocationSearchController>(
id: 'places_list',
builder: (locCtrl) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: locCtrl.placesDestination.isNotEmpty ? 300 : 0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: locCtrl.placesDestination.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
final res = locCtrl.placesDestination[index];
final title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
final address = res['address'] ?? 'Details not available';
final latitude = res['latitude'];
final longitude = res['longitude'];
return ListTile(
leading: const Icon(Icons.place, size: 30, color: Colors.grey),
title: Text(
title,
style:
AppStyle.subtitle.copyWith(fontWeight: FontWeight.w500),
),
subtitle: Text(
address,
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
trailing: IconButton(
icon: const Icon(Icons.favorite_border, color: Colors.grey),
onPressed: () => _handleAddToFavorites(
context, latitude, longitude, title),
),
onTap: () => _handlePlaceSelection(
controller,
mapEngine,
rideLifecycle,
latitude,
longitude,
title,
index,
),
);
},
),
);
},
);
}
Future<void> _handleAddToFavorites(BuildContext context, dynamic latitude,
dynamic longitude, String title) async {
if (latitude != null && longitude != null) {
await sql.insertMapLocation({
'latitude': latitude,
'longitude': longitude,
'name': title,
'rate': 'N/A',
}, TableName.placesFavorite);
Toast.show(
context,
'$title ${'Saved Successfully'.tr}',
AppColor.primaryColor,
);
} else {
Toast.show(
context,
'Invalid location data',
AppColor.redColor,
);
}
}
Future<void> _handlePlaceSelection(
LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
dynamic latitude,
dynamic longitude,
String title,
int index) async {
if (latitude == null || longitude == null) {
Toast.show(Get.context!, 'Invalid location data', AppColor.redColor);
return;
}
await sql.insertMapLocation({
'latitude': latitude,
'longitude': longitude,
'name': title,
'rate': 'N/A',
'createdAt': DateTime.now().toIso8601String(),
}, TableName.recentLocations);
final destLatLng = LatLng(
double.parse(latitude.toString()), double.parse(longitude.toString()));
if (rideLifecycle.isAnotherOreder) {
await _handleAnotherOrderSelection(
controller, mapEngine, rideLifecycle, destLatLng);
} else {
_handleRegularOrderSelection(
controller, mapEngine, rideLifecycle, destLatLng, index);
}
}
Future<void> _handleAnotherOrderSelection(
LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
LatLng destination) async {
controller.myDestination = destination;
controller.clearPlacesDestination();
await rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}');
mapEngine.isPickerShown = false;
controller.passengerStartLocationFromMap = false;
mapEngine.changeMainBottomMenuMap();
rideLifecycle.showBottomSheet1();
}
void _handleRegularOrderSelection(
LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
LatLng destination,
int index) {
controller.passengerLocation = controller.newMyLocation;
controller.myDestination = destination;
controller.convertHintTextDestinationNewPlaces(index);
controller.clearPlacesDestination();
mapEngine.changeMainBottomMenuMap();
controller.passengerStartLocationFromMap = true;
mapEngine.isPickerShown = true;
rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}');
}
}
Widget _buildQuickActionButton({
required IconData icon,
required String text,
VoidCallback? onTap,
VoidCallback? onLongPress,
}) {
return InkWell(
onTap: onTap,
onLongPress: onLongPress,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: AppColor.cyanBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: AppColor.cyanBlue.withOpacity(0.3)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: AppColor.cyanBlue),
const SizedBox(height: 4.0),
Text(
text,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
color: AppColor.cyanBlue, fontWeight: FontWeight.w500),
),
],
),
),
);
}
void _showChangeLocationDialog(LocationSearchController controller,
MapEngineController mapEngine, String locationType) {
MyDialog().getDialog(
locationType == 'Work'
? 'Change Work location ?'.tr
: 'Change Home location ?'.tr,
'',
() {
if (locationType == 'Work') {
controller.workLocationFromMap = true;
} else {
controller.homeLocationFromMap = true;
}
mapEngine.changeMainBottomMenuMap();
mapEngine.changePickerShown();
},
);
}
void _handleQuickAction(
LocationSearchController controller,
MapEngineController mapEngine,
RideLifecycleController rideLifecycle,
String boxName,
String hintText) async {
try {
final locationString = box.read(boxName).toString();
final parts = locationString.split(',');
final latLng = LatLng(
double.parse(parts[0]),
double.parse(parts[1]),
);
controller.hintTextDestinationPoint = hintText;
mapEngine.changeMainBottomMenuMap();
await rideLifecycle.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${latLng.latitude},${latLng.longitude}',
);
controller.currentLocationToFormPlaces = false;
controller.clearPlacesDestination();
controller.passengerStartLocationFromMap = false;
mapEngine.isPickerShown = false;
rideLifecycle.showBottomSheet1();
} catch (e) {
Log.print("Error handling quick action: $e");
Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor);
}
}

View File

@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
// ---------------------------------------------------
// -- Widget for Start Point Search (Updated) --
// ---------------------------------------------------
GetBuilder<LocationSearchController> formSearchPlacesStart() {
return GetBuilder<LocationSearchController>(
id: 'start_point_form',
builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: controller.placeStartController,
onChanged: (value) {
if (controller.placeStartController.text.length > 2) {
controller.getPlacesStart();
} else if (controller.placeStartController.text.isEmpty) {
controller.clearPlacesStart();
}
},
decoration: InputDecoration(
hintText: 'Search for a starting point'.tr,
hintStyle:
AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon:
Icon(Icons.search, color: AppColor.primaryColor),
suffixIcon: controller.placeStartController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, color: Colors.grey[400]),
onPressed: () {
controller.placeStartController.clear();
controller.clearPlacesStart();
},
)
: null,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
const SizedBox(width: 8.0),
IconButton(
onPressed: () {
controller.passengerStartLocationFromMap = true;
mapEngine.changeMainBottomMenuMap();
mapEngine.changePickerShown();
},
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: 'Pick start point on map'.tr,
),
],
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: controller.placesStart.isNotEmpty ? 300 : 0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: controller.placesStart.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
var res = controller.placesStart[index];
var title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
var address = res['address'] ?? 'Details not available';
return ListTile(
leading: const Icon(Icons.place, size: 30, color: Colors.grey),
title: Text(title,
style: AppStyle.subtitle
.copyWith(fontWeight: FontWeight.w500)),
subtitle: Text(address,
style: TextStyle(color: Colors.grey[600], fontSize: 12)),
onTap: () {
var latitude = res['latitude'];
var longitude = res['longitude'];
if (latitude != null && longitude != null) {
controller.passengerLocation =
LatLng(double.parse(latitude), double.parse(longitude));
controller.placeStartController.text = title;
controller.clearPlacesStart();
mapEngine.changeMainBottomMenuMap();
controller.update();
}
},
);
},
),
),
],
);
},
);
}

View File

@@ -0,0 +1,186 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/table_names.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../main.dart';
GetBuilder<LocationSearchController> formSearchPlaces(int index) {
return GetBuilder<LocationSearchController>(
builder: (controller) {
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Container(
decoration: BoxDecoration(color: AppColor.secondaryColor),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(
borderRadius: BorderRadius.only(),
gapPadding: 4,
borderSide: BorderSide(
color: AppColor.redColor,
width: 2,
)),
suffixIcon: const Icon(Icons.search),
hintText: controller.hintTextwayPoint0.tr,
hintStyle: AppStyle.title,
hintMaxLines: 1,
prefixIcon: IconButton(
onPressed: () {
controller.allTextEditingPlaces[index].clear();
controller.clearPlaces(index);
},
icon: Icon(
Icons.clear,
color: Colors.red[300],
),
),
),
controller: controller.allTextEditingPlaces[index],
onChanged: (value) {
if (controller.allTextEditingPlaces[index].text.length > 5) {
controller.getPlacesListsWayPoint(index);
mapEngine.changeHeightPlacesAll(index);
}
},
),
),
),
controller.placeListResponseAll[index].isEmpty
? InkWell(
onTap: () {
controller.startLocationFromMapAll[index] = true;
controller.wayPointIndex = index;
Get.back();
mapEngine.changeWayPointStopsSheet();
mapEngine.changePickerShown();
},
child: Text(
'Choose from Map'.tr + ' $index'.tr,
style:
AppStyle.title.copyWith(color: AppColor.blueColor),
),
)
: const SizedBox(),
Container(
height: controller.placeListResponseAll[index].isNotEmpty
? mapEngine.height
: 0,
color: AppColor.secondaryColor,
child: ListView.builder(
itemCount: controller.placeListResponseAll[index].length,
itemBuilder: (BuildContext context, int i) {
var res = controller.placeListResponseAll[index][i];
return InkWell(
onTap: () async {
final double lat = res['geometry']['location']['lat'];
final double lng = res['geometry']['location']['lng'];
final String placeName = res['name'].toString();
final selectedLatLng = LatLng(lat, lng);
mapEngine.changeHeightPlaces();
if (controller.currentLocationToFormPlacesAll[index] ==
true) {
controller.newStartPointLocation =
rideLifecycle.passengerLocation;
} else {
rideLifecycle.passengerLocation =
controller.newStartPointLocation;
}
controller.menuWaypoints[index] = selectedLatLng;
controller.menuWaypointNames[index] = placeName;
controller.convertHintTextPlaces(index, res);
final String start =
'${rideLifecycle.passengerLocation.latitude},${rideLifecycle.passengerLocation.longitude}';
final String dest =
'${rideLifecycle.myDestination.latitude},${rideLifecycle.myDestination.longitude}';
await rideLifecycle.getDirectionMap(start, dest);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Image.network(
res['icon'],
width: 20,
),
IconButton(
onPressed: () async {
await sql.insertMapLocation({
'latitude': res['geometry']
['location']['lat'],
'longitude': res['geometry']
['location']['lng'],
'name': res['name'].toString(),
'rate': res['rating'].toString(),
}, TableName.placesFavorite);
Toast.show(
context,
'${res['name']} ${'Saved Sucssefully'.tr}',
AppColor.primaryColor);
},
icon: const Icon(Icons.favorite_border),
),
],
),
Column(
children: [
Text(
res['name'].toString(),
style: AppStyle.title,
),
Text(
res['vicinity'].toString(),
style: AppStyle.subtitle,
),
],
),
Column(
children: [
Text(
'rate',
style: AppStyle.subtitle,
),
Text(
res['rating'].toString(),
style: AppStyle.subtitle,
),
],
),
],
),
const Divider(
thickness: 1,
)
],
),
),
);
},
),
)
],
);
},
);
}

View File

@@ -0,0 +1,84 @@
import 'package:siro_rider/print.dart';
import 'package:flutter/material.dart';
import 'package:siro_rider/env/env.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_rider/controller/home/points_for_rider_controller.dart';
import 'package:siro_rider/services/offline_map_service.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/nearby_drivers_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart';
class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({super.key});
final WayPointController wayPointController = Get.find<WayPointController>();
@override
Widget build(BuildContext context) {
final locationSearch = Get.find<LocationSearchController>();
final rideLifecycle = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
return GetBuilder<MapEngineController>(
builder: (controller) => rideLifecycle.isLoading
? const MyCircularProgressIndicator()
: Positioned(
bottom: Get.height * .2,
top: 0,
left: 0,
right: 0,
child: IntaleqMap(
apiKey: Env.mapSaasKey,
styleUrl: Get.isDarkMode
? 'assets/style_dark.json'
: 'assets/style.json',
onMapCreated: controller.onMapCreated,
onStyleLoaded: controller.onStyleLoaded,
onCameraMove: locationSearch.onCameraMoveThrottled,
onCameraIdle: () {
if (controller.mapController != null) {
final position = controller.mapController!.cameraPosition;
if (position != null) {
Log.print('✅ onCameraIdle targeted: ${position.target}');
locationSearch
.updateCurrentLocationFromCamera(position.target);
OfflineMapService.instance
.downloadRegion(position.target, radiusKm: 1.0);
} else {
Log.print('⚠️ onCameraIdle: cameraPosition is NULL');
}
} else {
Log.print('⚠️ onCameraIdle: mapController is NULL');
}
},
markers: controller.markers,
polylines: controller.polyLines,
polygons: controller.polygons,
circles: controller.circles,
initialCameraPosition: CameraPosition(
target: locationSearch.passengerLocation,
zoom: nearbyDrivers.lowPerf ? 14.5 : 15,
),
myLocationEnabled: true,
onTap: (latlng) => controller.hidePlaces(),
onLongPress: (latlng) {
MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async {
controller.clearPolyline();
rideLifecycle.getDirectionMap(
'${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${latlng.latitude},${latlng.longitude}',
);
rideLifecycle.showBottomSheet1();
});
},
),
),
);
}
}

View File

@@ -0,0 +1,52 @@
import 'dart:math';
import 'package:flutter/material.dart';
class HexagonClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
final height = size.height;
final width = size.width;
final centerX = width / 2;
final centerY = height / 2;
final radius = width / 2;
const angle = 2 * pi / 10; // Angle between each side of the hexagon
// Start at the top right vertex of the hexagon
final startX = centerX + radius * cos(0);
final startY = centerY + radius * sin(0);
path.moveTo(startX, startY);
// Draw the remaining sides of the hexagon
for (int i = 1; i < 10; i++) {
final x = centerX + radius * cos(angle * i);
final y = centerY + radius * sin(angle * i);
path.lineTo(x, y);
}
path.close();
return path;
}
@override
bool shouldReclip(HexagonClipper oldClipper) => false;
}
class ArrowClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.moveTo(0, size.height / 2);
path.lineTo(size.width / 2, 0);
path.lineTo(size.width, size.height / 2);
path.lineTo(size.width / 2, size.height);
path.close();
return path;
}
@override
bool shouldReclip(ArrowClipper oldClipper) => false;
}

View File

@@ -0,0 +1,134 @@
import 'dart:math';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import 'package:siro_rider/views/widgets/mycircular.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:ui';
import '../../../constant/colors.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/vip_waitting_page.dart';
import '../navigation/navigation_view.dart';
// --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapEngineController> leftMainMenuIcons() {
return GetBuilder<MapEngineController>(
builder: (controller) {
final locationSearch = Get.find<LocationSearchController>();
return Positioned(
top: Get.height * .01,
left: 0,
right: 0,
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.secondaryColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
_buildMapActionButton(
icon: Icons.near_me_outlined,
tooltip: 'Toggle Map Type',
onPressed: () => Get.to(() => NavigationView()),
),
_buildVerticalDivider(),
_buildMapActionButton(
icon: Icons.my_location_rounded,
tooltip: 'Go to My Location',
onPressed: () {
controller.mapController?.animateCamera(
CameraUpdate.newLatLng(
LatLng(
locationSearch.passengerLocation.latitude,
locationSearch.passengerLocation.longitude,
),
),
);
},
),
_buildVerticalDivider(),
_buildMapActionButton(
icon: Octicons.watch,
tooltip: 'VIP Waiting Page',
onPressed: () => Get.to(() => VipWaittingPage()),
),
],
),
),
),
),
),
);
},
);
}
Widget _buildMapActionButton({
required IconData icon,
required String tooltip,
required VoidCallback onPressed,
}) {
return IconButton(
onPressed: onPressed,
icon: Icon(icon, color: AppColor.writeColor, size: 22),
tooltip: tooltip,
splashRadius: 22,
padding: const EdgeInsets.all(12),
constraints: const BoxConstraints(),
);
}
Widget _buildVerticalDivider() {
return Container(
height: 20,
width: 1,
color: AppColor.writeColor.withValues(alpha: 0.2),
);
}
class TestPage extends StatelessWidget {
const TestPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('iOS Live Activity Test'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyCircularProgressIndicator(),
MyElevatedButton(
title: 'title',
onPressed: () {},
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: () async {},
child: const Text('End Activity'),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,533 @@
import 'dart:ui';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/home/my_wallet/passenger_wallet.dart';
import 'package:siro_rider/views/home/profile/complaint_page.dart';
import 'package:siro_rider/views/home/profile/order_history.dart';
import 'package:siro_rider/views/home/profile/promos_passenger_page.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../notification/notification_page.dart';
import '../HomePage/contact_us.dart';
import '../HomePage/share_app_page.dart';
import '../setting_page.dart';
import '../profile/passenger_profile_page.dart';
// ─── ألوان النظام (Integrated with AppColor) ──────────────────────────────────
Color get _kCyan => AppColor.cyanBlue;
Color get _kBg =>
Get.isDarkMode ? const Color(0xFF060B18) : AppColor.secondaryColor;
Color get _kBgSurface => Get.isDarkMode
? const Color(0xFF0D1525)
: AppColor.secondaryColor.withValues(alpha: 0.9);
const _kAmber = Color(0xFFFFB700);
Color get _kText => AppColor.writeColor;
Color get _kTextMuted => AppColor.grayColor;
class MapMenuWidget extends StatelessWidget {
const MapMenuWidget({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapEngineController>(
builder: (controller) => Stack(
children: [
// ── تعتيم الخلفية ───────────────────────────────────────────────
if (controller.widthMenu > 0)
GestureDetector(
onTap: controller.getDrawerMenu,
child: Container(color: Colors.black.withValues(alpha: 0.55)),
),
_buildSideMenu(controller),
_buildMenuButton(controller),
],
),
);
}
// ── زر القائمة العائم ────────────────────────────────────────────────────
Widget _buildMenuButton(MapEngineController controller) {
return Positioned(
top: 45,
left: 16,
child: SafeArea(
child: GestureDetector(
onTap: controller.getDrawerMenu,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
width: 48,
height: 48,
decoration: BoxDecoration(
color: _kBg.withValues(alpha: 0.88),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _kCyan.withValues(alpha: 0.25), width: 1),
boxShadow: [
BoxShadow(
color: _kCyan.withValues(alpha: 0.12),
blurRadius: 16,
),
],
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: Icon(
controller.widthMenu > 0
? Icons.close_rounded
: Icons.menu_rounded,
key: ValueKey(controller.widthMenu > 0),
color: _kCyan,
size: 22,
),
),
),
),
),
),
),
);
}
// ── القائمة الجانبية ─────────────────────────────────────────────────────
Widget _buildSideMenu(MapEngineController controller) {
return AnimatedPositioned(
duration: const Duration(milliseconds: 420),
curve: Curves.fastOutSlowIn,
top: 0,
bottom: 0,
left: controller.widthMenu > 0 ? 0 : -Get.width,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
width: Get.width * 0.8,
constraints: const BoxConstraints(maxWidth: 320),
decoration: BoxDecoration(
color: _kBg.withValues(alpha: 0.97),
border: Border(
right: BorderSide(color: _kCyan.withValues(alpha: 0.12), width: 1),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 32,
),
],
),
child: Stack(
children: [
// شبكة خلفية
Positioned.fill(
child: CustomPaint(painter: _MenuGridPainter())),
// المحتوى
SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMenuHeader(),
_buildQuickActionButtons(),
_buildDivider(),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
children: [
MenuListItem(
title: 'My Balance'.tr,
icon: Icons.account_balance_wallet_outlined,
onTap: () =>
Get.to(() => const PassengerWallet()),
),
MenuListItem(
title: 'Order History'.tr,
icon: Icons.history_rounded,
onTap: () => Get.to(() => const OrderHistory()),
),
MenuListItem(
title: 'Promos'.tr,
icon: Icons.local_offer_outlined,
onTap: () =>
Get.to(() => const PromosPassengerPage()),
),
MenuListItem(
title: 'Contact Us'.tr,
icon: Icons.contact_support_outlined,
onTap: () => Get.to(() => ContactUsPage()),
),
MenuListItem(
title: 'Complaint'.tr,
icon: Icons.flag_outlined,
onTap: () => Get.to(() => ComplaintPage()),
),
MenuListItem(
title: 'Driver'.tr,
icon: Ionicons.car_sport_outline,
onTap: () => _launchDriverAppUrl(),
),
MenuListItem(
title: 'Share App'.tr,
icon: Icons.share_outlined,
onTap: () => Get.to(() => ShareAppPage()),
),
MenuListItem(
title: 'Privacy Policy'.tr,
icon: Icons.shield_outlined,
onTap: () => launchUrl(
Uri.parse(
'${AppLink.server}/privacy_policy.php'),
),
),
],
),
),
_buildDivider(),
// زر الخروج
Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 12, 12),
child: MenuListItem(
title: 'Logout'.tr,
icon: Icons.logout_rounded,
isDestructive: true,
onTap: () {
Get.defaultDialog(
title: "Logout".tr,
middleText: "Are you sure you want to logout?".tr,
textConfirm: "Logout".tr,
textCancel: "Cancel".tr,
onConfirm: () {
// controller.logout();
Get.back();
},
);
},
),
),
],
),
),
],
),
),
),
),
);
}
// ── رأس القائمة ──────────────────────────────────────────────────────────
Widget _buildMenuHeader() {
return Container(
margin: const EdgeInsets.fromLTRB(16, 24, 16, 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _kBgSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _kCyan.withValues(alpha: 0.15), width: 1),
),
child: Row(
children: [
// أفاتار المستخدم
Stack(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
_kCyan.withValues(alpha: 0.2),
_kAmber.withValues(alpha: 0.12),
],
),
border:
Border.all(color: _kCyan.withValues(alpha: 0.35), width: 1.5),
),
child: Icon(Icons.person_rounded, color: _kCyan, size: 28),
),
// نقطة الحضور
Positioned(
bottom: 1,
right: 1,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: const Color(0xFF00E676),
shape: BoxShape.circle,
border: Border.all(color: _kBg, width: 2),
boxShadow: [
BoxShadow(
color: const Color(0xFF00E676).withValues(alpha: 0.5),
blurRadius: 6,
),
],
),
),
),
],
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
box.read(BoxName.name) ?? 'Guest',
style: TextStyle(
color: _kText,
fontSize: 17,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 5),
Row(
children: [
Container(
width: 5,
height: 5,
decoration:
BoxDecoration(color: _kCyan, shape: BoxShape.circle),
),
const SizedBox(width: 6),
Text(
"Intaleq Passenger".tr,
style: TextStyle(
color: _kTextMuted,
fontSize: 12,
letterSpacing: 0.4,
),
),
],
),
],
),
),
],
),
);
}
// ── أزرار الإجراءات السريعة ───────────────────────────────────────────────
Widget _buildQuickActionButtons() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
children: [
_QuickBtn(
icon: Icons.person_outline_rounded,
label: 'Profile'.tr,
onTap: () => Get.to(() => PassengerProfilePage()),
),
const SizedBox(width: 8),
_QuickBtn(
icon: Icons.notifications_none_rounded,
label: 'Alerts'.tr,
onTap: () => Get.to(() => const NotificationPage()),
),
const SizedBox(width: 8),
_QuickBtn(
icon: Icons.settings_outlined,
label: 'Settings'.tr,
onTap: () => Get.to(() => const SettingPage()),
),
],
),
);
}
Widget _buildDivider() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
height: 1,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
_kCyan.withValues(alpha: 0.15),
Colors.transparent,
],
),
),
);
}
void _launchDriverAppUrl() async {
final String driverAppUrl;
if (defaultTargetPlatform == TargetPlatform.android) {
driverAppUrl =
'https://play.google.com/store/apps/details?id=com.intaleq_driver';
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
driverAppUrl =
'https://apps.apple.com/st/app/intaleq-driver/id6482995159';
} else {
return;
}
try {
final Uri url = Uri.parse(driverAppUrl);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
Get.snackbar('Error', 'Could not launch driver app store.');
}
} catch (e) {
Get.snackbar('Error', 'Could not open the link.');
}
}
}
// ── زر الإجراء السريع ────────────────────────────────────────────────────────
class _QuickBtn extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
const _QuickBtn({
required this.icon,
required this.label,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _kBgSurface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _kCyan.withValues(alpha: 0.12), width: 1),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: _kCyan, size: 22),
const SizedBox(height: 6),
Text(
label,
style: TextStyle(
color: _kTextMuted,
fontSize: 11,
letterSpacing: 0.4,
),
),
],
),
),
),
);
}
}
// ── عنصر القائمة ─────────────────────────────────────────────────────────────
class MenuListItem extends StatelessWidget {
const MenuListItem({
super.key,
required this.title,
required this.onTap,
required this.icon,
this.color,
this.isDestructive = false,
});
final String title;
final IconData icon;
final VoidCallback onTap;
final Color? color;
final bool isDestructive;
@override
Widget build(BuildContext context) {
final iconColor = isDestructive
? const Color(0xFFFF5252)
: (color ?? _kCyan.withValues(alpha: 0.80));
final textColor =
isDestructive ? const Color(0xFFFF5252) : (color ?? _kText);
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
splashColor: _kCyan.withValues(alpha: 0.07),
highlightColor: _kCyan.withValues(alpha: 0.04),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row(
children: [
// أيقونة بخلفية دقيقة
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: isDestructive
? const Color(0xFFFF5252).withValues(alpha: 0.08)
: _kCyan.withValues(alpha: 0.07),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, size: 19, color: iconColor),
),
const SizedBox(width: 14),
Expanded(
child: Text(
title.tr,
style: TextStyle(
color: textColor,
fontSize: 15,
fontWeight: FontWeight.w500,
letterSpacing: 0.2,
),
),
),
Icon(
Icons.chevron_right_rounded,
color: _kTextMuted.withValues(alpha: 0.4),
size: 18,
),
],
),
),
),
);
}
}
// ── رسّام الشبكة ──────────────────────────────────────────────────────────────
class _MenuGridPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = AppColor.cyanBlue.withValues(alpha: 0.04)
..strokeWidth = 0.5;
const spacing = 36.0;
for (double y = 0; y < size.height; y += spacing) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
}
for (double x = 0; x < size.width; x += spacing) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
}
}
@override
bool shouldRepaint(_MenuGridPainter old) => false;
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../main.dart';
class MenuIconMapPageWidget extends StatelessWidget {
const MenuIconMapPageWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapEngineController>(
builder: (controller) => Positioned(
top: Get.height * .008,
left: box.read(BoxName.lang) != 'ar' ? 5 : null,
right: box.read(BoxName.lang) == 'ar' ? 5 : null,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.accentColor)),
child: AnimatedCrossFade(
sizeCurve: Curves.bounceOut,
duration: const Duration(
milliseconds: 300), // Adjust the duration as needed
crossFadeState: controller.heightMenuBool
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: IconButton(
onPressed: () {
controller.getDrawerMenu();
},
icon: const Icon(
Icons.close,
color: AppColor.primaryColor,
),
),
secondChild: IconButton(
onPressed: () {
controller.getDrawerMenu();
},
icon: const Icon(
Icons.menu,
color: AppColor.accentColor,
),
),
),
),
));
}
}

View File

@@ -0,0 +1,86 @@
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NewMainBottomSheet extends StatelessWidget {
const NewMainBottomSheet({super.key});
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 0,
left: 5,
right: 5,
child: Container(
decoration: AppStyle.boxDecoration,
width: Get.width,
height: Get.height * .15,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text('Home'.tr),
const Icon(Icons.home),
],
),
),
),
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text('Work'.tr),
const Icon(Icons.work_outline),
],
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15),
color: AppColor.blueColor.withOpacity(.5),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.search),
Text(
"${"Where you want go ".tr}${(box.read(BoxName.name).toString().split(' ')[0]).toString()} ?",
),
],
),
),
)
],
)
],
),
),
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/location_search_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class PassengerRideLocationWidget extends StatefulWidget {
const PassengerRideLocationWidget({super.key});
@override
State<PassengerRideLocationWidget> createState() =>
_PassengerRideLocationWidgetState();
}
class _PassengerRideLocationWidgetState
extends State<PassengerRideLocationWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// --- إعداد الأنيميشن للأيقونة ---
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..repeat(reverse: true); // التكرار بشكل عكسي لإنشاء تأثير النبض
_animation = Tween<double>(begin: 0.9, end: 1.1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GetBuilder<LocationSearchController>(builder: (controller) {
// --- نفس شرط الإظهار الخاص بك ---
return AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
bottom: controller.isPassengerRideLocationWidget
? 20
: -100, // حركة دخول وخروج ناعمة
left: 20,
right: 20,
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0), // حواف دائرية بالكامل
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), // تأثير زجاجي
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.writeColor.withValues(alpha: 0.2)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// --- أيقونة متحركة لجذب الانتباه ---
ScaleTransition(
scale: _animation,
child: Icon(
Icons.location_on,
color: AppColor.primaryColor,
size: 28,
),
),
const SizedBox(width: 12),
// --- نص إرشادي واضح ---
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Set pickup location".tr,
style: AppStyle.title
.copyWith(fontWeight: FontWeight.bold),
),
Text(
"Move the map to adjust the pin".tr,
style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withValues(alpha: 0.7),
),
),
],
),
],
),
),
),
),
);
});
}
}

View File

@@ -0,0 +1,417 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/controller/functions/secure_storage.dart';
import 'package:siro_rider/controller/home/payment/credit_card_controller.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/map_engine_controller.dart';
class PaymentMethodPage extends StatelessWidget {
const PaymentMethodPage({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapEngineController>(
builder: (controller) => Positioned(
right: 5,
bottom: 5,
left: 5,
child: AnimatedContainer(
duration: const Duration(milliseconds: 400),
height: controller.isPaymentMethodPageShown
? controller.paymentPageShown
: 0,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'My Card'.tr,
style: AppStyle.title.copyWith(fontSize: 22),
),
IconButton(
onPressed: () =>
controller.changePaymentMethodPageShown(),
icon: const Icon(Icons.close),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Add Card'.tr,
style: AppStyle.title,
),
// GetBuilder<CreditCardController>(
// builder: (controller) => IconButton(
// onPressed: () {
// // controller.scanCard();
// // Get.defaultDialog(content: OptionConfigureWidget(
// // initialOptions: scanOptions,
// // onScanOptionChanged: (newOptions) =>
// // scanOptions = newOptions,
// // ),
// // );
// },
// icon: const Icon(Icons.contact_emergency_sharp),
// ),
// )
],
),
const SizedBox(
height: 10,
),
const MyCreditCardWidget(),
const Spacer(),
GetBuilder<CreditCardController>(
builder: (controller) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyElevatedButton(
title: 'Add Credit Card'.tr,
onPressed: () async {
if (controller.formKey.currentState!
.validate()) {
SecureStorage().saveData(
BoxName.cardNumber,
controller
.cardNumberController.text);
SecureStorage().saveData(
BoxName.cardHolderName,
controller
.cardHolderNameController.text);
SecureStorage().saveData(
BoxName.cvvCode,
controller.cvvCodeController.text);
SecureStorage().saveData(
BoxName.expiryDate,
controller
.expiryDateController.text);
}
},
),
],
))
],
),
),
),
));
}
}
class MyCreditCardWidget extends StatelessWidget {
const MyCreditCardWidget({
super.key,
});
@override
Widget build(BuildContext context) {
Get.put(CreditCardController());
return GetBuilder<CreditCardController>(
builder: (controller) => Container(
height: Get.height * .4,
width: Get.width * .9,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.all(Radius.circular(15)),
gradient: LinearGradient(colors: [
AppColor.secondaryColor,
// AppColor.blueColor,
// AppColor.greenColor,
AppColor.accentColor,
// AppColor.primaryColor,
// AppColor.redColor,
// AppColor.yellowColor
]),
boxShadow: const [
BoxShadow(
spreadRadius: 3,
offset: Offset(3, 3),
blurRadius: 3,
color: AppColor.redColor),
BoxShadow(
offset: Offset(-3, -3),
blurRadius: 3,
spreadRadius: 3,
color: AppColor.redColor),
],
),
child: Form(
key: controller.formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Row(
children: [
getCardIcon(controller.cardNumberController
.text), // Dynamic credit card icon
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .25,
child: Text(
'Card Number'.tr,
style: AppStyle.title,
),
),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .4,
height: 70,
child: TextFormField(
maxLength: 16,
keyboardType: TextInputType.number,
controller: controller.cardNumberController,
style: const TextStyle(
color: AppColor.blueColor,
fontFamily: 'digital-counter-7',
fontWeight: FontWeight.bold),
decoration: const InputDecoration(
helperStyle: TextStyle(
fontFamily: 'digital-counter-7'),
// labelText: 'Card Number',
),
// inputFormatters: [DigitObscuringFormatter()],
validator: (value) {
if (value!.isEmpty || value.length != 16) {
return 'Please enter a valid 16-digit card number'
.tr;
}
return null;
},
),
),
],
),
),
Row(
children: [
const Icon(Icons.person),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .25,
child: Text(
'Holder Name',
style: AppStyle.title,
),
),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .3,
child: SizedBox(
// height: 50,
child: TextFormField(
style: AppStyle.title,
keyboardType: TextInputType.text,
// maxLength: 16,
controller: controller.cardHolderNameController,
decoration: const InputDecoration(
// labelText: 'Cardholder Name',
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the cardholder name'
.tr;
}
return null;
},
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: Row(
children: [
const Icon(Icons.date_range_outlined),
SizedBox(
width: Get.width * .03,
),
Column(
children: [
SizedBox(
width: Get.width * .2,
child: Text(
'Expiry Date',
style: AppStyle.subtitle,
),
),
SizedBox(
width: Get.width * .1,
child: SizedBox(
height: 60,
child: TextFormField(
maxLength: 4,
keyboardType: TextInputType.datetime,
controller:
controller.expiryDateController,
style: AppStyle.title,
decoration: const InputDecoration(),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the expiry date'
.tr;
}
return null;
},
),
),
)
],
),
],
),
),
SizedBox(
width: Get.width * .4,
child: Row(
children: [
const Icon(Icons.security),
SizedBox(
width: Get.width * .021,
),
Column(
children: [
SizedBox(
width: Get.width * .2,
child: Text(
'CVV Code',
style: AppStyle.subtitle,
),
),
SizedBox(
width: Get.width * .2,
child: SizedBox(
height: 60,
child: TextFormField(
obscureText: true,
keyboardType: TextInputType.number,
style: const TextStyle(
color: AppColor.primaryColor,
fontFamily: 'digital-counter-7'),
maxLength: 3,
controller:
controller.cvvCodeController,
decoration: const InputDecoration(
// labelText: 'CVV Code',
),
validator: (value) {
if (value!.isEmpty &&
value.length != 3) {
return 'Please enter the CVV code'
.tr;
}
return null;
},
),
),
),
],
)
],
),
),
],
),
// const SizedBox(
// height: 20,
// ),
MyElevatedButton(
title: 'Save'.tr,
onPressed: () {
if (controller.formKey.currentState!.validate()) {
// final creditCard = CreditCardModel(
// cardNumber: controller.cardNumberController.text,
// cardHolderName:
// controller.cardHolderNameController.text,
// expiryDate: controller.expiryDateController.text,
// cvvCode: controller.cvvCodeController.text,
// );
// Process the credit card details
// You can use GetX to handle the logic here
if (controller.formKey.currentState!.validate()) {
SecureStorage().saveData(BoxName.cardNumber,
controller.cardNumberController.text);
SecureStorage().saveData(BoxName.cardHolderName,
controller.cardHolderNameController.text);
SecureStorage().saveData(BoxName.cvvCode,
controller.cvvCodeController.text);
SecureStorage().saveData(BoxName.expiryDate,
controller.expiryDateController.text);
}
}
},
),
],
),
))));
}
Widget getCardIcon(String cardNumber) {
String cardType = detectCardType(
cardNumber); // Function to detect card type based on the first digit
IconData iconData;
Color iconColor;
switch (cardType) {
case 'Visa':
iconData = Icons.credit_card_rounded;
iconColor = Colors.blue; // Change color for Visa cards
break;
case 'Mastercard':
iconData = Icons.credit_card_rounded;
iconColor = Colors.red; // Change color for Mastercard cards
break;
default:
iconData = Icons.credit_card_rounded;
iconColor = Colors.black; // Default color for other card types
break;
}
return Icon(
iconData,
color: iconColor,
);
}
String detectCardType(String cardNumber) {
if (cardNumber.startsWith('4')) {
return 'Visa';
} else if (cardNumber.startsWith('5')) {
return 'Mastercard';
} else {
return 'Other';
}
}
}

View File

@@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/table_names.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import 'form_search_places_destenation.dart';
class PickerAnimtionContainerFormPlaces extends StatelessWidget {
const PickerAnimtionContainerFormPlaces({super.key});
@override
Widget build(BuildContext context) {
final mapEngine = Get.find<MapEngineController>();
final locationSearch = Get.find<LocationSearchController>();
final rideLifecycle = Get.find<RideLifecycleController>();
return GetBuilder<MapEngineController>(
builder: (controller) => Positioned(
bottom: 0,
left: 0,
right: 5,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: controller.heightPickerContainer,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: AppColor.accentColor, offset: Offset(2, 2)),
BoxShadow(
color: AppColor.accentColor, offset: Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
)),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
controller.isPickerShown
? const SizedBox()
: Text(
'Hi, Where to '.tr,
style: AppStyle.title,
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
height: 5,
),
controller.isPickerShown
? InkWell(
onTapDown: (details) {
controller.changePickerShown();
controller.changeHeightPlaces();
},
child: Container(
height: 7,
width: Get.width * .3,
decoration: BoxDecoration(
color: AppColor.accentColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColor.accentColor,
)),
),
)
: const SizedBox(),
controller.isPickerShown
? InkWell(
onTap: () {},
child: formSearchPlacesDestenation(),
)
: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
controller.changePickerShown();
},
child: Text(
"Pick your destination from Map".tr,
style: AppStyle.subtitle,
),
),
TextButton(
onPressed: () async {
List favoritePlaces = await sql
.getAllData(TableName.placesFavorite);
Get.defaultDialog(
title: 'Favorite Places'.tr,
content: SizedBox(
width: Get.width * .8,
height: 300,
child: favoritePlaces.isEmpty
? Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Icon(
Icons
.hourglass_empty_rounded,
size: 99,
color: AppColor
.primaryColor,
),
Text(
'You Dont Have Any places yet !'
.tr,
style: AppStyle.title,
),
],
),
)
: ListView.builder(
itemCount:
favoritePlaces.length,
itemBuilder:
(BuildContext context,
int index) {
return Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
TextButton(
onPressed: () async {
await rideLifecycle
.getDirectionMap(
'${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
);
controller
.changePickerShown();
controller
.changeBottomSheetShown(
forceValue:
true);
rideLifecycle
.bottomSheet();
Get.back();
},
child: Text(
favoritePlaces[
index]['name'],
style:
AppStyle.title,
),
),
IconButton(
onPressed: () async {
await sql.deleteData(
TableName
.placesFavorite,
favoritePlaces[
index]
['id']);
Get.back();
Get.snackbar(
'Deleted ',
'${'You are Delete'.tr} ${favoritePlaces[index]['name']} from your list',
backgroundColor:
AppColor
.accentColor);
},
icon: const Icon(Icons
.favorite_outlined),
),
],
);
},
),
),
onCancel: () {},
);
},
child: Text(
"Go To Favorite Places".tr,
style: AppStyle.subtitle,
),
),
],
),
if (controller.isPickerShown &&
locationSearch.placesDestination.isEmpty)
MyElevatedButton(
title: 'Go to this Target'.tr,
onPressed: () async {
await rideLifecycle.getDirectionMap(
'${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
'${locationSearch.newMyLocation.latitude},${locationSearch.newMyLocation.longitude}',
);
controller.changePickerShown();
controller.changeBottomSheetShown(
forceValue: true);
rideLifecycle.bottomSheet();
},
),
if (controller.isPickerShown &&
locationSearch.placesDestination.isEmpty)
const SizedBox(),
],
),
],
),
),
));
}
}

View File

@@ -0,0 +1,329 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/style.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/map/location_search_controller.dart';
import '../../../controller/home/map/map_engine_controller.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/points_for_rider_controller.dart';
class PointsPageForRider extends StatelessWidget {
PointsPageForRider({
super.key,
});
final locationSearch = Get.find<LocationSearchController>();
final mapEngine = Get.find<MapEngineController>();
final rideLifecycle = Get.find<RideLifecycleController>();
@override
Widget build(BuildContext context) {
Get.find<WayPointController>();
return GetBuilder<RideLifecycleController>(builder: (controller) {
return Positioned(
bottom: 2,
left: 2,
right: 2,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: controller.wayPointSheetHeight,
decoration: AppStyle.boxDecoration,
child: ListView(
children: [
// const AppBarPointsPageForRider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
mapEngine.downPoints();
},
icon: const Icon(Icons.arrow_drop_down_circle_outlined),
),
GetBuilder<WayPointController>(builder: (wayPointController) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
wayPointController.addWayPoints();
controller.isWayPointStopsSheetUtilGetMap = true;
},
child: const Text('Add Stops'),
),
wayPointController.wayPoints.length > 1
? ElevatedButton(
onPressed: () async {
locationSearch
.getMapPointsForAllMethods();
},
child: const Text('Get Direction'),
)
: const SizedBox()
],
);
}),
],
),
SizedBox(
height: Get.height * .36,
child: GetBuilder<WayPointController>(
builder: (wayPointController) {
return ReorderableListView(
// The children of the list are the text fields
children: wayPointController.wayPoints
.asMap()
.entries
.map((entry) {
final index = entry.key;
return Padding(
key: ValueKey(index),
padding: const EdgeInsets.all(1),
child: ListTile(
leading: Container(
decoration: BoxDecoration(
color: AppColor.deepPurpleAccent,
border: Border.all(),
shape: BoxShape.rectangle),
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
index.toString(),
style: AppStyle.title,
),
)),
title: InkWell(
onTap: () {
// showAddLocationDialog(context);
Get.defaultDialog(
content: SizedBox(
width: Get.width,
height: 400,
child: locationSearch
.placeListResponse[index]),
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(),
color:
AppColor.accentColor.withValues(alpha: 0.5)),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(index > 0
? locationSearch
.currentLocationStringAll[index]
.toString()
: ''),
const Icon(
Icons.reorder,
size: 20,
),
],
),
),
),
trailing: index > 0
? IconButton(
icon: const Icon(Icons.close),
onPressed: () {
wayPointController.removeTextField(index);
},
)
: IconButton(
icon: Icon(
Icons.close,
color: AppColor.secondaryColor,
),
onPressed: () {},
)),
);
}).toList(),
// The callback when the user reorders the text fields
onReorder: (int oldIndex, int newIndex) {
wayPointController.reorderTextFields(oldIndex, newIndex);
},
);
}),
),
],
),
),
);
});
}
// GetBuilder<PointsForRiderController>(
// builder: (controller) => Container(
// decoration: AppStyle.boxDecoration,
// height: Get.height *
// .5, // height: controller.heightPointsPageForRider,
// width: Get.width,
// child: Column(
// children: [
// SizedBox(
// height: 300,
// child: ReorderableListView(
// onReorder: (oldIndex, newIndex) {
// if (oldIndex < newIndex) {
// newIndex -= 1;
// }
// pointsForRiderController.locations.insert(
// newIndex,
// pointsForRiderController.locations
// .removeAt(oldIndex));
// },
// children: [
// for (int i = 0;
// i < pointsForRiderController.locations.length;
// i++)
// ListTile(
// key: Key('$i'),
// title: DragTarget<int>(
// onAccept: (int data) {
// pointsForRiderController.locations
// .insert(i, 'New Text Field');
// },
// builder: (context, candidateData, rejectedData) {
// return Row(
// children: [
// SizedBox(
// width: 300,
// child: TextField(
// controller: TextEditingController(
// text: pointsForRiderController
// .locations[i]),
// onChanged: (value) {
// pointsForRiderController
// .locations[i] = value;
// },
// decoration: InputDecoration(
// prefixIcon: IconButton(
// onPressed: () {
// pointsForRiderController
// .removeLocation(i);
// },
// icon: const Icon(Icons.delete),
// ),
// labelText: 'Text Field ${i + 1}',
// border: const OutlineInputBorder(),
// ),
// ),
// ),
// IconButton(
// onPressed: () {
// // pointsForRiderController.onReorder(
// // index, newIndex);
// },
// icon: const Icon(Icons.reorder),
// ),
// ],
// );
// },
// ),
// ),
// ],
// ),
// ),
// ElevatedButton(
// onPressed: () {
// pointsForRiderController.addLocation('location');
// },
// child: const Text('Add Text Field'),
// ),
// ],
// ),
// ));
}
void showAddLocationDialog(BuildContext context, int index) {
// Get.put(WayPointController());
showDialog(
context: context,
builder: (context) {
return Dialog.fullscreen(
// title: const Text('Add Location'),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Icons.close,
size: 40,
),
),
Text(
'Add Location'.tr,
style: AppStyle.title,
),
Icon(
Icons.clear,
color: AppColor.secondaryColor,
)
],
),
// SizedBox(
// width: Get.width,
// child: formSearchCaptain(),
// ),
],
),
);
},
);
}
class AppBarPointsPageForRider extends StatelessWidget {
const AppBarPointsPageForRider({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.arrow_back_ios_new_rounded,
color: AppColor.primaryColor,
),
),
Row(
children: [
CircleAvatar(
backgroundColor: AppColor.primaryColor,
maxRadius: 15,
child: Icon(
Icons.person,
color: AppColor.secondaryColor,
),
),
TextButton(
onPressed: () {},
child: Text(
"Switch Rider".tr,
style: AppStyle.title,
),
),
],
),
Icon(
Icons.clear,
color: AppColor.secondaryColor,
)
],
);
}
}

View File

@@ -0,0 +1,347 @@
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// تأكد من المسارات
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart';
import '../../../views/home/profile/complaint_page.dart';
class RideBeginPassenger extends StatelessWidget {
const RideBeginPassenger({super.key});
@override
Widget build(BuildContext context) {
final ProfileController profileController = Get.put(ProfileController());
final AudioRecorderController audioController =
Get.put(AudioRecorderController());
final uiController = Get.find<UiInteractionsController>();
return Obx(() {
final controller = Get.find<RideLifecycleController>();
// شرط الإظهار
final bool isVisible =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == false;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
// تم تقليل قيمة الإخفاء لأن الارتفاع الكلي للنافذة أصبح أصغر
bottom: isVisible ? 0 : -300,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Get.isDarkMode
? Colors.black.withValues(alpha: 0.4)
: Colors.black.withValues(alpha: 0.1),
blurRadius: 20,
spreadRadius: 2,
offset: const Offset(0, -3),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 1. مقبض السحب
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.grayColor.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 12),
// 2. هيدر المعلومات (سائق + سيارة + سعر)
_buildCompactHeader(controller),
const SizedBox(height: 12),
// خط فاصل خفيف
Divider(
height: 1,
thickness: 0.5,
color: AppColor.grayColor.withValues(alpha: 0.2)),
const SizedBox(height: 12),
// 3. الأزرار (إجراءات)
_buildCompactActionButtons(
context, controller, profileController, audioController),
// إضافة هامش سفلي بسيط لرفع الأزرار عن حافة الشاشة
const SizedBox(height: 5),
],
),
),
),
);
});
}
// --- الهيدر (بدون تغيير، ممتاز) ---
Widget _buildCompactHeader(RideLifecycleController controller) {
return Row(
children: [
// صورة السائق
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withValues(alpha: 0.5), width: 1.5),
),
child: CircleAvatar(
radius: 24,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) => const Icon(Icons.person),
),
),
const SizedBox(width: 10),
// الاسم ومعلومات السيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
controller.driverName,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: AppColor.writeColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 4),
const Icon(Icons.star, color: Colors.amber, size: 14),
Text(
controller.driverRate,
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 2),
Row(
children: [
Flexible(
child: Text(
'${controller.model}',
style: TextStyle(fontSize: 12, color: AppColor.grayColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: AppColor.writeColor.withValues(alpha: 0.05),
border: Border.all(
color: AppColor.grayColor.withValues(alpha: 0.2)),
borderRadius: BorderRadius.circular(4),
),
child: Text(
controller.licensePlate,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 11,
fontWeight: FontWeight.w900,
),
),
),
],
),
],
),
),
// السعر
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: AppColor.primaryColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
NumberFormat('#,###').format(controller.totalPassenger),
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 16,
color: AppColor.primaryColor,
),
),
Text('SYP',
style: TextStyle(fontSize: 9, color: AppColor.grayColor)),
],
),
),
],
);
}
// --- الأزرار (بدون تغيير) ---
Widget _buildCompactActionButtons(
BuildContext context,
RideLifecycleController controller,
ProfileController profileController,
AudioRecorderController audioController) {
final uiController = Get.find<UiInteractionsController>();
return SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_compactBtn(
icon: Icons.sos_rounded,
label: 'SOS'.tr,
color: AppColor.redColor,
bgColor: AppColor.redColor.withValues(alpha: 0.1),
onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
} else {
makePhoneCall('112');
}
},
),
_compactBtn(
icon: FontAwesome.whatsapp,
label: 'WhatsApp'.tr,
color: const Color(0xFF25D366),
bgColor: const Color(0xFF25D366).withValues(alpha: 0.1),
onTap: () async {
final phone = box.read(BoxName.sosPhonePassenger);
if (phone == null || phone.toString().isEmpty) {
// لا يوجد رقم طوارئ — نعرض الديالوج لإدخاله
await uiController.shareTripWithFamily();
} else {
final formattedPhone = uiController.formatSyrianPhoneNumber(
phone.toString());
uiController.sendWhatsapp(formattedPhone);
}
},
),
_compactBtn(
icon: Icons.share,
label: 'Share'.tr,
color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withValues(alpha: 0.1),
onTap: () async => await uiController.shareTripWithFamily(),
),
GetBuilder<AudioRecorderController>(
init: audioController,
builder: (audioCtx) {
return _compactBtn(
icon: audioCtx.isRecording
? Icons.stop_circle_outlined
: Icons.mic_none_outlined,
label: audioCtx.isRecording ? 'Stop'.tr : 'Record'.tr,
color: audioCtx.isRecording
? AppColor.redColor
: AppColor.primaryColor,
bgColor: audioCtx.isRecording
? AppColor.redColor.withValues(alpha: 0.1)
: AppColor.primaryColor.withValues(alpha: 0.1),
onTap: () async {
if (!audioCtx.isRecording) {
await audioCtx.startRecording(rideId: controller.rideId);
if (context.mounted) {
Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
}
} else {
await audioCtx.stopRecording();
if (context.mounted) {
Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
}
}
},
);
},
),
_compactBtn(
icon: Icons.info_outline_rounded,
label: 'Report'.tr,
color: AppColor.grayColor,
bgColor: AppColor.writeColor.withValues(alpha: 0.1),
onTap: () => Get.to(() => ComplaintPage()),
),
],
),
);
}
Widget _compactBtn({
required IconData icon,
required String label,
required Color color,
required Color bgColor,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: bgColor,
shape: BoxShape.circle,
),
child: Icon(icon, size: 20, color: color),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
color: AppColor.grayColor,
fontWeight: FontWeight.w500),
),
],
),
);
}
}

View File

@@ -0,0 +1,324 @@
import 'package:siro_rider/controller/functions/launch.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
// ... استيراد ملفاتك الأخرى ...
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
import '../../../controller/home/map/ride_state.dart';
import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart';
class RideFromStartApp extends StatelessWidget {
const RideFromStartApp({super.key});
@override
Widget build(BuildContext context) {
final profileController = Get.put(ProfileController());
final RideLifecycleController controller =
Get.find<RideLifecycleController>();
final UiInteractionsController uiController =
Get.find<UiInteractionsController>();
return Obx(() {
final bool isRideActive =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == true;
if (!isRideActive) return const SizedBox();
// قراءة البيانات
final rideData = controller.rideStatusFromStartApp['data'] ?? {};
final driverId = rideData['driver_id'];
final driverName = rideData['driverName'] ?? 'Captain'.tr;
final driverRate = controller.driverRate;
final carType = rideData['carType'] ?? 'Car'.tr;
final carModel = controller.model ?? '';
final arrivalTime = box.read(BoxName.arrivalTime) ?? '--:--';
// تحديد البيانات للعرض
final displayTime = controller.stringRemainingTimeRideBegin.isNotEmpty
? controller.stringRemainingTimeRideBegin
: arrivalTime;
final displayDistance = rideData['distance']?.toStringAsFixed(1) ?? 'N/A';
final displayPrice = rideData['price']?.toString() ?? 'N/A';
return Positioned(
left: 0,
right: 0,
bottom: 0, // ملتصق بالأسفل تماماً
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: AppColor.secondaryColor, // خلفية متفاعلة
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Get.isDarkMode
? Colors.black.withValues(alpha: 0.4)
: Colors.black12,
blurRadius: 15.0,
spreadRadius: 5.0,
offset: const Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 1. مقبض صغير للدلالة على السحب (تصميم جمالي)
Center(
child: Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
color: AppColor.grayColor.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10),
),
),
),
// 2. حالة الرحلة + معلومات السائق
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// صورة السائق
Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(color: AppColor.primaryColor, width: 2),
),
child: CircleAvatar(
radius: 28,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/$driverId.jpg'),
),
),
const SizedBox(width: 12),
// الاسم والسيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
driverName,
style: AppStyle.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.writeColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.star,
color: AppColor.yellowColor, size: 16),
const SizedBox(width: 4),
Text(
driverRate,
style: AppStyle.title.copyWith(
fontSize: 13, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Container(
width: 1,
height: 12,
color: AppColor.grayColor.withValues(alpha: 0.3)),
const SizedBox(width: 8),
Text(
"$carType - $carModel",
style: AppStyle.title.copyWith(
fontSize: 13, color: AppColor.grayColor),
),
],
),
],
),
),
// حالة الرحلة (نص ملون)
_buildStatusBadge(controller.currentRideState.value),
],
),
const SizedBox(height: 20),
// 3. شريط المعلومات (وقت، مسافة، سعر)
Container(
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
decoration: BoxDecoration(
color: AppColor.grayColor
.withValues(alpha: 0.1), // خلفية رمادية خفيفة جداً
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoColumn(
Icons.access_time_filled, displayTime, "Time".tr),
_buildVerticalDivider(),
_buildInfoColumn(Icons.location_on, "$displayDistance KM",
"Distance".tr),
_buildVerticalDivider(),
_buildInfoColumn(
Icons.attach_money, "$displayPrice SYP", "Price".tr),
],
),
),
const SizedBox(height: 20),
// 4. أزرار التحكم (SOS & Share)
Row(
children: [
// زر المشاركة (يأخذ مساحة أكبر)
Expanded(
flex: 2,
child: ElevatedButton.icon(
onPressed: () => _checkAndCall(
uiController.sendWhatsapp, profileController),
icon:
const Icon(FontAwesome.whatsapp, color: Colors.white),
label: Text("Share Trip".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
const SizedBox(width: 10),
// زر الاستغاثة SOS
Expanded(
flex: 1,
child: ElevatedButton.icon(
onPressed: () =>
makePhoneCall(box.read(BoxName.sosPhonePassenger)),
icon: const Icon(Icons.sos, color: Colors.white),
label: Text("SOS".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.redColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
],
),
],
),
),
);
});
}
// ------------------------- Widgets Helper Methods -------------------------
Widget _buildStatusBadge(RideState status) {
String text;
Color color;
if (status == RideState.inProgress) {
text = 'On Trip'.tr;
color = AppColor.primaryColor;
} else if (status == RideState.driverArrived) {
text = 'Arrived'.tr;
color = AppColor.greenColor;
} else {
text = 'Coming'.tr;
color = AppColor.yellowColor;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withValues(alpha: 0.5)),
),
child: Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
);
}
Widget _buildInfoColumn(IconData icon, String value, String label) {
return Column(
children: [
Icon(icon,
color: AppColor.primaryColor,
size: 22), // افترضت أن السكندري لون داكن، أو استخدم Primary
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: AppColor.writeColor,
),
),
Text(
label,
style: TextStyle(
fontSize: 11,
color: AppColor.grayColor,
),
),
],
);
}
Widget _buildVerticalDivider() {
return Container(
height: 30,
width: 1,
color: AppColor.grayColor.withValues(alpha: 0.2),
);
}
// دالة المساعدة للمنطق (بقيت كما هي ولكن تم تمرير البروفايل كونترولر)
Future<void> _checkAndCall(
Function(String) action, ProfileController profileController) async {
String? sosPhone = box.read(BoxName.sosPhonePassenger);
if (sosPhone == null || sosPhone == 'sos') {
await profileController.updatField('sosPhone', TextInputType.phone);
sosPhone = profileController.prfoileData['sosPhone'];
}
if (sosPhone != null && sosPhone != 'sos') {
action(sosPhone);
} else {
Get.snackbar('Warning'.tr, 'Please set a valid SOS phone number.'.tr,
backgroundColor: AppColor.redColor, colorText: Colors.white);
}
}
}

View File

@@ -0,0 +1,470 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:siro_rider/controller/home/map/ride_state.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class SearchingCaptainWindow extends StatefulWidget {
const SearchingCaptainWindow({super.key});
@override
State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
}
class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة
return Obx(() {
// ابحث عن الكنترولر مرة واحدة
final controller = Get.find<RideLifecycleController>();
// [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة
final bool isVisible =
controller.currentRideState.value == RideState.searching;
return AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// --- 1. أنيميشن الرادار ---
_buildRadarAnimation(controller),
const SizedBox(height: 20),
// --- 2. زر الإلغاء ---
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () {
// [تعديل 3] استدعاء دالة الإلغاء الموحدة
controller.changeCancelRidePageShow();
// ();
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor,
side:
BorderSide(color: AppColor.writeColor.withValues(alpha: 0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text('Cancel Search'.tr),
),
),
],
),
),
);
});
}
// --- ويدجت بناء أنيميشن الرادار ---
Widget _buildRadarAnimation(RideLifecycleController controller) {
return SizedBox(
height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
child: Stack(
alignment: Alignment.center,
children: [
// --- دوائر الرادار المتحركة (تبقى كما هي) ---
...List.generate(3, (index) {
return FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
),
),
child: ScaleTransition(
scale: Tween<double>(begin: 0.3, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
),
),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withValues(alpha: 0.7),
width: 2,
),
),
),
),
);
}),
// --- المحتوى في المنتصف ---
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// [تعديل 4] النص يأتي مباشرة من الكنترولر
controller.driversStatusForSearchWindow,
style: AppStyle.headTitle.copyWith(fontSize: 20),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Searching for the nearest captain...'.tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withValues(alpha: 0.7)),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// --- [!! تعديل جوهري !!] ---
// لم نعد بحاجة لـ buildTimerForIncrease
// المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار
// وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق)
// الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة
SizedBox(
height: 40,
width: 40,
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
strokeWidth: 3,
color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withValues(alpha: 0.2),
),
Center(
child: Icon(
Icons.search,
color: AppColor.writeColor.withValues(alpha: 0.8),
),
),
],
),
),
],
),
],
),
);
}
}
// --- [!! تعديل جوهري !!] ---
// تم حذف دالة `buildTimerForIncrease` بالكامل.
// تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف.
// لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار.
// دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart`
// وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية.
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// class SearchingCaptainWindow extends StatefulWidget {
// const SearchingCaptainWindow({super.key});
// @override
// State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
// }
// class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
// with SingleTickerProviderStateMixin {
// late AnimationController _animationController;
// @override
// void initState() {
// super.initState();
// _animationController = AnimationController(
// vsync: this,
// duration: const Duration(seconds: 2),
// )..repeat();
// }
// @override
// void dispose() {
// _animationController.dispose();
// super.dispose();
// }
// @override
// Widget build(BuildContext context) {
// return GetBuilder<MapPassengerController>(
// builder: (controller) {
// return AnimatedPositioned(
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeInOut,
// bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4,
// left: 0,
// right: 0,
// child: Container(
// padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(24),
// topRight: Radius.circular(24),
// ),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.2),
// blurRadius: 20,
// offset: const Offset(0, -5),
// ),
// ],
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// // --- 1. أنيميشن الرادار ---
// _buildRadarAnimation(controller),
// const SizedBox(height: 20),
// // --- 2. زر الإلغاء ---
// SizedBox(
// width: double.infinity,
// child: OutlinedButton(
// onPressed: () {
// // --- نفس منطقك للإلغاء ---
// controller.changeCancelRidePageShow();
// },
// style: OutlinedButton.styleFrom(
// foregroundColor: AppColor.writeColor,
// side: BorderSide(
// color: AppColor.writeColor.withOpacity(0.3)),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12)),
// padding: const EdgeInsets.symmetric(vertical: 12),
// ),
// child: Text('Cancel Search'.tr),
// ),
// ),
// ],
// ),
// ),
// );
// },
// );
// }
// // --- ويدجت بناء أنيميشن الرادار ---
// Widget _buildRadarAnimation(MapPassengerController controller) {
// return SizedBox(
// height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
// child: Stack(
// alignment: Alignment.center,
// children: [
// // --- دوائر الرادار المتحركة ---
// ...List.generate(3, (index) {
// return FadeTransition(
// opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: ScaleTransition(
// scale: Tween<double>(begin: 0.3, end: 1.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: Container(
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(
// color: AppColor.primaryColor.withOpacity(0.7),
// width: 2,
// ),
// ),
// ),
// ),
// );
// }),
// // --- المحتوى في المنتصف ---
// Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text(
// controller.driversStatusForSearchWindow,
// style: AppStyle.headTitle.copyWith(fontSize: 20),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 8),
// Text(
// 'Searching for the nearest captain...'.tr,
// style: AppStyle.subtitle
// .copyWith(color: AppColor.writeColor.withOpacity(0.7)),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// // --- استدعاء نفس دالة المؤقت الخاصة بك ---
// buildTimerForIncrease(controller),
// ],
// ),
// ],
// ),
// );
// }
// }
// // --- نفس دالة المؤقت الخاصة بك مع تعديلات شكلية بسيطة ---
// Widget buildTimerForIncrease(MapPassengerController mapPassengerController) {
// return StreamBuilder<int>(
// stream: Stream.periodic(const Duration(seconds: 1))
// .map((_) => ++mapPassengerController.currentTimeSearchingCaptainWindow),
// initialData: 0,
// builder: (context, snapshot) {
// if (snapshot.hasData && snapshot.data! > 45) {
// // --- عرض زر زيادة الأجرة بنفس منطقك القديم ---
// return TextButton(
// onPressed: () =>
// _showIncreaseFeeDialog(context, mapPassengerController),
// child: Text(
// "No one accepted? Try increasing the fare.".tr,
// style: AppStyle.title.copyWith(
// color: AppColor.primaryColor,
// decoration: TextDecoration.underline),
// textAlign: TextAlign.center,
// ),
// );
// }
// final double progress = (snapshot.data ?? 0).toDouble() / 30.0;
// return SizedBox(
// height: 40,
// width: 40,
// child: Stack(
// fit: StackFit.expand,
// children: [
// CircularProgressIndicator(
// value: progress,
// strokeWidth: 3,
// color: AppColor.primaryColor,
// backgroundColor: AppColor.primaryColor.withOpacity(0.2),
// ),
// Center(
// child: Text(
// '${snapshot.data ?? 0}',
// style: AppStyle.title.copyWith(
// color: AppColor.writeColor, fontWeight: FontWeight.bold),
// ),
// ),
// ],
// ),
// );
// },
// );
// }
// // --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) ---
// void _showIncreaseFeeDialog(
// BuildContext context, MapPassengerController mapPassengerController) {
// Get.defaultDialog(
// barrierDismissible: false,
// title: "Increase Your Trip Fee (Optional)".tr,
// titleStyle: AppStyle.title,
// content: Column(
// children: [
// Text(
// "We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers."
// .tr,
// style: AppStyle.subtitle,
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger + 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.add_circle,
// size: 40, color: AppColor.greenColor),
// ),
// SizedBox(
// width: 100,
// child: Form(
// key: mapPassengerController.increaseFeeFormKey,
// child: MyTextForm(
// controller: mapPassengerController.increasFeeFromPassenger,
// label:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// hint:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// type: TextInputType.number,
// ),
// ),
// ),
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger - 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.remove_circle,
// size: 40, color: AppColor.redColor),
// ),
// ],
// ),
// ],
// ),
// actions: [
// TextButton(
// child: Text("No, thanks".tr,
// style: const TextStyle(color: AppColor.redColor)),
// onPressed: () {
// Get.back();
// // mapPassengerController.cancelRide();
// mapPassengerController.changeCancelRidePageShow();
// },
// ),
// ElevatedButton(
// style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor),
// child: Text("Increase Fee".tr),
// onPressed: () =>
// mapPassengerController.increaseFeeByPassengerAndReOrder(),
// ),
// ],
// );
// }

View File

@@ -0,0 +1,342 @@
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/links.dart';
import '../../../print.dart';
class CupertinoDriverListWidget extends StatelessWidget {
CupertinoDriverListWidget({super.key});
final RideLifecycleController mapPassengerController =
Get.find<RideLifecycleController>();
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Driver List'.tr), // Ensure text is properly localized
),
child: SafeArea(
child: mapPassengerController.driversForMishwari.isEmpty
? Center(
child: Text(
'No drivers available at the moment. Please try again later.'
.tr,
style: const TextStyle(
fontSize: 18, // Adjust the size as needed
fontWeight: FontWeight.w600,
color: CupertinoColors.inactiveGray, // Customize color
),
textAlign: TextAlign.center, // Center-align the text
),
)
: ListView.separated(
itemCount: mapPassengerController.driversForMishwari.length,
separatorBuilder: (context, index) =>
const Divider(height: 1),
itemBuilder: (context, index) {
var driver =
mapPassengerController.driversForMishwari[index];
return Container(
decoration: AppStyle.boxDecoration1,
child: CupertinoListTile(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 8),
leading: CircleAvatar(
radius: 25,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
),
child: Builder(
builder: (context) {
return Image.network(
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
fit: BoxFit.cover,
loadingBuilder: (BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child; // Image is loaded
} else {
return Center(
child: CircularProgressIndicator(
value: loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
(loadingProgress
.expectedTotalBytes ??
1)
: null,
),
);
}
},
errorBuilder: (BuildContext context,
Object error, StackTrace? stackTrace) {
return const Icon(
Icons
.person, // Icon to show when image fails to load
size: 25, // Adjust the size as needed
color: AppColor
.blueColor, // Color for the error icon
);
},
);
},
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${driver['NAME'].toString().split(' ')[0]} ${driver['NAME'].toString().split(' ')[1]}',
style:
const TextStyle(fontWeight: FontWeight.bold),
),
Text('${'Age'.tr}: ${driver['age'].toString()}'),
Row(
children: [
const Icon(CupertinoIcons.star_fill,
size: 16,
color: CupertinoColors.systemYellow),
const SizedBox(width: 4),
Text(driver['rating']?.toStringAsFixed(1) ??
'N/A'.tr),
const SizedBox(width: 8),
Text('${'Rides'.tr}: ${driver['ride_count']}'),
],
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text('${'Plate'.tr}: ${driver['car_plate']}'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
// width: Get.width * .3,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex']
.toString() ==
'null'
? Colors.amber
: hexToColor(driver['color_hex']
.toString()),
borderRadius:
BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
),
],
),
],
),
onTap: () {
Log.print(' driver["id"]: ${driver['driver_id']}');
Get.find<RideLifecycleController>().driverIdVip =
driver['driver_id'];
// Handle driver selection
Get.defaultDialog(
title:
'${'Selected driver'.tr}: ${driver['NAME']}',
content: Column(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text(
'${'Plate'.tr}: ${driver['car_plate']}'),
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
'${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex']
.toString() ==
'null'
? Colors.amber
: hexToColor(
driver['color_hex']
.toString()),
borderRadius:
BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
],
),
],
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
showDateTimePickerDialog(driver);
}));
Log.print('${'Selected driver'.tr}: ${driver['NAME']}');
// Get.back(); // Close the dialog
},
),
);
},
)),
);
}
Color hexToColor(String hexColor) {
hexColor = hexColor.replaceAll("#", "");
String colorString = "ff$hexColor";
return Color(int.parse(colorString, radix: 16));
}
void showDriverSelectionDialog(Map<String, dynamic> driver) {
Get.defaultDialog(
title: '${'Selected driver'.tr}: ${driver['name']}',
content: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text('${'Plate'.tr}: ${driver['car_plate']}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex'].toString() == 'null'
? Colors.amber
: hexToColor(driver['color_hex'].toString()),
borderRadius: BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
],
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
showDateTimePickerDialog(driver);
},
),
);
}
void showDateTimePickerDialog(Map<String, dynamic> driver) {
Get.defaultDialog(
barrierDismissible: false,
title: "Select date and time of trip".tr,
content: SizedBox(
// height: 400, // Adjust height as needed
width: double.maxFinite,
child: Column(
children: [
DateTimePickerWidget(),
],
),
),
confirm: MyElevatedButton(
title: 'Confirm Trip'.tr,
onPressed: () async {
DateTime selectedDateTime =
mapPassengerController.selectedDateTime.value;
// Save trip data and set up notifications
Get.back();
await mapPassengerController.saveTripData(driver, selectedDateTime);
},
),
cancel: MyElevatedButton(
kolor: AppColor.redColor,
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
}
}
class DateTimePickerWidget extends StatelessWidget {
DateTimePickerWidget({super.key});
final RideLifecycleController controller = Get.find<RideLifecycleController>();
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
automaticallyImplyLeading: false,
middle: Text('Date and Time Picker'.tr),
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() => Text(
'${'Selected Date and Time'.tr}: ${controller.selectedDateTime.value}',
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
)),
const SizedBox(height: 20),
SizedBox(
height: 200,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
initialDateTime: controller.selectedDateTime.value,
onDateTimeChanged: (newDateTime) {
controller.updateDateTime(newDateTime);
},
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
GetBuilder<RideLifecycleController> timerForCancelTripFromPassenger() {
return GetBuilder<RideLifecycleController>(
builder: (controller) {
final isNearEnd =
controller.remainingTime <= 5; // Define a threshold for "near end"
return controller.remainingTime > 0 && controller.remainingTime != 25
? Positioned(
bottom: 5,
left: 10,
right: 10,
child: Container(
height: 180,
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(
value: controller.progress,
// Set the color based on the "isNearEnd" condition
color: isNearEnd ? Colors.red : Colors.blue,
),
Text(
'${controller.remainingTime}',
style: AppStyle.number,
),
],
),
const SizedBox(
width: 30,
),
Text(
'You can cancel Ride now'.tr,
style: AppStyle.title,
)
],
),
Text(
'After this period\nYou can\'t cancel!'.tr,
style: AppStyle.title,
)
],
),
),
),
)
: const SizedBox();
},
);
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import 'ride_begin_passenger.dart';
class TimerToPassengerFromDriver extends StatelessWidget {
const TimerToPassengerFromDriver({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.remainingTime == 0 &&
(controller.isDriverInPassengerWay == true ||
controller.timeToPassengerFromDriverAfterApplied > 0)) {
// ) {
return Positioned(
left: 10,
right: 10,
bottom: 5,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.remainingTime == 0 &&
(controller.isDriverInPassengerWay == true ||
controller.timeToPassengerFromDriverAfterApplied > 0)
? 200
: 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'You Can cancel Ride After Captain did not come in the time'
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
Stack(
children: [
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color: controller
.remainingTimeToPassengerFromDriverAfterApplied <
60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 25,
borderRadius: BorderRadius.circular(15),
value: controller
.progressTimerToPassengerFromDriverAfterApplied
.toDouble(),
),
Center(
child: Text(
controller.stringRemainingTimeToPassenger,
style: AppStyle.title,
),
)
],
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.phone,
color: AppColor.blueColor,
),
),
controller.remainingTimeToPassengerFromDriverAfterApplied < 60
? MyElevatedButton(
title: 'You can cancel trip'.tr,
onPressed: () async {
await controller
.calculateDistanceBetweenPassengerAndDriverBeforeCancelRide();
})
: const SizedBox()
],
),
),
),
);
} else if (controller.remainingTime == 0 &&
controller.isDriverArrivePassenger == true) {
return Positioned(
left: 10,
right: 10,
bottom: 5,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.remainingTime == 0 &&
controller.isDriverArrivePassenger == true
? 150
: 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'The driver waiting you in picked location .'.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
Stack(
children: [
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color:
controller.remainingTimeDriverWaitPassenger5Minute <
60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 50,
borderRadius: BorderRadius.circular(15),
value: controller
.progressTimerDriverWaitPassenger5Minute
.toDouble(),
),
Center(
child: Text(
controller
.stringRemainingTimeDriverWaitPassenger5Minute,
style: AppStyle.title,
),
)
],
),
Text(
'Please go to Car now '.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
],
),
),
),
);
} else {
return const RideBeginPassenger();
}
});
}
}

View File

@@ -0,0 +1,319 @@
import 'package:siro_rider/constant/links.dart';
import 'package:siro_rider/views/home/profile/complaint_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/controller/profile/profile_controller.dart';
import 'package:siro_rider/main.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map/ride_lifecycle_controller.dart';
import '../../../controller/home/map/ui_interactions_controller.dart';
class VipRideBeginPassenger extends StatelessWidget {
const VipRideBeginPassenger({
super.key,
});
@override
Widget build(BuildContext context) {
ProfileController profileController = Get.put(ProfileController());
AudioRecorderController audioController =
Get.put(AudioRecorderController());
final uiController = Get.find<UiInteractionsController>();
return GetBuilder<RideLifecycleController>(builder: (controller) {
if (controller.statusRideVip == 'Begin' ||
!controller.statusRideFromStart) {
return Positioned(
left: 10,
right: 10,
bottom: 10,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.statusRideVip == 'Begin' ? Get.height * .33 : 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg',
),
onBackgroundImageError: (_, __) {
// Handle error here
},
backgroundColor: Colors.grey,
child: const Icon(
Icons.person, // Default icon or placeholder
size: 30,
color: Colors.white,
), // Placeholder background color
),
Column(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
child: Text(
controller.driverName,
style: AppStyle.title,
),
),
),
const SizedBox(
height: 10,
),
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
controller.make,
style: AppStyle.title,
),
const SizedBox(
width: 10,
),
Text(
controller.model,
style: AppStyle.title,
),
],
),
),
),
],
),
Column(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(3),
child: Text(
'vip',
style: AppStyle.title,
),
),
),
Text(
'${controller.driverRate} 📈',
style: AppStyle.title,
),
],
),
],
),
// SizedBox(
// height: 5,
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration,
child: IconButton(
onPressed: () => Get.to(
() => ComplaintPage(),
transition: Transition.downToUp,
),
icon: const Icon(
Icons.note_add,
color: AppColor.redColor,
),
tooltip: ' Add Note', // Optional tooltip for clarity
),
),
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration,
child: audioController.isRecording == false
? IconButton(
onPressed: () async {
await audioController.startRecording(rideId: controller.rideId);
if (context.mounted) {
Toast.show(context, 'Start Record'.tr,
AppColor.greenColor);
}
},
icon: const Icon(
Icons.play_circle_fill_outlined,
color: AppColor.greenColor,
),
tooltip:
' Add Note', // Optional tooltip for clarity
)
: IconButton(
onPressed: () async {
await audioController.stopRecording();
if (context.mounted) {
Toast.show(context, 'Record saved'.tr,
AppColor.greenColor);
}
},
icon: const Icon(
Icons.stop_circle,
color: AppColor.greenColor,
),
tooltip:
' Add Note', // Optional tooltip for clarity
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
{
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
makePhoneCall('122');
// box.read(BoxName.sosPhonePassenger));
}
},
icon: const Icon(
Icons.sos_rounded,
color: AppColor.redColor,
),
),
),
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
if (box.read(BoxName.sosPhonePassenger) == null ||
box.read(BoxName.sosPhonePassenger) == 'sos') {
{
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
var phone = box.read(BoxName.countryCode) ==
'Egypt'
? '+2${box.read(BoxName.sosPhonePassenger)}'
: '+962${box.read(BoxName.sosPhonePassenger)}';
uiController.sendWhatsapp(phone);
}
},
icon: const Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
),
),
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
await uiController.shareTripWithFamily();
},
icon: const Icon(
AntDesign.Safety,
color: AppColor.blueColor,
),
),
),
],
),
Stack(
children: [
// StreamCounter(),
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color:
// controller.remainingTimeTimerRideBegin < 60
// ? AppColor.redColor
// :
AppColor.greenColor,
minHeight: 25,
borderRadius: BorderRadius.circular(15),
value:
24 //controller.progressTimerRideBegin.toDouble(),
),
Center(
child: Text(
controller.stringElapsedTimeRideBeginVip,
style: AppStyle.title,
),
)
],
)
],
),
),
),
);
} else {
return const SizedBox();
}
});
}
}
class StreamCounter extends StatelessWidget {
const StreamCounter({super.key});
@override
// Build the UI based on the timer value
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
return StreamBuilder<int>(
initialData: 0,
stream: controller.timerController.stream,
builder: (context, snapshot) {
// Calculate the remaining time based on the current tick
final remainingTime = controller.durationToRide - snapshot.data!;
// Format the remaining time as a string
final formattedRemainingTime =
'${(remainingTime / 60).floor()}:${(remainingTime % 60).toString().padLeft(2, '0')}';
// Return the UI widgets based on the remaining time
return Column(
children: [
Text(formattedRemainingTime),
// ElevatedButton(
// onPressed: () {
// // Handle button press here
// },
// ),
],
);
},
);
});
}
}

View File

@@ -0,0 +1,260 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/views/home/my_wallet/payment_history_passenger_page.dart';
import 'dart:ui'; // لاستخدام تأثيرات متقدمة
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/payment/credit_card_controller.dart';
import '../../../controller/payment/payment_controller.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import 'passenger_wallet_dialoge.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class PassengerWallet extends StatelessWidget {
const PassengerWallet({super.key});
@override
Widget build(BuildContext context) {
// نفس منطق استدعاء الكنترولرز
Get.put(PaymentController());
Get.put(CreditCardController());
return MyScafolld(
title: 'My Balance'.tr,
isleading: true,
body: [
// استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة
Stack(
children: [
// استخدام Column لتنظيم المحتوى بشكل أفضل
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 16),
// --- 1. بطاقة المحفظة العصرية ---
_buildModernWalletCard(),
const SizedBox(height: 32),
Text("Actions".tr,
style: AppStyle.title.copyWith(
color: AppColor.writeColor.withOpacity(0.6))),
const Divider(height: 24),
// --- 2. قائمة الخيارات المنظمة ---
_buildActionTile(
icon: Icons.add_card_rounded,
title: 'Top up Balance'.tr,
subtitle: 'Add funds using our secure methods'.tr,
onTap: () =>
showPaymentBottomSheet(context), // نفس دالتك القديمة
),
_buildActionTile(
icon: Icons.history_rounded,
title: 'Payment History'.tr,
subtitle: 'View your past transactions'.tr,
onTap: () => Get.to(
() => const PaymentHistoryPassengerPage(),
transition: Transition.rightToLeftWithFade),
),
_buildActionTile(
icon: Icons.phone_iphone_rounded,
title: 'Set Phone Number'.tr,
subtitle: 'Link a phone number for transfers'.tr,
onTap: () => _showWalletPhoneDialog(context,
Get.find<PaymentController>()), // نفس دالتك القديمة
),
],
),
),
// --- عرض الـ Dialog بنفس طريقتك القديمة ---
const PassengerWalletDialog(),
],
),
],
);
}
// --- ويدجت مساعدة لبناء بطاقة المحفظة ---
Widget _buildModernWalletCard() {
return GetBuilder<PaymentController>(
builder: (paymentController) {
return Container(
width: double.infinity,
height: Get.height * 0.25,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
colors: [
AppColor.primaryColor,
Color(0xFF1E3A8A)
], // تدرج لوني أنيق
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.3),
blurRadius: 25,
offset: const Offset(0, 10),
),
],
),
child: Stack(
children: [
// --- عنصر تزييني (شكل موجة) ---
Positioned(
right: -100,
bottom: -100,
child: Icon(
Icons.waves,
size: 250,
color: Colors.white.withOpacity(0.05),
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${AppInformation.appName} ${'Balance'.tr}',
style: AppStyle.headTitle.copyWith(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Icon(Icons.memory_rounded,
color: Colors.white.withOpacity(0.7),
size: 30), // أيقونة الشريحة
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Current Balance".tr,
style: AppStyle.subtitle
.copyWith(color: Colors.white.withOpacity(0.7)),
),
Text(
'${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 1.5,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
(box.read(BoxName.name) ?? "User Name").toString(),
style: AppStyle.title.copyWith(
color: Colors.white.withOpacity(0.8),
fontSize: 16,
),
),
],
),
],
),
),
],
),
);
},
);
}
// --- ويدجت مساعدة لبناء عناصر القائمة ---
Widget _buildActionTile({
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
leading: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: AppColor.primaryColor, size: 24),
),
title: Text(title.tr, style: AppStyle.title),
subtitle: Text(subtitle.tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.6))),
trailing: Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: AppColor.writeColor),
);
}
// --- نفس دالة الـ Dialog الخاصة بك ---
void _showWalletPhoneDialog(
BuildContext context, PaymentController controller) {
Get.dialog(
CupertinoAlertDialog(
title: Text('Insert Wallet phone number'.tr),
content: Column(
children: [
const SizedBox(height: 10),
Form(
key: controller.formKey,
child: CupertinoTextField(
controller: controller.walletphoneController,
placeholder: 'Insert Wallet phone number'.tr,
keyboardType: TextInputType.phone,
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text('Cancel'.tr,
style: const TextStyle(color: CupertinoColors.destructiveRed)),
onPressed: () => Get.back(),
),
CupertinoDialogAction(
child: Text('OK'.tr,
style: const TextStyle(color: CupertinoColors.activeGreen)),
onPressed: () {
Get.back();
box.write(
BoxName.phoneWallet, (controller.walletphoneController.text));
Toast.show(context, 'Phone Wallet Saved Successfully'.tr,
AppColor.greenColor);
},
),
],
),
barrierDismissible: false,
);
}
}
// الكلاس القديم CardIntaleqWallet لم نعد بحاجة إليه لأنه تم دمجه وتطويره

View File

@@ -0,0 +1,486 @@
import 'package:siro_rider/print.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/functions/encrypt_decrypt.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/controller/functions/toast.dart';
import 'package:siro_rider/controller/payment/payment_controller.dart';
import 'package:local_auth/local_auth.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_textField.dart';
import 'payment_screen_sham.dart';
class PassengerWalletDialog extends StatelessWidget {
const PassengerWalletDialog({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<PaymentController>(
builder: (controller) => Positioned(
top: Get.height * .1,
right: Get.width * .15,
left: Get.width * .15,
bottom: Get.height * .1,
child: controller.isPromoSheetDialogue
? CupertinoActionSheet(
title: Text('Select Payment Amount'.tr),
actions: [
CupertinoActionSheetAction(
onPressed: () {
controller.updateSelectedAmount(
box.read(BoxName.countryCode) == 'Syria' ? 10000 : 10,
);
showPaymentOptions(context, controller);
},
child: Text(
box.read(BoxName.countryCode) == 'Syria'
? '10000 ${'LE'.tr}'
: '10 ${'SYP'.tr}',
),
),
CupertinoActionSheetAction(
onPressed: () {
controller.updateSelectedAmount(
box.read(BoxName.countryCode) == 'Syria' ? 20000 : 20,
);
showPaymentOptions(context, controller);
},
child: Text(
box.read(BoxName.countryCode) == 'Syria'
? '20000 ${'LE'.tr} = 2050 ${'LE'.tr}'
: '20 ${'SYP'.tr}',
),
),
CupertinoActionSheetAction(
onPressed: () {
controller.updateSelectedAmount(
box.read(BoxName.countryCode) == 'Syria' ? 40000 : 40,
);
showPaymentOptions(context, controller);
},
child: Text(
box.read(BoxName.countryCode) == 'Syria'
? '40000 ${'LE'.tr} = 4150 ${'LE'.tr}'
: '40 ${'SYP'.tr}',
),
),
CupertinoActionSheetAction(
onPressed: () {
controller.updateSelectedAmount(
box.read(BoxName.countryCode) == 'Syria' ? 100000 : 50,
);
showPaymentOptions(context, controller);
},
child: Text(
box.read(BoxName.countryCode) == 'Syria'
? '100000 ${'LE'.tr} = 11000 ${'LE'.tr}'
: '50 ${'SYP'.tr}',
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () {
controller.changePromoSheetDialogue();
},
child: Text('Cancel'.tr),
),
)
: const SizedBox(),
),
);
}
}
// class PassengerWalletDialog extends StatelessWidget {
// const PassengerWalletDialog({
// super.key,
// });
// @override
// Widget build(BuildContext context) {
// return GetBuilder<PaymentController>(
// builder: (controller) {
// return Positioned(
// top: Get.height * .1,
// right: Get.width * .15,
// left: Get.width * .15,
// bottom: Get.height * .1,
// child: controller.isPromoSheetDialogue
// ? Container()
// : SizedBox
// .shrink(), // If condition is false, return an empty widget
// );
// },
// );
// }
// }
void showPaymentBottomSheet(BuildContext context) {
final controller = Get.find<PaymentController>();
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
),
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
Get.back();
return false;
},
child: Container(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Select Payment Amount'.tr,
style: AppStyle.headTitle2,
textAlign: TextAlign.center,
),
const SizedBox(height: 16.0),
// Payment Options List
_buildPaymentOption(
context: context,
controller: controller,
amount: 500,
bonusAmount: 30,
currency: 'SYP'.tr,
),
const SizedBox(height: 8.0),
_buildPaymentOption(
context: context,
controller: controller,
amount: 1000,
bonusAmount: 70,
currency: 'SYP'.tr,
),
const SizedBox(height: 8.0),
_buildPaymentOption(
context: context,
controller: controller,
amount: 2000,
bonusAmount: 180,
currency: 'SYP'.tr,
),
const SizedBox(height: 8.0),
_buildPaymentOption(
context: context,
controller: controller,
amount: 5000,
bonusAmount: 700,
currency: 'SYP'.tr,
),
const SizedBox(height: 16.0),
TextButton(
onPressed: () => Get.back(),
child: Text('Cancel'.tr),
),
],
),
),
);
},
);
}
Widget _buildPaymentOption({
required BuildContext context,
required PaymentController controller,
required int amount,
required double bonusAmount,
required String currency,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
controller.updateSelectedAmount(amount);
Get.back();
showPaymentOptions(context, controller);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
bonusAmount > 0
? '${'Pay'.tr} $amount $currency, ${'Get'.tr} ${amount + bonusAmount} $currency'
: '$amount $currency',
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
),
);
}
void showPaymentOptions(BuildContext context, PaymentController controller) {
showCupertinoModalPopup(
context: context,
builder: (context) => CupertinoActionSheet(
title: Text('Payment Options'.tr),
actions: [
box.read(BoxName.countryCode) == 'Syria'
? CupertinoActionSheetAction(
child: Text('💳 Pay with Credit Card'.tr),
onPressed: () async {
if (controller.selectedAmount != 0) {
controller.payWithEcash(
context,
controller.selectedAmount.toString(),
// () async {
// await controller.addPassengerWallet();
// controller.changePromoSheetDialogue();
);
await controller.getPassengerWallet();
} else {
Toast.show(context, '⚠️ You need to choose an amount!'.tr,
AppColor.redColor);
}
},
)
: const SizedBox(),
// box.read(BoxName.phoneWallet) != null
// ? CupertinoActionSheetAction(
// child: Text('💰 Pay with Wallet'.tr),
// onPressed: () async {
// if (controller.selectedAmount != 0) {
// controller.isLoading = true;
// controller.update();
// controller.payWithMTNWallet(
// context,
// controller.selectedAmount.toString(),
// 'SYP',
// );
// await controller.getPassengerWallet();
// controller.isLoading = false;
// controller.update();
// } else {
// Toast.show(context, '⚠️ You need to choose an amount!'.tr,
// AppColor.redColor);
// }
// },
// )
// : CupertinoActionSheetAction(
// child: Text('Add wallet phone you use'.tr),
// onPressed: () {
// Get.dialog(
// CupertinoAlertDialog(
// title: Text('Insert Wallet phone number'.tr),
// content: Column(
// children: [
// const SizedBox(height: 10),
// CupertinoTextField(
// controller: controller.walletphoneController,
// placeholder: 'Insert Wallet phone number'.tr,
// keyboardType: TextInputType.phone,
// padding: const EdgeInsets.symmetric(
// vertical: 12,
// horizontal: 10,
// ),
// ),
// ],
// ),
// actions: [
// CupertinoDialogAction(
// child: Text('Cancel'.tr,
// style: const TextStyle(
// color: CupertinoColors.destructiveRed)),
// onPressed: () {
// Get.back();
// },
// ),
// CupertinoDialogAction(
// child: Text('OK'.tr,
// style: const TextStyle(
// color: CupertinoColors.activeGreen)),
// onPressed: () async {
// Get.back();
// box.write(BoxName.phoneWallet,
// (controller.walletphoneController.text));
// Toast.show(
// context,
// 'Phone Wallet Saved Successfully'.tr,
// AppColor.greenColor);
// },
// ),
// ],
// ),
// barrierDismissible: false,
// );
// },
// ),
// GestureDetector(
// onTap: () async {
// Get.back();
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Insert Wallet phone number'.tr,
// content: Form(
// key: controller.formKey,
// child: MyTextForm(
// controller: controller.walletphoneController,
// label: 'Insert Wallet phone number'.tr,
// hint: '963941234567',
// type: TextInputType.phone)),
// confirm: MyElevatedButton(
// title: 'OK'.tr,
// onPressed: () async {
// Get.back();
// if (controller.formKey.currentState!.validate()) {
// if (controller.selectedAmount != 0) {
// controller.isLoading = true;
// controller.update();
// box.write(BoxName.phoneWallet,
// (controller.walletphoneController.text));
// Get.back();
// await controller.payWithMTNWallet(
// context,
// controller.selectedAmount.toString(),
// 'SYP',
// );
// await controller.getPassengerWallet();
// controller.isLoading = false;
// controller.update();
// } else {
// Toast.show(
// context,
// '⚠️ You need to choose an amount!'.tr,
// AppColor.redColor,
// );
// }
// }
// }));
// },
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// 'Pay by MTN Wallet'.tr,
// style: AppStyle.title,
// ),
// const SizedBox(width: 10),
// Image.asset(
// 'assets/images/cashMTN.png',
// width: 70,
// height: 70,
// fit: BoxFit.contain,
// ),
// ],
// ),
// )),
GestureDetector(
onTap: () async {
Get.back();
Get.defaultDialog(
barrierDismissible: false,
title: 'Insert Wallet phone number'.tr,
content: Form(
key: controller.formKey,
child: MyTextForm(
controller: controller.walletphoneController,
label: 'Insert Wallet phone number'.tr,
hint: '963941234567',
type: TextInputType.phone)),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () async {
Get.back();
if (controller.formKey.currentState!.validate()) {
box.write(BoxName.phoneWallet,
controller.walletphoneController.text);
await controller.payWithSyriaTelWallet(
controller.selectedAmount.toString(), 'SYP');
}
}));
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Pay by Syriatel Wallet'.tr,
style: AppStyle.title,
),
const SizedBox(width: 10),
Image.asset(
'assets/images/syriatel.png',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
),
)),
GestureDetector(
onTap: () async {
// التحقق بالبصمة قبل أي شيء
bool isAuthSupported =
await LocalAuthentication().isDeviceSupported();
if (isAuthSupported) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
);
if (!didAuthenticate) {
Log.print("❌ User did not authenticate with biometrics");
return;
}
}
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
Get.to(() => PaymentScreenSmsProvider(
amount: double.parse(controller.selectedAmount.toString())));
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Pay by Sham Cash'.tr,
style: AppStyle.title,
),
const SizedBox(width: 10),
Image.asset(
'assets/images/shamCash.png',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
),
)),
],
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr),
onPressed: () {
// controller.changePromoSheetDialogue();
Get.back();
},
),
),
);
}

View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'package:siro_rider/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart';
class PaymentHistoryDriverPage extends StatelessWidget {
const PaymentHistoryDriverPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverWalletHistoryController());
return MyScafolld(
title: 'Payment History'.tr,
body: [
GetBuilder<DriverWalletHistoryController>(
builder: (controller) => controller.isLoading
? const MyCircularProgressIndicator()
: ListView.builder(
itemCount: controller.archive.length,
itemBuilder: (BuildContext context, int index) {
var list = controller.archive[index];
return Padding(
padding: const EdgeInsets.all(4),
child: Container(
decoration: BoxDecoration(
color: double.parse(list['amount']) < 0
? AppColor.redColor.withOpacity(.4)
: AppColor.greenColor.withOpacity(.4)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
list['amount'],
style: AppStyle.title,
),
Text(
list['created_at'],
style: AppStyle.title,
),
],
),
),
);
},
),
)
],
isleading: true);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/payment/passenger_wallet_history_controller.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'package:siro_rider/views/widgets/mycircular.dart';
class PaymentHistoryPassengerPage extends StatelessWidget {
const PaymentHistoryPassengerPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(PassengerWalletHistoryController());
return MyScafolld(
title: 'Payment History'.tr,
body: [
GetBuilder<PassengerWalletHistoryController>(
builder: (controller) => controller.isLoading
? const MyCircularProgressIndicator() // iOS-style loading indicator
: controller.archive.isEmpty
? Center(
child: Text(
'No wallet record found'.tr,
style: AppStyle.title,
),
)
: CupertinoListSection.insetGrouped(
children: List.generate(
controller.archive.length,
(index) {
var list = controller.archive[index];
return CupertinoListTile(
backgroundColor: double.parse(list['balance']) < 0
? AppColor.redColor.withOpacity(.2)
: AppColor.greenColor.withOpacity(.2),
title: Text(
list['balance'],
style: AppStyle.title.copyWith(
color: CupertinoColors.black,
),
),
additionalInfo: Text(
list['created_at'],
style: AppStyle.title.copyWith(
fontSize: 12,
color: CupertinoColors.systemGrey,
),
),
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 16),
);
},
),
),
)
],
isleading: true);
}
}

View File

@@ -0,0 +1,515 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
// --- خدمة الدفع (نفس المنطق السابق) ---
class PaymentService {
final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash/passenger";
Future<String?> createInvoice({required double amount}) async {
final url = "$_baseUrl/create_invoice.php";
try {
final response = await CRUD().postWallet(
link: url,
payload: {
'passengerID': box.read(BoxName.passengerID),
'amount': amount.toString(),
},
).timeout(const Duration(seconds: 15));
if (response != 'failure') {
final data = response;
if (data['status'] == 'success' && data['invoice_number'] != null) {
return data['invoice_number'].toString();
}
}
return null;
} catch (e) {
debugPrint("Create Invoice Error: $e");
return null;
}
}
Future<bool> checkInvoiceStatus(String invoiceNumber) async {
final url = "$_baseUrl/check_status.php";
try {
final response = await CRUD().postWallet(link: url, payload: {
'invoice_number': invoiceNumber,
}).timeout(const Duration(seconds: 10));
if (response != 'failure') {
final data = response;
return data['status'] == 'success' &&
data['invoice_status'] == 'completed';
}
return false;
} catch (e) {
return false;
}
}
}
enum PaymentStatus {
creatingInvoice,
waitingForPayment,
paymentSuccess,
paymentTimeout,
paymentError
}
class PaymentScreenSmsProvider extends StatefulWidget {
final double amount;
final String providerName;
final String providerLogo;
final String qrImagePath;
const PaymentScreenSmsProvider({
super.key,
required this.amount,
this.providerName = 'شام كاش',
this.providerLogo = 'assets/images/shamCash.png',
this.qrImagePath = 'assets/images/shamcashsend.png',
});
@override
_PaymentScreenSmsProviderState createState() =>
_PaymentScreenSmsProviderState();
}
class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider>
with SingleTickerProviderStateMixin {
final PaymentService _paymentService = PaymentService();
Timer? _pollingTimer;
PaymentStatus _status = PaymentStatus.creatingInvoice;
String? _invoiceNumber;
// العنوان الثابت للدفع (المستخرج من الصورة)
final String _paymentAddress = "80f23afe40499b02f49966c3340ae0fc";
// متحكم الأنيميشن للوميض
late AnimationController _blinkController;
late Animation<Color?> _colorAnimation;
late Animation<double> _shadowAnimation;
@override
void initState() {
super.initState();
// إعداد الأنيميشن (وميض أحمر)
_blinkController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
)..repeat(reverse: true); // يكرر الحركة ذهاباً وإياباً
_colorAnimation = ColorTween(
begin: Colors.red.shade700,
end: Colors.red.shade100,
).animate(_blinkController);
_shadowAnimation = Tween<double>(begin: 2.0, end: 15.0).animate(
CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut),
);
_createAndPollInvoice();
}
@override
void dispose() {
_pollingTimer?.cancel();
_blinkController.dispose();
super.dispose();
}
void _createAndPollInvoice() async {
setState(() => _status = PaymentStatus.creatingInvoice);
final invoiceNumber =
await _paymentService.createInvoice(amount: widget.amount);
if (invoiceNumber != null && mounted) {
setState(() {
_invoiceNumber = invoiceNumber;
_status = PaymentStatus.waitingForPayment;
});
_startPolling(invoiceNumber);
} else if (mounted) {
setState(() => _status = PaymentStatus.paymentError);
}
}
void _startPolling(String invoiceNumber) {
const timeoutDuration = Duration(minutes: 5);
var elapsed = Duration.zero;
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
elapsed += const Duration(seconds: 5);
if (elapsed >= timeoutDuration) {
timer.cancel();
if (mounted) setState(() => _status = PaymentStatus.paymentTimeout);
return;
}
final isCompleted =
await _paymentService.checkInvoiceStatus(invoiceNumber);
if (isCompleted && mounted) {
timer.cancel();
setState(() => _status = PaymentStatus.paymentSuccess);
}
});
}
Future<bool> _onPopInvoked() async {
if (_status == PaymentStatus.waitingForPayment) {
final shouldPop = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('إلغاء العملية؟', textAlign: TextAlign.right),
content: const Text('الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
textAlign: TextAlign.right),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء')),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('خروج', style: TextStyle(color: Colors.red))),
],
),
);
return shouldPop ?? false;
}
return true;
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onPopInvoked,
child: Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: Text("دفع عبر ${widget.providerName}"),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: _buildContentByStatus()),
),
),
),
);
}
Widget _buildContentByStatus() {
switch (_status) {
case PaymentStatus.creatingInvoice:
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text("جاري إنشاء رقم البيان...", style: TextStyle(fontSize: 16)),
],
);
case PaymentStatus.waitingForPayment:
return _buildWaitingForPaymentUI();
case PaymentStatus.paymentSuccess:
return _buildSuccessUI();
case PaymentStatus.paymentTimeout:
case PaymentStatus.paymentError:
return _buildErrorUI();
}
}
Widget _buildWaitingForPaymentUI() {
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
final invoiceText = _invoiceNumber ?? '---';
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 1. المبلغ
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600]),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.25),
blurRadius: 10,
offset: const Offset(0, 5))
],
),
child: Column(
children: [
const Text("المبلغ المطلوب",
style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 5),
Text("${currencyFormat.format(widget.amount)} ل.س",
style: const TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold)),
],
),
),
const SizedBox(height: 25),
// 2. رقم البيان (هام جداً - وميض أحمر)
AnimatedBuilder(
animation: _blinkController,
builder: (context, child) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: _colorAnimation.value ?? Colors.red,
width: 3.0, // إطار سميك
),
boxShadow: [
BoxShadow(
color: (_colorAnimation.value ?? Colors.red)
.withOpacity(0.4),
blurRadius: _shadowAnimation.value,
spreadRadius: 2,
)
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.warning_rounded,
color: Colors.red.shade800, size: 28),
const SizedBox(width: 8),
Text(
"هام جداً: لا تنسَ!",
style: TextStyle(
color: Colors.red.shade900,
fontWeight: FontWeight.bold,
fontSize: 18),
),
],
),
const SizedBox(height: 10),
const Text(
"يجب نسخ (رقم البيان) هذا ووضعه في تطبيق شام كاش لضمان نجاح العملية.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Colors.black87,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 15),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text("تم نسخ رقم البيان ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.red.shade700));
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.red.shade200, width: 1)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("رقم البيان (Invoice No)",
style: TextStyle(
fontSize: 12, color: Colors.grey)),
Text(invoiceText,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
color: Colors.red.shade900)),
],
),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.copy_rounded,
color: Colors.red.shade900, size: 24),
),
],
),
),
),
],
),
);
},
),
const SizedBox(height: 25),
// 3. عنوان الدفع (اختياري / عادي)
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("عنوان الدفع (Payment Address)",
style: TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 8),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: _paymentAddress));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text("تم نسخ عنوان الدفع ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.green.shade600));
},
child: Row(
children: [
Expanded(
child: Text(_paymentAddress,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
fontFamily: 'Courier',
color: Colors.black87,
),
overflow: TextOverflow.ellipsis),
),
const SizedBox(width: 8),
const Icon(Icons.copy, size: 18, color: Colors.grey),
],
),
),
],
),
),
const SizedBox(height: 30),
// 4. QR Code
const Text("أو امسح الرمز للدفع",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: InteractiveViewer(
child: Image.asset(widget.qrImagePath))));
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.grey.shade300)),
child: Image.asset(widget.qrImagePath,
width: 150,
height: 150,
fit: BoxFit.contain,
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
size: 100, color: Colors.grey)),
),
),
const SizedBox(height: 30),
const LinearProgressIndicator(backgroundColor: Colors.white),
const SizedBox(height: 10),
const Text("جاري التحقق من الدفع تلقائياً...",
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20),
],
),
);
}
Widget _buildSuccessUI() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.verified_rounded, color: Colors.green, size: 100),
const SizedBox(height: 20),
const Text("تم الدفع بنجاح!",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16)),
onPressed: () => Navigator.of(context).pop(),
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
),
),
],
);
}
Widget _buildErrorUI() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline_rounded, color: Colors.red.shade400, size: 80),
const SizedBox(height: 20),
Text(
_status == PaymentStatus.paymentTimeout
? "انتهى الوقت"
: "لم يتم التحقق",
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 15),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Text("لم يصلنا إشعار الدفع خلال الوقت المحدد.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15)),
onPressed: _createAndPollInvoice,
icon: const Icon(Icons.refresh),
label: const Text("حاول مرة أخرى"),
),
),
const SizedBox(height: 15),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("إلغاء", style: TextStyle(color: Colors.grey)))
],
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/payment/payment_controller.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../my_wallet/passenger_wallet.dart';
class PointsCaptain extends StatelessWidget {
PaymentController paymentController = Get.put(PaymentController());
PointsCaptain({
super.key,
required this.kolor,
required this.countPoint,
required this.pricePoint,
});
final Color kolor;
final String countPoint;
double pricePoint;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
Get.to(() => const PassengerWallet());
paymentController.changePromoSheetDialogue();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
child: Container(
width: Get.width * .21,
height: Get.width * .29,
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
kolor.withOpacity(0.3),
kolor,
kolor.withOpacity(0.7),
kolor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(color: AppColor.accentColor),
borderRadius: BorderRadius.circular(12),
shape: BoxShape.rectangle,
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'$countPoint ${'LE'.tr}',
style: AppStyle.subtitle,
),
Text(
'$pricePoint ${box.read(BoxName.countryCode) == 'Jordan' ? 'JOD'.tr : 'LE'.tr}',
style: AppStyle.title,
textAlign: TextAlign.center,
),
],
),
)),
),
);
}
}

View File

@@ -0,0 +1,229 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/home/profile/complaint_controller.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart'; // سنستخدم السكافولد الخاص بك
import 'package:siro_rider/views/widgets/mycircular.dart';
import 'package:siro_rider/views/widgets/mydialoug.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart'; // سنستخدم الزر الخاص بك
import '../../../constant/colors.dart';
import '../../../controller/functions/audio_record1.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class ComplaintPage extends StatelessWidget {
ComplaintPage({super.key});
final ComplaintController complaintController =
Get.put(ComplaintController());
final AudioRecorderController audioRecorderController =
Get.put(AudioRecorderController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Submit a Complaint'.tr,
isleading: true,
body: [
GetBuilder<ComplaintController>(
builder: (controller) {
if (controller.isLoading && controller.feedBack.isEmpty) {
// عرض التحميل المبدئي فقط
return const MyCircularProgressIndicator();
}
return Stack(
children: [
Form(
key: controller.formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
// --- 1. بطاقة إدخال نص الشكوى ---
_buildSectionCard(
title: '1. Describe Your Issue'.tr,
child: TextFormField(
controller: controller.complaintController,
decoration: InputDecoration(
hintText: 'Enter your complaint here...'.tr,
filled: true,
fillColor: AppColor.secondaryColor.withOpacity(0.5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.all(16),
),
maxLines: 6,
style: AppStyle.subtitle,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a description of the issue.'
.tr;
}
return null;
},
),
),
// --- 2. بطاقة إرفاق التسجيل الصوتي ---
_buildSectionCard(
title: '2. Attach Recorded Audio (Optional)'.tr,
child: FutureBuilder<List<String>>(
future: audioRecorderController.getRecordedFiles(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator());
}
if (snapshot.hasError ||
!snapshot.hasData ||
snapshot.data!.isEmpty) {
return Center(
child: Text('No audio files found.'.tr,
style: AppStyle.subtitle));
}
return Column(
children: snapshot.data!.map((audioFilePath) {
final audioFile = File(audioFilePath);
final isUploaded =
controller.audioLink.isNotEmpty &&
controller.audioLink.contains(
audioFilePath.split('/').last);
return ListTile(
leading: Icon(
isUploaded
? Icons.check_circle
: Icons.mic,
color: isUploaded
? AppColor.greenColor
: AppColor.redColor),
title: Text(audioFilePath.split('/').last,
style: AppStyle.subtitle,
overflow: TextOverflow.ellipsis),
subtitle: isUploaded
? Text('Uploaded'.tr,
style: const TextStyle(
color: AppColor.greenColor))
: null,
onTap: isUploaded
? null
: () {
MyDialogContent().getDialog(
'Confirm Attachment'.tr,
Text(
'Attach this audio file?'.tr),
() async {
await controller
.uploadAudioFile(audioFile);
});
},
);
}).toList(),
);
},
),
),
// --- 3. بطاقة تفاصيل الرحلة والرد ---
_buildSectionCard(
title: '3. Review Details & Response'.tr,
child: Column(
children: [
if (controller.feedBack.isNotEmpty) ...[
_buildDetailRow(Icons.calendar_today_outlined,
'Date'.tr, controller.feedBack[0]['date']),
_buildDetailRow(
Icons.monetization_on_outlined,
'Price'.tr,
'${controller.feedBack[0]['price']}'),
],
const Divider(height: 24),
ListTile(
leading: const Icon(Icons.support_agent_outlined,
color: AppColor.primaryColor),
title: Text("Intaleq's Response".tr,
style: AppStyle.title),
subtitle: Text(
// --- تعديل هنا لعرض الرد من الخادم ---
controller.passengerReport?['body']
?.toString() ??
'Awaiting response...'.tr,
style: AppStyle.subtitle.copyWith(height: 1.5),
),
),
],
),
),
// --- 4. زر الإرسال ---
const SizedBox(height: 24),
MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Submit Complaint'.tr,
onPressed: () async {
// --- تعديل: استدعاء الدالة الجديدة فقط ---
await controller.submitComplaintToServer();
},
),
const SizedBox(height: 24), // مسافة إضافية بالأسفل
],
),
),
// --- عرض مؤشر التحميل فوق كل شيء ---
if (controller.isLoading)
Container(
color: Colors.black.withOpacity(0.5),
child: const MyCircularProgressIndicator(),
),
],
);
},
),
],
);
}
// --- ويدجت مساعدة لبناء البطاقات ---
Widget _buildSectionCard({required String title, required Widget child}) {
return Card(
margin: const EdgeInsets.only(bottom: 20),
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppStyle.headTitle.copyWith(fontSize: 18)),
const SizedBox(height: 12),
child,
],
),
),
);
}
// --- ويدجت مساعدة لعرض صفوف التفاصيل ---
Widget _buildDetailRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Icon(icon, color: AppColor.writeColor.withOpacity(0.6), size: 20),
const SizedBox(width: 12),
Text('${label.tr}:',
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.7))),
const Spacer(),
Text(value,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
],
),
);
}
}

View File

@@ -0,0 +1,599 @@
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:siro_rider/env/env.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/home/profile/order_history_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mycircular.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Main Screen
// ─────────────────────────────────────────────────────────────────────────────
class OrderHistory extends StatelessWidget {
const OrderHistory({super.key});
@override
Widget build(BuildContext context) {
Get.put(OrderHistoryController());
return MyScafolld(
title: 'Order History'.tr,
isleading: true,
body: [
GetBuilder<OrderHistoryController>(
builder: (controller) {
if (controller.isloading) {
return const MyCircularProgressIndicator();
}
if (controller.orderHistoryListPassenger.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.route_outlined,
size: 80, color: AppColor.writeColor.withOpacity(0.3)),
const SizedBox(height: 16),
Text('No trip history found'.tr,
style: AppStyle.headTitle2),
const SizedBox(height: 6),
Text('Your past trips will appear here.'.tr,
style: AppStyle.subtitle),
],
),
);
}
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
itemCount: controller.orderHistoryListPassenger.length,
separatorBuilder: (_, __) => const SizedBox(height: 14),
itemBuilder: (context, index) {
final ride = controller.orderHistoryListPassenger[index];
return _HistoryCard(
key: ValueKey(ride['id'] ?? index),
ride: ride,
);
},
);
},
),
],
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Coordinate helpers
// ─────────────────────────────────────────────────────────────────────────────
LatLng _parseLatLng(String raw, LatLng fallback) {
try {
final parts = raw.split(',');
return LatLng(double.parse(parts[0]), double.parse(parts[1]));
} catch (_) {
return fallback;
}
}
const LatLng _kDamascus = LatLng(33.5, 36.3);
// ─────────────────────────────────────────────────────────────────────────────
// Lightweight card — NO native map in the list
// ─────────────────────────────────────────────────────────────────────────────
class _HistoryCard extends StatelessWidget {
final Map<String, dynamic> ride;
const _HistoryCard({Key? key, required this.ride}) : super(key: key);
@override
Widget build(BuildContext context) {
final start = _parseLatLng(ride['start_location'] ?? '', _kDamascus);
final end = _parseLatLng(ride['end_location'] ?? '', _kDamascus);
final status = ride['status'] ?? '';
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _openDetail(context, ride, start, end),
borderRadius: BorderRadius.circular(18),
child: Ink(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.12),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Lightweight route preview (pure Flutter, zero native cost) ──
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(18),
topRight: Radius.circular(18),
),
child: SizedBox(
height: 130,
width: double.infinity,
child: CustomPaint(
painter: _RoutePainter(start: start, end: end),
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.45),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.map_outlined,
color: Colors.white, size: 13),
const SizedBox(width: 4),
Text('View Map'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w600)),
],
),
),
),
),
),
),
),
// ── Details ──────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.access_time_rounded,
size: 14,
color: AppColor.writeColor.withOpacity(0.5)),
const SizedBox(width: 4),
Text(
'${ride['date']} · ${ride['time']}',
style: AppStyle.subtitle.copyWith(
fontSize: 12,
color: AppColor.writeColor.withOpacity(0.6)),
),
],
),
_StatusChip(status: status),
],
),
const Divider(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Total Price'.tr,
style: AppStyle.title.copyWith(fontSize: 15)),
Text(
'${ride['price']} ${'SYP'.tr}',
style: AppStyle.headTitle.copyWith(
fontSize: 20, color: AppColor.primaryColor),
),
],
),
],
),
),
],
),
),
),
);
}
void _openDetail(BuildContext context, Map<String, dynamic> ride,
LatLng start, LatLng end) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => _RideDetailSheet(ride: ride, start: start, end: end),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Pure-Flutter route painter — grid background + animated dashed line
// ─────────────────────────────────────────────────────────────────────────────
class _RoutePainter extends CustomPainter {
final LatLng start;
final LatLng end;
const _RoutePainter({required this.start, required this.end});
@override
void paint(Canvas canvas, Size size) {
// Background gradient
final bgPaint = Paint()
..shader = LinearGradient(
colors: [
AppColor.primaryColor.withOpacity(0.08),
AppColor.primaryColor.withOpacity(0.18),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPaint);
// Subtle grid
final gridPaint = Paint()
..color = AppColor.primaryColor.withOpacity(0.06)
..strokeWidth = 1;
const step = 20.0;
for (double x = 0; x < size.width; x += step) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint);
}
for (double y = 0; y < size.height; y += step) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
}
// Map lat/lng to canvas — simple linear projection
final minLat = math.min(start.latitude, end.latitude);
final maxLat = math.max(start.latitude, end.latitude);
final minLng = math.min(start.longitude, end.longitude);
final maxLng = math.max(start.longitude, end.longitude);
final latRange = maxLat - minLat;
final lngRange = maxLng - minLng;
final pad = 32.0;
Offset project(LatLng p) {
double x, y;
if (lngRange < 0.0005) {
x = size.width / 2;
} else {
x = pad + ((p.longitude - minLng) / lngRange) * (size.width - 2 * pad);
}
if (latRange < 0.0005) {
y = size.height / 2;
} else {
// Invert y (latitude grows up, canvas grows down)
y = size.height -
pad -
((p.latitude - minLat) / latRange) * (size.height - 2 * pad);
}
return Offset(x, y);
}
final startPt = project(start);
final endPt = project(end);
// Dashed route line
final linePaint = Paint()
..color = AppColor.primaryColor
..strokeWidth = 2.5
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
_drawDashedLine(canvas, startPt, endPt, linePaint, 8, 5);
// Glow behind markers
final glowPaint = Paint()
..color = AppColor.primaryColor.withOpacity(0.2)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8);
canvas.drawCircle(startPt, 14, glowPaint);
canvas.drawCircle(endPt, 14, glowPaint);
// Start dot (A)
_drawMarker(canvas, startPt, AppColor.primaryColor, 'A');
// End dot (B)
_drawMarker(canvas, endPt, AppColor.redColor, 'B');
}
void _drawDashedLine(Canvas canvas, Offset p1, Offset p2, Paint paint,
double dashLen, double gapLen) {
final dx = p2.dx - p1.dx;
final dy = p2.dy - p1.dy;
final dist = math.sqrt(dx * dx + dy * dy);
if (dist == 0) return;
final ux = dx / dist;
final uy = dy / dist;
double traveled = 0;
bool drawing = true;
while (traveled < dist) {
final segLen = drawing ? dashLen : gapLen;
final next = math.min(traveled + segLen, dist);
if (drawing) {
canvas.drawLine(
Offset(p1.dx + ux * traveled, p1.dy + uy * traveled),
Offset(p1.dx + ux * next, p1.dy + uy * next),
paint,
);
}
traveled = next;
drawing = !drawing;
}
}
void _drawMarker(Canvas canvas, Offset center, Color color, String label) {
// Outer ring
canvas.drawCircle(center, 12, Paint()..color = color.withOpacity(0.25));
// Solid circle
canvas.drawCircle(center, 8, Paint()..color = color);
// White inner
canvas.drawCircle(center, 4, Paint()..color = Colors.white);
// Label text
final tp = TextPainter(
text: TextSpan(
text: label,
style: TextStyle(
color: color, fontSize: 7, fontWeight: FontWeight.w900)),
textDirection: TextDirection.ltr,
)..layout();
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
}
@override
bool shouldRepaint(_RoutePainter old) => old.start != start || old.end != end;
}
// ─────────────────────────────────────────────────────────────────────────────
// Status chip
// ─────────────────────────────────────────────────────────────────────────────
class _StatusChip extends StatelessWidget {
final String status;
const _StatusChip({required this.status});
@override
Widget build(BuildContext context) {
Color color;
IconData icon;
if (status == 'Canceled'.tr) {
color = AppColor.redColor;
icon = Icons.cancel_outlined;
} else if (status == 'Finished'.tr) {
color = AppColor.greenColor;
icon = Icons.check_circle_outline;
} else {
color = AppColor.yellowColor;
icon = Icons.hourglass_empty_rounded;
}
return 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.3), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 13),
const SizedBox(width: 4),
Text(status,
style: AppStyle.subtitle.copyWith(
color: color, fontWeight: FontWeight.bold, fontSize: 11)),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Detail bottom sheet — ONE MapLibre instance, only when user requests it
// ─────────────────────────────────────────────────────────────────────────────
class _RideDetailSheet extends StatefulWidget {
final Map<String, dynamic> ride;
final LatLng start;
final LatLng end;
const _RideDetailSheet(
{required this.ride, required this.start, required this.end});
@override
State<_RideDetailSheet> createState() => _RideDetailSheetState();
}
class _RideDetailSheetState extends State<_RideDetailSheet> {
IntaleqMapController? _mc;
Set<Marker> _markers = {};
Set<Polyline> _polylines = {};
LatLngBounds? get _bounds {
final latDiff = (widget.start.latitude - widget.end.latitude).abs();
final lngDiff = (widget.start.longitude - widget.end.longitude).abs();
if (latDiff < 0.0005 && lngDiff < 0.0005) return null;
return LatLngBounds(
northeast: LatLng(
math.max(widget.start.latitude, widget.end.latitude),
math.max(widget.start.longitude, widget.end.longitude),
),
southwest: LatLng(
math.min(widget.start.latitude, widget.end.latitude),
math.min(widget.start.longitude, widget.end.longitude),
),
);
}
void _onMapCreated(IntaleqMapController c) => _mc = c;
Future<void> _onStyleLoaded() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 400));
if (!mounted || _mc == null) return;
await _draw();
});
}
Future<void> _draw() async {
if (!mounted) return;
setState(() {
_polylines = {
Polyline(
polylineId: const PolylineId('route'),
points: [widget.start, widget.end],
color: AppColor.primaryColor,
width: 4,
),
};
_markers = {
Marker(
markerId: const MarkerId('start'),
position: widget.start,
icon: InlqBitmap.fromAsset('assets/images/A.png'),
anchor: const Offset(0.5, 1.0),
),
Marker(
markerId: const MarkerId('end'),
position: widget.end,
icon: InlqBitmap.fromAsset('assets/images/b.png'),
anchor: const Offset(0.5, 1.0),
),
};
});
final b = _bounds;
if (b != null) {
await _mc?.animateCamera(CameraUpdate.newLatLngBounds(b,
left: 60, top: 60, right: 60, bottom: 60));
} else {
await _mc?.animateCamera(CameraUpdate.newLatLngZoom(widget.start, 14));
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final ride = widget.ride;
final center = LatLng(
(widget.start.latitude + widget.end.latitude) / 2,
(widget.start.longitude + widget.end.longitude) / 2,
);
return DraggableScrollableSheet(
initialChildSize: 0.88,
minChildSize: 0.5,
maxChildSize: 0.95,
builder: (_, scrollController) => Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
children: [
// Handle
Container(
margin: const EdgeInsets.symmetric(vertical: 10),
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.writeColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(2),
),
),
// Map — only ONE instance, created on demand
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
child: IntaleqMap(
apiKey: Env.mapSaasKey,
styleUrl: Get.isDarkMode
? 'assets/style_dark.json'
: 'assets/style.json',
initialCameraPosition:
CameraPosition(target: center, zoom: 12),
onMapCreated: (c) {
_mc = c;
_onStyleLoaded();
},
myLocationEnabled: false,
markers: _markers,
polylines: _polylines,
),
),
),
// Trip info strip
Container(
padding: const EdgeInsets.fromLTRB(20, 14, 20, 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${ride['date']} · ${ride['time']}',
style: AppStyle.subtitle.copyWith(
fontSize: 12,
color:
AppColor.writeColor.withOpacity(0.55))),
const SizedBox(height: 2),
Text('${ride['price']} ${'SYP'.tr}',
style: AppStyle.headTitle.copyWith(
fontSize: 22, color: AppColor.primaryColor)),
],
),
_StatusChip(status: ride['status'] ?? ''),
],
),
const SizedBox(height: 14),
// Open in Google Maps
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
final url =
'https://www.google.com/maps/dir/${ride['start_location']}/${ride['end_location']}/';
showInBrowser(url);
},
icon: const Icon(Icons.open_in_new, size: 16),
label: Text('Open in Google Maps'.tr),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
),
),
],
),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/profile/captain_profile_controller.dart';
import 'package:siro_rider/main.dart';
import 'package:siro_rider/views/widgets/elevated_btn.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import '../../../constant/api_key.dart';
import '../../widgets/my_textField.dart';
class ProfileCaptain extends StatelessWidget {
const ProfileCaptain({super.key});
@override
Widget build(BuildContext context) {
Get.put(CaptainProfileController());
return MyScafolld(
title: 'My Profile'.tr,
body: [
GetBuilder<CaptainProfileController>(
builder: (controller) => Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: Get.width * 0.26,
backgroundColor: Colors.white,
backgroundImage: CachedNetworkImageProvider(
'${AK.serverPHP}/portrate_captain_image/${box.read(BoxName.driverID)}.jpg',
),
),
const SizedBox(height: 8.0),
Text(
box.read(BoxName.nameDriver) +
' ' +
box.read(BoxName.lastNameDriver).toString(),
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Email is'.tr} :${box.read(BoxName.emailDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Phone Number is'.tr} :${box.read(BoxName.phoneDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Date of Birth is'.tr} :${box.read(BoxName.dobDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Sex is '.tr}:${box.read(BoxName.sexDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
const Divider(
// height: 2,
endIndent: 1,
indent: 2,
thickness: 2,
),
const SizedBox(height: 8.0),
Text('Car Details'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 8.0),
Text('${'VIN is'.tr} :${box.read(BoxName.vin)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Color is '.tr} :${box.read(BoxName.color)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Car Plate is '.tr} :${box.read(BoxName.carPlate)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Make is '.tr}:${box.read(BoxName.make)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Model is'.tr} :${box.read(BoxName.model)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Year is'.tr} :${box.read(BoxName.year)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Expiration Date '.tr} :${box.read(BoxName.expirationDate)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
],
),
),
),
),
)
],
isleading: true,
action: GetBuilder<CaptainProfileController>(
builder: (controller) => IconButton(
onPressed: () {
Get.defaultDialog(
title: 'Edit Your data'.tr,
titleStyle: AppStyle.title,
content: SizedBox(
height: Get.height * .4,
child: SingleChildScrollView(
child: Column(
children: [
MyTextForm(
controller: controller.vin,
hint: 'write vin for your car'.tr,
label: 'VIN'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.color,
hint: 'write Color for your car'.tr,
label: 'Color'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.make,
hint: 'write Make for your car'.tr,
label: 'Make'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.model,
hint: 'write Model for your car'.tr,
label: 'Model'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.year,
hint: 'write Year for your car'.tr,
label: 'Year'.tr,
type: TextInputType.number,
),
MyTextForm(
controller: controller.expirationDate,
hint: 'write Expiration Date for your car'.tr,
label: 'Expiration Date'.tr,
type: TextInputType.datetime),
MyElevatedButton(
title: 'Update'.tr,
onPressed: () => controller.updateFields())
],
),
),
));
},
icon: const Icon(Icons.edit),
),
));
}
}

View File

@@ -0,0 +1,206 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:siro_rider/controller/home/profile/promos_controller.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
import 'dart:ui'; // لاستخدامه في الفاصل
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../widgets/mycircular.dart';
import 'package:dotted_line/dotted_line.dart'; // ستحتاج لإضافة هذا الباكج
// ملاحظة: ستحتاج لإضافة هذا الباكج إلى ملف pubspec.yaml الخاص بك
// flutter pub add dotted_line
// --- الويدجت الرئيسية بالتصميم الجديد ---
class PromosPassengerPage extends StatelessWidget {
const PromosPassengerPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(PromosController()); // نفس منطقك القديم
return MyScafolld(
title: "Today's Promos".tr, // عنوان أكثر جاذبية
isleading: true,
body: [
GetBuilder<PromosController>(
builder: (controller) {
if (controller.isLoading) {
return const MyCircularProgressIndicator();
}
if (controller.promoList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.local_offer_outlined,
size: 80, color: Colors.grey),
const SizedBox(height: 16),
Text("No promos available right now.".tr,
style: AppStyle.headTitle2),
Text("Check back later for new offers!".tr,
style: AppStyle.subtitle),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: controller.promoList.length,
itemBuilder: (BuildContext context, int index) {
final promo = controller.promoList[index];
// --- استدعاء ويدجت الكوبون الجديدة ---
return _buildPromoTicket(context, promo);
},
);
},
)
],
);
}
// --- ويدجت بناء كوبون الخصم ---
Widget _buildPromoTicket(BuildContext context, Map<String, dynamic> promo) {
return Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
height: 140, // ارتفاع ثابت للكوبون
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: ClipPath(
clipper: TicketClipper(), // Clipper مخصص لرسم شكل التذكرة
child: Container(
color: AppColor.secondaryColor,
child: Row(
children: [
// --- الجزء الأيسر: تفاصيل العرض ---
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
promo['description'],
style: AppStyle.headTitle.copyWith(fontSize: 18),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Row(
children: [
const Icon(Icons.calendar_today_outlined,
size: 14, color: Colors.grey),
const SizedBox(width: 6),
Text(
'${'Valid Until:'.tr} ${promo['validity_end_date']}',
style: AppStyle.subtitle
.copyWith(fontSize: 12, color: Colors.grey),
),
],
),
],
),
),
),
// --- الفاصل المنقط ---
SizedBox(
height: 110,
child: DottedLine(
direction: Axis.vertical,
lineThickness: 2.0,
dashLength: 8.0,
dashColor: AppColor.writeColor.withOpacity(0.2),
dashGapLength: 4.0,
),
),
// --- الجزء الأيمن: كود الخصم وزر النسخ ---
Expanded(
flex: 2,
child: GestureDetector(
onTap: () {
// --- نفس منطقك القديم للنسخ ---
Clipboard.setData(
ClipboardData(text: promo['promo_code']));
Get.snackbar(
'Promo Copied!'.tr,
'${'Code'.tr} ${promo['promo_code']} ${'copied to clipboard'.tr}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
);
},
child: Container(
color: AppColor.primaryColor.withOpacity(0.1),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'CODE'.tr,
style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor, letterSpacing: 2),
),
const SizedBox(height: 8),
Text(
promo['promo_code'],
style: AppStyle.headTitle.copyWith(
fontSize: 24, color: AppColor.primaryColor),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.copy,
size: 14, color: AppColor.primaryColor),
const SizedBox(width: 4),
Text('Copy'.tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.primaryColor)),
],
),
],
),
),
),
),
],
),
),
),
),
);
}
}
// --- كلاس مخصص لرسم شكل التذكرة ---
class TicketClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0.0);
double radius = 10;
path.addOval(
Rect.fromCircle(center: Offset(0, size.height / 2), radius: radius));
path.addOval(Rect.fromCircle(
center: Offset(size.width, size.height / 2), radius: radius));
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

View File

@@ -0,0 +1,88 @@
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/views/widgets/my_scafold.dart';
class TaarifPage extends StatelessWidget {
const TaarifPage({super.key});
@override
Widget build(BuildContext context) {
return MyScafolld(isleading: true, title: 'Tariffs'.tr, body: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ListView(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge,
children: [
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.symmetric(),
textBaseline: TextBaseline.alphabetic,
children: [
TableRow(
// decoration: AppStyle.boxDecoration,
children: [
Text('Minimum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title)
: Text('20 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Maximum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('200 ${'JOD'.tr}', style: AppStyle.title)
: Text('15000 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Flag-down fee'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title)
: Text('15 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km',
style: AppStyle.title)
: Text('1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km',
style: AppStyle.title),
Text('Including Tax'.tr, style: AppStyle.title),
],
),
],
),
const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text('10%', style: AppStyle.title),
const SizedBox(height: 20),
Text('Morning'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text(
'from 07:30 till 10:30 (Thursday, Friday, Saturday, Monday)'.tr,
style: AppStyle.title),
const SizedBox(height: 20),
Text('Evening'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text(
'from 12:00 till 15:00 (Thursday, Friday, Saturday, Monday)'.tr,
style: AppStyle.title),
const SizedBox(height: 20),
Text('Night'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text('from 23:59 till 05:30'.tr, style: AppStyle.title),
],
),
),
]);
}
}

View File

@@ -0,0 +1,296 @@
import 'package:siro_rider/controller/home/home_page_controller.dart';
import 'package:siro_rider/controller/local/local_controller.dart';
import 'package:siro_rider/constant/colors.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_rider/views/lang/languages.dart';
import 'HomePage/about_page.dart';
import 'HomePage/frequentlyQuestionsPage.dart';
import 'HomePage/share_app_page.dart';
import 'HomePage/trip_record_page.dart';
// NOTE: This is a placeholder for your actual CountryPickerFromSetting widget.
// You should remove this and import your own widget.
class CountryPickerFromSetting extends StatelessWidget {
const CountryPickerFromSetting({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Change Country'.tr)),
body: Center(
child: Text('Country Picker Page Placeholder'.tr),
),
);
}
}
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
// Using lazyPut to ensure the controller is available when needed.
Get.lazyPut(() => HomePageController());
return Scaffold(
backgroundColor: AppColor.secondaryColor.withOpacity(0.94),
appBar: AppBar(
title: Text('Setting'.tr,
style: AppStyle.headTitle2.copyWith(fontSize: 20)),
backgroundColor: AppColor.secondaryColor,
elevation: 0.5,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new, color: AppColor.writeColor),
onPressed: () => Get.back(),
),
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
children: [
_buildSectionHeader('General'.tr),
_buildSettingsCard(
children: [
_buildSettingsTile(
icon: Icons.language,
color: Colors.blue,
title: 'Language'.tr,
subtitle: 'To change Language the App'.tr,
onTap: () => Get.to(() => const Language()),
),
Divider(
height: 1,
indent: 68,
endIndent: 16,
color: AppColor.grayColor.withOpacity(0.1)),
GetBuilder<LocaleController>(
builder: (localeController) {
return _buildSettingsTile(
icon: Icons.palette_outlined,
color: Colors.deepPurpleAccent,
title: 'Appearance'.tr,
subtitle: (localeController.themeMode == ThemeMode.system
? 'System Default'.tr
: localeController.themeMode == ThemeMode.dark
? 'Dark Mode'.tr
: 'Light Mode'.tr)
.tr,
onTap: () => _showThemeSheet(context, localeController),
);
},
),
],
),
const SizedBox(height: 24),
_buildSectionHeader('Preferences'.tr),
_buildSettingsCard(
children: [
GetBuilder<HomePageController>(
builder: (controller) {
return _buildSettingsSwitchTile(
icon: Icons.vibration,
color: Colors.purple,
title: 'Vibration'.tr,
subtitle: 'Vibration feedback for all buttons'.tr,
value: controller.isVibrate,
onChanged: controller.changeVibrateOption,
);
},
),
const Divider(height: 1, indent: 68, endIndent: 16),
_buildSettingsTile(
icon: Icons.mic_none,
color: Colors.orange,
title: 'Trips recorded'.tr,
subtitle: 'Here recorded trips audio'.tr,
onTap: () => Get.to(() => const TripsRecordedPage()),
),
],
),
const SizedBox(height: 24),
_buildSectionHeader('Support & Info'.tr),
_buildSettingsCard(
children: [
_buildSettingsTile(
icon: Icons.help_outline,
color: Colors.cyan,
title: 'Frequently Questions'.tr,
subtitle: 'Find answers to common questions'.tr,
onTap: () => Get.to(() => const FrequentlyQuestionsPage()),
),
const Divider(height: 1, indent: 68, endIndent: 16),
_buildSettingsTile(
icon: Icons.info_outline,
color: Colors.indigo,
title: 'About Us'.tr,
subtitle: 'Learn more about our app and mission'.tr,
onTap: () => Get.to(() => const AboutPage()),
),
const Divider(height: 1, indent: 68, endIndent: 16),
_buildSettingsTile(
icon: Icons.share_outlined,
color: Colors.redAccent,
title: 'Share App'.tr,
subtitle: 'Share with friends and earn rewards'.tr,
onTap: () => Get.to(() => ShareAppPage()),
),
],
),
],
),
),
);
}
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0, left: 8.0),
child: Text(
title,
style: TextStyle(
color: AppColor.grayColor,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
);
}
void _showThemeSheet(BuildContext context, LocaleController controller) {
final options = [
{'label': 'System Default'.tr, 'mode': ThemeMode.system},
{'label': 'Light Mode'.tr, 'mode': ThemeMode.light},
{'label': 'Dark Mode'.tr, 'mode': ThemeMode.dark},
];
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: AppColor.grayColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(2),
),
),
Text('Select Appearance'.tr,
style: AppStyle.headTitle2.copyWith(fontSize: 18)),
const SizedBox(height: 20),
...options.map((opt) {
final isSelected = controller.themeMode == opt['mode'];
return ListTile(
title: Text(opt['label'] as String,
style: TextStyle(
color: isSelected
? AppColor.primaryColor
: AppColor.writeColor,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal)),
trailing: isSelected
? Icon(Icons.check_circle, color: AppColor.primaryColor)
: null,
onTap: () {
Get.back();
controller.changeThemeMode(opt['mode'] as ThemeMode);
},
);
}).toList(),
const SizedBox(height: 20),
],
),
),
);
}
Widget _buildSettingsCard({required List<Widget> children}) {
return Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
clipBehavior: Clip.antiAlias,
child: Column(
children: children,
),
);
}
Widget _buildSettingsTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
onTap: onTap,
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 22),
),
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
color: AppColor.writeColor)),
subtitle: Text(subtitle,
style: TextStyle(color: AppColor.grayColor, fontSize: 13)),
trailing:
Icon(Icons.chevron_right, color: AppColor.grayColor.withOpacity(0.5)),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
);
}
Widget _buildSettingsSwitchTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return SwitchListTile(
secondary: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 22),
),
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
color: AppColor.writeColor)),
subtitle: Text(subtitle,
style: TextStyle(color: AppColor.grayColor, fontSize: 13)),
value: value,
onChanged: onChanged,
activeColor: const Color(0xFF007AFF), // iOS-like blue
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
);
}
}