This commit is contained in:
Hamza-Ayed
2025-09-01 18:29:05 +03:00
parent d71686d9f1
commit 6c87f7291d
33 changed files with 3118 additions and 7333 deletions

View File

@@ -47,8 +47,8 @@ android {
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 29 minSdk = 29
targetSdk = 36 targetSdk = 36
versionCode = 7 versionCode = 11
versionName = '1.0.7' versionName = '1.0.11'
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

View File

@@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<!-- منع أي اتصال HTTP غير مشفّر -->
<base-config cleartextTrafficPermitted="false"> <base-config cleartextTrafficPermitted="false">
<trust-anchors> <trust-anchors>
<!-- الاعتماد على شهادات النظام فقط -->
<certificates src="system" /> <certificates src="system" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
<!-- اسمح بالاتصال بدوميناتك فقط -->
<domain-config cleartextTrafficPermitted="false"> <domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">intaleq.xyz</domain> <domain includeSubdomains="true">intaleq.xyz</domain>
<!-- أضف أي ساب دومين تحتاجه هنا -->
<!-- <domain includeSubdomains="true">walletintaleq.intaleq.xyz</domain> <pin-set expiration="2027-01-01">
<domain includeSubdomains="true">syria.intaleq.xyz</domain> --> <pin digest="SHA-256">pXmP2hTQLxDEvlTVmP5N7xpiA32sycBsxB6hBFT2uL4=</pin>
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
</pin-set>
</domain-config> </domain-config>
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
<debug-overrides> <debug-overrides>
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
<certificates src="user" /> <certificates src="user" />
</trust-anchors> </trust-anchors>
</debug-overrides> </debug-overrides>
</network-security-config> </network-security-config>

BIN
assets/images/bus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
assets/images/electric.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -322,10 +322,14 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -339,10 +343,14 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5</string> <string>6</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -35,7 +35,7 @@
<dict/> <dict/>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0.5</string> <string>1.0.6</string>
<key>FirebaseAppDelegateProxyEnabled</key> <key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string> <string>NO</string>
<key>GMSApiKey</key> <key>GMSApiKey</key>

View File

@@ -8,6 +8,8 @@ class BoxName {
static const String serverChosen = "serverChosen"; static const String serverChosen = "serverChosen";
static const String gender = "gender"; static const String gender = "gender";
static const String jwt = "jwt"; static const String jwt = "jwt";
static const String lowEndMode = "lowEndMode";
static const String appVersionChecked = "appVersionChecked";
static const String lastName = "lastName"; static const String lastName = "lastName";
static const String fingerPrint = "fingerPrint"; static const String fingerPrint = "fingerPrint";
static const String payMobApikey = "payMobApikey"; static const String payMobApikey = "payMobApikey";

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
Future<bool> verifyCertificateManually(
String host, int port, String expectedPin) async {
try {
final socket = await SecureSocket.connect(host, port,
timeout: const Duration(seconds: 5));
final certificate = socket.peerCertificate;
if (certificate == null) {
print("❌ لا يوجد شهادة.");
return false;
}
final der = certificate.der;
final actualPin = base64.encode(sha256.convert(der).bytes);
print("📛 HOST: $host");
print("📜 Subject: ${certificate.subject}");
print("📜 Issuer: ${certificate.issuer}");
print("📅 Valid From: ${certificate.startValidity}");
print("📅 Valid To: ${certificate.endValidity}");
print(
"🔐 Server Pin: $actualPin${actualPin == expectedPin ? '✅ MATCH' : '❌ MISMATCH'}");
socket.destroy();
return actualPin == expectedPin;
} catch (e) {
print("❌ خطأ أثناء الاتصال أو الفحص: $e");
return false;
}
}
/// تحويل المفتاح العام إلى بصمة SHA-256
List<int> sha256Convert(Uint8List der) {
return sha256.convert(der).bytes;
}

View File

@@ -25,7 +25,7 @@ class PhoneAuthHelper {
link: _sendOtpUrl, link: _sendOtpUrl,
payload: {'receiver': phoneNumber}, payload: {'receiver': phoneNumber},
); );
// Log.print('response: ${response}'); Log.print('response: ${response}');
if (response != 'failure') { if (response != 'failure') {
final data = (response); final data = (response);
// if (data['status'] == 'success') { // if (data['status'] == 'success') {

View File

@@ -216,7 +216,7 @@ class FirebaseMessagesController extends GetxController {
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding'); 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
} }
update(); update();
} else if (message.notification!.title! == 'Hi ,I Arrive your site') { } else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
'Hi ,I Arrive your site'.tr, ''.tr, 'ding'); 'Hi ,I Arrive your site'.tr, ''.tr, 'ding');

View File

@@ -0,0 +1,86 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import '../../main.dart';
// مفاتيح التخزين (بسيطة)
const _kDeviceTierKey = 'deviceTier'; // 'low' | 'mid' | 'high'
const _kDeviceTierCheckedAtKey = 'deviceTierTime'; // millisSinceEpoch
Future<String> detectAndCacheDeviceTier({bool force = false}) async {
// لا تعيد الفحص إذا عملناه خلال آخر 24 ساعة
if (!force) {
final last = box.read(_kDeviceTierCheckedAtKey);
if (last is int) {
final dt = DateTime.fromMillisecondsSinceEpoch(last);
if (DateTime.now().difference(dt) < const Duration(hours: 24)) {
final cached = box.read(_kDeviceTierKey);
if (cached is String && cached.isNotEmpty)
return cached; // low/mid/high
}
}
}
final info = DeviceInfoPlugin();
int score = 0;
if (Platform.isAndroid) {
final a = await info.androidInfo;
final int sdk = a.version.sdkInt ?? 0;
final int cores = Platform.numberOfProcessors;
final int abisCount = a.supportedAbis.length;
final bool isEmu = !(a.isPhysicalDevice ?? true);
// SDK (أقدم = أضعف)
if (sdk <= 26)
score += 3; // 8.0 وأقدم
else if (sdk <= 29)
score += 2; // 9-10
else if (sdk <= 30) score += 1; // 11
// الأنوية
if (cores <= 4)
score += 3;
else if (cores <= 6)
score += 2;
else if (cores <= 8) score += 1;
// ABI count (القليل غالباً أضعف)
if (abisCount <= 1)
score += 2;
else if (abisCount == 2) score += 1;
// محاكي
if (isEmu) score += 2;
} else {
// iOS/منصات أخرى: تقدير سريع بالأنوية فقط
final int cores = Platform.numberOfProcessors;
if (cores <= 4)
score += 3;
else if (cores <= 6)
score += 2;
else if (cores <= 8) score += 1;
}
// تحويل السكور إلى تصنيف
final String tier = (score >= 6)
? 'low'
: (score >= 3)
? 'mid'
: 'high';
box.write(_kDeviceTierKey, tier);
box.write(_kDeviceTierCheckedAtKey, DateTime.now().millisecondsSinceEpoch);
return tier;
}
// للقراءة السريعة وقت ما تحتاج:
String getCachedDeviceTier() {
final t = box.read(_kDeviceTierKey);
if (t is String && t.isNotEmpty) return t;
return 'mid';
}
bool isLowEnd() => getCachedDeviceTier() == 'low';
bool isMidEnd() => getCachedDeviceTier() == 'mid';
bool isHighEnd() => getCachedDeviceTier() == 'high';

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,6 @@ import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/controller/functions/crud.dart'; import 'package:Intaleq/controller/functions/crud.dart';
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
import 'package:Intaleq/controller/payment/payment_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -17,6 +15,7 @@ import '../../../views/widgets/error_snakbar.dart';
import '../../../views/widgets/mydialoug.dart'; import '../../../views/widgets/mydialoug.dart';
import '../../functions/launch.dart'; import '../../functions/launch.dart';
import '../../notification/notification_captain_controller.dart'; import '../../notification/notification_captain_controller.dart';
import '../../payment/payment_controller.dart';
class InviteController extends GetxController { class InviteController extends GetxController {
final TextEditingController invitePhoneController = TextEditingController(); final TextEditingController invitePhoneController = TextEditingController();
@@ -27,21 +26,31 @@ class InviteController extends GetxController {
int selectedTab = 0; int selectedTab = 0;
PassengerStats passengerStats = PassengerStats(); PassengerStats passengerStats = PassengerStats();
List<Contact> contacts = <Contact>[];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
// It's good practice to fetch initial data in onInit or onReady
// fetchDriverStats();
// fetchDriverStatsPassengers();
}
void updateSelectedTab(int index) { void updateSelectedTab(int index) {
selectedTab = index; selectedTab = index;
update(); update();
} }
Future<void> shareCouponCode() async { // --- Sharing Methods ---
// TODO: Implement sharing functionality
// You can use share_plus package to share the coupon code
}
Future<void> shareDriverCode() async { Future<void> shareDriverCode() async {
if (driverCouponCode != null) { if (driverCouponCode != null) {
final String shareText = ''' final String shareText = '''
Join Intaleq as a driver using my referral code! ${'Join Intaleq as a driver using my referral code!'.tr}
Use code: $driverCouponCode ${'Use code:'.tr} $driverCouponCode
Download the Intaleq Driver app now and earn rewards! ${'Download the Intaleq Driver app now and earn rewards!'.tr}
'''; ''';
await Share.share(shareText); await Share.share(shareText);
} }
@@ -50,19 +59,15 @@ Download the Intaleq Driver app now and earn rewards!
Future<void> sharePassengerCode() async { Future<void> sharePassengerCode() async {
if (couponCode != null) { if (couponCode != null) {
final String shareText = ''' final String shareText = '''
Get a discount on your first Intaleq ride! ${'Get a discount on your first Intaleq ride!'.tr}
Use my referral code: $couponCode ${'Use my referral code:'.tr} $couponCode
Download the Intaleq app now and enjoy your ride! ${'Download the Intaleq app now and enjoy your ride!'.tr}
'''; ''';
await Share.share(shareText); await Share.share(shareText);
} }
} }
@override // --- Data Fetching ---
void onInit() {
super.onInit();
// fetchDriverStats();
}
void fetchDriverStats() async { void fetchDriverStats() async {
try { try {
@@ -74,7 +79,9 @@ Download the Intaleq app now and enjoy your ride!
driverInvitationData = data['message']; driverInvitationData = data['message'];
update(); update();
} }
} catch (e) {} } catch (e) {
Log.print("Error fetching driver stats: $e");
}
} }
void fetchDriverStatsPassengers() async { void fetchDriverStatsPassengers() async {
@@ -88,216 +95,207 @@ Download the Intaleq app now and enjoy your ride!
driverInvitationDataToPassengers = data['message']; driverInvitationDataToPassengers = data['message'];
update(); update();
} }
} catch (e) {}
}
void selectPhone(String phone) {
if (box.read(BoxName.countryCode) == 'Egypt') {
invitePhoneController.text = phone;
update();
Get.back();
}
}
Future<void> saveContactsToServer() async {
try {
// TODO: Implement the actual server upload logic here
// Simulating a server request
await Future.delayed(Duration(seconds: 2));
Get.snackbar('Success'.tr,
'${selectedContacts.length} contacts saved to server'.tr);
} catch (e) { } catch (e) {
Get.snackbar('Error'.tr, Log.print("Error fetching passenger stats: $e");
'An error occurred while saving contacts to server: $e'.tr);
} }
} }
List<Contact> contacts = <Contact>[]; // --- Contact Handling ---
List<Contact> selectedContacts = <Contact>[];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
/// **IMPROVEMENT**: This function now filters out contacts without any phone numbers.
/// This is the fix for the `RangeError` you were seeing, which happened when the UI
/// tried to access the first phone number of a contact that had none.
Future<void> pickContacts() async { Future<void> pickContacts() async {
try { try {
// Request contacts permission
if (await FlutterContacts.requestPermission(readonly: true)) { if (await FlutterContacts.requestPermission(readonly: true)) {
// Fetch all contacts with full properties final List<Contact> allContacts =
final List<Contact> allContacts = await FlutterContacts.getContacts( await FlutterContacts.getContacts(withProperties: true);
withProperties: true, final int totalContactsOnDevice = allContacts.length;
withThumbnail: false,
withPhoto: true,
);
// Check if contacts are available // **FIX**: Filter contacts to only include those with at least one phone number.
if (allContacts.isNotEmpty) { contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
// Store the contacts final int contactsWithPhones = contacts.length;
contacts = allContacts;
Log.print('contacts: $contacts');
// Convert contacts to a list of maps if (contactsWithPhones > 0) {
contactMaps.value = await Future.wait(contacts.map((contact) async { Log.print('Found $contactsWithPhones contacts with phone numbers.');
Log.print('Contact name: ${contact.displayName}'); contactMaps.value = contacts.map((contact) {
// Fetch phone numbers separately
final phones = await contact.phones;
Log.print('Contact phones: $phones');
// Fetch email addresses separately
final emails = await contact.emails;
Log.print('Contact emails: $emails');
// Handle empty or null values
return { return {
'name': contact.displayName ?? '', 'name': contact.displayName,
'phones': phones 'phones': contact.phones.map((p) => p.number).toList(),
.where((phone) => phone.normalizedNumber != null) 'emails': contact.emails.map((e) => e.address).toList(),
.map((phone) => phone.normalizedNumber ?? 'No number')
.toList(),
'emails': emails
.where((email) => email.address != null)
.map((email) => email.address ?? 'No email')
.toList(),
}; };
}).toList()); }).toList();
update(); update();
// **IMPROVEMENT**: Provide feedback if some contacts were filtered out.
if (contactsWithPhones < totalContactsOnDevice) {
Get.snackbar('Contacts Loaded'.tr,
'${'Showing'.tr} $contactsWithPhones ${'of'.tr} $totalContactsOnDevice ${'contacts. Others were hidden because they don\'t have a phone number.'.tr}',
snackPosition: SnackPosition.BOTTOM);
}
} else { } else {
Get.snackbar('No contacts available'.tr, Get.snackbar('No contacts found'.tr,
'Please add contacts to your phone.'.tr); 'No contacts with phone numbers were found on your device.'.tr);
} }
} else { } else {
Get.snackbar('Permission denied'.tr, Get.snackbar('Permission denied'.tr,
'Contact permission is required to pick contacts'.tr); 'Contact permission is required to pick contacts'.tr);
} }
} catch (e) { } catch (e) {
Log.print('Error picking contacts: $e');
Get.snackbar( Get.snackbar(
'Error'.tr, 'An error occurred while picking contacts: $e'.tr); 'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
} }
} }
void onSelectPassengerInvitation(int index) async { void selectPhone(String phone) {
MyDialog().getDialog( invitePhoneController.text = phone;
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver'] update();
.toString()) < Get.back();
2
? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} '
: 'You deserve the gift'.tr,
'${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver'].toString()} / 2 ${'Trip'.tr}',
() async {
if (int.parse(driverInvitationDataToPassengers[index]
['countOfInvitDriver']
.toString()) <
2) {
Get.back();
} else {
// Claim the gift if 100 trips are completed
if (driverInvitationDataToPassengers[index]['isGiftToken']
.toString() ==
'0') {
Get.back();
// Add wallet to the inviter
await Get.find<PaymentController>().addPassengersWallet('20');
// add for invitor too
// await Get.find<CaptainWalletController>().addDriverWalletToInvitor(
// 'paymentMethod',
// driverInvitationData[index]['driverInviterId'],
// '50');
// Update invitation as claimed
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': driverInvitationDataToPassengers[index]['id']},
);
// Notify the inviter
NotificationCaptainController().addNotificationCaptain(
driverInvitationDataToPassengers[index]['passengerInviterId']
.toString(),
"You have got a gift for invitation".tr,
'${"You have 20".tr} ${'LE'}',
false,
);
} else {
Get.back();
MyDialog().getDialog(
"You have got a gift".tr,
"Share the app with another new passenger".tr,
() {
Get.back();
},
);
}
}
},
);
} }
savePhoneToServer() async { /// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
for (var i = 0; i < contactMaps.length; i++) { /// It handles various user inputs gracefully to produce a standardized international format.
var phones = contactMaps[i]['phones']; String _formatSyrianPhoneNumber(String phone) {
if (phones != null && phones.isNotEmpty && phones[0].isNotEmpty) { // 1. Remove all non-digit characters to clean the input.
var res = await CRUD().post(link: AppLink.savePhones, payload: { String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
"name": contactMaps[i]['name'] ?? 'none',
"phones": phones[0] ?? 'none', // 2. If it already starts with the country code, we assume it's correct.
"phones2": phones.join(', ') ?? if (digitsOnly.startsWith('963')) {
'none', // Convert List<String> to a comma-separated string return '+$digitsOnly';
});
if (res != 'failure') {}
} else {}
} }
}
String formatPhoneNumber(String input) { // 3. If it starts with '09' (common local format), remove the leading '0'.
// Remove any non-digit characters if (digitsOnly.startsWith('09')) {
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
// Ensure the number starts with the country code
if (digitsOnly.startsWith('20')) {
digitsOnly = digitsOnly.substring(1); digitsOnly = digitsOnly.substring(1);
} }
return digitsOnly; // 4. Prepend the Syrian country code.
return '+963$digitsOnly';
} }
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and
/// sends a much-improved, user-friendly WhatsApp message.
void sendInviteToPassenger() async { void sendInviteToPassenger() async {
if (invitePhoneController.text.isEmpty || if (invitePhoneController.text.isEmpty ||
invitePhoneController.text.length < 11) { invitePhoneController.text.length < 9) {
mySnackeBarError('Please enter a correct phone'.tr); mySnackeBarError('Please enter a correct phone'.tr);
return; return;
} }
// try { try {
String phoneNumber = formatPhoneNumber(invitePhoneController.text); // Use the new formatting function to ensure the number is correct.
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text);
var response = var response =
await CRUD().post(link: AppLink.addInvitationPassenger, payload: { await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
"driverId": box.read(BoxName.passengerID), "driverId": box.read(BoxName.passengerID),
"inviterPassengerPhone": ('+2$phoneNumber') "inviterPassengerPhone": formattedPhoneNumber,
}); });
if (response != 'failure') { if (response != 'failure') {
var d = response; var d = response;
Get.snackbar('Success', 'Invite sent successfully'.tr); Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
backgroundColor: Colors.green, colorText: Colors.white);
String message = '${'*Intaleq APP CODE*'.tr}\n\n' String expirationTime = d['message']['expirationTime'].toString();
'${"Use this code in registration".tr}\n' String inviteCode = d['message']['inviteCode'].toString();
'${"To get a gift for both".tr}\n\n'
'${"The period of this code is 1 hour".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'_*${d['message']['inviteCode'].toString()}*_\n\n'
'${"Install our app:".tr}\n'
'*Android:* https://play.google.com/store/apps/details?id=com.mobileapp.store.ride\n\n\n'
'*iOS:* https://apps.apple.com/us/app/sefer/id6458734951';
launchCommunication('whatsapp', '+2$phoneNumber', message); // New and improved WhatsApp message for better user engagement.
String message =
"👋 ${'Hello! I\'m inviting you to try Intaleq.'.tr}\n\n"
"🎁 ${'Use my invitation code to get a special gift on your first ride!'.tr}\n\n"
"${'Your personal invitation code is:'.tr}\n"
"*$inviteCode*\n\n"
"${'Be sure to use it quickly! This code expires at'.tr} *$expirationTime*.\n\n"
"📲 ${'Download the app now:'.tr}\n"
"• *Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n"
"• *iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179\n\n"
"${'See you on the road!'.tr} 🚗";
invitePhoneController.clear(); launchCommunication('whatsapp', formattedPhoneNumber, message);
} else { invitePhoneController.clear();
Get.snackbar('Error'.tr, "Invite code already used".tr, update();
backgroundColor: AppColor.redColor, } else {
duration: const Duration(seconds: 4)); Get.snackbar(
'Error'.tr, "This phone number has already been invited.".tr,
backgroundColor: AppColor.redColor,
duration: const Duration(seconds: 4));
}
} catch (e) {
Log.print("Error sending invite: $e");
Get.snackbar(
'Error'.tr, 'An unexpected error occurred. Please try again.'.tr,
backgroundColor: AppColor.redColor);
}
}
// This function is dependent on the `pickContacts` method filtering out contacts without phones.
savePhoneToServer() async {
for (var contactMap in contactMaps) {
// The `pickContacts` function ensures the 'phones' list is not empty here.
var phones = contactMap['phones'] as List<String>;
var res = await CRUD().post(link: AppLink.savePhones, payload: {
"name": contactMap['name'] ?? 'No Name',
"phones": phones.first, // Safely access the first phone number
"phones2": phones.join(', '),
});
if (res == 'failure') {
Log.print('Failed to save contact: ${contactMap['name']}');
}
}
}
void onSelectPassengerInvitation(int index) async {
try {
final invitation = driverInvitationDataToPassengers[index];
final tripCount =
int.tryParse(invitation['countOfInvitDriver'].toString()) ?? 0;
final passengerName = invitation['passengerName'].toString();
final isGiftTaken = invitation['isGiftToken'].toString() == '1';
if (tripCount >= 2) {
// Gift can be claimed
if (!isGiftTaken) {
MyDialog().getDialog(
'You deserve the gift'.tr,
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
() async {
Get.back(); // Close dialog first
await Get.find<PaymentController>().addPassengersWallet('20');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': invitation['id']},
);
NotificationCaptainController().addNotificationCaptain(
invitation['passengerInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have earned 20".tr} ${'LE'}',
false,
);
fetchDriverStatsPassengers(); // Refresh list
},
);
} else {
MyDialog().getDialog(
"Gift Already Claimed".tr,
"You have already received your gift for inviting $passengerName."
.tr,
() => Get.back(),
);
}
} else {
// Gift not yet earned
MyDialog().getDialog(
'${'Keep it up!'.tr}',
'$passengerName ${'has completed'.tr} $tripCount / 2 ${'trips'.tr}. ${"You can claim your gift once they complete 2 trips.".tr}',
() => Get.back(),
);
}
} catch (e) {
Log.print("Error in onSelectPassengerInvitation: $e");
} }
// } catch (e) {
// Get.snackbar('Error', 'An error occurred'.tr);
// }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@ import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart'; import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:path/path.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../constant/info.dart'; import '../../constant/info.dart';
import '../../controller/auth/apple_signin_controller.dart'; import '../../controller/auth/apple_signin_controller.dart';
@@ -73,7 +75,11 @@ class LoginPage extends StatelessWidget {
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
color: AppColor.blueColor, color: AppColor.blueColor,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
recognizer: TapGestureRecognizer()..onTap = () {}), recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(Uri.parse(
'https://intaleq.xyz/intaleq/privacy_policy.php'));
}),
TextSpan(text: " and acknowledge our Privacy Policy.".tr), TextSpan(text: " and acknowledge our Privacy Policy.".tr),
], ],
), ),
@@ -88,7 +94,7 @@ class LoginPage extends StatelessWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar' child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar'
? AppInformation.privacyPolicyArabic ? AppInformation.privacyPolicyArabic
: AppInformation.privacyPolicyEnglish), : AppInformation.privacyPolicy),
), ),
), ),
), ),

View File

@@ -43,6 +43,12 @@ class AuthScreen extends StatelessWidget {
children: [ children: [
Image.asset('assets/images/logo.gif', height: 120), Image.asset('assets/images/logo.gif', height: 120),
const SizedBox(height: 20), const SizedBox(height: 20),
// IconButton(
// onPressed: () {
// Get.find<LoginController>().getJWT();
// },
// icon: const Icon(Icons.add),
// ),
Text( Text(
title, title,
style: const TextStyle( style: const TextStyle(

View File

@@ -28,13 +28,13 @@ class AboutPage extends StatelessWidget {
// Company Name and Introduction // Company Name and Introduction
Text( Text(
'Tripz LLC', 'Intaleq LLC',
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle, style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Egypt\'s pioneering ride-sharing service, proudly developed by Arabian and local owners. We prioritize being near you both our valued passengers and our dedicated captains.' "Syria's pioneering ride-sharing service, proudly developed by Arabian and local owners. We prioritize being near you both our valued passengers and our dedicated captains."
.tr, .tr,
style: CupertinoTheme.of(context).textTheme.textStyle, style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -43,7 +43,7 @@ class AboutPage extends StatelessWidget {
// Key Features Section // Key Features Section
Text( Text(
'Why Choose Tripz?'.tr, 'Why Choose Intaleq?'.tr,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle, style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),

View File

@@ -432,8 +432,7 @@ class ShareAppPage extends StatelessWidget {
), ),
), ),
Text( Text(
controller.formatPhoneNumber( (contact['phones'][0].toString()),
contact['phones'][0].toString()),
style: const TextStyle( style: const TextStyle(
color: CupertinoColors.secondaryLabel, color: CupertinoColors.secondaryLabel,
fontSize: 15, fontSize: 15,

View File

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

View File

@@ -71,7 +71,7 @@ class MapPagePassenger extends StatelessWidget {
const RideFromStartApp(), const RideFromStartApp(),
// cancelRidePage(), // cancelRidePage(),
const MenuIconMapPageWidget(), // const MenuIconMapPageWidget(),
PointsPageForRider() PointsPageForRider()
], ],
), ),

View File

@@ -40,15 +40,19 @@ List<CarType> carTypes = [
carType: 'Electric', carType: 'Electric',
carDetail: 'Quiet & Eco-Friendly'.tr, carDetail: 'Quiet & Eco-Friendly'.tr,
image: image:
'assets/images/electric_car.jpg'), // Third choice - NOTE: Use your actual image path 'assets/images/electric.png'), // Third choice - NOTE: Use your actual image path
CarType( CarType(
carType: 'Lady', carType: 'Lady',
carDetail: 'Lady Captain for girls'.tr, carDetail: 'Lady Captain for girls'.tr,
image: 'assets/images/lady.png'), image: 'assets/images/lady.png'),
CarType( CarType(
carType: 'Scooter', carType: 'Van',
carDetail: 'Delivery service'.tr, carDetail: 'Van for familly'.tr,
image: 'assets/images/moto.png'), image: 'assets/images/bus.png'),
// CarType(
// carType: 'Scooter',
// carDetail: 'Delivery service'.tr,
// image: 'assets/images/moto.png'),
CarType( CarType(
carType: 'Rayeh Gai', carType: 'Rayeh Gai',
carDetail: "Best choice for cities".tr, carDetail: "Best choice for cities".tr,
@@ -365,6 +369,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1); return mapPassengerController.totalPassengerBalash.toStringAsFixed(1);
case 'Scooter': case 'Scooter':
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1); return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
case 'Van':
return mapPassengerController.totalPassengerVan.toStringAsFixed(1);
case 'Lady': case 'Lady':
return mapPassengerController.totalPassengerLady.toStringAsFixed(1); return mapPassengerController.totalPassengerLady.toStringAsFixed(1);
case 'Pink Bike': case 'Pink Bike':
@@ -476,6 +482,9 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.' return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.'
.tr; .tr;
case 'Scooter': case 'Scooter':
case 'Van':
return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together."
.tr;
case 'Pink Bike': case 'Pink Bike':
return 'This is for delivery or a motorcycle.'.tr; return 'This is for delivery or a motorcycle.'.tr;
case 'Mishwar Vip': case 'Mishwar Vip':
@@ -556,6 +565,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
return mapPassengerController.totalPassengerElectric; return mapPassengerController.totalPassengerElectric;
case 'Awfar Car': case 'Awfar Car':
return mapPassengerController.totalPassengerBalash; return mapPassengerController.totalPassengerBalash;
case 'Van':
return mapPassengerController.totalPassengerVan;
case 'Lady': case 'Lady':
return mapPassengerController.totalPassengerLady; return mapPassengerController.totalPassengerLady;
default: default:

View File

@@ -78,7 +78,7 @@ class CashConfirmPageShown extends StatelessWidget {
// بطاقة المحفظة // بطاقة المحفظة
_buildPaymentOptionCard( _buildPaymentOptionCard(
icon: Icons.account_balance_wallet_outlined, icon: Icons.account_balance_wallet_outlined,
title: '${AppInformation.appName} Wallet'.tr, title: '${AppInformation.appName} Balance'.tr,
subtitle: subtitle:
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}', '${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
isSelected: paymentCtrl.isWalletChecked, isSelected: paymentCtrl.isWalletChecked,
@@ -115,7 +115,7 @@ class CashConfirmPageShown extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
MyElevatedButton( MyElevatedButton(
title: 'Top up Wallet to continue'.tr, title: 'Top up Balance to continue'.tr,
onPressed: () => onPressed: () =>
Get.to(() => const PassengerWallet()), Get.to(() => const PassengerWallet()),
kolor: AppColor.redColor, kolor: AppColor.redColor,

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
@@ -6,15 +8,15 @@ import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
import '../../../controller/functions/location_controller.dart'; import '../../../controller/functions/location_controller.dart';
import '../../../controller/home/device_tier.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart'; import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
class GoogleMapPassengerWidget extends StatelessWidget { class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({ GoogleMapPassengerWidget({super.key});
super.key,
}); final WayPointController wayPointController = Get.put(WayPointController());
WayPointController wayPointController = Get.put(WayPointController());
final LocationController locationController = Get.find<LocationController>(); final LocationController locationController = Get.find<LocationController>();
@override @override
@@ -30,19 +32,25 @@ class GoogleMapPassengerWidget extends StatelessWidget {
child: GoogleMap( child: GoogleMap(
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
// ✅ حدود الكاميرا كما هي
cameraTargetBounds: CameraTargetBounds(controller.boundsdata), cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
// ✅ Zoom أهدأ للأجهزة الضعيفة
minMaxZoomPreference: controller.lowPerf
? const MinMaxZoomPreference(6, 17)
: const MinMaxZoomPreference(6, 18),
onLongPress: (argument) { onLongPress: (argument) {
MyDialog().getDialog('Are you want to go to this site'.tr, '', MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async { () async {
controller.clearPolyline(); controller.clearPolyline();
if (controller.dataCarsLocationByPassenger != null) { if (controller.dataCarsLocationByPassenger != null) {
await controller.getDirectionMap( await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${argument.latitude.toString()},${argument.longitude.toString()}'); '${argument.latitude},${argument.longitude}',
);
Get.back(); Get.back();
controller.bottomSheet(); await controller.bottomSheet();
controller.showBottomSheet1(); controller.showBottomSheet1();
} else { } else {
Get.back(); Get.back();
@@ -54,15 +62,12 @@ class GoogleMapPassengerWidget extends StatelessWidget {
duration: const Duration(seconds: 11), duration: const Duration(seconds: 11),
instantInit: true, instantInit: true,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
titleText: Text( titleText: Text('Error'.tr,
'Error'.tr, style: const TextStyle(color: AppColor.redColor)),
style: const TextStyle(color: AppColor.redColor),
),
messageText: Text( messageText: Text(
'We Are Sorry That we dont have cars in your Location!' 'We Are Sorry That we dont have cars in your Location!'
.tr, .tr,
style: AppStyle.title, style: AppStyle.title),
),
icon: const Icon(Icons.error), icon: const Icon(Icons.error),
shouldIconPulse: true, shouldIconPulse: true,
maxWidth: double.infinity, maxWidth: double.infinity,
@@ -86,370 +91,98 @@ class GoogleMapPassengerWidget extends StatelessWidget {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
// mainButton: TextButton(
// onPressed: () {
// controller.getCarsLocationByPassenger();
// },
// child: Text(
// 'Try Again'.tr,
// style: const TextStyle(
// color: AppColor.secondaryColor),
// ),
// ),
onTap: (GetSnackBar snackBar) {
// Do something when the snackbar is tapped.
},
isDismissible: true, isDismissible: true,
showProgressIndicator: false, showProgressIndicator: false,
dismissDirection: DismissDirection.up, dismissDirection: DismissDirection.up,
progressIndicatorController: null,
progressIndicatorBackgroundColor: Colors.transparent,
progressIndicatorValueColor: null,
snackStyle: SnackStyle.GROUNDED, snackStyle: SnackStyle.GROUNDED,
forwardAnimationCurve: Curves.easeInToLinear, forwardAnimationCurve: Curves.easeInToLinear,
reverseAnimationCurve: Curves.easeInOut, reverseAnimationCurve: Curves.easeInOut,
animationDuration: const Duration(milliseconds: 4000), animationDuration: const Duration(milliseconds: 4000),
barBlur: 8, barBlur: 8,
overlayBlur: 0,
snackbarStatus: null,
overlayColor: AppColor.primaryColor.withOpacity(0.5), overlayColor: AppColor.primaryColor.withOpacity(0.5),
userInputForm: null,
); );
} }
//
}); });
// Get.defaultDialog(
// title: 'Are you want to go to this site'.tr,
// content: Column(
// children: [
// Text('${argument.latitude},${argument.longitude}'),
// ],
// ),
// confirm: MyElevatedButton(
// title: 'Ok'.tr,
// onPressed: () async {
// controller.clearPolyline();
// if (controller.dataCarsLocationByPassenger != null) {
// await controller.getMap(
// '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
// '${argument.latitude.toString()},${argument.longitude.toString()}');
// Get.back();
// controller.bottomSheet();
// controller.showBottomSheet1();
// } else {
// Get.back();
// Get.snackbar(
// 'We Are Sorry That we dont have cars in your Location!'
// .tr,
// '',
// colorText: AppColor.redColor,
// duration: const Duration(seconds: 11),
// instantInit: true,
// snackPosition: SnackPosition.TOP,
// titleText: Text(
// 'Error'.tr,
// style:
// const TextStyle(color: AppColor.redColor),
// ),
// messageText: Text(
// 'We Are Sorry That we dont have cars in your Location!'
// .tr,
// style: AppStyle.title,
// ),
// icon: const Icon(Icons.error),
// shouldIconPulse: true,
// maxWidth: double.infinity,
// margin: const EdgeInsets.all(16),
// padding: const EdgeInsets.all(16),
// borderRadius: 8,
// borderColor: AppColor.redColor,
// borderWidth: 2,
// backgroundColor: AppColor.secondaryColor,
// leftBarIndicatorColor: AppColor.redColor,
// boxShadows: [
// BoxShadow(
// color: Colors.black.withOpacity(0.25),
// blurRadius: 4,
// spreadRadius: 2,
// offset: const Offset(0, 4),
// ),
// ],
// backgroundGradient: const LinearGradient(
// colors: [
// AppColor.redColor,
// AppColor.accentColor
// ],
// begin: Alignment.topLeft,
// end: Alignment.bottomRight,
// ),
// // mainButton: TextButton(
// // onPressed: () {
// // controller.getCarsLocationByPassenger();
// // },
// // child: Text(
// // 'Try Again'.tr,
// // style: const TextStyle(
// // color: AppColor.secondaryColor),
// // ),
// // ),
// onTap: (GetSnackBar snackBar) {
// // Do something when the snackbar is tapped.
// },
// isDismissible: true,
// showProgressIndicator: false,
// dismissDirection: DismissDirection.up,
// progressIndicatorController: null,
// progressIndicatorBackgroundColor:
// Colors.transparent,
// progressIndicatorValueColor: null,
// snackStyle: SnackStyle.GROUNDED,
// forwardAnimationCurve: Curves.easeInToLinear,
// reverseAnimationCurve: Curves.easeInOut,
// animationDuration:
// const Duration(milliseconds: 4000),
// barBlur: 8,
// overlayBlur: 0,
// snackbarStatus: null,
// overlayColor:
// AppColor.primaryColor.withOpacity(0.5),
// userInputForm: null,
// );
// }
// //
// }),
// );
}, },
onTap: (argument) { onTap: (argument) {
controller.hidePlaces(); controller.hidePlaces();
// controller.changeBottomSheetShown();
// controller.bottomSheet();
}, },
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
target: controller.passengerLocation, target: controller.passengerLocation,
zoom: 15, zoom: controller.lowPerf ? 14.5 : 15,
), ),
// ✅ ماركرز (احرص أن الأيقونات محجّمة ومخزّنة Cache في الكنترولر)
markers: controller.markers.toSet(), markers: controller.markers.toSet(),
// {
// if (controller.statusRide != 'Apply' ||
// !controller.rideTimerBegin)
// for (var carLocation in controller.carLocationsModels)
// // Marker( // ✅ بوليغونز كما هي
// // // anchor: const Offset(4, 4),
// // position: LatLng(
// // carLocation.latitude,
// // carLocation.longitude,
// // ),
// // icon: controller.carIcon,
// // markerId: MarkerId(carLocation.toString()),
// // rotation: carLocation.heading,
// // ),
// // controller.carMarrkerAplied,
// if (controller.statusRide == 'Apply')
// // for (var carLocation
// // in controller.driverCarsLocationToPassengerAfterApplied)
// Marker(
// // anchor: const Offset(4, 4),
// position: LatLng(
// double.parse(
// controller
// .datadriverCarsLocationToPassengerAfterApplied[
// 'message'][0]['latitude'],
// ),
// double.parse(
// controller
// .datadriverCarsLocationToPassengerAfterApplied[
// 'message'][0]['longitude'],
// ),
// ), //carLocation,
// icon: controller.carIcon,
// rotation: double.parse(controller
// .datadriverCarsLocationToPassengerAfterApplied[
// 'message'][0]['heading']),
// markerId: MarkerId(controller
// .datadriverCarsLocationToPassengerAfterApplied[
// 'message'][0]['longitude']
// .toString())),
// for (int i = 1;
// i < controller.coordinatesWithoutEmpty.length - 1;
// i++)
// Marker(
// // anchor: const Offset(4, 4),
// position: LatLng(
// double.parse(controller.coordinatesWithoutEmpty[i]
// .split(',')[0]),
// double.parse(controller.coordinatesWithoutEmpty[i]
// .split(',')[1])),
// icon: controller.tripIcon,
// markerId: MarkerId(
// controller.coordinatesWithoutEmpty[i].toString())),
// if (controller.isMarkersShown)
// Marker(
// markerId: MarkerId('MyLocation'.tr),
// position: controller.newStartPointLocation,
// draggable: true,
// icon: controller.startIcon,
// ),
// if (controller.isMarkersShown)
// Marker(
// markerId: MarkerId('Destination'.tr),
// position: controller.myDestination,
// draggable: true,
// icon: controller.endIcon,
// ),
// if (controller.haveSteps)
// Marker(
// markerId: MarkerId('StartSteps'.tr),
// position: LatLng(
// double.parse(
// controller.placesCoordinate[0].split(',')[0]),
// double.parse(
// controller.placesCoordinate[0].split(',')[1])),
// draggable: true,
// icon: controller.startIcon,
// ),
// if (controller.haveSteps)
// Marker(
// markerId: MarkerId('EndSteps'.tr),
// position: controller.latestPosition,
// draggable: true,
// icon: controller.endIcon,
// ),
// },
polygons: controller.polygons, polygons: controller.polygons,
polylines: controller.polyLines.toSet(),
// {
// Polyline(
// polylineId: const PolylineId('route'),
// points: controller.polylineCoordinates,
// color: AppColor.primaryColor,
// width: 4,
// // patterns: [
// // PatternItem.dot,
// // PatternItem.gap(10),
// // ],
// endCap: Cap.roundCap,
// startCap: Cap.roundCap,
// geodesic: true,
// ),
// Polyline( // Polyline مُبسّطة للأجهزة الضعيفة (الكنترولر يجهّز مجموعة مبسطة عند lowPerf)
// zIndex: 1, polylines: controller.lowPerf
// consumeTapEvents: true, ? controller.polyLinesLight
// geodesic: true, .toSet() // <- استخدم مجموعة خفيفة
// endCap: Cap.buttCap, : controller.polyLines.toSet(),
// startCap: Cap.buttCap,
// visible: true, // ✅ دوائر خفيفة على الأجهزة الضعيفة
// polylineId: const PolylineId('route0'),
// points: controller.polylineCoordinatesPointsAll[0],
// color: AppColor.blueColor,
// width: 5,
// ),
// Polyline(
// zIndex: 2,
// consumeTapEvents: true,
// geodesic: true,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
// visible: true,
// polylineId: const PolylineId('route1'),
// points: controller.polylineCoordinatesPointsAll[1],
// color: AppColor.yellowColor,
// width: 5,
// ),
// Polyline(
// zIndex: 2,
// consumeTapEvents: true,
// geodesic: true,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
// visible: true,
// polylineId: const PolylineId('route2'),
// points: controller.polylineCoordinatesPointsAll[2],
// color: AppColor.greenColor,
// width: 5,
// ),
// Polyline(
// zIndex: 2,
// consumeTapEvents: true,
// geodesic: true,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
// visible: true,
// polylineId: const PolylineId('route3'),
// points: controller.polylineCoordinatesPointsAll[2],
// color: AppColor.deepPurpleAccent,
// width: 5,
// ),
// // Polyline(
// // zIndex: 2,
// // consumeTapEvents: true,
// // geodesic: true,
// // endCap: Cap.buttCap,
// // startCap: Cap.buttCap,
// // visible: true,
// // polylineId: PolylineId('g'),
// // points: [
// // LatLng(controller.southwest.latitude,
// // controller.southwest.longitude),
// // LatLng(controller.northeast.latitude,
// // controller.northeast.longitude)
// // ],
// // color: AppColor.primaryColor,
// // width: 5,
// // ),
// },
// circles: { // circles: {
// Circle( // Circle(
// circleId: const CircleId('kk'), // circleId: const CircleId('circle_id'),
// center: controller.mylocation, // center: controller.passengerLocation,
// radius: 60, // radius: controller.lowPerf ? 80 : 100,
// fillColor: AppColor.primaryColor,) // fillColor:
// Colors.blue.withOpacity(controller.lowPerf ? 0.2 : 0.3),
// strokeColor: Colors.blue,
// strokeWidth: controller.lowPerf ? 1 : 2,
// ),
// }, // },
circles: <Circle>{ // ✅ الوضع الخفيف: liteMode + تعطيل الطبقات المكلفة + خريطة Normal
Circle( mapType: controller.lowPerf
circleId: const CircleId('circle_id'), ? MapType.normal
center: controller.passengerLocation, : (controller.mapType
radius: 100, ? MapType.satellite
fillColor: Colors.blue.withOpacity(0.3), : MapType.terrain),
strokeColor: Colors.blue,
strokeWidth: 2,
),
},
mapType: myLocationButtonEnabled: false,
controller.mapType ? MapType.satellite : MapType.terrain,
myLocationButtonEnabled: true,
// liteModeEnabled: true, tiltGesturesEnabled: false,
// indoorViewEnabled: true, // ⚠️ liteMode (Android فقط): فعّله على الأجهزة الضعيفة
trafficEnabled: controller.mapTrafficON, // liteModeEnabled: controller.lowPerf,
buildingsEnabled: true, liteModeEnabled: Platform.isAndroid ? isLowEnd() : false,
mapToolbarEnabled: true, trafficEnabled: controller.mapTrafficON && !isLowEnd(),
buildingsEnabled: !isLowEnd(),
// ✅ تقليل الكلفة الرسومية
mapToolbarEnabled: false,
rotateGesturesEnabled: isLowEnd() ? false : true,
tiltGesturesEnabled: false, // تعطيل الميلان لتقليل الحمل
// ✅ Throttle لحركة الكاميرا على الأجهزة الضعيفة
onCameraMove: (position) { onCameraMove: (position) {
int waypointsLength = if (controller.lowPerf) {
Get.find<WayPointController>().wayPoints.length; controller.onCameraMoveThrottled(position);
int index = controller.wayPointIndex; } else {
if (waypointsLength > 0) { // منطقك الحالي
controller.placesCoordinate[index] = int waypointsLength =
'${position.target.latitude.toString()},${position.target.longitude}'; Get.find<WayPointController>().wayPoints.length;
int index = controller.wayPointIndex;
if (waypointsLength > 0) {
controller.placesCoordinate[index] =
'${position.target.latitude},${position.target.longitude}';
}
if (controller.startLocationFromMap == true) {
controller.newStartPointLocation = position.target;
} else if (controller.passengerStartLocationFromMap ==
true) {
controller.newStartPointLocation = position.target;
}
controller.newMyLocation = position.target;
} }
if (controller.startLocationFromMap == true) {
controller.newStartPointLocation = position.target;
} else if (controller.passengerStartLocationFromMap == true) {
controller.newStartPointLocation = position.target;
}
controller.newMyLocation = position.target;
}, },
myLocationEnabled: true, myLocationEnabled: true,
// liteModeEnabled: true,
), ),
), ),
); );

View File

@@ -1,25 +1,16 @@
import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/controller/firebase/firbase_messge.dart'; import 'package:Intaleq/controller/firebase/firbase_messge.dart';
import 'package:Intaleq/env/env.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/char_map.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../controller/auth/login_controller.dart'; import '../../../controller/auth/login_controller.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../controller/functions/tts.dart'; import '../../../controller/functions/tts.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/home/vip_waitting_page.dart'; import '../../../controller/home/vip_waitting_page.dart';
import '../../../print.dart';
import '../../auth/otp_page.dart'; import '../../auth/otp_page.dart';
import '../../auth/otp_token_page.dart';
// --- الدالة الرئيسية بالتصميم الجديد --- // --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapPassengerController> leftMainMenuIcons() { GetBuilder<MapPassengerController> leftMainMenuIcons() {

View File

@@ -1,3 +1,5 @@
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import 'package:Intaleq/constant/box_name.dart'; import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -10,12 +12,13 @@ import 'package:Intaleq/views/home/profile/complaint_page.dart';
import 'package:Intaleq/views/home/profile/order_history.dart'; import 'package:Intaleq/views/home/profile/order_history.dart';
import 'package:Intaleq/views/home/profile/promos_passenger_page.dart'; import 'package:Intaleq/views/home/profile/promos_passenger_page.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
import '../../notification/notification_page.dart'; import '../../notification/notification_page.dart';
import '../HomePage/contact_us.dart'; import '../HomePage/contact_us.dart';
import '../HomePage/share_app_page.dart';
import '../setting_page.dart'; import '../setting_page.dart';
import '../profile/passenger_profile_page.dart'; import '../profile/passenger_profile_page.dart';
@@ -25,17 +28,25 @@ class MapMenuWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// استخدام Get.lazyPut لضمان وجود الكنترولر
Get.lazyPut(() => MapPassengerController()); Get.lazyPut(() => MapPassengerController());
return GetBuilder<MapPassengerController>( return GetBuilder<MapPassengerController>(
builder: (controller) => Stack( builder: (controller) => Stack(
children: [ children: [
// --- خلفية معتمة عند فتح القائمة ---
if (controller.widthMenu > 0)
GestureDetector(
onTap: controller.getDrawerMenu,
child: Container(
color: Colors.black.withOpacity(0.4),
),
),
// --- القائمة الجانبية المنزلقة --- // --- القائمة الجانبية المنزلقة ---
_buildSideMenu(controller), _buildSideMenu(controller),
// --- زر القائمة العائم --- // --- زر القائمة العائم ---
// _buildMenuButton(controller), _buildMenuButton(controller),
], ],
), ),
); );
@@ -48,7 +59,7 @@ class MapMenuWidget extends StatelessWidget {
left: 16, left: 16,
child: SafeArea( child: SafeArea(
child: InkWell( child: InkWell(
onTap: controller.getDrawerMenu, // نفس دالتك القديمة onTap: controller.getDrawerMenu,
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
@@ -64,9 +75,7 @@ class MapMenuWidget extends StatelessWidget {
Border.all(color: AppColor.writeColor.withOpacity(0.2)), Border.all(color: AppColor.writeColor.withOpacity(0.2)),
), ),
child: Icon( child: Icon(
controller.widthMenu > 0 controller.widthMenu > 0 ? Icons.close : Icons.menu,
? Icons.close_rounded
: Icons.menu_rounded,
color: AppColor.writeColor, color: AppColor.writeColor,
size: 26, size: 26,
), ),
@@ -85,63 +94,100 @@ class MapMenuWidget extends StatelessWidget {
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
top: 0, top: 0,
bottom: 0, bottom: 0,
// تحريك القائمة من خارج الشاشة إلى داخلها
left: controller.widthMenu > 0 ? 0 : -Get.width, left: controller.widthMenu > 0 ? 0 : -Get.width,
child: ClipRRect( child: ClipRRect(
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container( child: Container(
width: Get.width * 0.75, // عرض القائمة width: Get.width * 0.8,
constraints: const BoxConstraints(maxWidth: 300), constraints: const BoxConstraints(maxWidth: 320),
color: AppColor.secondaryColor.withOpacity(0.9), decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.95),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
)
],
),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// --- 1. رأس القائمة (معلومات المستخدم) ---
_buildMenuHeader(), _buildMenuHeader(),
// --- 2. الأزرار السريعة المدمجة ---
_buildQuickActionButtons(), _buildQuickActionButtons(),
const Divider( const Divider(
color: AppColor.writeColor, color: Colors.white24,
indent: 16, indent: 16,
endIndent: 16, endIndent: 16,
height: 1), height: 24),
// --- 3. قائمة الخيارات الرئيسية ---
Expanded( Expanded(
child: ListView( child: ListView(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
children: [ children: [
IconMainPageMap( MenuListItem(
title: 'My Wallet'.tr, title: 'My Balance'.tr,
icon: Icons.account_balance_wallet_outlined, icon: Icons.account_balance_wallet_outlined,
onTap: () => Get.to(() => const PassengerWallet())), onTap: () => Get.to(() => const PassengerWallet())),
IconMainPageMap( MenuListItem(
title: 'Order History'.tr, title: 'Order History'.tr,
icon: Icons.history_edu_rounded, icon: Icons.history_rounded,
onTap: () => Get.to(() => const OrderHistory())), onTap: () => Get.to(() => const OrderHistory())),
IconMainPageMap( MenuListItem(
title: 'Contact Us'.tr,
icon: Icons.contact_support_outlined,
onTap: () => Get.to(() => ContactUsPage())),
IconMainPageMap(
title: 'Driver'.tr,
icon: Ionicons.car_sport_outline,
onTap: () => _launchDriverAppUrl()),
IconMainPageMap(
title: 'Complaint'.tr,
icon: Icons.feedback_outlined,
onTap: () => Get.to(() => ComplaintPage())),
IconMainPageMap(
title: 'Promos'.tr, title: 'Promos'.tr,
icon: Icons.local_offer_outlined, icon: Icons.local_offer_outlined,
onTap: () => onTap: () =>
Get.to(() => const PromosPassengerPage())), Get.to(() => const PromosPassengerPage())),
MenuListItem(
title: 'Contact Us'.tr,
icon: Icons.contact_support_outlined,
onTap: () => Get.to(() => ContactUsPage())),
MenuListItem(
title: 'Complaint'.tr,
icon: Icons.flag_outlined,
onTap: () => Get.to(() => ComplaintPage())),
MenuListItem(
title: 'Driver'.tr,
icon: Ionicons.car_sport_outline,
onTap: () => _launchDriverAppUrl()),
MenuListItem(
title: 'Share App'.tr,
icon: Icons.share_outlined,
onTap: () => Get.to(() => ShareAppPage())),
MenuListItem(
title: 'Privacy Policy'.tr,
icon: Icons.shield_outlined,
onTap: () => launchUrl(Uri.parse(
'${AppLink.server}/privacy_policy.php')),
),
], ],
), ),
), ),
const Divider(
color: Colors.white24,
indent: 16,
endIndent: 16,
height: 1),
Padding(
padding: const EdgeInsets.all(8.0),
child: MenuListItem(
title: 'Logout'.tr,
icon: Icons.logout_rounded,
onTap: () {
Get.defaultDialog(
title: "Logout".tr,
middleText: "Are you sure you want to logout?".tr,
textConfirm: "Logout".tr,
textCancel: "Cancel".tr,
onConfirm: () {
// controller.logout();
Get.back();
},
);
},
color: Colors.red.shade300,
),
),
], ],
), ),
), ),
@@ -151,49 +197,57 @@ class MapMenuWidget extends StatelessWidget {
); );
} }
// --- ويدجت مساعدة لرأس القائمة --- // --- ويدجت رأس القائمة ---
Widget _buildMenuHeader() { Widget _buildMenuHeader() {
return Padding( return Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.fromLTRB(20, 30, 20, 16),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// CircleAvatar( const CircleAvatar(
// radius: 30, radius: 30,
// backgroundColor: AppColor.primaryColor, backgroundColor: AppColor.primaryColor,
// child: child: Icon(Icons.person, color: AppColor.writeColor, size: 35),
// const Icon(Icons.person, color: AppColor.writeColor, size: 35),
// ),
// const SizedBox(height: 12),
Text(
"Welcome Back!".tr, // يمكنك تغييرها لاسم المستخدم
style: AppStyle.title
.copyWith(color: AppColor.writeColor.withOpacity(0.7)),
), ),
Text( const SizedBox(width: 16),
box.read(BoxName.name), // يمكنك تغييرها لاسم المستخدم Expanded(
style: AppStyle.headTitle.copyWith(fontSize: 22), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
box.read(BoxName.name) ?? 'Guest',
style: AppStyle.headTitle.copyWith(fontSize: 20),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
"Intaleq Passenger".tr,
style: AppStyle.title.copyWith(
color: AppColor.writeColor.withOpacity(0.7),
fontSize: 14),
),
],
),
), ),
], ],
), ),
); );
} }
// --- ويدجت مساعدة للأزرار السريعة --- // --- ويدجت الأزرار السريعة ---
Widget _buildQuickActionButtons() { Widget _buildQuickActionButtons() {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildSmallActionButton(
icon: Icons.notifications_none_rounded,
label: 'Alerts'.tr,
onTap: () => Get.to(() => const NotificationPage())),
_buildSmallActionButton( _buildSmallActionButton(
icon: Icons.person_outline_rounded, icon: Icons.person_outline_rounded,
label: 'Profile'.tr, label: 'Profile'.tr,
onTap: () => Get.to(() => PassengerProfilePage())), onTap: () => Get.to(() => PassengerProfilePage())),
_buildSmallActionButton(
icon: Icons.notifications_none_rounded,
label: 'Alerts'.tr,
onTap: () => Get.to(() => const NotificationPage())),
_buildSmallActionButton( _buildSmallActionButton(
icon: Icons.settings_outlined, icon: Icons.settings_outlined,
label: 'Settings'.tr, label: 'Settings'.tr,
@@ -209,29 +263,31 @@ class MapMenuWidget extends StatelessWidget {
required VoidCallback onTap}) { required VoidCallback onTap}) {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(12),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, color: AppColor.writeColor, size: 24), Icon(icon, color: AppColor.writeColor.withOpacity(0.9), size: 24),
const SizedBox(height: 4), const SizedBox(height: 6),
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)), Text(label,
style: AppStyle.subtitle.copyWith(
fontSize: 12, color: AppColor.writeColor.withOpacity(0.9))),
], ],
), ),
), ),
); );
} }
// --- نفس دالتك القديمة لفتح رابط تطبيق السائق ---
void _launchDriverAppUrl() async { void _launchDriverAppUrl() async {
final String driverAppUrl; final String driverAppUrl;
if (defaultTargetPlatform == TargetPlatform.android) { if (defaultTargetPlatform == TargetPlatform.android) {
driverAppUrl = driverAppUrl =
'https://play.google.com/store/apps/details?id=com.sefer_driver'; 'https://play.google.com/store/apps/details?id=com.intaleq_driver';
} else if (defaultTargetPlatform == TargetPlatform.iOS) { } else if (defaultTargetPlatform == TargetPlatform.iOS) {
driverAppUrl = 'https://apps.apple.com/eg/app/tripz-driver/id6502189302'; driverAppUrl =
'https://apps.apple.com/st/app/intaleq-driver/id6482995159';
} else { } else {
return; return;
} }
@@ -248,28 +304,39 @@ class MapMenuWidget extends StatelessWidget {
} }
} }
// --- كلاس عناصر القائمة بالتصميم الجديد (يستخدم ListTile) --- // --- ويدجت عناصر القائمة بتصميم محسن ---
class IconMainPageMap extends StatelessWidget { class MenuListItem extends StatelessWidget {
const IconMainPageMap({ const MenuListItem({
super.key, super.key,
required this.title, required this.title,
required this.onTap, required this.onTap,
required this.icon, required this.icon,
this.color,
}); });
final String title; final String title;
final IconData icon; final IconData icon;
final VoidCallback onTap; final VoidCallback onTap;
final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
onTap: onTap, onTap: onTap,
leading: leading: Icon(
Icon(icon, size: 26, color: AppColor.writeColor.withOpacity(0.8)), icon,
size: 26,
color: color ?? AppColor.writeColor.withOpacity(0.8),
),
title: Text( title: Text(
title.tr, title.tr,
style: AppStyle.title.copyWith(fontSize: 16), style: AppStyle.title.copyWith(
fontSize: 16,
color: color ?? AppColor.writeColor,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
), ),
splashColor: AppColor.primaryColor.withOpacity(0.2), splashColor: AppColor.primaryColor.withOpacity(0.2),
); );

View File

@@ -193,7 +193,7 @@ class RideBeginPassenger extends StatelessWidget {
box.write(BoxName.sosPhonePassenger, box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']); profileController.prfoileData['sosPhone']);
} else { } else {
makePhoneCall('122'); makePhoneCall('112');
} }
}), }),
_buildActionButton( _buildActionButton(
@@ -211,18 +211,17 @@ class RideBeginPassenger extends StatelessWidget {
} else { } else {
final phoneNumber = final phoneNumber =
box.read(BoxName.sosPhonePassenger).toString(); box.read(BoxName.sosPhonePassenger).toString();
final phone = box.read(BoxName.countryCode) == 'Egypt'
? '+2$phoneNumber' final phone = controller.formatSyrianPhoneNumber(phoneNumber);
: '+962$phoneNumber';
controller.sendWhatsapp(phone); controller.sendWhatsapp(phone);
} }
}), }),
_buildActionButton( _buildActionButton(
icon: Foundation.video, icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
label: 'Video Call'.tr, label: 'Share'.tr, // اسم جديد وواضح
color: AppColor.blueColor, color: AppColor.blueColor,
onTap: () async { onTap: () async {
// --- نفس منطقك القديم --- // نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
await controller.getTokenForParent(); await controller.getTokenForParent();
}), }),
_buildActionButton( _buildActionButton(

View File

@@ -27,7 +27,7 @@ class PassengerWallet extends StatelessWidget {
Get.put(CreditCardController()); Get.put(CreditCardController());
return MyScafolld( return MyScafolld(
title: 'My Wallet'.tr, title: 'My Balance'.tr,
isleading: true, isleading: true,
body: [ body: [
// استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة // استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة
@@ -53,7 +53,7 @@ class PassengerWallet extends StatelessWidget {
// --- 2. قائمة الخيارات المنظمة --- // --- 2. قائمة الخيارات المنظمة ---
_buildActionTile( _buildActionTile(
icon: Icons.add_card_rounded, icon: Icons.add_card_rounded,
title: 'Top up Wallet'.tr, title: 'Top up Balance'.tr,
subtitle: 'Add funds using our secure methods'.tr, subtitle: 'Add funds using our secure methods'.tr,
onTap: () => onTap: () =>
showPaymentBottomSheet(context), // نفس دالتك القديمة showPaymentBottomSheet(context), // نفس دالتك القديمة
@@ -68,7 +68,7 @@ class PassengerWallet extends StatelessWidget {
), ),
_buildActionTile( _buildActionTile(
icon: Icons.phone_iphone_rounded, icon: Icons.phone_iphone_rounded,
title: 'Set Wallet Phone Number'.tr, title: 'Set Phone Number'.tr,
subtitle: 'Link a phone number for transfers'.tr, subtitle: 'Link a phone number for transfers'.tr,
onTap: () => _showWalletPhoneDialog(context, onTap: () => _showWalletPhoneDialog(context,
Get.find<PaymentController>()), // نفس دالتك القديمة Get.find<PaymentController>()), // نفس دالتك القديمة
@@ -132,7 +132,7 @@ class PassengerWallet extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'${AppInformation.appName} Wallet'.tr, '${AppInformation.appName} ${'Balance'.tr}',
style: AppStyle.headTitle.copyWith( style: AppStyle.headTitle.copyWith(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,

View File

@@ -1,5 +1,3 @@
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -8,8 +6,8 @@ import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/profile/profile_controller.dart'; import 'package:Intaleq/controller/profile/profile_controller.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart'; import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:Intaleq/views/widgets/my_textField.dart'; import 'package:Intaleq/views/widgets/my_textField.dart';
import 'package:Intaleq/views/widgets/mycircular.dart'; import 'package:Intaleq/views/widgets/mycircular.dart';
@@ -18,239 +16,319 @@ import '../../../controller/functions/log_out.dart';
class PassengerProfilePage extends StatelessWidget { class PassengerProfilePage extends StatelessWidget {
PassengerProfilePage({super.key}); PassengerProfilePage({super.key});
LogOutController logOutController = Get.put(LogOutController());
final LogOutController logOutController = Get.put(LogOutController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(ProfileController()); Get.put(ProfileController());
return MyScafolld( return Scaffold(
isleading: true, backgroundColor: Colors.grey[100],
title: 'My Profile'.tr, appBar: AppBar(
body: [ title: Text('My Profile'.tr,
GetBuilder<ProfileController>( style: const TextStyle(fontWeight: FontWeight.bold)),
builder: (controller) => controller.isloading backgroundColor: Colors.grey[100],
? const MyCircularProgressIndicator() elevation: 0,
: Padding( centerTitle: true,
padding: const EdgeInsets.symmetric(horizontal: 15), ),
child: SizedBox( body: GetBuilder<ProfileController>(
height: Get.height, builder: (controller) {
child: SingleChildScrollView( if (controller.isloading) {
child: Column( return const MyCircularProgressIndicator();
mainAxisAlignment: MainAxisAlignment.start, }
crossAxisAlignment: CrossAxisAlignment.start, return ListView(
children: [ padding:
Text( const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
'Edit Profile'.tr, children: [
style: AppStyle.headTitle2, _buildProfileHeader(controller),
), const SizedBox(height: 24),
ListTile( _buildSectionCard(
title: Text( 'Personal Information'.tr,
'Name'.tr, [
style: AppStyle.title, _buildProfileTile(
), icon: Icons.person_outline,
leading: const Icon( color: Colors.blue,
Icons.person_pin_rounded, title: 'Name'.tr,
size: 35, subtitle:
), '${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}',
trailing: const Icon(Icons.arrow_forward_ios), onTap: () =>
subtitle: Text( controller.updatField('first_name', TextInputType.name),
'${(controller.prfoileData['first_name'])} ${(controller.prfoileData['last_name'])}'), ),
onTap: () { _buildProfileTile(
controller.updatField( icon: Icons.wc_outlined,
'first_name', TextInputType.name); color: Colors.pink,
}, title: 'Gender'.tr,
), subtitle: controller.prfoileData['gender']?.toString() ??
ListTile( 'Not set'.tr,
title: Text( onTap: () => _showGenderDialog(controller),
'Gender'.tr, ),
style: AppStyle.title, _buildProfileTile(
), icon: Icons.school_outlined,
leading: Image.asset( color: Colors.orange,
'assets/images/gender.png', title: 'Education'.tr,
width: 35, subtitle: controller.prfoileData['education']?.toString() ??
), 'Not set'.tr,
trailing: const Icon(Icons.arrow_forward_ios), onTap: () => _showEducationDialog(controller),
subtitle: Text((controller.prfoileData['gender'] ),
.toString())), ],
onTap: () { ),
Get.defaultDialog( const SizedBox(height: 24),
title: 'Update Gender'.tr, _buildSectionCard(
content: Column( 'Work & Contact'.tr,
children: [ [
GenderPicker(), _buildProfileTile(
MyElevatedButton( icon: Icons.work_outline,
title: 'Update'.tr, color: Colors.green,
onPressed: () { title: 'Employment Type'.tr,
controller.updateColumn({ subtitle:
'id': controller.prfoileData['id'] controller.prfoileData['employmentType']?.toString() ??
.toString(), 'Not set'.tr,
'gender': (controller.gender), onTap: () => controller.updatField(
}); 'employmentType', TextInputType.name),
Get.back(); ),
}, _buildProfileTile(
) icon: Icons.favorite_border,
], color: Colors.purple,
)); title: 'Marital Status'.tr,
// controller.updatField('gender'); subtitle:
}, controller.prfoileData['maritalStatus']?.toString() ??
), 'Not set'.tr,
ListTile( onTap: () => controller.updatField(
title: Text( 'maritalStatus', TextInputType.name),
'Education'.tr, ),
style: AppStyle.title, _buildProfileTile(
), icon: Icons.sos_outlined,
leading: Image.asset( color: Colors.red,
'assets/images/education.png', title: 'SOS Phone'.tr,
width: 35, subtitle: controller.prfoileData['sosPhone']?.toString() ??
), 'Not set'.tr,
trailing: const Icon(Icons.arrow_forward_ios), onTap: () async {
subtitle: Text(controller.prfoileData['education'] await controller.updatField(
.toString()), 'sosPhone', TextInputType.phone);
onTap: () { box.write(BoxName.sosPhonePassenger,
Get.defaultDialog( controller.prfoileData['sosPhone']);
barrierDismissible: true, },
title: 'Update Education'.tr, ),
content: SizedBox( ],
height: 200, ),
child: Column( const SizedBox(height: 32),
children: [ _buildAccountActions(context, logOutController),
EducationDegreePicker(), ],
], );
), },
), ),
confirm: MyElevatedButton( );
title: 'Update Education'.tr, }
onPressed: () {
controller.updateColumn({ Widget _buildProfileHeader(ProfileController controller) {
'id': controller.prfoileData['id'] String fullName =
.toString(), '${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}';
'education': String initials = (fullName.isNotEmpty && fullName.contains(" "))
controller.selectedDegree, ? fullName.split(" ").map((e) => e.isNotEmpty ? e[0] : "").join()
}); : (fullName.isNotEmpty ? fullName[0] : "");
Get.back();
}, // Logic to hide email if it contains 'intaleqapp.com'
)); String email = box.read(BoxName.email) ?? '';
}, if (email.contains('intaleqapp.com')) {
), email = ''; // Clear the email if it contains the domain
ListTile( }
title: Text(
'Employment Type'.tr, return Center(
style: AppStyle.title, child: Column(
), children: [
leading: Image.asset( CircleAvatar(
'assets/images/employmentType.png', radius: 50,
width: 35, backgroundColor: AppColor.primaryColor.withOpacity(0.2),
), child: Text(
trailing: const Icon(Icons.arrow_forward_ios), initials,
subtitle: Text(controller style:
.prfoileData['employmentType'] const TextStyle(fontSize: 40, color: AppColor.primaryColor),
.toString()), ),
onTap: () { ),
controller.updatField( const SizedBox(height: 12),
'employmentType', TextInputType.name); Text(
}, fullName,
), style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
ListTile( ),
title: Text( if (email
'Marital Status'.tr, .isNotEmpty) // Only show the Text widget if the email is not empty
style: AppStyle.title, Text(
), email,
leading: Image.asset( style: TextStyle(fontSize: 16, color: Colors.grey[600]),
'assets/images/maritalStatus.png', ),
width: 35, ],
), ),
trailing: const Icon(Icons.arrow_forward_ios), );
subtitle: Text(controller }
.prfoileData['maritalStatus']
.toString()), Widget _buildSectionCard(String title, List<Widget> children) {
onTap: () { return Column(
controller.updatField( crossAxisAlignment: CrossAxisAlignment.start,
'maritalStatus', TextInputType.name); children: [
}, Padding(
), padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
ListTile( child: Text(
title: Text( title,
'SOS Phone'.tr, style: TextStyle(
style: AppStyle.title, fontSize: 16,
), fontWeight: FontWeight.bold,
leading: const Icon( color: Colors.grey[700]),
Icons.sos, ),
color: AppColor.redColor, ),
size: 35, Container(
), decoration: BoxDecoration(
trailing: const Icon(Icons.arrow_forward_ios), color: Colors.white,
subtitle: Text( borderRadius: BorderRadius.circular(12),
(controller.prfoileData['sosPhone']) ),
.toString()), child: Column(
onTap: () async { children: children,
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();
}));
});
}),
],
),
],
),
),
),
)),
], ],
); );
} }
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();
},
),
);
}
} }
class GenderPicker extends StatelessWidget { // --- Helper Widgets for Pickers ---
final ProfileController controller = Get.put(ProfileController());
class GenderPicker extends StatelessWidget {
final ProfileController controller = Get.find<ProfileController>();
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr]; final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
GenderPicker({super.key}); GenderPicker({super.key});
@@ -258,14 +336,14 @@ class GenderPicker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 100, height: 150,
child: CupertinoPicker( child: CupertinoPicker(
itemExtent: 32.0, itemExtent: 40.0,
onSelectedItemChanged: (int index) { onSelectedItemChanged: (int index) {
controller.setGender(genderOptions[index]); controller.setGender(genderOptions[index]);
}, },
children: genderOptions.map((String value) { children: genderOptions.map((String value) {
return Text(value); return Center(child: Text(value));
}).toList(), }).toList(),
), ),
); );
@@ -273,216 +351,34 @@ class GenderPicker extends StatelessWidget {
} }
class EducationDegreePicker extends StatelessWidget { class EducationDegreePicker extends StatelessWidget {
final ProfileController controller = Get.put(ProfileController()); final ProfileController controller = Get.find<ProfileController>();
final List<String> degreeOptions = [ final List<String> degreeOptions = [
'High School Diploma'.tr, 'High School Diploma'.tr,
'Associate Degree'.tr, 'Associate Degree'.tr,
'Bachelor\'s Degree'.tr, "Bachelor's Degree".tr,
'Master\'s Degree'.tr, "Master's Degree".tr,
'Doctoral Degree'.tr, 'Doctoral Degree'.tr,
]; ];
EducationDegreePicker({Key? key}) : super(key: key); EducationDegreePicker({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 200, height: 180,
child: CupertinoPicker( child: CupertinoPicker(
// backgroundColor: AppColor.accentColor, itemExtent: 40.0,
// looping: true,
squeeze: 2,
// diameterRatio: 5,
itemExtent: 32,
onSelectedItemChanged: (int index) { onSelectedItemChanged: (int index) {
controller.setDegree(degreeOptions[index]); controller.setDegree(degreeOptions[index]);
}, },
children: degreeOptions.map((String value) { children: degreeOptions.map((String value) {
return Text(value); return Center(child: Text(value));
}).toList(), }).toList(),
), ),
); );
} }
} }
class CountryPicker extends StatelessWidget { // NOTE: The CountryPicker and CountryPickerFromSetting widgets were not part of the main
final ProfileController controller = Get.put(ProfileController()); // 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.
final List<String> countryOptions = [
'Jordan',
'Syria',
'Egypt',
'Turkey',
'Saudi Arabia',
'Qatar',
'Bahrain',
'Kuwait',
'USA'
];
CountryPicker({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<ProfileController>(builder: (controller) {
return Padding(
padding: const EdgeInsets.all(20),
child: ListView(
children: [
const SizedBox(
height: 20,
),
Text(
"Select Your Country".tr,
style: AppStyle.headTitle2,
textAlign: TextAlign.center,
),
// const SizedBox(
// height: 20,
// ),
Padding(
padding: const EdgeInsets.all(10),
child: Text(
"To ensure you receive the most accurate information for your location, please select your country below. This will help tailor the app experience and content to your country."
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
SizedBox(
height: 200,
child: CupertinoPicker(
itemExtent: 32,
onSelectedItemChanged: (int index) {
controller.setCountry(countryOptions[index]);
box.write(BoxName.countryCode,
countryOptions[index]); // Save in English
},
children: List.generate(
countryOptions.length,
(index) => Center(
child: Text(
countryOptions[index]
.tr, // Display translated if not English
style: AppStyle.title,
),
),
),
),
),
MyElevatedButton(
title: 'Select Country'.tr, // Use translated text for button
onPressed: () {
Get.find<LoginController>().saveCountryCode(controller
.selectedCountry
.toString()); // No conversion needed
box.write(
BoxName.countryCode, //
controller.selectedCountry); // Already saved in English
if (controller.selectedCountry == null) {
Get.snackbar("You should select your country".tr, '');
} else {
Get.snackbar(controller.selectedCountry.toString().tr, '');
Get.off(LoginPage());
}
},
)
],
),
);
});
}
}
class CountryPickerFromSetting extends StatelessWidget {
final ProfileController controller = Get.put(ProfileController());
final LoginController loginController = Get.put(LoginController());
final List<String> countryOptions = [
'Jordan',
'USA',
'Egypt',
'Turkey',
'Saudi Arabia',
'Qatar',
'Bahrain',
'Kuwait',
];
CountryPickerFromSetting({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<ProfileController>(builder: (controller) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Select Your Country'.tr),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: ListView(
children: [
const SizedBox(
height: 20,
),
// Text(
// "Select Your Country".tr,
// style: AppStyle.headTitle2,
// textAlign: TextAlign.center,
// ),
// const SizedBox(
// height: 20,
// ),
Padding(
padding: const EdgeInsets.all(10),
child: Text(
"To ensure you receive the most accurate information for your location, please select your country below. This will help tailor the app experience and content to your country."
.tr,
style: AppStyle.headTitle2,
textAlign: TextAlign.center,
),
),
SizedBox(
height: 200,
child: CupertinoPicker(
itemExtent: 32,
onSelectedItemChanged: (int index) {
controller.setCountry(countryOptions[index]);
box.write(BoxName.countryCode,
countryOptions[index]); // Save in English
},
children: List.generate(
countryOptions.length,
(index) => Center(
child: Text(
countryOptions[index]
.tr, // Display translated if not English
style: AppStyle.title,
),
),
),
),
),
MyElevatedButton(
title: 'Select Country'.tr, // Use translated text for button
onPressed: () async {
loginController.saveCountryCode(controller.selectedCountry
.toString()); // No conversion needed
box.write(
BoxName.countryCode, //
controller.selectedCountry); // Already saved in English
Get.snackbar(controller.selectedCountry.toString().tr, '',
backgroundColor: AppColor.greenColor);
// Get.back();//
// Get.back();
},
)
],
)),
);
});
}
}

View File

@@ -1,5 +1,4 @@
import 'package:Intaleq/controller/home/home_page_controller.dart'; import 'package:Intaleq/controller/home/home_page_controller.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/views/lang/languages.dart'; import 'package:Intaleq/views/lang/languages.dart';
@@ -8,109 +7,207 @@ import 'HomePage/about_page.dart';
import 'HomePage/frequentlyQuestionsPage.dart'; import 'HomePage/frequentlyQuestionsPage.dart';
import 'HomePage/share_app_page.dart'; import 'HomePage/share_app_page.dart';
import 'HomePage/trip_record_page.dart'; import 'HomePage/trip_record_page.dart';
import 'profile/passenger_profile_page.dart';
// NOTE: This is a placeholder for your actual CountryPickerFromSetting widget.
// You should remove this and import your own widget.
class CountryPickerFromSetting extends StatelessWidget {
const CountryPickerFromSetting({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Change Country'.tr)),
body: Center(
child: Text('Country Picker Page Placeholder'.tr),
),
);
}
}
class SettingPage extends StatelessWidget { class SettingPage extends StatelessWidget {
const SettingPage({super.key}); const SettingPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(HomePageController()); // Using lazyPut to ensure the controller is available when needed.
return CupertinoPageScaffold( Get.lazyPut(() => HomePageController());
navigationBar: CupertinoNavigationBar(
middle: Text('Setting'.tr), return Scaffold(
leading: CupertinoButton( backgroundColor:
padding: EdgeInsets.zero, const Color(0xFFF5F5F7), // A slightly off-white background
child: const Icon(CupertinoIcons.back), appBar: AppBar(
onPressed: () { title: Text('Setting'.tr,
Navigator.pop(context); style: const TextStyle(
}, color: Colors.black87, fontWeight: FontWeight.bold)),
backgroundColor: Colors.white,
elevation: 0.5,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black87),
onPressed: () => Get.back(),
), ),
), ),
child: SafeArea( body: SafeArea(
child: ListView( child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
children: [ children: [
CupertinoListTile( _buildSectionHeader('General'.tr),
onTap: () { _buildSettingsCard(
Get.to(() => const Language()); children: [
}, _buildSettingsTile(
leading: const Icon(CupertinoIcons.globe, icon: Icons.language,
color: CupertinoColors.activeBlue), color: Colors.blue,
title: Text('Language'.tr), title: 'Language'.tr,
subtitle: Text('To change Language the App'.tr), subtitle: 'To change Language the App'.tr,
trailing: const CupertinoListTileChevron(), onTap: () => Get.to(() => const Language()),
),
// const Divider(height: 1, indent: 68, endIndent: 16),
// _buildSettingsTile(
// icon: Icons.map_outlined,
// color: Colors.green,
// title: 'Change Country'.tr,
// subtitle: 'You can change the Country to get all features'.tr,
// onTap: () => Get.to(() => const CountryPickerFromSetting()),
// ),
],
), ),
CupertinoListTile( const SizedBox(height: 24),
onTap: () { _buildSectionHeader('Preferences'.tr),
Get.to(() => CountryPickerFromSetting()); _buildSettingsCard(
}, children: [
leading: const Icon(CupertinoIcons.location, GetBuilder<HomePageController>(
color: CupertinoColors.activeBlue), builder: (controller) {
title: Text('Change Country'.tr), return _buildSettingsSwitchTile(
subtitle: icon: Icons.vibration,
Text('You can change the Country to get all features'.tr), color: Colors.purple,
trailing: const CupertinoListTileChevron(), title: 'Vibration'.tr,
subtitle: 'Vibration feedback for all buttons'.tr,
value: controller.isVibrate,
onChanged: controller.changeVibrateOption,
);
},
),
const Divider(height: 1, indent: 68, endIndent: 16),
_buildSettingsTile(
icon: Icons.mic_none,
color: Colors.orange,
title: 'Trips recorded'.tr,
subtitle: 'Here recorded trips audio'.tr,
onTap: () => Get.to(() => const TripsRecordedPage()),
),
],
), ),
CupertinoListTile( const SizedBox(height: 24),
onTap: () { _buildSectionHeader('Support & Info'.tr),
Get.to(() => const FrequentlyQuestionsPage()); _buildSettingsCard(
}, children: [
leading: const Icon(CupertinoIcons.question, _buildSettingsTile(
color: CupertinoColors.activeBlue), icon: Icons.help_outline,
title: Text('Frequently Questions'.tr), color: Colors.cyan,
subtitle: Text('Find answers to common questions'.tr), title: 'Frequently Questions'.tr,
trailing: const CupertinoListTileChevron(), subtitle: 'Find answers to common questions'.tr,
), onTap: () => Get.to(() => const FrequentlyQuestionsPage()),
CupertinoListTile( ),
leading: const Icon(Icons.vibration, const Divider(height: 1, indent: 68, endIndent: 16),
color: CupertinoColors.activeBlue), _buildSettingsTile(
title: Text('Vibration'.tr), icon: Icons.info_outline,
trailing: GetBuilder<HomePageController>( color: Colors.indigo,
builder: (controller) { title: 'About Us'.tr,
return CupertinoSwitch( subtitle: 'Learn more about our app and mission'.tr,
value: controller.isVibrate, onTap: () => Get.to(() => const AboutPage()),
onChanged: controller.changeVibrateOption, ),
); const Divider(height: 1, indent: 68, endIndent: 16),
}, _buildSettingsTile(
), icon: Icons.share_outlined,
subtitle: Text( color: Colors.redAccent,
'You can change the vibration feedback for all buttons'.tr), title: 'Share App'.tr,
), subtitle: 'Share with friends and earn rewards'.tr,
CupertinoListTile( onTap: () => Get.to(() => ShareAppPage()),
onTap: () { ),
Get.to(() => const TripsRecordedPage()); ],
},
leading: const Icon(CupertinoIcons.mic_circle,
color: CupertinoColors.activeBlue),
title: Text('Trips recorded'.tr),
subtitle: Text('Here recorded trips audio'.tr),
trailing: const CupertinoListTileChevron(),
),
CupertinoListTile(
onTap: () {
Get.to(() => const AboutPage());
},
leading: const Icon(CupertinoIcons.info_circle,
color: CupertinoColors.activeBlue),
title: Text('About Us'.tr),
subtitle: Text('Learn more about our app and mission'.tr),
trailing: const CupertinoListTileChevron(),
),
CupertinoListTile(
onTap: () {
Get.to(() => ShareAppPage());
},
leading: const Icon(CupertinoIcons.share,
color: CupertinoColors.activeBlue),
title: Text('Share App'.tr),
subtitle: Text(
'You can share the Intaleq App with your friends and earn rewards for rides they take using your code'
.tr),
trailing: const CupertinoListTileChevron(),
), ),
], ],
), ),
), ),
); );
} }
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0, left: 8.0),
child: Text(
title,
style: TextStyle(
color: Colors.grey[700],
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
);
}
Widget _buildSettingsCard({required List<Widget> children}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
clipBehavior: Clip.antiAlias,
child: Column(
children: children,
),
);
}
Widget _buildSettingsTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return ListTile(
onTap: onTap,
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 22),
),
title: Text(title,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
subtitle: Text(subtitle,
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
trailing: Icon(Icons.chevron_right, color: Colors.grey[400]),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
);
}
Widget _buildSettingsSwitchTile({
required IconData icon,
required Color color,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return SwitchListTile(
secondary: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 22),
),
title: Text(title,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
subtitle: Text(subtitle,
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
value: value,
onChanged: onChanged,
activeColor: const Color(0xFF007AFF), // iOS-like blue
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
);
}
} }

View File

@@ -89,7 +89,7 @@ class Language extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Select your preferred language for the app interface.', "Select your preferred language for the app interface.".tr,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: CupertinoColors.secondaryLabel, color: CupertinoColors.secondaryLabel,

View File

@@ -50,7 +50,7 @@ packages:
source: hosted source: hosted
version: "2.7.0" version: "2.7.0"
asn1lib: asn1lib:
dependency: transitive dependency: "direct main"
description: description:
name: asn1lib name: asn1lib
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"

View File

@@ -74,6 +74,7 @@ dependencies:
dotted_line: ^3.2.3 dotted_line: ^3.2.3
shimmer: ^3.0.0 shimmer: ^3.0.0
share_plus: ^11.0.0 share_plus: ^11.0.0
asn1lib: ^1.6.5
# home_widget: ^0.7.0+1 # home_widget: ^0.7.0+1
dev_dependencies: dev_dependencies: