first commit
This commit is contained in:
214
siro_driver/lib/views/home/profile/behavior_page.dart
Normal file
214
siro_driver/lib/views/home/profile/behavior_page.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
171
siro_driver/lib/views/home/profile/captains_cars.dart
Executable file
171
siro_driver/lib/views/home/profile/captains_cars.dart
Executable 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);
|
||||
}
|
||||
}
|
||||
300
siro_driver/lib/views/home/profile/cars_inserting_page.dart
Executable file
300
siro_driver/lib/views/home/profile/cars_inserting_page.dart
Executable 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
278
siro_driver/lib/views/home/profile/complaint_page.dart
Normal file
278
siro_driver/lib/views/home/profile/complaint_page.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
60
siro_driver/lib/views/home/profile/feed_back_page.dart
Executable file
60
siro_driver/lib/views/home/profile/feed_back_page.dart
Executable 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
303
siro_driver/lib/views/home/profile/passenger_profile_page.dart
Executable file
303
siro_driver/lib/views/home/profile/passenger_profile_page.dart
Executable 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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
586
siro_driver/lib/views/home/profile/profile_captain.dart
Executable file
586
siro_driver/lib/views/home/profile/profile_captain.dart
Executable 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
siro_driver/lib/views/home/profile/promos_passenger_page.dart
Executable file
146
siro_driver/lib/views/home/profile/promos_passenger_page.dart
Executable 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
95
siro_driver/lib/views/home/profile/taarif_page.dart
Executable file
95
siro_driver/lib/views/home/profile/taarif_page.dart
Executable 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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user