flutter(rider): rebuild lib on Cubit/Bloc + Tripz backend (core + auth slice)

- keep store identity (com.mobileapp.store.ride, shorebird, firebase, fonts); old lib -> lib_old_getx
- core: config/di/router(go_router)/theme/api_client(dio+tenant+bearer)/token_store(secure)
- auth: repo + AuthCubit + OTP phone/otp screens; home: flutter_map (OSM placeholder, انطلق TODO)
- android usesCleartextTraffic=true for http API; maps=flutter_map (no Google), state=Cubit

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 20:47:55 +03:00
co-authored by Claude Opus 4.8
parent 6234e66471
commit 6116d23d0c
203 changed files with 573 additions and 83094 deletions
@@ -1,71 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/payment/payment_controller.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../my_wallet/passenger_wallet.dart';
class PointsCaptain extends StatelessWidget {
PaymentController paymentController = Get.put(PaymentController());
PointsCaptain({
super.key,
required this.kolor,
required this.countPoint,
required this.pricePoint,
});
final Color kolor;
final String countPoint;
double pricePoint;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
Get.to(() => const PassengerWallet());
paymentController.changePromoSheetDialogue();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
child: Container(
width: Get.width * .21,
height: Get.width * .29,
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
kolor.withOpacity(0.3),
kolor,
kolor.withOpacity(0.7),
kolor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(color: AppColor.accentColor),
borderRadius: BorderRadius.circular(12),
shape: BoxShape.rectangle,
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'$countPoint ${'LE'.tr}',
style: AppStyle.subtitle,
),
Text(
'$pricePoint ${box.read(BoxName.countryCode) == 'Jordan' ? 'JOD'.tr : 'LE'.tr}',
style: AppStyle.title,
textAlign: TextAlign.center,
),
],
),
)),
),
);
}
}
@@ -1,229 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/profile/complaint_controller.dart';
import 'package:Tripz/views/widgets/my_scafold.dart'; // سنستخدم السكافولد الخاص بك
import 'package:Tripz/views/widgets/mycircular.dart';
import 'package:Tripz/views/widgets/mydialoug.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart'; // سنستخدم الزر الخاص بك
import '../../../constant/colors.dart';
import '../../../controller/functions/audio_record1.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class ComplaintPage extends StatelessWidget {
ComplaintPage({super.key});
final ComplaintController complaintController =
Get.put(ComplaintController());
final AudioRecorderController audioRecorderController =
Get.put(AudioRecorderController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Submit a Complaint'.tr,
isleading: true,
body: [
GetBuilder<ComplaintController>(
builder: (controller) {
if (controller.isLoading && controller.feedBack.isEmpty) {
// عرض التحميل المبدئي فقط
return const MyCircularProgressIndicator();
}
return Stack(
children: [
Form(
key: controller.formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
// --- 1. بطاقة إدخال نص الشكوى ---
_buildSectionCard(
title: '1. Describe Your Issue'.tr,
child: TextFormField(
controller: controller.complaintController,
decoration: InputDecoration(
hintText: 'Enter your complaint here...'.tr,
filled: true,
fillColor: AppColor.secondaryColor.withOpacity(0.5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.all(16),
),
maxLines: 6,
style: AppStyle.subtitle,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a description of the issue.'
.tr;
}
return null;
},
),
),
// --- 2. بطاقة إرفاق التسجيل الصوتي ---
_buildSectionCard(
title: '2. Attach Recorded Audio (Optional)'.tr,
child: FutureBuilder<List<String>>(
future: audioRecorderController.getRecordedFiles(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator());
}
if (snapshot.hasError ||
!snapshot.hasData ||
snapshot.data!.isEmpty) {
return Center(
child: Text('No audio files found.'.tr,
style: AppStyle.subtitle));
}
return Column(
children: snapshot.data!.map((audioFilePath) {
final audioFile = File(audioFilePath);
final isUploaded =
controller.audioLink.isNotEmpty &&
controller.audioLink.contains(
audioFilePath.split('/').last);
return ListTile(
leading: Icon(
isUploaded
? Icons.check_circle
: Icons.mic,
color: isUploaded
? AppColor.greenColor
: AppColor.redColor),
title: Text(audioFilePath.split('/').last,
style: AppStyle.subtitle,
overflow: TextOverflow.ellipsis),
subtitle: isUploaded
? Text('Uploaded'.tr,
style: const TextStyle(
color: AppColor.greenColor))
: null,
onTap: isUploaded
? null
: () {
MyDialogContent().getDialog(
'Confirm Attachment'.tr,
Text(
'Attach this audio file?'.tr),
() async {
await controller
.uploadAudioFile(audioFile);
});
},
);
}).toList(),
);
},
),
),
// --- 3. بطاقة تفاصيل الرحلة والرد ---
_buildSectionCard(
title: '3. Review Details & Response'.tr,
child: Column(
children: [
if (controller.feedBack.isNotEmpty) ...[
_buildDetailRow(Icons.calendar_today_outlined,
'Date'.tr, controller.feedBack[0]['date']),
_buildDetailRow(
Icons.monetization_on_outlined,
'Price'.tr,
'${controller.feedBack[0]['price']}'),
],
const Divider(height: 24),
ListTile(
leading: const Icon(Icons.support_agent_outlined,
color: AppColor.primaryColor),
title: Text("Tripz's Response".tr,
style: AppStyle.title),
subtitle: Text(
// --- تعديل هنا لعرض الرد من الخادم ---
controller.passengerReport?['body']
?.toString() ??
'Awaiting response...'.tr,
style: AppStyle.subtitle.copyWith(height: 1.5),
),
),
],
),
),
// --- 4. زر الإرسال ---
const SizedBox(height: 24),
MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Submit Complaint'.tr,
onPressed: () async {
// --- تعديل: استدعاء الدالة الجديدة فقط ---
await controller.submitComplaintToServer();
},
),
const SizedBox(height: 24), // مسافة إضافية بالأسفل
],
),
),
// --- عرض مؤشر التحميل فوق كل شيء ---
if (controller.isLoading)
Container(
color: Colors.black.withOpacity(0.5),
child: const MyCircularProgressIndicator(),
),
],
);
},
),
],
);
}
// --- ويدجت مساعدة لبناء البطاقات ---
Widget _buildSectionCard({required String title, required Widget child}) {
return Card(
margin: const EdgeInsets.only(bottom: 20),
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppStyle.headTitle.copyWith(fontSize: 18)),
const SizedBox(height: 12),
child,
],
),
),
);
}
// --- ويدجت مساعدة لعرض صفوف التفاصيل ---
Widget _buildDetailRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Icon(icon, color: AppColor.writeColor.withOpacity(0.6), size: 20),
const SizedBox(width: 12),
Text('${label.tr}:',
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.7))),
const Spacer(),
Text(value,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
],
),
);
}
}
@@ -1,233 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/views/widgets/my_scafold.dart';
import 'package:Tripz/views/widgets/mycircular.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../constant/colors.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/home/profile/order_history_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class OrderHistory extends StatelessWidget {
const OrderHistory({super.key});
@override
Widget build(BuildContext context) {
// نفس منطق استدعاء الكنترولر
Get.put(OrderHistoryController());
return MyScafolld(
title: 'Order History'.tr,
isleading: true,
body: [
GetBuilder<OrderHistoryController>(
builder: (controller) {
// --- نفس منطق التحميل والحالة الفارغة ---
if (controller.isloading) {
return const MyCircularProgressIndicator();
}
if (controller.orderHistoryListPassenger.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.map_outlined,
size: 80, color: AppColor.writeColor.withOpacity(0.4)),
const SizedBox(height: 16),
Text('No trip history found'.tr,
style: AppStyle.headTitle2),
Text("Your past trips will appear here.".tr,
style: AppStyle.subtitle),
],
),
);
}
// --- استخدام ListView.separated لفصل البطاقات ---
return ListView.separated(
padding: const EdgeInsets.all(16.0),
itemCount: controller.orderHistoryListPassenger.length,
separatorBuilder: (context, index) => const SizedBox(height: 16),
itemBuilder: (BuildContext context, int index) {
final ride = controller.orderHistoryListPassenger[index];
// --- استدعاء ويدجت البطاقة الجديدة ---
return _buildHistoryCard(context, ride);
},
);
},
)
],
);
}
// --- ويدجت بناء بطاقة الرحلة ---
Widget _buildHistoryCard(BuildContext context, Map<String, dynamic> ride) {
// --- نفس منطق حساب إحداثيات الخريطة ---
final LatLng startLocation = LatLng(
double.parse(ride['start_location'].toString().split(',')[0]),
double.parse(ride['start_location'].toString().split(',')[1]),
);
final LatLng endLocation = LatLng(
double.parse(ride['end_location'].toString().split(',')[0]),
double.parse(ride['end_location'].toString().split(',')[1]),
);
final LatLngBounds bounds = LatLngBounds(
northeast: LatLng(
startLocation.latitude > endLocation.latitude
? startLocation.latitude
: endLocation.latitude,
startLocation.longitude > endLocation.longitude
? startLocation.longitude
: endLocation.longitude,
),
southwest: LatLng(
startLocation.latitude < endLocation.latitude
? startLocation.latitude
: endLocation.latitude,
startLocation.longitude < endLocation.longitude
? startLocation.longitude
: endLocation.longitude,
),
);
return InkWell(
// --- نفس دالة onTap القديمة ---
onTap: () {
String mapUrl =
'https://www.google.com/maps/dir/${ride['start_location']}/${ride['end_location']}/';
showInBrowser(mapUrl);
},
borderRadius: BorderRadius.circular(16),
child: Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- 1. قسم الخريطة ---
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
child: SizedBox(
height: 150, // ارتفاع ثابت للخريطة
child: AbsorbPointer(
// لمنع التفاعل المباشر مع الخريطة داخل القائمة
child: GoogleMap(
initialCameraPosition:
CameraPosition(target: startLocation, zoom: 12),
// --- نفس منطق الخريطة والخطوط ---
onMapCreated: (GoogleMapController controller) {
controller.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 60));
},
polylines: {
Polyline(
polylineId: const PolylineId('route'),
points: [startLocation, endLocation],
color: AppColor.primaryColor,
width: 4,
),
},
markers: {
Marker(
markerId: const MarkerId('start'),
position: startLocation),
Marker(
markerId: const MarkerId('end'),
position: endLocation),
},
mapToolbarEnabled: false,
zoomControlsEnabled: false,
),
),
),
),
// --- 2. قسم تفاصيل الرحلة ---
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${ride['date']} - ${ride['time']}',
style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withOpacity(0.7)),
),
// --- ويدجت جديدة لعرض حالة الرحلة ---
_buildStatusChip(ride['status']),
],
),
const Divider(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Total Price'.tr,
style: AppStyle.title.copyWith(fontSize: 16)),
Text(
'${ride['price']} ${'SYP'.tr}',
style: AppStyle.headTitle.copyWith(
fontSize: 20, color: AppColor.primaryColor),
),
],
),
],
),
),
],
),
),
);
}
// --- ويدجت مساعدة لعرض حالة الرحلة بشكل أنيق ---
Widget _buildStatusChip(String status) {
Color chipColor;
IconData chipIcon;
// --- نفس منطق تحديد اللون ---
if (status == 'Canceled'.tr) {
chipColor = AppColor.redColor;
chipIcon = Icons.cancel_outlined;
} else if (status == 'Finished'.tr) {
chipColor = AppColor.greenColor;
chipIcon = Icons.check_circle_outline;
} else {
chipColor = AppColor.yellowColor;
chipIcon = Icons.hourglass_empty_rounded;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: chipColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(chipIcon, color: chipColor, size: 16),
const SizedBox(width: 6),
Text(
status,
style: AppStyle.subtitle.copyWith(
color: chipColor, fontWeight: FontWeight.bold, fontSize: 12),
),
],
),
);
}
}
@@ -1,384 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/profile/profile_controller.dart';
import 'package:Tripz/main.dart';
import 'package:Tripz/views/auth/login_page.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import 'package:Tripz/views/widgets/my_textField.dart';
import 'package:Tripz/views/widgets/mycircular.dart';
import '../../../controller/auth/login_controller.dart';
import '../../../controller/functions/log_out.dart';
class PassengerProfilePage extends StatelessWidget {
PassengerProfilePage({super.key});
final LogOutController logOutController = Get.put(LogOutController());
@override
Widget build(BuildContext context) {
Get.put(ProfileController());
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text('My Profile'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: Colors.grey[100],
elevation: 0,
centerTitle: true,
),
body: GetBuilder<ProfileController>(
builder: (controller) {
if (controller.isloading) {
return const MyCircularProgressIndicator();
}
return ListView(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
children: [
_buildProfileHeader(controller),
const SizedBox(height: 24),
_buildSectionCard(
'Personal Information'.tr,
[
_buildProfileTile(
icon: Icons.person_outline,
color: Colors.blue,
title: 'Name'.tr,
subtitle:
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}',
onTap: () =>
controller.updatField('first_name', TextInputType.name),
),
_buildProfileTile(
icon: Icons.wc_outlined,
color: Colors.pink,
title: 'Gender'.tr,
subtitle: controller.prfoileData['gender']?.toString() ??
'Not set'.tr,
onTap: () => _showGenderDialog(controller),
),
_buildProfileTile(
icon: Icons.school_outlined,
color: Colors.orange,
title: 'Education'.tr,
subtitle: controller.prfoileData['education']?.toString() ??
'Not set'.tr,
onTap: () => _showEducationDialog(controller),
),
],
),
const SizedBox(height: 24),
_buildSectionCard(
'Work & Contact'.tr,
[
_buildProfileTile(
icon: Icons.work_outline,
color: Colors.green,
title: 'Employment Type'.tr,
subtitle:
controller.prfoileData['employmentType']?.toString() ??
'Not set'.tr,
onTap: () => controller.updatField(
'employmentType', TextInputType.name),
),
_buildProfileTile(
icon: Icons.favorite_border,
color: Colors.purple,
title: 'Marital Status'.tr,
subtitle:
controller.prfoileData['maritalStatus']?.toString() ??
'Not set'.tr,
onTap: () => controller.updatField(
'maritalStatus', TextInputType.name),
),
_buildProfileTile(
icon: Icons.sos_outlined,
color: Colors.red,
title: 'SOS Phone'.tr,
subtitle: controller.prfoileData['sosPhone']?.toString() ??
'Not set'.tr,
onTap: () async {
await controller.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
controller.prfoileData['sosPhone']);
},
),
],
),
const SizedBox(height: 32),
_buildAccountActions(context, logOutController),
],
);
},
),
);
}
Widget _buildProfileHeader(ProfileController controller) {
String fullName =
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}';
String initials = (fullName.isNotEmpty && fullName.contains(" "))
? fullName.split(" ").map((e) => e.isNotEmpty ? e[0] : "").join()
: (fullName.isNotEmpty ? fullName[0] : "");
// Logic to hide email if it contains 'Tripzapp.com'
String email = box.read(BoxName.email) ?? '';
if (email.contains('Tripzapp.com')) {
email = ''; // Clear the email if it contains the domain
}
return Center(
child: Column(
children: [
CircleAvatar(
radius: 50,
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
child: Text(
initials,
style:
const TextStyle(fontSize: 40, color: AppColor.primaryColor),
),
),
const SizedBox(height: 12),
Text(
fullName,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
if (email
.isNotEmpty) // Only show the Text widget if the email is not empty
Text(
email,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
],
),
);
}
Widget _buildSectionCard(String title, List<Widget> children) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
child: Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[700]),
),
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: children,
),
),
],
);
}
Widget _buildProfileTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
onTap: onTap,
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 24),
),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text(subtitle, style: TextStyle(color: Colors.grey[600])),
trailing:
Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey[400]),
);
}
Widget _buildAccountActions(
BuildContext context, LogOutController logOutController) {
return Column(
children: [
SizedBox(
width: double.infinity,
child: TextButton.icon(
icon: const Icon(Icons.logout),
label: Text('Sign Out'.tr),
style: TextButton.styleFrom(
foregroundColor: Colors.blueGrey,
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
logOutController.logOutPassenger();
},
),
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: TextButton.icon(
icon: const Icon(Icons.delete_forever_outlined),
label: Text('Delete My Account'.tr),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () =>
_showDeleteAccountDialog(context, logOutController),
),
),
],
);
}
void _showGenderDialog(ProfileController controller) {
Get.defaultDialog(
title: 'Update Gender'.tr,
content: Column(
children: [
GenderPicker(),
const SizedBox(height: 16),
MyElevatedButton(
title: 'Update'.tr,
onPressed: () {
controller.updateColumn({
'id': controller.prfoileData['id'].toString(),
'gender': controller.gender,
});
Get.back();
},
)
],
),
);
}
void _showEducationDialog(ProfileController controller) {
Get.defaultDialog(
title: 'Update Education'.tr,
content: Column(
children: [
EducationDegreePicker(),
const SizedBox(height: 16),
MyElevatedButton(
title: 'Update'.tr,
onPressed: () {
controller.updateColumn({
'id': controller.prfoileData['id'].toString(),
'education': controller.selectedDegree,
});
Get.back();
},
),
],
),
);
}
void _showDeleteAccountDialog(
BuildContext context, LogOutController logOutController) {
Get.defaultDialog(
title: 'Delete My Account'.tr,
middleText: 'Are you sure? This action cannot be undone.'.tr,
content: Form(
key: logOutController.formKey1,
child: MyTextForm(
controller: logOutController.emailTextController,
label: 'Confirm your Email'.tr,
hint: 'Type your Email'.tr,
type: TextInputType.emailAddress,
),
),
confirm: MyElevatedButton(
title: 'Delete Permanently'.tr,
kolor: AppColor.redColor,
onPressed: () async {
await logOutController.deletePassengerAccount();
},
),
cancel: TextButton(
child: Text('Cancel'.tr),
onPressed: () {
logOutController.emailTextController.clear();
Get.back();
},
),
);
}
}
// --- Helper Widgets for Pickers ---
class GenderPicker extends StatelessWidget {
final ProfileController controller = Get.find<ProfileController>();
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
GenderPicker({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
child: CupertinoPicker(
itemExtent: 40.0,
onSelectedItemChanged: (int index) {
controller.setGender(genderOptions[index]);
},
children: genderOptions.map((String value) {
return Center(child: Text(value));
}).toList(),
),
);
}
}
class EducationDegreePicker extends StatelessWidget {
final ProfileController controller = Get.find<ProfileController>();
final List<String> degreeOptions = [
'High School Diploma'.tr,
'Associate Degree'.tr,
"Bachelor's Degree".tr,
"Master's Degree".tr,
'Doctoral Degree'.tr,
];
EducationDegreePicker({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 180,
child: CupertinoPicker(
itemExtent: 40.0,
onSelectedItemChanged: (int index) {
controller.setDegree(degreeOptions[index]);
},
children: degreeOptions.map((String value) {
return Center(child: Text(value));
}).toList(),
),
);
}
}
// NOTE: The CountryPicker and CountryPickerFromSetting widgets were not part of the main
// profile page UI, so they are excluded here to keep the file focused.
// If they are needed elsewhere, they should be moved to their own files.
@@ -1,157 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/profile/captain_profile_controller.dart';
import 'package:Tripz/main.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import 'package:Tripz/views/widgets/my_scafold.dart';
import '../../../constant/api_key.dart';
import '../../widgets/my_textField.dart';
class ProfileCaptain extends StatelessWidget {
const ProfileCaptain({super.key});
@override
Widget build(BuildContext context) {
Get.put(CaptainProfileController());
return MyScafolld(
title: 'My Profile'.tr,
body: [
GetBuilder<CaptainProfileController>(
builder: (controller) => Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: Get.width * 0.26,
backgroundColor: Colors.white,
backgroundImage: CachedNetworkImageProvider(
'${AK.serverPHP}/portrate_captain_image/${box.read(BoxName.driverID)}.jpg',
),
),
const SizedBox(height: 8.0),
Text(
box.read(BoxName.nameDriver) +
' ' +
box.read(BoxName.lastNameDriver).toString(),
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Email is'.tr} :${box.read(BoxName.emailDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Phone Number is'.tr} :${box.read(BoxName.phoneDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Date of Birth is'.tr} :${box.read(BoxName.dobDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Sex is '.tr}:${box.read(BoxName.sexDriver)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
const Divider(
// height: 2,
endIndent: 1,
indent: 2,
thickness: 2,
),
const SizedBox(height: 8.0),
Text('Car Details'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 8.0),
Text('${'VIN is'.tr} :${box.read(BoxName.vin)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Color is '.tr} :${box.read(BoxName.color)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Car Plate is '.tr} :${box.read(BoxName.carPlate)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Make is '.tr}:${box.read(BoxName.make)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Model is'.tr} :${box.read(BoxName.model)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text('${'Year is'.tr} :${box.read(BoxName.year)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
Text(
'${'Expiration Date '.tr} :${box.read(BoxName.expirationDate)}',
style: AppStyle.title),
const SizedBox(height: 8.0),
],
),
),
),
),
)
],
isleading: true,
action: GetBuilder<CaptainProfileController>(
builder: (controller) => IconButton(
onPressed: () {
Get.defaultDialog(
title: 'Edit Your data'.tr,
titleStyle: AppStyle.title,
content: SizedBox(
height: Get.height * .4,
child: SingleChildScrollView(
child: Column(
children: [
MyTextForm(
controller: controller.vin,
hint: 'write vin for your car'.tr,
label: 'VIN'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.color,
hint: 'write Color for your car'.tr,
label: 'Color'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.make,
hint: 'write Make for your car'.tr,
label: 'Make'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.model,
hint: 'write Model for your car'.tr,
label: 'Model'.tr,
type: TextInputType.emailAddress,
),
MyTextForm(
controller: controller.year,
hint: 'write Year for your car'.tr,
label: 'Year'.tr,
type: TextInputType.number,
),
MyTextForm(
controller: controller.expirationDate,
hint: 'write Expiration Date for your car'.tr,
label: 'Expiration Date'.tr,
type: TextInputType.datetime),
MyElevatedButton(
title: 'Update'.tr,
onPressed: () => controller.updateFields())
],
),
),
));
},
icon: const Icon(Icons.edit),
),
));
}
}
@@ -1,206 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:Tripz/controller/home/profile/promos_controller.dart';
import 'package:Tripz/views/widgets/my_scafold.dart';
import 'dart:ui'; // لاستخدامه في الفاصل
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../widgets/mycircular.dart';
import 'package:dotted_line/dotted_line.dart'; // ستحتاج لإضافة هذا الباكج
// ملاحظة: ستحتاج لإضافة هذا الباكج إلى ملف pubspec.yaml الخاص بك
// flutter pub add dotted_line
// --- الويدجت الرئيسية بالتصميم الجديد ---
class PromosPassengerPage extends StatelessWidget {
const PromosPassengerPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(PromosController()); // نفس منطقك القديم
return MyScafolld(
title: "Today's Promos".tr, // عنوان أكثر جاذبية
isleading: true,
body: [
GetBuilder<PromosController>(
builder: (controller) {
if (controller.isLoading) {
return const MyCircularProgressIndicator();
}
if (controller.promoList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.local_offer_outlined,
size: 80, color: Colors.grey),
const SizedBox(height: 16),
Text("No promos available right now.".tr,
style: AppStyle.headTitle2),
Text("Check back later for new offers!".tr,
style: AppStyle.subtitle),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: controller.promoList.length,
itemBuilder: (BuildContext context, int index) {
final promo = controller.promoList[index];
// --- استدعاء ويدجت الكوبون الجديدة ---
return _buildPromoTicket(context, promo);
},
);
},
)
],
);
}
// --- ويدجت بناء كوبون الخصم ---
Widget _buildPromoTicket(BuildContext context, Map<String, dynamic> promo) {
return Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
height: 140, // ارتفاع ثابت للكوبون
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: ClipPath(
clipper: TicketClipper(), // Clipper مخصص لرسم شكل التذكرة
child: Container(
color: AppColor.secondaryColor,
child: Row(
children: [
// --- الجزء الأيسر: تفاصيل العرض ---
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
promo['description'],
style: AppStyle.headTitle.copyWith(fontSize: 18),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Row(
children: [
const Icon(Icons.calendar_today_outlined,
size: 14, color: Colors.grey),
const SizedBox(width: 6),
Text(
'${'Valid Until:'.tr} ${promo['validity_end_date']}',
style: AppStyle.subtitle
.copyWith(fontSize: 12, color: Colors.grey),
),
],
),
],
),
),
),
// --- الفاصل المنقط ---
SizedBox(
height: 110,
child: DottedLine(
direction: Axis.vertical,
lineThickness: 2.0,
dashLength: 8.0,
dashColor: AppColor.writeColor.withOpacity(0.2),
dashGapLength: 4.0,
),
),
// --- الجزء الأيمن: كود الخصم وزر النسخ ---
Expanded(
flex: 2,
child: GestureDetector(
onTap: () {
// --- نفس منطقك القديم للنسخ ---
Clipboard.setData(
ClipboardData(text: promo['promo_code']));
Get.snackbar(
'Promo Copied!'.tr,
'${'Code'.tr} ${promo['promo_code']} ${'copied to clipboard'.tr}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
);
},
child: Container(
color: AppColor.primaryColor.withOpacity(0.1),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'CODE'.tr,
style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor, letterSpacing: 2),
),
const SizedBox(height: 8),
Text(
promo['promo_code'],
style: AppStyle.headTitle.copyWith(
fontSize: 24, color: AppColor.primaryColor),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.copy,
size: 14, color: AppColor.primaryColor),
const SizedBox(width: 4),
Text('Copy'.tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.primaryColor)),
],
),
],
),
),
),
),
],
),
),
),
),
);
}
}
// --- كلاس مخصص لرسم شكل التذكرة ---
class TicketClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0.0);
double radius = 10;
path.addOval(
Rect.fromCircle(center: Offset(0, size.height / 2), radius: radius));
path.addOval(Rect.fromCircle(
center: Offset(size.width, size.height / 2), radius: radius));
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
@@ -1,88 +0,0 @@
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/views/widgets/my_scafold.dart';
class TaarifPage extends StatelessWidget {
const TaarifPage({super.key});
@override
Widget build(BuildContext context) {
return MyScafolld(isleading: true, title: 'Tariffs'.tr, body: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ListView(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge,
children: [
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.symmetric(),
textBaseline: TextBaseline.alphabetic,
children: [
TableRow(
// decoration: AppStyle.boxDecoration,
children: [
Text('Minimum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title)
: Text('20 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Maximum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('200 ${'JOD'.tr}', style: AppStyle.title)
: Text('15000 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Flag-down fee'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title)
: Text('15 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km',
style: AppStyle.title)
: Text('1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km',
style: AppStyle.title),
Text('Including Tax'.tr, style: AppStyle.title),
],
),
],
),
const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text('10%', style: AppStyle.title),
const SizedBox(height: 20),
Text('Morning'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text(
'from 07:30 till 10:30 (Thursday, Friday, Saturday, Monday)'.tr,
style: AppStyle.title),
const SizedBox(height: 20),
Text('Evening'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text(
'from 12:00 till 15:00 (Thursday, Friday, Saturday, Monday)'.tr,
style: AppStyle.title),
const SizedBox(height: 20),
Text('Night'.tr, style: AppStyle.headTitle2),
const SizedBox(height: 10),
Text('from 23:59 till 05:30'.tr, style: AppStyle.title),
],
),
),
]);
}
}