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,214 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/finance_design_system.dart';
import '../../../controller/home/captin/behavior_controller.dart';
class BehaviorPage extends StatelessWidget {
const BehaviorPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(DriverBehaviorController());
controller.fetchDriverBehavior();
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Driver Behavior'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
centerTitle: true,
backgroundColor: FinanceDesignSystem.cardColor,
elevation: 0,
iconTheme: IconThemeData(color: FinanceDesignSystem.primaryDark),
),
body: Obx(() {
if (controller.isLoading.value) {
return Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.accentBlue));
}
double score = controller.overallScore.value;
bool isExcellent = score >= 90;
bool isGood = score >= 75 && score < 90;
Color statusColor =
isExcellent ? Colors.green : (isGood ? Colors.orange : Colors.red);
String statusText = isExcellent
? 'Excellent'.tr
: (isGood ? 'Good'.tr : 'Needs Improvement'.tr);
return CustomScrollView(
slivers: [
SliverPadding(
padding:
const EdgeInsets.all(FinanceDesignSystem.horizontalPadding),
sliver: SliverList(
delegate: SliverChildListDelegate([
// Overall Score Card
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(24),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: Offset(0, 4))
],
),
child: Column(
children: [
Icon(Icons.shield_rounded,
size: 48, color: statusColor),
const SizedBox(height: 16),
Text(
"Overall Behavior Score".tr,
style: FinanceDesignSystem.headingStyle,
),
const SizedBox(height: 8),
Text(
"${score.toStringAsFixed(1)} / 100",
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.bold),
),
),
],
),
),
const SizedBox(height: 30),
Text("Last 10 Trips".tr,
style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 16),
]),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: FinanceDesignSystem.horizontalPadding),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
var trip = controller.lastTrips[index];
double tripScore =
double.tryParse(trip['behavior_score'].toString()) ?? 0;
Color tColor = tripScore >= 90
? Colors.green
: (tripScore >= 75 ? Colors.orange : Colors.red);
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.02),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
child: ListTile(
contentPadding: const EdgeInsets.all(16),
leading: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: tColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.drive_eta_rounded, color: tColor),
),
title: Text(
"Trip ID: ${trip['trip_id']}",
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 16),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Wrap(
spacing: 12,
runSpacing: 8,
children: [
_buildBadge(Icons.speed,
"${trip['max_speed']} km/h", Colors.blue),
_buildBadge(
Icons.warning_amber_rounded,
"${trip['hard_brakes']} ${'Hard Brakes'.tr}",
Colors.orange),
_buildBadge(
Icons.map_rounded,
"${trip['total_distance']} km",
Colors.purple),
],
),
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${tripScore.toStringAsFixed(0)}",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: tColor),
),
Text('Score'.tr,
style: const TextStyle(
fontSize: 10, color: Colors.grey)),
],
),
),
);
},
childCount: controller.lastTrips.length,
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 40)),
],
);
}),
);
}
Widget _buildBadge(IconData icon, String label, Color color) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 4),
Text(label,
style: TextStyle(
fontSize: 12, color: color, fontWeight: FontWeight.w600)),
],
),
);
}
}

View File

@@ -0,0 +1,171 @@
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/functions/encrypt_decrypt.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../auth/captin/driver_car_controller.dart';
import '../../widgets/elevated_btn.dart';
import 'cars_inserting_page.dart';
class CaptainsCars extends StatelessWidget {
const CaptainsCars({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverCarController());
return MyScafolld(
title: "Add new car".tr,
body: [
Column(
children: [
MyElevatedButton(
title: "Add new car".tr,
onPressed: () async {
Get.to(() => CarsInsertingPage());
},
),
Expanded(
child: GetBuilder<DriverCarController>(
builder: (controller) {
return controller.isLoading
? const MyCircularProgressIndicator()
: ListView.builder(
itemCount: controller.cars.length,
itemBuilder: (context, index) {
final car = controller.cars[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
child: Card(
elevation: 2,
color: car['isDefault'].toString() == '1'
? AppColor.primaryColor
: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: car['isDefault'].toString() == '1'
? BorderSide.none
: BorderSide(color: Theme.of(context).dividerColor)),
child: ListTile(
contentPadding: const EdgeInsets.all(12),
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white12,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Fontisto.car,
size: 32,
color: car['isDefault'].toString() == '1'
? Colors.white
: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
),
),
title: Text(
car['make'],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: car['isDefault'].toString() == '1'
? Colors.white
: Theme.of(context).textTheme.bodyLarge?.color,
),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Text(
car['model'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: car['isDefault'].toString() == '1'
? Colors.white54
: AppColor.primaryColor,
),
),
child: Text(
car['car_plate'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: car['isDefault'].toString() == '1'
? Colors.white
: AppColor.primaryColor,
),
),
),
const Spacer(),
Text(
car['year'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
],
),
),
// trailing: IconButton(
// icon: const Icon(
// Icons.delete,
// color: AppColor.redColor,
// ),
// onPressed: () {
// // Add logic here to remove a car
// MyDialog()
// .getDialog('Are you sure to delete this car', '', () {
// controller
// .removeCar(car['id'].toString());
// });
// },
// ),
onTap: () {
MyDialog().getDialog(
'Are you sure to make this car as default'
.tr,
'', () {
Get.back();
//make it default
controller.updateCarRegistration(
car['id'].toString(),
box.read(BoxName.driverID).toString(),
);
});
// Add logic to view or edit the car details
},
),
),
);
},
);
},
),
),
],
)
],
isleading: true);
}
}

View File

@@ -0,0 +1,300 @@
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../controller/functions/gemeni.dart';
import '../../auth/captin/driver_car_controller.dart';
class CarsInsertingPage extends StatelessWidget {
CarsInsertingPage({super.key});
final driverCarController = Get.put(DriverCarController());
@override
Widget build(BuildContext context) {
Get.put(AI());
return MyScafolld(
title: "Add new car".tr,
body: [
Container(
color: AppColor.accentColor.withOpacity(.2),
child: Padding(
padding: const EdgeInsets.all(22),
child: ListView(
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
syriaCarLicenceFront(),
syriaCarLicenceBack(),
const SizedBox(height: 10),
Text('Please make sure to read the license carefully.'.tr),
Text(
'If your car license has the new design, upload the front side with two images.'
.tr),
const SizedBox(height: 10),
MyElevatedButton(
title: 'Please upload this license.'.tr,
onPressed: () {
final aiFront =
Get.find<AI>().responseIdCardDriverEgyptFront;
final aiBack =
Get.find<AI>().responseIdCardDriverEgyptBack;
driverCarController.addCarsForDrivers(
aiBack['chassis'].toString(),
aiBack['car_plate'].toString(),
aiBack['make'].toString(),
aiBack['model'].toString(),
aiBack['year'].toString(),
aiFront['LicenseExpirationDate'].toString(),
aiBack['color'].toString(),
aiBack['color_hex'].toString(),
aiFront['address'].toString(),
aiFront['owner'].toString(),
aiBack['inspection_date'].toString(),
aiBack['engine'].toString(),
aiBack['fuel'].toString(),
);
})
],
),
),
)
],
isleading: true);
}
}
GetBuilder<AI> syriaCarLicenceFront() {
return GetBuilder<AI>(
builder: (ai) {
if (ai.responseIdCardDriverEgyptFront.isNotEmpty) {
// No need to access ai.responseIdCardDriverEgyptBack anymore
final licenseExpiryDate = DateTime.parse(
ai.responseIdCardDriverEgyptFront['LicenseExpirationDate']);
// Check if license has expired
final today = DateTime.now();
final isLicenseExpired = licenseExpiryDate.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('Vehicle Details Front'.tr,
style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
ai.allMethodForAI((ai.prompts[3]['prompt'].toString()),
AppLink.uploadEgypt, 'car_front');
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
// Removed Make, Model, etc. as they are not available
Text(
'${'Plate Number'.tr}: ${ai.responseIdCardDriverEgyptFront['car_plate']}',
),
const SizedBox(height: 8.0),
Text(
'${'Owner Name'.tr}: ${ai.responseIdCardDriverEgyptFront['owner']}',
),
const SizedBox(height: 8.0),
Text(
'${'Address'.tr}: ${ai.responseIdCardDriverEgyptFront['address']}',
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'License Expiry Date'.tr}: ${licenseExpiryDate.toString().substring(0, 10)}',
style: TextStyle(
color: isLicenseExpired ? Colors.red : Colors.green,
),
),
// Removed Fuel as it's not available
],
),
// Removed Inspection Date as it's not available
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
ai.allMethodForAINewCar((ai.prompts[3]['prompt'].toString()),
AppLink.uploadEgypt1, 'car_front', 'carId'); //todo
},
child: Column(
children: [
Image.asset(
'assets/images/3.png',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your car license front '.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<AI> syriaCarLicenceBack() {
return GetBuilder<AI>(
builder: (ai) {
if (ai.responseIdCardDriverEgyptBack.isNotEmpty) {
// Get the tax expiry date from the response
final taxExpiryDate =
ai.responseIdCardDriverEgyptBack['tax_expiry'].toString();
// final displacement = ai.responseIdCardDriverEgyptBack['displacement'];
// if (int.parse(displacement) < 1000) {}
// Get the inspection date from the response
final inspectionDate =
ai.responseIdCardDriverEgyptBack['inspection_date'].toString();
final year = int.parse(inspectionDate.toString().split('-')[0]);
// Set inspectionDateTime to December 31st of the given year
final inspectionDateTime = DateTime(year, 12, 31);
String carBackLicenseExpired =
inspectionDateTime.toString().split(' ')[0];
// Get the current date
final today = DateTime.now();
// Try parsing the tax expiry date. If it fails, set it to null.
final taxExpiryDateTime = DateTime.tryParse(taxExpiryDate ?? '');
final isExpired =
taxExpiryDateTime != null && taxExpiryDateTime.isBefore(today);
// Check if the inspection date is before today
bool isInspectionExpired = inspectionDateTime.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Vehicle Details Back'.tr, style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
ai.allMethodForAI((ai.prompts[4]['prompt'].toString()),
AppLink.uploadEgypt, 'car_back');
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Make'.tr}: ${ai.responseIdCardDriverEgyptBack['make']}'),
Text(
'${'Model'.tr}: ${ai.responseIdCardDriverEgyptBack['model']}'),
],
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Year'.tr}: ${ai.responseIdCardDriverEgyptBack['year']}'),
Text(
'${'Chassis'.tr}: ${ai.responseIdCardDriverEgyptBack['chassis']}'),
],
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Color'.tr}: ${ai.responseIdCardDriverEgyptBack['color']}'),
Text(
'${'Displacement'.tr}: ${ai.responseIdCardDriverEgyptBack['displacement']} cc'),
],
),
const SizedBox(height: 8.0),
Text(
'${'Fuel'.tr}: ${ai.responseIdCardDriverEgyptBack['fuel']}'),
const SizedBox(height: 8.0),
if (taxExpiryDateTime != null)
Text(
'${'Tax Expiry Date'.tr}: $taxExpiryDate',
style: TextStyle(
color: isExpired ? Colors.red : Colors.green,
),
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Inspection Date'.tr}: $carBackLicenseExpired',
style: TextStyle(
color: isInspectionExpired ? Colors.red : Colors.green,
),
),
],
),
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
ai.allMethodForAI((ai.prompts[4]['prompt'].toString()),
AppLink.uploadEgypt, 'car_back');
},
child: Column(
children: [
Image.asset(
'assets/images/4.png',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your car license back'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}

View File

@@ -0,0 +1,278 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/home/profile/complaint_controller.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../controller/functions/audio_recorder_controller.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.ridesList.isEmpty) {
return const MyCircularProgressIndicator();
}
return Stack(
children: [
Form(
key: controller.formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
// --- 1. Select Ride Section ---
_buildSectionCard(
title: '1. Select Ride'.tr,
child: controller.ridesList.isEmpty
? Text('No rides found to complain about.'.tr,
style: AppStyle.subtitle)
: DropdownButtonFormField<Map<String, dynamic>>(
value: controller.selectedRide,
dropdownColor: AppColor.surfaceColor,
items: controller.ridesList.map((ride) {
return DropdownMenuItem<Map<String, dynamic>>(
value: ride,
child: Text(
'${'Ride'.tr} #${ride['id']} (${ride['date']})',
style: AppStyle.subtitle,
),
);
}).toList(),
onChanged: (ride) {
if (ride != null) {
controller.selectRide(ride);
}
},
decoration: InputDecoration(
filled: true,
fillColor:
AppColor.secondaryColor.withOpacity(0.5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
),
),
),
// --- 2. Describe Your Issue Section ---
_buildSectionCard(
title: '2. 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;
},
),
),
// --- 3. Attach Recorded Audio Section ---
if (controller.selectedRide != null)
_buildSectionCard(
title: '3. 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());
}
final rideId =
controller.selectedRide!['id'].toString();
// Filter files to only show the audio file associated with the selected Ride ID
final matchingFiles = snapshot.data
?.where((path) =>
path.endsWith('_${rideId}.m4a'))
.toList() ??
[];
if (snapshot.hasError || matchingFiles.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0),
child: Text(
'No audio files found for this ride.'.tr,
style: AppStyle.subtitle),
),
);
}
return Column(
children: matchingFiles.map((audioFilePath) {
final audioFile = File(audioFilePath);
final isUploaded =
controller.audioLink.isNotEmpty &&
controller.attachedFileName ==
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(),
);
},
),
),
// --- 4. Review Details & Response Section ---
if (controller.selectedRide != null)
_buildSectionCard(
title: '4. Review Details & Response'.tr,
child: Column(
children: [
_buildDetailRow(
Icons.calendar_today_outlined,
'Date'.tr,
controller.selectedRide!['date'] ?? ''),
_buildDetailRow(
Icons.monetization_on_outlined,
'Price'.tr,
'${controller.selectedRide!['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.driverReport?['body']
?.toString() ??
'Awaiting response...'.tr,
style:
AppStyle.subtitle.copyWith(height: 1.5),
),
),
],
),
),
// --- 5. Submit Button ---
const SizedBox(height: 24),
MyElevatedButton(
onPressed: () async {
await controller.submitComplaintToServer();
},
title: 'Submit Complaint'.tr,
),
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,60 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/controller/home/profile/feed_back_controller.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import '../../widgets/elevated_btn.dart';
class FeedBackPage extends StatelessWidget {
FeedBackPage({super.key});
FeedBackController feedBackController = Get.put(FeedBackController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Feed Back'.tr,
body: [
Padding(
padding: const EdgeInsets.all(26),
child: Form(
key: feedBackController.formKey,
child: Column(
children: [
TextFormField(
controller: feedBackController.feedbackController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: 'Enter your feedback here'.tr,
labelText: 'Feedback',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your feedback.';
}
return null;
},
),
const SizedBox(height: 20),
feedBackController.isLoading
? const MyCircularProgressIndicator()
: MyElevatedButton(
onPressed: () {
if (feedBackController.formKey.currentState!
.validate()) {
feedBackController.addFeedBack();
// Clear the feedback form
feedBackController.formKey.currentState!.reset();
}
},
title: 'Submit '.tr,
),
],
),
),
),
],
isleading: true,
);
}
}

View File

@@ -0,0 +1,303 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/profile/profile_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/my_textField.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import '../../../controller/functions/log_out.dart';
class PassengerProfilePage extends StatelessWidget {
PassengerProfilePage({super.key});
LogOutController logOutController = Get.put(LogOutController());
@override
Widget build(BuildContext context) {
Get.put(ProfileController());
return MyScafolld(
isleading: true,
title: 'My Profile'.tr,
body: [
GetBuilder<ProfileController>(
builder: (controller) => controller.isloading
? const MyCircularProgressIndicator()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: SizedBox(
height: Get.height,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Edit Profile'.tr,
style: AppStyle.headTitle2,
),
ListTile(
title: Text(
'Name'.tr,
style: AppStyle.title,
),
leading: const Icon(
Icons.person_pin_rounded,
size: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(
'${controller.prfoileData['first_name']} ${controller.prfoileData['last_name']}'),
onTap: () {
controller.updatField(
'first_name', TextInputType.name);
},
),
ListTile(
title: Text(
'Gender'.tr,
style: AppStyle.title,
),
leading: Image.asset(
'assets/images/gender.png',
width: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(
controller.prfoileData['gender'].toString()),
onTap: () {
Get.defaultDialog(
title: 'Update Gender'.tr,
content: Column(
children: [
GenderPicker(),
MyElevatedButton(
title: 'Update'.tr,
onPressed: () {
controller.updateColumn({
'id': controller.prfoileData['id']
.toString(),
'gender': controller.gender,
});
Get.back();
},
)
],
));
// controller.updatField('gender');
},
),
ListTile(
title: Text(
'Education'.tr,
style: AppStyle.title,
),
leading: Image.asset(
'assets/images/education.png',
width: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(controller.prfoileData['education']
.toString()),
onTap: () {
Get.defaultDialog(
barrierDismissible: true,
title: 'Update Education'.tr,
content: SizedBox(
height: 200,
child: Column(
children: [
EducationDegreePicker(),
],
),
),
confirm: MyElevatedButton(
title: 'Update Education'.tr,
onPressed: () {
controller.updateColumn({
'id': controller.prfoileData['id']
.toString(),
'education':
controller.selectedDegree,
});
Get.back();
},
));
},
),
ListTile(
title: Text(
'Employment Type'.tr,
style: AppStyle.title,
),
leading: Image.asset(
'assets/images/employmentType.png',
width: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(controller
.prfoileData['employmentType']
.toString()),
onTap: () {
controller.updatField(
'employmentType', TextInputType.name);
},
),
ListTile(
title: Text(
'Marital Status'.tr,
style: AppStyle.title,
),
leading: Image.asset(
'assets/images/maritalStatus.png',
width: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(controller
.prfoileData['maritalStatus']
.toString()),
onTap: () {
controller.updatField(
'maritalStatus', TextInputType.name);
},
),
ListTile(
title: Text(
'SOS Phone'.tr,
style: AppStyle.title,
),
leading: const Icon(
Icons.sos,
color: AppColor.redColor,
size: 35,
),
trailing: const Icon(Icons.arrow_forward_ios),
subtitle: Text(controller.prfoileData['sosPhone']
.toString()),
onTap: () async {
await controller.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
controller.prfoileData['sosPhone']);
},
),
// const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Sign Out'.tr,
onPressed: () {
LogOutController().logOutPassenger();
}),
GetBuilder<LogOutController>(
builder: (logOutController) {
return MyElevatedButton(
title: 'Delete My Account'.tr,
onPressed: () {
Get.defaultDialog(
title:
'Are you sure to delete your account?'
.tr,
content: Form(
key: logOutController.formKey1,
child: MyTextForm(
controller: logOutController
.emailTextController,
label: 'Type your Email'.tr,
hint: 'Type your Email'.tr,
type:
TextInputType.emailAddress,
),
),
confirm: MyElevatedButton(
title: 'Delete My Account'.tr,
kolor: AppColor.redColor,
onPressed: () async {
await logOutController
.deletePassengerAccount();
}),
cancel: MyElevatedButton(
title: 'No I want'.tr,
onPressed: () {
logOutController
.emailTextController
.clear();
logOutController.update();
Get.back();
}));
});
}),
],
),
],
),
),
),
)),
],
);
}
}
class GenderPicker extends StatelessWidget {
final ProfileController controller = Get.put(ProfileController());
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
GenderPicker({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
child: CupertinoPicker(
itemExtent: 32.0,
onSelectedItemChanged: (int index) {
controller.setGender(genderOptions[index]);
},
children: genderOptions.map((String value) {
return Text(value);
}).toList(),
),
);
}
}
class EducationDegreePicker extends StatelessWidget {
final ProfileController controller = Get.put(ProfileController());
final List<String> degreeOptions = [
'High School Diploma'.tr,
'Associate Degree'.tr,
'Bachelor\'s Degree'.tr,
'Master\'s Degree'.tr,
'Doctoral Degree'.tr,
];
EducationDegreePicker({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: CupertinoPicker(
// backgroundColor: AppColor.accentColor,
// looping: true,
squeeze: 2,
// diameterRatio: 5,
itemExtent: 32,
onSelectedItemChanged: (int index) {
controller.setDegree(degreeOptions[index]);
},
children: degreeOptions.map((String value) {
return Text(value);
}).toList(),
),
);
}
}

View File

@@ -0,0 +1,586 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/controller/profile/captain_profile_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import 'behavior_page.dart';
import 'complaint_page.dart';
// الصفحة الرئيسية الجديدة
class ProfileCaptain extends StatelessWidget {
const ProfileCaptain({super.key});
@override
Widget build(BuildContext context) {
// Get.put() يجب أن يكون في مكان يتم استدعاؤه مرة واحدة فقط،
// لكن سنبقيه هنا حسب الكود الأصلي
final controller = Get.put(CaptainProfileController());
return MyScafolld(
title: 'My Profile'.tr,
isleading: true,
body: [
GetBuilder<CaptainProfileController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
}
if (controller.captainProfileData.isEmpty) {
return Center(child: Text('Failed to load profile data.'.tr));
}
return SingleChildScrollView(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
child: Column(
children: [
// 1. رأس الصفحة: صورة واسم وتقييم
ProfileHeader(
name:
'${controller.captainProfileData['first_name'] ?? ''} ${controller.captainProfileData['last_name'] ?? ''}',
rating:
controller.captainProfileData['ratingDriver'] != null
? double.tryParse(controller
.captainProfileData['ratingDriver']
.toString()) ??
0.0
: 0.0,
ratingCount: int.tryParse(controller
.captainProfileData['ratingCount']
.toString()) ??
0,
),
const SizedBox(height: 24),
// 2. قسم الإجراءات السريعة
ActionsGrid(),
const SizedBox(height: 24),
// 3. بطاقة المعلومات الشخصية
PersonalInfoCard(
data: controller.captainProfileData
.cast<String, dynamic>()),
const SizedBox(height: 16),
// 4. بطاقة معلومات المركبة (قابلة للتوسيع)
VehicleInfoCard(
data: controller.captainProfileData
.cast<String, dynamic>()),
],
),
);
},
),
],
);
}
}
// --- الويدجتس الجديدة المنفصلة لتحسين التصميم ---
/// 1. ويدجت رأس الصفحة
class ProfileHeader extends StatelessWidget {
final String name;
final double rating;
final int ratingCount;
const ProfileHeader({
super.key,
required this.name,
required this.rating,
required this.ratingCount,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
CircleAvatar(
radius: 50,
backgroundColor: theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: theme.primaryColor),
),
const SizedBox(height: 12),
Text(
name,
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text(
'${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)',
style:
theme.textTheme.titleMedium?.copyWith(color: theme.hintColor),
),
],
),
],
);
}
}
/// 2. ويدجت شبكة الأزرار
class ActionsGrid extends StatelessWidget {
const ActionsGrid({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 2.5, // للتحكم في ارتفاع الأزرار
children: [
// _ActionTile(
// title: 'My Cars'.tr,
// icon: Icons.directions_car_filled,
// onTap: () => Get.to(() => CaptainsCars()),
// ),
// _ActionTile(
// title: 'Criminal Record'.tr,
// icon: Icons.description,
// onTap: () => Get.to(() => CriminalDocumemtPage()),
// ),
_ActionTile(
title: 'ShamCash Account'.tr, // غيرت الاسم ليكون أوضح
icon: Icons.account_balance_wallet_rounded, // أيقونة محفظة
// trailing: Icon(Icons.arrow_forward_ios,
// size: 16, color: Colors.grey), // سهم صغير للجمالية
onTap: () {
// استدعاء دالة فتح النافذة
showShamCashInput();
},
),
_ActionTile(
title: 'Behavior Page'.tr,
icon: Icons.checklist_rtl,
onTap: () => Get.to(() => BehaviorPage()),
),
_ActionTile(
title: 'Submit a Complaint'.tr,
icon: Icons.note_add_rounded,
onTap: () => Get.to(() => ComplaintPage()),
),
],
);
}
}
void showShamCashInput() {
// 1. القراءة من الذاكرة المحلية (GetStorage) عند فتح النافذة
// إذا لم يتم العثور على قيمة، يتم تعيينها إلى نص فارغ
final String existingName = box.read('shamcash_name') ?? '';
final String existingCode = box.read('shamcash_code') ?? '';
// تعريف أدوات التحكم للحقلين مع تحميل القيمة المحفوظة
final TextEditingController nameController =
TextEditingController(text: existingName);
final TextEditingController codeController =
TextEditingController(text: existingCode);
Get.bottomSheet(
Builder(builder: (context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- 1. المقبض العلوي ---
Center(
child: Container(
height: 5,
width: 50,
decoration: BoxDecoration(
color: theme.dividerColor,
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.only(bottom: 20),
),
),
// --- 2. العنوان والأيقونة ---
Image.asset(
'assets/images/shamCash.png',
height: 50,
),
// const Icon(Icons.account_balance_wallet_rounded,
// size: 45, color: Colors.blueAccent),
const SizedBox(height: 10),
Text(
"ربط حساب شام كاش 🔗",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blueGrey[900]),
),
const SizedBox(height: 5),
const Text(
"أدخل بيانات حسابك لاستقبال الأرباح فوراً",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: Colors.grey),
),
const SizedBox(height: 25),
// --- 3. الحقل الأول: اسم الحساب (أعلى الباركود) ---
const Text("1. اسم الحساب (أعلى الباركود)",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: TextField(
controller: nameController,
decoration: InputDecoration(
hintText: "مثال: intaleq",
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 13),
border: InputBorder.none,
prefixIcon: const Icon(Icons.person_outline_rounded,
color: Colors.blueGrey),
contentPadding: const EdgeInsets.symmetric(
vertical: 15, horizontal: 10),
),
),
),
const SizedBox(height: 15),
// --- 4. الحقل الثاني: الكود (أسفل الباركود) ---
const Text("2. كود المحفظة (أسفل الباركود)",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: TextField(
controller: codeController,
style: const TextStyle(
fontSize: 13,
letterSpacing: 0.5), // خط أصغر قليلاً للكود الطويل
decoration: InputDecoration(
hintText: "مثال: 80f23afe40...",
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 13),
border: InputBorder.none,
prefixIcon: const Icon(Icons.qr_code_2_rounded,
color: Colors.blueGrey),
contentPadding: const EdgeInsets.symmetric(
vertical: 15, horizontal: 10),
// زر لصق الكود
suffixIcon: IconButton(
icon: const Icon(Icons.paste_rounded, color: Colors.blue),
tooltip: "لصق الكود",
onPressed: () async {
ClipboardData? data =
await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) {
codeController.text = data.text!;
// تحريك المؤشر للنهاية بعد اللصق
codeController.selection = TextSelection.fromPosition(
TextPosition(offset: codeController.text.length),
);
}
},
),
),
),
),
const SizedBox(height: 30),
// --- 5. زر الحفظ ---
SizedBox(
height: 50,
child: ElevatedButton(
onPressed: () async {
String name = nameController.text.trim();
String code = codeController.text.trim();
// التحقق من صحة البيانات
if (name.isNotEmpty && code.length > 5) {
// 1. إرسال البيانات إلى السيرفر
var res = await CRUD()
.post(link: AppLink.updateShamCashDriver, payload: {
"id": box.read(BoxName.driverID),
"accountBank": name,
"bankCode": code,
});
if (res != 'failure') {
// 2. 🔴 الحفظ في الذاكرة المحلية (GetStorage) بعد نجاح التحديث
box.write('shamcash_name', name);
box.write('shamcash_code', code);
Get.back(); // إغلاق النافذة
Get.snackbar(
"تم الحفظ بنجاح",
"تم ربط حساب ($name) لاستلام الأرباح.",
backgroundColor: Colors.green,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(20),
icon: const Icon(Icons.check_circle_outline,
color: Colors.white),
);
return;
} else {
// في حال فشل الإرسال إلى السيرفر
Get.snackbar(
"خطأ في السيرفر",
"فشل تحديث البيانات، يرجى المحاولة لاحقاً.",
backgroundColor: Colors.redAccent,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(20),
);
}
} else {
Get.snackbar(
"بيانات ناقصة",
"يرجى التأكد من إدخال الاسم والكود بشكل صحيح.",
backgroundColor: Colors.orange,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(20),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2ecc71), // الأخضر المالي
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 2,
),
child: const Text(
"حفظ وتفعيل الحساب",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
const SizedBox(height: 10), // مسافة سفلية إضافية للأمان
],
),
),
);
}),
isScrollControlled: true,
);
}
/// ويدجت داخلية لزر في الشبكة
class _ActionTile extends StatelessWidget {
final String title;
final IconData icon;
final VoidCallback onTap;
const _ActionTile(
{required this.title, required this.icon, required this.onTap});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: theme.primaryColor, size: 20),
const SizedBox(width: 8),
Flexible(
child: Text(
title,
style: theme.textTheme.labelLarge,
textAlign: TextAlign.center,
)),
],
),
),
),
);
}
}
/// 3. بطاقة المعلومات الشخصية
class PersonalInfoCard extends StatelessWidget {
final Map<String, dynamic> data;
PersonalInfoCard({super.key, required this.data});
final controller = Get.find<CaptainProfileController>();
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Personal Information'.tr, style: Get.textTheme.titleLarge),
const Divider(height: 24),
_InfoRow(
icon: Icons.phone,
label: 'Phone Number'.tr,
value: data['phone'] ?? ''),
if (data['email'] != null &&
data['email'].toString().contains('intaleqapp')) ...[
TextFormField(
controller: controller.emailController,
keyboardType:
TextInputType.emailAddress, // ✅ لوحة مفاتيح خاصة بالإيميل
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email'.tr,
hintText: 'Enter your email'.tr,
prefixIcon: Icon(Icons.email),
),
autofillHints: [
AutofillHints.email
], // اختياري لتحسين تجربة المستخدم
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () async {
await controller.updateEmail();
},
child: Text('Update'.tr),
),
] else
_InfoRow(
icon: Icons.email,
label: 'Email'.tr,
value: data['email'] ?? '',
),
_InfoRow(
icon: Icons.cake,
label: 'Age'.tr,
value: data['age']?.toString() ?? 'N/A'),
_InfoRow(
icon: Icons.wc,
label: 'Gender'.tr,
value: data['gender'] ?? 'N/A'),
],
),
),
);
}
}
/// 4. بطاقة معلومات المركبة
class VehicleInfoCard extends StatelessWidget {
final Map<String, dynamic> data;
const VehicleInfoCard({super.key, required this.data});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ExpansionTile(
title: Text('Vehicle Details'.tr, style: Get.textTheme.titleLarge),
leading: Icon(Icons.directions_car, color: Get.theme.primaryColor),
childrenPadding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
children: [
_InfoRow(
icon: Icons.branding_watermark,
label: 'Make'.tr,
value: data['make'] ?? ''),
_InfoRow(
icon: Icons.category,
label: 'Model'.tr,
value: data['model'] ?? ''),
_InfoRow(
icon: Icons.palette,
label: 'Color'.tr,
value: data['color'] ?? ''),
_InfoRow(
icon: Icons.pin,
label: 'Plate Number'.tr,
value: data['car_plate'] ?? ''),
_InfoRow(
icon: Icons.confirmation_number,
label: 'VIN'.tr,
value: data['vin'] ?? ''),
_InfoRow(
icon: Icons.event,
label: 'Expiration Date'.tr,
value: data['expiration_date'] ?? ''),
],
),
);
}
}
/// ويدجت لعرض سطر معلومة (أيقونة + عنوان + قيمة) لتجنب التكرار
class _InfoRow extends StatelessWidget {
final IconData icon;
final String label;
final String value;
const _InfoRow(
{required this.icon, required this.label, required this.value});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(icon, color: theme.hintColor.withOpacity(0.6), size: 20),
const SizedBox(width: 16),
Text(label, style: theme.textTheme.bodyLarge),
const Spacer(),
Flexible(
child: Text(
value,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
fontWeight: FontWeight.w500),
textAlign: TextAlign.end,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,146 @@
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/controller/home/profile/promos_controller.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../widgets/mycircular.dart';
class PromosPassengerPage extends StatelessWidget {
const PromosPassengerPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(PromosController());
return MyScafolld(
title: 'Promos For today'.tr,
isleading: true,
body: [
GetBuilder<PromosController>(
builder: (orderHistoryController) => orderHistoryController.isLoading
? const MyCircularProgressIndicator()
: ListView.builder(
itemCount: orderHistoryController.promoList.length,
itemBuilder: (BuildContext context, int index) {
final rides = orderHistoryController.promoList[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Card(
elevation: 4,
shadowColor:
Theme.of(context).shadowColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 30,
child: AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
WavyAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
],
repeatForever: true,
),
),
const SizedBox(height: 8),
Text(
rides['description'],
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_buildDateBadge(context,
rides['validity_start_date'], true),
const SizedBox(height: 4),
_buildDateBadge(context,
rides['validity_end_date'], false),
],
),
],
),
const Divider(height: 24),
Text(
'Copy this Promo to use it in your Ride!'.tr,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
)
],
),
),
),
);
},
),
)
],
);
}
Widget _buildDateBadge(BuildContext context, String date, bool isStart) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isStart
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
date,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isStart ? Colors.green : Colors.red,
),
),
);
}
}

View File

@@ -0,0 +1,95 @@
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.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: [
_buildTariffItem(
context,
'Minimum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '1 ${'JOD'.tr}'
: '20 ${'LE'.tr}'),
_buildTariffItem(
context,
'Maximum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '200 ${'JOD'.tr}'
: '15000 ${'LE'.tr}'),
_buildTariffItem(
context,
'Flag-down fee'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.47 ${'JOD'.tr}'
: '15 ${'LE'.tr}'),
_buildTariffItem(
context,
'Rate'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km'
: '1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text('Including Tax'.tr,
style: AppStyle.subtitle
.copyWith(color: Theme.of(context).hintColor)),
),
const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2),
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),
],
),
),
]);
}
Widget _buildTariffItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(label, style: AppStyle.title),
),
Text(value,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
],
),
);
}
}