25-9-1-1
This commit is contained in:
@@ -47,8 +47,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 7
|
||||
versionName = '1.0.7'
|
||||
versionCode = 11
|
||||
versionName = '1.0.11'
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- منع أي اتصال HTTP غير مشفّر -->
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<!-- الاعتماد على شهادات النظام فقط -->
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<!-- اسمح بالاتصال بدوميناتك فقط -->
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
|
||||
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||
<!-- أضف أي ساب دومين تحتاجه هنا -->
|
||||
<!-- <domain includeSubdomains="true">walletintaleq.intaleq.xyz</domain>
|
||||
<domain includeSubdomains="true">syria.intaleq.xyz</domain> -->
|
||||
|
||||
<pin-set expiration="2027-01-01">
|
||||
<pin digest="SHA-256">pXmP2hTQLxDEvlTVmP5N7xpiA32sycBsxB6hBFT2uL4=</pin>
|
||||
|
||||
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
|
||||
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
|
||||
<debug-overrides>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
|
||||
</network-security-config>
|
||||
BIN
assets/images/bus.png
Normal file
BIN
assets/images/bus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
BIN
assets/images/electric.png
Normal file
BIN
assets/images/electric.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
@@ -322,10 +322,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -339,10 +343,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5</string>
|
||||
<string>6</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dict/>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.5</string>
|
||||
<string>1.0.6</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<string>NO</string>
|
||||
<key>GMSApiKey</key>
|
||||
|
||||
@@ -8,6 +8,8 @@ class BoxName {
|
||||
static const String serverChosen = "serverChosen";
|
||||
static const String gender = "gender";
|
||||
static const String jwt = "jwt";
|
||||
static const String lowEndMode = "lowEndMode";
|
||||
static const String appVersionChecked = "appVersionChecked";
|
||||
static const String lastName = "lastName";
|
||||
static const String fingerPrint = "fingerPrint";
|
||||
static const String payMobApikey = "payMobApikey";
|
||||
|
||||
File diff suppressed because one or more lines are too long
40
lib/controller/auth/certificate.dart
Normal file
40
lib/controller/auth/certificate.dart
Normal 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;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class PhoneAuthHelper {
|
||||
link: _sendOtpUrl,
|
||||
payload: {'receiver': phoneNumber},
|
||||
);
|
||||
// Log.print('response: ${response}');
|
||||
Log.print('response: ${response}');
|
||||
if (response != 'failure') {
|
||||
final data = (response);
|
||||
// if (data['status'] == 'success') {
|
||||
|
||||
@@ -216,7 +216,7 @@ class FirebaseMessagesController extends GetxController {
|
||||
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
|
||||
}
|
||||
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) {
|
||||
notificationController.showNotification(
|
||||
'Hi ,I Arrive your site'.tr, ''.tr, 'ding');
|
||||
|
||||
86
lib/controller/home/device_tier.dart
Normal file
86
lib/controller/home/device_tier.dart
Normal 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
@@ -4,8 +4,6 @@ import 'package:Intaleq/constant/box_name.dart';
|
||||
import 'package:Intaleq/constant/colors.dart';
|
||||
import 'package:Intaleq/constant/links.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_contacts/flutter_contacts.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -17,6 +15,7 @@ import '../../../views/widgets/error_snakbar.dart';
|
||||
import '../../../views/widgets/mydialoug.dart';
|
||||
import '../../functions/launch.dart';
|
||||
import '../../notification/notification_captain_controller.dart';
|
||||
import '../../payment/payment_controller.dart';
|
||||
|
||||
class InviteController extends GetxController {
|
||||
final TextEditingController invitePhoneController = TextEditingController();
|
||||
@@ -27,21 +26,31 @@ class InviteController extends GetxController {
|
||||
|
||||
int selectedTab = 0;
|
||||
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) {
|
||||
selectedTab = index;
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> shareCouponCode() async {
|
||||
// TODO: Implement sharing functionality
|
||||
// You can use share_plus package to share the coupon code
|
||||
}
|
||||
// --- Sharing Methods ---
|
||||
|
||||
Future<void> shareDriverCode() async {
|
||||
if (driverCouponCode != null) {
|
||||
final String shareText = '''
|
||||
Join Intaleq as a driver using my referral code!
|
||||
Use code: $driverCouponCode
|
||||
Download the Intaleq Driver app now and earn rewards!
|
||||
${'Join Intaleq as a driver using my referral code!'.tr}
|
||||
${'Use code:'.tr} $driverCouponCode
|
||||
${'Download the Intaleq Driver app now and earn rewards!'.tr}
|
||||
''';
|
||||
await Share.share(shareText);
|
||||
}
|
||||
@@ -50,19 +59,15 @@ Download the Intaleq Driver app now and earn rewards!
|
||||
Future<void> sharePassengerCode() async {
|
||||
if (couponCode != null) {
|
||||
final String shareText = '''
|
||||
Get a discount on your first Intaleq ride!
|
||||
Use my referral code: $couponCode
|
||||
Download the Intaleq app now and enjoy your ride!
|
||||
${'Get a discount on your first Intaleq ride!'.tr}
|
||||
${'Use my referral code:'.tr} $couponCode
|
||||
${'Download the Intaleq app now and enjoy your ride!'.tr}
|
||||
''';
|
||||
await Share.share(shareText);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// fetchDriverStats();
|
||||
}
|
||||
// --- Data Fetching ---
|
||||
|
||||
void fetchDriverStats() async {
|
||||
try {
|
||||
@@ -74,7 +79,9 @@ Download the Intaleq app now and enjoy your ride!
|
||||
driverInvitationData = data['message'];
|
||||
update();
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
Log.print("Error fetching driver stats: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void fetchDriverStatsPassengers() async {
|
||||
@@ -88,216 +95,207 @@ Download the Intaleq app now and enjoy your ride!
|
||||
driverInvitationDataToPassengers = data['message'];
|
||||
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) {
|
||||
Get.snackbar('Error'.tr,
|
||||
'An error occurred while saving contacts to server: $e'.tr);
|
||||
Log.print("Error fetching passenger stats: $e");
|
||||
}
|
||||
}
|
||||
|
||||
List<Contact> contacts = <Contact>[];
|
||||
List<Contact> selectedContacts = <Contact>[];
|
||||
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
||||
// --- Contact Handling ---
|
||||
|
||||
/// **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 {
|
||||
try {
|
||||
// Request contacts permission
|
||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
||||
// Fetch all contacts with full properties
|
||||
final List<Contact> allContacts = await FlutterContacts.getContacts(
|
||||
withProperties: true,
|
||||
withThumbnail: false,
|
||||
withPhoto: true,
|
||||
);
|
||||
final List<Contact> allContacts =
|
||||
await FlutterContacts.getContacts(withProperties: true);
|
||||
final int totalContactsOnDevice = allContacts.length;
|
||||
|
||||
// Check if contacts are available
|
||||
if (allContacts.isNotEmpty) {
|
||||
// Store the contacts
|
||||
contacts = allContacts;
|
||||
Log.print('contacts: $contacts');
|
||||
// **FIX**: Filter contacts to only include those with at least one phone number.
|
||||
contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
|
||||
final int contactsWithPhones = contacts.length;
|
||||
|
||||
// Convert contacts to a list of maps
|
||||
contactMaps.value = await Future.wait(contacts.map((contact) async {
|
||||
Log.print('Contact name: ${contact.displayName}');
|
||||
|
||||
// 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
|
||||
if (contactsWithPhones > 0) {
|
||||
Log.print('Found $contactsWithPhones contacts with phone numbers.');
|
||||
contactMaps.value = contacts.map((contact) {
|
||||
return {
|
||||
'name': contact.displayName ?? '',
|
||||
'phones': phones
|
||||
.where((phone) => phone.normalizedNumber != null)
|
||||
.map((phone) => phone.normalizedNumber ?? 'No number')
|
||||
.toList(),
|
||||
'emails': emails
|
||||
.where((email) => email.address != null)
|
||||
.map((email) => email.address ?? 'No email')
|
||||
.toList(),
|
||||
'name': contact.displayName,
|
||||
'phones': contact.phones.map((p) => p.number).toList(),
|
||||
'emails': contact.emails.map((e) => e.address).toList(),
|
||||
};
|
||||
}).toList());
|
||||
|
||||
}).toList();
|
||||
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 {
|
||||
Get.snackbar('No contacts available'.tr,
|
||||
'Please add contacts to your phone.'.tr);
|
||||
Get.snackbar('No contacts found'.tr,
|
||||
'No contacts with phone numbers were found on your device.'.tr);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('Permission denied'.tr,
|
||||
'Contact permission is required to pick contacts'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error picking contacts: $e');
|
||||
Get.snackbar(
|
||||
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
void onSelectPassengerInvitation(int index) async {
|
||||
MyDialog().getDialog(
|
||||
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver']
|
||||
.toString()) <
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
void selectPhone(String phone) {
|
||||
invitePhoneController.text = phone;
|
||||
update();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
savePhoneToServer() async {
|
||||
for (var i = 0; i < contactMaps.length; i++) {
|
||||
var phones = contactMaps[i]['phones'];
|
||||
if (phones != null && phones.isNotEmpty && phones[0].isNotEmpty) {
|
||||
var res = await CRUD().post(link: AppLink.savePhones, payload: {
|
||||
"name": contactMaps[i]['name'] ?? 'none',
|
||||
"phones": phones[0] ?? 'none',
|
||||
"phones2": phones.join(', ') ??
|
||||
'none', // Convert List<String> to a comma-separated string
|
||||
});
|
||||
if (res != 'failure') {}
|
||||
} else {}
|
||||
/// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
|
||||
/// It handles various user inputs gracefully to produce a standardized international format.
|
||||
String _formatSyrianPhoneNumber(String phone) {
|
||||
// 1. Remove all non-digit characters to clean the input.
|
||||
String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
|
||||
|
||||
// 2. If it already starts with the country code, we assume it's correct.
|
||||
if (digitsOnly.startsWith('963')) {
|
||||
return '+$digitsOnly';
|
||||
}
|
||||
}
|
||||
|
||||
String formatPhoneNumber(String input) {
|
||||
// Remove any non-digit characters
|
||||
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
|
||||
|
||||
// Ensure the number starts with the country code
|
||||
if (digitsOnly.startsWith('20')) {
|
||||
// 3. If it starts with '09' (common local format), remove the leading '0'.
|
||||
if (digitsOnly.startsWith('09')) {
|
||||
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 {
|
||||
if (invitePhoneController.text.isEmpty ||
|
||||
invitePhoneController.text.length < 11) {
|
||||
invitePhoneController.text.length < 9) {
|
||||
mySnackeBarError('Please enter a correct phone'.tr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// try {
|
||||
String phoneNumber = formatPhoneNumber(invitePhoneController.text);
|
||||
try {
|
||||
// Use the new formatting function to ensure the number is correct.
|
||||
String formattedPhoneNumber =
|
||||
_formatSyrianPhoneNumber(invitePhoneController.text);
|
||||
|
||||
var response =
|
||||
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
||||
"driverId": box.read(BoxName.passengerID),
|
||||
"inviterPassengerPhone": ('+2$phoneNumber')
|
||||
});
|
||||
var response =
|
||||
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
||||
"driverId": box.read(BoxName.passengerID),
|
||||
"inviterPassengerPhone": formattedPhoneNumber,
|
||||
});
|
||||
|
||||
if (response != 'failure') {
|
||||
var d = response;
|
||||
Get.snackbar('Success', 'Invite sent successfully'.tr);
|
||||
if (response != 'failure') {
|
||||
var d = response;
|
||||
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
|
||||
backgroundColor: Colors.green, colorText: Colors.white);
|
||||
|
||||
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
|
||||
'${"Use this code in registration".tr}\n'
|
||||
'${"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';
|
||||
String expirationTime = d['message']['expirationTime'].toString();
|
||||
String inviteCode = d['message']['inviteCode'].toString();
|
||||
|
||||
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();
|
||||
} else {
|
||||
Get.snackbar('Error'.tr, "Invite code already used".tr,
|
||||
backgroundColor: AppColor.redColor,
|
||||
duration: const Duration(seconds: 4));
|
||||
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
||||
invitePhoneController.clear();
|
||||
update();
|
||||
} else {
|
||||
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
@@ -8,6 +8,8 @@ import 'package:Intaleq/constant/colors.dart';
|
||||
import 'package:Intaleq/constant/style.dart';
|
||||
import 'package:Intaleq/main.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 '../../controller/auth/apple_signin_controller.dart';
|
||||
@@ -73,7 +75,11 @@ class LoginPage extends StatelessWidget {
|
||||
decoration: TextDecoration.underline,
|
||||
color: AppColor.blueColor,
|
||||
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),
|
||||
],
|
||||
),
|
||||
@@ -88,7 +94,7 @@ class LoginPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar'
|
||||
? AppInformation.privacyPolicyArabic
|
||||
: AppInformation.privacyPolicyEnglish),
|
||||
: AppInformation.privacyPolicy),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -43,6 +43,12 @@ class AuthScreen extends StatelessWidget {
|
||||
children: [
|
||||
Image.asset('assets/images/logo.gif', height: 120),
|
||||
const SizedBox(height: 20),
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// Get.find<LoginController>().getJWT();
|
||||
// },
|
||||
// icon: const Icon(Icons.add),
|
||||
// ),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -28,13 +28,13 @@ class AboutPage extends StatelessWidget {
|
||||
|
||||
// Company Name and Introduction
|
||||
Text(
|
||||
'Tripz LLC',
|
||||
'Intaleq LLC',
|
||||
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
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,
|
||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
||||
textAlign: TextAlign.center,
|
||||
@@ -43,7 +43,7 @@ class AboutPage extends StatelessWidget {
|
||||
|
||||
// Key Features Section
|
||||
Text(
|
||||
'Why Choose Tripz?'.tr,
|
||||
'Why Choose Intaleq?'.tr,
|
||||
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -432,8 +432,7 @@ class ShareAppPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
controller.formatPhoneNumber(
|
||||
contact['phones'][0].toString()),
|
||||
(contact['phones'][0].toString()),
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.secondaryLabel,
|
||||
fontSize: 15,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:Intaleq/views/widgets/my_scafold.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@@ -8,205 +6,258 @@ import 'package:share_plus/share_plus.dart';
|
||||
import '../../../controller/functions/audio_record1.dart';
|
||||
|
||||
class TripsRecordedPage extends StatelessWidget {
|
||||
const TripsRecordedPage({
|
||||
super.key,
|
||||
});
|
||||
const TripsRecordedPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScafolld(
|
||||
title: 'Trips recorded'.tr,
|
||||
body: [
|
||||
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}'),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// Ensure the controller is available.
|
||||
// If it's not initialized elsewhere, you can use Get.put() or Get.lazyPut() here.
|
||||
// Get.lazyPut(() => AudioRecorderController());
|
||||
|
||||
// Cupertino-style slider for seeking audio
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: CupertinoSlider(
|
||||
value: audio.totalDuration > 0
|
||||
? audio.currentPosition / audio.totalDuration
|
||||
: 0.0, // Normalize to a value between 0.0 and 1.0
|
||||
min: 0.0,
|
||||
max: 1.0, // Maximum value is now 1.0
|
||||
activeColor: CupertinoColors.activeBlue,
|
||||
onChanged: (value) {
|
||||
final newPosition = value * audio.totalDuration;
|
||||
audio.currentPosition = newPosition;
|
||||
audio.audioPlayer
|
||||
.seek(Duration(seconds: newPosition.toInt()));
|
||||
audio.update();
|
||||
},
|
||||
),
|
||||
),
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Trips recorded'.tr),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
actions: [
|
||||
GetBuilder<AudioRecorderController>(
|
||||
builder: (controller) => IconButton(
|
||||
tooltip: 'Delete All'.tr,
|
||||
icon: const Icon(Icons.delete_sweep_outlined),
|
||||
onPressed: () {
|
||||
_showDeleteConfirmation(context, controller, isDeleteAll: true);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: GetBuilder<AudioRecorderController>(
|
||||
builder: (controller) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildRecordingsList(controller),
|
||||
),
|
||||
// Show player controls only when a file is selected
|
||||
if (controller.selectedFilePath != null)
|
||||
_buildPlayerControls(context, controller),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// iOS-style playback controls
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Icon(
|
||||
audio.isPlaying
|
||||
? CupertinoIcons.pause
|
||||
: CupertinoIcons.play_arrow,
|
||||
color: CupertinoColors.activeBlue,
|
||||
),
|
||||
onPressed: () {
|
||||
if (audio.isPlaying) {
|
||||
audio.pausePlayback();
|
||||
} else {
|
||||
audio.resumePlayback();
|
||||
}
|
||||
audio.update();
|
||||
},
|
||||
),
|
||||
CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
child: const Icon(CupertinoIcons.stop,
|
||||
color: CupertinoColors.destructiveRed),
|
||||
onPressed: () {
|
||||
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),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
/// Builds the list of recorded audio files.
|
||||
Widget _buildRecordingsList(AudioRecorderController controller) {
|
||||
return FutureBuilder<List<String>>(
|
||||
future: controller.getRecordedFiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'.tr));
|
||||
}
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.mic_off_outlined, size: 80, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No Recordings Found'.tr,
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: Text(
|
||||
'Record your trips to see them here.'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// File selection and sharing
|
||||
if (audio.selectedFilePath != null)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
color: CupertinoColors.systemGrey6,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Selected file: ${path.basename(audio.selectedFilePath!)}',
|
||||
style: CupertinoTheme.of(context)
|
||||
.textTheme
|
||||
.textStyle,
|
||||
),
|
||||
CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
child: const Icon(CupertinoIcons.share),
|
||||
onPressed: () {
|
||||
Share.shareXFiles(
|
||||
[XFile(audio.selectedFilePath!)]);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
final recordedFiles = snapshot.data!;
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
||||
itemCount: recordedFiles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final file = recordedFiles[index];
|
||||
final fileName = path.basename(file);
|
||||
final isSelected = controller.selectedFilePath == file;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
elevation: isSelected ? 4 : 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
isSelected && controller.isPlaying
|
||||
? Icons.pause_circle_filled
|
||||
: Icons.play_circle_fill,
|
||||
color:
|
||||
isSelected ? Theme.of(context).primaryColor : Colors.grey,
|
||||
size: 40,
|
||||
),
|
||||
title: Text(fileName,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: Text("Audio Recording".tr),
|
||||
onTap: () {
|
||||
if (isSelected) {
|
||||
controller.isPlaying
|
||||
? controller.pausePlayback()
|
||||
: controller.resumePlayback();
|
||||
} else {
|
||||
controller.playRecordedFile(file);
|
||||
}
|
||||
},
|
||||
selected: isSelected,
|
||||
selectedTileColor:
|
||||
Theme.of(context).primaryColor.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class MapPagePassenger extends StatelessWidget {
|
||||
const RideFromStartApp(),
|
||||
|
||||
// cancelRidePage(),
|
||||
const MenuIconMapPageWidget(),
|
||||
// const MenuIconMapPageWidget(),
|
||||
PointsPageForRider()
|
||||
],
|
||||
),
|
||||
|
||||
@@ -40,15 +40,19 @@ List<CarType> carTypes = [
|
||||
carType: 'Electric',
|
||||
carDetail: 'Quiet & Eco-Friendly'.tr,
|
||||
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: 'Lady',
|
||||
carDetail: 'Lady Captain for girls'.tr,
|
||||
image: 'assets/images/lady.png'),
|
||||
CarType(
|
||||
carType: 'Scooter',
|
||||
carDetail: 'Delivery service'.tr,
|
||||
image: 'assets/images/moto.png'),
|
||||
carType: 'Van',
|
||||
carDetail: 'Van for familly'.tr,
|
||||
image: 'assets/images/bus.png'),
|
||||
// CarType(
|
||||
// carType: 'Scooter',
|
||||
// carDetail: 'Delivery service'.tr,
|
||||
// image: 'assets/images/moto.png'),
|
||||
CarType(
|
||||
carType: 'Rayeh Gai',
|
||||
carDetail: "Best choice for cities".tr,
|
||||
@@ -365,6 +369,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
||||
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1);
|
||||
case 'Scooter':
|
||||
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
|
||||
case 'Van':
|
||||
return mapPassengerController.totalPassengerVan.toStringAsFixed(1);
|
||||
case 'Lady':
|
||||
return mapPassengerController.totalPassengerLady.toStringAsFixed(1);
|
||||
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.'
|
||||
.tr;
|
||||
case 'Scooter':
|
||||
case 'Van':
|
||||
return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together."
|
||||
.tr;
|
||||
case 'Pink Bike':
|
||||
return 'This is for delivery or a motorcycle.'.tr;
|
||||
case 'Mishwar Vip':
|
||||
@@ -556,6 +565,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
||||
return mapPassengerController.totalPassengerElectric;
|
||||
case 'Awfar Car':
|
||||
return mapPassengerController.totalPassengerBalash;
|
||||
case 'Van':
|
||||
return mapPassengerController.totalPassengerVan;
|
||||
case 'Lady':
|
||||
return mapPassengerController.totalPassengerLady;
|
||||
default:
|
||||
|
||||
@@ -78,7 +78,7 @@ class CashConfirmPageShown extends StatelessWidget {
|
||||
// بطاقة المحفظة
|
||||
_buildPaymentOptionCard(
|
||||
icon: Icons.account_balance_wallet_outlined,
|
||||
title: '${AppInformation.appName} Wallet'.tr,
|
||||
title: '${AppInformation.appName} Balance'.tr,
|
||||
subtitle:
|
||||
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
|
||||
isSelected: paymentCtrl.isWalletChecked,
|
||||
@@ -115,7 +115,7 @@ class CashConfirmPageShown extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
MyElevatedButton(
|
||||
title: 'Top up Wallet to continue'.tr,
|
||||
title: 'Top up Balance to continue'.tr,
|
||||
onPressed: () =>
|
||||
Get.to(() => const PassengerWallet()),
|
||||
kolor: AppColor.redColor,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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/style.dart';
|
||||
import '../../../controller/functions/location_controller.dart';
|
||||
import '../../../controller/home/device_tier.dart';
|
||||
import '../../../controller/home/map_passenger_controller.dart';
|
||||
import '../../widgets/mycircular.dart';
|
||||
import '../../widgets/mydialoug.dart';
|
||||
|
||||
class GoogleMapPassengerWidget extends StatelessWidget {
|
||||
GoogleMapPassengerWidget({
|
||||
super.key,
|
||||
});
|
||||
WayPointController wayPointController = Get.put(WayPointController());
|
||||
GoogleMapPassengerWidget({super.key});
|
||||
|
||||
final WayPointController wayPointController = Get.put(WayPointController());
|
||||
final LocationController locationController = Get.find<LocationController>();
|
||||
|
||||
@override
|
||||
@@ -30,19 +32,25 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
||||
child: GoogleMap(
|
||||
onMapCreated: controller.onMapCreated,
|
||||
|
||||
// ✅ حدود الكاميرا كما هي
|
||||
cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
|
||||
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
|
||||
|
||||
// ✅ Zoom أهدأ للأجهزة الضعيفة
|
||||
minMaxZoomPreference: controller.lowPerf
|
||||
? const MinMaxZoomPreference(6, 17)
|
||||
: const MinMaxZoomPreference(6, 18),
|
||||
|
||||
onLongPress: (argument) {
|
||||
MyDialog().getDialog('Are you want to go to this site'.tr, '',
|
||||
() async {
|
||||
controller.clearPolyline();
|
||||
if (controller.dataCarsLocationByPassenger != null) {
|
||||
await controller.getDirectionMap(
|
||||
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
||||
'${argument.latitude.toString()},${argument.longitude.toString()}');
|
||||
|
||||
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
||||
'${argument.latitude},${argument.longitude}',
|
||||
);
|
||||
Get.back();
|
||||
controller.bottomSheet();
|
||||
await controller.bottomSheet();
|
||||
controller.showBottomSheet1();
|
||||
} else {
|
||||
Get.back();
|
||||
@@ -54,15 +62,12 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
||||
duration: const Duration(seconds: 11),
|
||||
instantInit: true,
|
||||
snackPosition: SnackPosition.TOP,
|
||||
titleText: Text(
|
||||
'Error'.tr,
|
||||
style: const TextStyle(color: AppColor.redColor),
|
||||
),
|
||||
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,
|
||||
),
|
||||
'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,
|
||||
@@ -86,370 +91,98 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
});
|
||||
// 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) {
|
||||
controller.hidePlaces();
|
||||
|
||||
// controller.changeBottomSheetShown();
|
||||
// controller.bottomSheet();
|
||||
},
|
||||
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: controller.passengerLocation,
|
||||
zoom: 15,
|
||||
zoom: controller.lowPerf ? 14.5 : 15,
|
||||
),
|
||||
|
||||
// ✅ ماركرز (احرص أن الأيقونات محجّمة ومخزّنة Cache في الكنترولر)
|
||||
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,
|
||||
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(
|
||||
// zIndex: 1,
|
||||
// consumeTapEvents: true,
|
||||
// geodesic: true,
|
||||
// endCap: Cap.buttCap,
|
||||
// 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,
|
||||
// // ),
|
||||
// },
|
||||
// ✅ Polyline مُبسّطة للأجهزة الضعيفة (الكنترولر يجهّز مجموعة مبسطة عند lowPerf)
|
||||
polylines: controller.lowPerf
|
||||
? controller.polyLinesLight
|
||||
.toSet() // <- استخدم مجموعة خفيفة
|
||||
: controller.polyLines.toSet(),
|
||||
|
||||
// ✅ دوائر خفيفة على الأجهزة الضعيفة
|
||||
// circles: {
|
||||
// Circle(
|
||||
// circleId: const CircleId('kk'),
|
||||
// center: controller.mylocation,
|
||||
// radius: 60,
|
||||
// fillColor: AppColor.primaryColor,)
|
||||
// circleId: const CircleId('circle_id'),
|
||||
// center: controller.passengerLocation,
|
||||
// radius: controller.lowPerf ? 80 : 100,
|
||||
// fillColor:
|
||||
// Colors.blue.withOpacity(controller.lowPerf ? 0.2 : 0.3),
|
||||
// strokeColor: Colors.blue,
|
||||
// strokeWidth: controller.lowPerf ? 1 : 2,
|
||||
// ),
|
||||
// },
|
||||
|
||||
circles: <Circle>{
|
||||
Circle(
|
||||
circleId: const CircleId('circle_id'),
|
||||
center: controller.passengerLocation,
|
||||
radius: 100,
|
||||
fillColor: Colors.blue.withOpacity(0.3),
|
||||
strokeColor: Colors.blue,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
},
|
||||
// ✅ الوضع الخفيف: liteMode + تعطيل الطبقات المكلفة + خريطة Normal
|
||||
mapType: controller.lowPerf
|
||||
? MapType.normal
|
||||
: (controller.mapType
|
||||
? MapType.satellite
|
||||
: MapType.terrain),
|
||||
|
||||
mapType:
|
||||
controller.mapType ? MapType.satellite : MapType.terrain,
|
||||
myLocationButtonEnabled: true,
|
||||
// liteModeEnabled: true, tiltGesturesEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
|
||||
// indoorViewEnabled: true,
|
||||
trafficEnabled: controller.mapTrafficON,
|
||||
buildingsEnabled: true,
|
||||
mapToolbarEnabled: true,
|
||||
// ⚠️ liteMode (Android فقط): فعّله على الأجهزة الضعيفة
|
||||
// liteModeEnabled: controller.lowPerf,
|
||||
liteModeEnabled: Platform.isAndroid ? isLowEnd() : false,
|
||||
trafficEnabled: controller.mapTrafficON && !isLowEnd(),
|
||||
buildingsEnabled: !isLowEnd(),
|
||||
// ✅ تقليل الكلفة الرسومية
|
||||
|
||||
mapToolbarEnabled: false,
|
||||
rotateGesturesEnabled: isLowEnd() ? false : true,
|
||||
tiltGesturesEnabled: false, // تعطيل الميلان لتقليل الحمل
|
||||
|
||||
// ✅ Throttle لحركة الكاميرا على الأجهزة الضعيفة
|
||||
onCameraMove: (position) {
|
||||
int waypointsLength =
|
||||
Get.find<WayPointController>().wayPoints.length;
|
||||
int index = controller.wayPointIndex;
|
||||
if (waypointsLength > 0) {
|
||||
controller.placesCoordinate[index] =
|
||||
'${position.target.latitude.toString()},${position.target.longitude}';
|
||||
if (controller.lowPerf) {
|
||||
controller.onCameraMoveThrottled(position);
|
||||
} else {
|
||||
// منطقك الحالي
|
||||
int waypointsLength =
|
||||
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,
|
||||
// liteModeEnabled: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import 'package:Intaleq/constant/box_name.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_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||
|
||||
import '../../../constant/char_map.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../controller/auth/login_controller.dart';
|
||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../controller/functions/tts.dart';
|
||||
import '../../../controller/home/map_passenger_controller.dart';
|
||||
import '../../../controller/home/vip_waitting_page.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../auth/otp_page.dart';
|
||||
import '../../auth/otp_token_page.dart';
|
||||
|
||||
// --- الدالة الرئيسية بالتصميم الجديد ---
|
||||
GetBuilder<MapPassengerController> leftMainMenuIcons() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||
|
||||
import 'package:Intaleq/constant/box_name.dart';
|
||||
import 'package:Intaleq/main.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/promos_passenger_page.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/home/map_passenger_controller.dart';
|
||||
import '../../notification/notification_page.dart';
|
||||
import '../HomePage/contact_us.dart';
|
||||
import '../HomePage/share_app_page.dart';
|
||||
import '../setting_page.dart';
|
||||
import '../profile/passenger_profile_page.dart';
|
||||
|
||||
@@ -25,17 +28,25 @@ class MapMenuWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// استخدام Get.lazyPut لضمان وجود الكنترولر
|
||||
Get.lazyPut(() => MapPassengerController());
|
||||
|
||||
return GetBuilder<MapPassengerController>(
|
||||
builder: (controller) => Stack(
|
||||
children: [
|
||||
// --- خلفية معتمة عند فتح القائمة ---
|
||||
if (controller.widthMenu > 0)
|
||||
GestureDetector(
|
||||
onTap: controller.getDrawerMenu,
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
),
|
||||
),
|
||||
|
||||
// --- القائمة الجانبية المنزلقة ---
|
||||
_buildSideMenu(controller),
|
||||
|
||||
// --- زر القائمة العائم ---
|
||||
// _buildMenuButton(controller),
|
||||
_buildMenuButton(controller),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -48,7 +59,7 @@ class MapMenuWidget extends StatelessWidget {
|
||||
left: 16,
|
||||
child: SafeArea(
|
||||
child: InkWell(
|
||||
onTap: controller.getDrawerMenu, // نفس دالتك القديمة
|
||||
onTap: controller.getDrawerMenu,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
@@ -64,9 +75,7 @@ class MapMenuWidget extends StatelessWidget {
|
||||
Border.all(color: AppColor.writeColor.withOpacity(0.2)),
|
||||
),
|
||||
child: Icon(
|
||||
controller.widthMenu > 0
|
||||
? Icons.close_rounded
|
||||
: Icons.menu_rounded,
|
||||
controller.widthMenu > 0 ? Icons.close : Icons.menu,
|
||||
color: AppColor.writeColor,
|
||||
size: 26,
|
||||
),
|
||||
@@ -85,63 +94,100 @@ class MapMenuWidget extends StatelessWidget {
|
||||
curve: Curves.fastOutSlowIn,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
// تحريك القائمة من خارج الشاشة إلى داخلها
|
||||
left: controller.widthMenu > 0 ? 0 : -Get.width,
|
||||
child: ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: Container(
|
||||
width: Get.width * 0.75, // عرض القائمة
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
color: AppColor.secondaryColor.withOpacity(0.9),
|
||||
width: Get.width * 0.8,
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.secondaryColor.withOpacity(0.95),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 20,
|
||||
)
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- 1. رأس القائمة (معلومات المستخدم) ---
|
||||
_buildMenuHeader(),
|
||||
|
||||
// --- 2. الأزرار السريعة المدمجة ---
|
||||
_buildQuickActionButtons(),
|
||||
const Divider(
|
||||
color: AppColor.writeColor,
|
||||
color: Colors.white24,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
height: 1),
|
||||
|
||||
// --- 3. قائمة الخيارات الرئيسية ---
|
||||
height: 24),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
children: [
|
||||
IconMainPageMap(
|
||||
title: 'My Wallet'.tr,
|
||||
MenuListItem(
|
||||
title: 'My Balance'.tr,
|
||||
icon: Icons.account_balance_wallet_outlined,
|
||||
onTap: () => Get.to(() => const PassengerWallet())),
|
||||
IconMainPageMap(
|
||||
MenuListItem(
|
||||
title: 'Order History'.tr,
|
||||
icon: Icons.history_edu_rounded,
|
||||
icon: Icons.history_rounded,
|
||||
onTap: () => Get.to(() => const OrderHistory())),
|
||||
IconMainPageMap(
|
||||
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(
|
||||
MenuListItem(
|
||||
title: 'Promos'.tr,
|
||||
icon: Icons.local_offer_outlined,
|
||||
onTap: () =>
|
||||
Get.to(() => const PromosPassengerPage())),
|
||||
MenuListItem(
|
||||
title: 'Contact Us'.tr,
|
||||
icon: Icons.contact_support_outlined,
|
||||
onTap: () => Get.to(() => ContactUsPage())),
|
||||
MenuListItem(
|
||||
title: 'Complaint'.tr,
|
||||
icon: Icons.flag_outlined,
|
||||
onTap: () => Get.to(() => ComplaintPage())),
|
||||
MenuListItem(
|
||||
title: 'Driver'.tr,
|
||||
icon: Ionicons.car_sport_outline,
|
||||
onTap: () => _launchDriverAppUrl()),
|
||||
MenuListItem(
|
||||
title: 'Share App'.tr,
|
||||
icon: Icons.share_outlined,
|
||||
onTap: () => Get.to(() => ShareAppPage())),
|
||||
MenuListItem(
|
||||
title: 'Privacy Policy'.tr,
|
||||
icon: Icons.shield_outlined,
|
||||
onTap: () => launchUrl(Uri.parse(
|
||||
'${AppLink.server}/privacy_policy.php')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
padding: const EdgeInsets.fromLTRB(20, 30, 20, 16),
|
||||
child: Row(
|
||||
children: [
|
||||
// CircleAvatar(
|
||||
// radius: 30,
|
||||
// backgroundColor: AppColor.primaryColor,
|
||||
// child:
|
||||
// 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)),
|
||||
const CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundColor: AppColor.primaryColor,
|
||||
child: Icon(Icons.person, color: AppColor.writeColor, size: 35),
|
||||
),
|
||||
Text(
|
||||
box.read(BoxName.name), // يمكنك تغييرها لاسم المستخدم
|
||||
style: AppStyle.headTitle.copyWith(fontSize: 22),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
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() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSmallActionButton(
|
||||
icon: Icons.notifications_none_rounded,
|
||||
label: 'Alerts'.tr,
|
||||
onTap: () => Get.to(() => const NotificationPage())),
|
||||
_buildSmallActionButton(
|
||||
icon: Icons.person_outline_rounded,
|
||||
label: 'Profile'.tr,
|
||||
onTap: () => Get.to(() => PassengerProfilePage())),
|
||||
_buildSmallActionButton(
|
||||
icon: Icons.notifications_none_rounded,
|
||||
label: 'Alerts'.tr,
|
||||
onTap: () => Get.to(() => const NotificationPage())),
|
||||
_buildSmallActionButton(
|
||||
icon: Icons.settings_outlined,
|
||||
label: 'Settings'.tr,
|
||||
@@ -209,29 +263,31 @@ class MapMenuWidget extends StatelessWidget {
|
||||
required VoidCallback onTap}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: AppColor.writeColor, size: 24),
|
||||
const SizedBox(height: 4),
|
||||
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
||||
Icon(icon, color: AppColor.writeColor.withOpacity(0.9), size: 24),
|
||||
const SizedBox(height: 6),
|
||||
Text(label,
|
||||
style: AppStyle.subtitle.copyWith(
|
||||
fontSize: 12, color: AppColor.writeColor.withOpacity(0.9))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- نفس دالتك القديمة لفتح رابط تطبيق السائق ---
|
||||
void _launchDriverAppUrl() async {
|
||||
final String driverAppUrl;
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
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) {
|
||||
driverAppUrl = 'https://apps.apple.com/eg/app/tripz-driver/id6502189302';
|
||||
driverAppUrl =
|
||||
'https://apps.apple.com/st/app/intaleq-driver/id6482995159';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -248,28 +304,39 @@ class MapMenuWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// --- كلاس عناصر القائمة بالتصميم الجديد (يستخدم ListTile) ---
|
||||
class IconMainPageMap extends StatelessWidget {
|
||||
const IconMainPageMap({
|
||||
// --- ويدجت عناصر القائمة بتصميم محسن ---
|
||||
class MenuListItem extends StatelessWidget {
|
||||
const MenuListItem({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
leading:
|
||||
Icon(icon, size: 26, color: AppColor.writeColor.withOpacity(0.8)),
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 26,
|
||||
color: color ?? AppColor.writeColor.withOpacity(0.8),
|
||||
),
|
||||
title: Text(
|
||||
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),
|
||||
);
|
||||
|
||||
@@ -193,7 +193,7 @@ class RideBeginPassenger extends StatelessWidget {
|
||||
box.write(BoxName.sosPhonePassenger,
|
||||
profileController.prfoileData['sosPhone']);
|
||||
} else {
|
||||
makePhoneCall('122');
|
||||
makePhoneCall('112');
|
||||
}
|
||||
}),
|
||||
_buildActionButton(
|
||||
@@ -211,18 +211,17 @@ class RideBeginPassenger extends StatelessWidget {
|
||||
} else {
|
||||
final phoneNumber =
|
||||
box.read(BoxName.sosPhonePassenger).toString();
|
||||
final phone = box.read(BoxName.countryCode) == 'Egypt'
|
||||
? '+2$phoneNumber'
|
||||
: '+962$phoneNumber';
|
||||
|
||||
final phone = controller.formatSyrianPhoneNumber(phoneNumber);
|
||||
controller.sendWhatsapp(phone);
|
||||
}
|
||||
}),
|
||||
_buildActionButton(
|
||||
icon: Foundation.video,
|
||||
label: 'Video Call'.tr,
|
||||
icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
|
||||
label: 'Share'.tr, // اسم جديد وواضح
|
||||
color: AppColor.blueColor,
|
||||
onTap: () async {
|
||||
// --- نفس منطقك القديم ---
|
||||
// نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
|
||||
await controller.getTokenForParent();
|
||||
}),
|
||||
_buildActionButton(
|
||||
|
||||
@@ -27,7 +27,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
Get.put(CreditCardController());
|
||||
|
||||
return MyScafolld(
|
||||
title: 'My Wallet'.tr,
|
||||
title: 'My Balance'.tr,
|
||||
isleading: true,
|
||||
body: [
|
||||
// استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة
|
||||
@@ -53,7 +53,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
// --- 2. قائمة الخيارات المنظمة ---
|
||||
_buildActionTile(
|
||||
icon: Icons.add_card_rounded,
|
||||
title: 'Top up Wallet'.tr,
|
||||
title: 'Top up Balance'.tr,
|
||||
subtitle: 'Add funds using our secure methods'.tr,
|
||||
onTap: () =>
|
||||
showPaymentBottomSheet(context), // نفس دالتك القديمة
|
||||
@@ -68,7 +68,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
),
|
||||
_buildActionTile(
|
||||
icon: Icons.phone_iphone_rounded,
|
||||
title: 'Set Wallet Phone Number'.tr,
|
||||
title: 'Set Phone Number'.tr,
|
||||
subtitle: 'Link a phone number for transfers'.tr,
|
||||
onTap: () => _showWalletPhoneDialog(context,
|
||||
Get.find<PaymentController>()), // نفس دالتك القديمة
|
||||
@@ -132,7 +132,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${AppInformation.appName} Wallet'.tr,
|
||||
'${AppInformation.appName} ${'Balance'.tr}',
|
||||
style: AppStyle.headTitle.copyWith(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
|
||||
@@ -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/material.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/controller/profile/profile_controller.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/my_scafold.dart';
|
||||
import 'package:Intaleq/views/widgets/my_textField.dart';
|
||||
import 'package:Intaleq/views/widgets/mycircular.dart';
|
||||
|
||||
@@ -18,239 +16,319 @@ import '../../../controller/functions/log_out.dart';
|
||||
|
||||
class PassengerProfilePage extends StatelessWidget {
|
||||
PassengerProfilePage({super.key});
|
||||
LogOutController logOutController = Get.put(LogOutController());
|
||||
|
||||
final LogOutController logOutController = Get.put(LogOutController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(ProfileController());
|
||||
|
||||
return MyScafolld(
|
||||
isleading: true,
|
||||
title: 'My Profile'.tr,
|
||||
body: [
|
||||
GetBuilder<ProfileController>(
|
||||
builder: (controller) => controller.isloading
|
||||
? const MyCircularProgressIndicator()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: SizedBox(
|
||||
height: Get.height,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Edit Profile'.tr,
|
||||
style: AppStyle.headTitle2,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'Name'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.person_pin_rounded,
|
||||
size: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text(
|
||||
'${(controller.prfoileData['first_name'])} ${(controller.prfoileData['last_name'])}'),
|
||||
onTap: () {
|
||||
controller.updatField(
|
||||
'first_name', TextInputType.name);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'Gender'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: Image.asset(
|
||||
'assets/images/gender.png',
|
||||
width: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text((controller.prfoileData['gender']
|
||||
.toString())),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
title: 'Update Gender'.tr,
|
||||
content: Column(
|
||||
children: [
|
||||
GenderPicker(),
|
||||
MyElevatedButton(
|
||||
title: 'Update'.tr,
|
||||
onPressed: () {
|
||||
controller.updateColumn({
|
||||
'id': controller.prfoileData['id']
|
||||
.toString(),
|
||||
'gender': (controller.gender),
|
||||
});
|
||||
Get.back();
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
// controller.updatField('gender');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'Education'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: Image.asset(
|
||||
'assets/images/education.png',
|
||||
width: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text(controller.prfoileData['education']
|
||||
.toString()),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: true,
|
||||
title: 'Update Education'.tr,
|
||||
content: SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: [
|
||||
EducationDegreePicker(),
|
||||
],
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Update Education'.tr,
|
||||
onPressed: () {
|
||||
controller.updateColumn({
|
||||
'id': controller.prfoileData['id']
|
||||
.toString(),
|
||||
'education':
|
||||
controller.selectedDegree,
|
||||
});
|
||||
Get.back();
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'Employment Type'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: Image.asset(
|
||||
'assets/images/employmentType.png',
|
||||
width: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text(controller
|
||||
.prfoileData['employmentType']
|
||||
.toString()),
|
||||
onTap: () {
|
||||
controller.updatField(
|
||||
'employmentType', TextInputType.name);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'Marital Status'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: Image.asset(
|
||||
'assets/images/maritalStatus.png',
|
||||
width: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text(controller
|
||||
.prfoileData['maritalStatus']
|
||||
.toString()),
|
||||
onTap: () {
|
||||
controller.updatField(
|
||||
'maritalStatus', TextInputType.name);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'SOS Phone'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.sos,
|
||||
color: AppColor.redColor,
|
||||
size: 35,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
subtitle: Text(
|
||||
(controller.prfoileData['sosPhone'])
|
||||
.toString()),
|
||||
onTap: () async {
|
||||
await controller.updatField(
|
||||
'sosPhone', TextInputType.phone);
|
||||
box.write(BoxName.sosPhonePassenger,
|
||||
controller.prfoileData['sosPhone']);
|
||||
},
|
||||
),
|
||||
// const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
MyElevatedButton(
|
||||
title: 'Sign Out'.tr,
|
||||
onPressed: () {
|
||||
LogOutController().logOutPassenger();
|
||||
}),
|
||||
GetBuilder<LogOutController>(
|
||||
builder: (logOutController) {
|
||||
return MyElevatedButton(
|
||||
title: 'Delete My Account'.tr,
|
||||
onPressed: () {
|
||||
Get.defaultDialog(
|
||||
title:
|
||||
'Are you sure to delete your account?'
|
||||
.tr,
|
||||
content: Form(
|
||||
key: logOutController.formKey1,
|
||||
child: MyTextForm(
|
||||
controller: logOutController
|
||||
.emailTextController,
|
||||
label: 'Type your Email'.tr,
|
||||
hint: 'Type your Email'.tr,
|
||||
type:
|
||||
TextInputType.emailAddress,
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Delete My Account'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () async {
|
||||
await logOutController
|
||||
.deletePassengerAccount();
|
||||
}),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'No I want'.tr,
|
||||
onPressed: () {
|
||||
logOutController
|
||||
.emailTextController
|
||||
.clear();
|
||||
logOutController.update();
|
||||
Get.back();
|
||||
}));
|
||||
});
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
appBar: AppBar(
|
||||
title: Text('My Profile'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
backgroundColor: Colors.grey[100],
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
body: GetBuilder<ProfileController>(
|
||||
builder: (controller) {
|
||||
if (controller.isloading) {
|
||||
return const MyCircularProgressIndicator();
|
||||
}
|
||||
return ListView(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
|
||||
children: [
|
||||
_buildProfileHeader(controller),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionCard(
|
||||
'Personal Information'.tr,
|
||||
[
|
||||
_buildProfileTile(
|
||||
icon: Icons.person_outline,
|
||||
color: Colors.blue,
|
||||
title: 'Name'.tr,
|
||||
subtitle:
|
||||
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}',
|
||||
onTap: () =>
|
||||
controller.updatField('first_name', TextInputType.name),
|
||||
),
|
||||
_buildProfileTile(
|
||||
icon: Icons.wc_outlined,
|
||||
color: Colors.pink,
|
||||
title: 'Gender'.tr,
|
||||
subtitle: controller.prfoileData['gender']?.toString() ??
|
||||
'Not set'.tr,
|
||||
onTap: () => _showGenderDialog(controller),
|
||||
),
|
||||
_buildProfileTile(
|
||||
icon: Icons.school_outlined,
|
||||
color: Colors.orange,
|
||||
title: 'Education'.tr,
|
||||
subtitle: controller.prfoileData['education']?.toString() ??
|
||||
'Not set'.tr,
|
||||
onTap: () => _showEducationDialog(controller),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionCard(
|
||||
'Work & Contact'.tr,
|
||||
[
|
||||
_buildProfileTile(
|
||||
icon: Icons.work_outline,
|
||||
color: Colors.green,
|
||||
title: 'Employment Type'.tr,
|
||||
subtitle:
|
||||
controller.prfoileData['employmentType']?.toString() ??
|
||||
'Not set'.tr,
|
||||
onTap: () => controller.updatField(
|
||||
'employmentType', TextInputType.name),
|
||||
),
|
||||
_buildProfileTile(
|
||||
icon: Icons.favorite_border,
|
||||
color: Colors.purple,
|
||||
title: 'Marital Status'.tr,
|
||||
subtitle:
|
||||
controller.prfoileData['maritalStatus']?.toString() ??
|
||||
'Not set'.tr,
|
||||
onTap: () => controller.updatField(
|
||||
'maritalStatus', TextInputType.name),
|
||||
),
|
||||
_buildProfileTile(
|
||||
icon: Icons.sos_outlined,
|
||||
color: Colors.red,
|
||||
title: 'SOS Phone'.tr,
|
||||
subtitle: controller.prfoileData['sosPhone']?.toString() ??
|
||||
'Not set'.tr,
|
||||
onTap: () async {
|
||||
await controller.updatField(
|
||||
'sosPhone', TextInputType.phone);
|
||||
box.write(BoxName.sosPhonePassenger,
|
||||
controller.prfoileData['sosPhone']);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildAccountActions(context, logOutController),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileHeader(ProfileController controller) {
|
||||
String fullName =
|
||||
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}';
|
||||
String initials = (fullName.isNotEmpty && fullName.contains(" "))
|
||||
? fullName.split(" ").map((e) => e.isNotEmpty ? e[0] : "").join()
|
||||
: (fullName.isNotEmpty ? fullName[0] : "");
|
||||
|
||||
// Logic to hide email if it contains 'intaleqapp.com'
|
||||
String email = box.read(BoxName.email) ?? '';
|
||||
if (email.contains('intaleqapp.com')) {
|
||||
email = ''; // Clear the email if it contains the domain
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
|
||||
child: Text(
|
||||
initials,
|
||||
style:
|
||||
const TextStyle(fontSize: 40, color: AppColor.primaryColor),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
fullName,
|
||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (email
|
||||
.isNotEmpty) // Only show the Text widget if the email is not empty
|
||||
Text(
|
||||
email,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionCard(String title, List<Widget> children) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[700]),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileTile({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 24),
|
||||
),
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: Text(subtitle, style: TextStyle(color: Colors.grey[600])),
|
||||
trailing:
|
||||
Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey[400]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAccountActions(
|
||||
BuildContext context, LogOutController logOutController) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.logout),
|
||||
label: Text('Sign Out'.tr),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blueGrey,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
logOutController.logOutPassenger();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.delete_forever_outlined),
|
||||
label: Text('Delete My Account'.tr),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
onPressed: () =>
|
||||
_showDeleteAccountDialog(context, logOutController),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showGenderDialog(ProfileController controller) {
|
||||
Get.defaultDialog(
|
||||
title: 'Update Gender'.tr,
|
||||
content: Column(
|
||||
children: [
|
||||
GenderPicker(),
|
||||
const SizedBox(height: 16),
|
||||
MyElevatedButton(
|
||||
title: 'Update'.tr,
|
||||
onPressed: () {
|
||||
controller.updateColumn({
|
||||
'id': controller.prfoileData['id'].toString(),
|
||||
'gender': controller.gender,
|
||||
});
|
||||
Get.back();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showEducationDialog(ProfileController controller) {
|
||||
Get.defaultDialog(
|
||||
title: 'Update Education'.tr,
|
||||
content: Column(
|
||||
children: [
|
||||
EducationDegreePicker(),
|
||||
const SizedBox(height: 16),
|
||||
MyElevatedButton(
|
||||
title: 'Update'.tr,
|
||||
onPressed: () {
|
||||
controller.updateColumn({
|
||||
'id': controller.prfoileData['id'].toString(),
|
||||
'education': controller.selectedDegree,
|
||||
});
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDeleteAccountDialog(
|
||||
BuildContext context, LogOutController logOutController) {
|
||||
Get.defaultDialog(
|
||||
title: 'Delete My Account'.tr,
|
||||
middleText: 'Are you sure? This action cannot be undone.'.tr,
|
||||
content: Form(
|
||||
key: logOutController.formKey1,
|
||||
child: MyTextForm(
|
||||
controller: logOutController.emailTextController,
|
||||
label: 'Confirm your Email'.tr,
|
||||
hint: 'Type your Email'.tr,
|
||||
type: TextInputType.emailAddress,
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Delete Permanently'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () async {
|
||||
await logOutController.deletePassengerAccount();
|
||||
},
|
||||
),
|
||||
cancel: TextButton(
|
||||
child: Text('Cancel'.tr),
|
||||
onPressed: () {
|
||||
logOutController.emailTextController.clear();
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GenderPicker extends StatelessWidget {
|
||||
final ProfileController controller = Get.put(ProfileController());
|
||||
// --- Helper Widgets for Pickers ---
|
||||
|
||||
class GenderPicker extends StatelessWidget {
|
||||
final ProfileController controller = Get.find<ProfileController>();
|
||||
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
|
||||
|
||||
GenderPicker({super.key});
|
||||
@@ -258,14 +336,14 @@ class GenderPicker extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 100,
|
||||
height: 150,
|
||||
child: CupertinoPicker(
|
||||
itemExtent: 32.0,
|
||||
itemExtent: 40.0,
|
||||
onSelectedItemChanged: (int index) {
|
||||
controller.setGender(genderOptions[index]);
|
||||
},
|
||||
children: genderOptions.map((String value) {
|
||||
return Text(value);
|
||||
return Center(child: Text(value));
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
@@ -273,216 +351,34 @@ class GenderPicker extends StatelessWidget {
|
||||
}
|
||||
|
||||
class EducationDegreePicker extends StatelessWidget {
|
||||
final ProfileController controller = Get.put(ProfileController());
|
||||
|
||||
final ProfileController controller = Get.find<ProfileController>();
|
||||
final List<String> degreeOptions = [
|
||||
'High School Diploma'.tr,
|
||||
'Associate Degree'.tr,
|
||||
'Bachelor\'s Degree'.tr,
|
||||
'Master\'s Degree'.tr,
|
||||
"Bachelor's Degree".tr,
|
||||
"Master's Degree".tr,
|
||||
'Doctoral Degree'.tr,
|
||||
];
|
||||
|
||||
EducationDegreePicker({Key? key}) : super(key: key);
|
||||
EducationDegreePicker({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
height: 180,
|
||||
child: CupertinoPicker(
|
||||
// backgroundColor: AppColor.accentColor,
|
||||
// looping: true,
|
||||
squeeze: 2,
|
||||
// diameterRatio: 5,
|
||||
itemExtent: 32,
|
||||
itemExtent: 40.0,
|
||||
onSelectedItemChanged: (int index) {
|
||||
controller.setDegree(degreeOptions[index]);
|
||||
},
|
||||
children: degreeOptions.map((String value) {
|
||||
return Text(value);
|
||||
return Center(child: Text(value));
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CountryPicker extends StatelessWidget {
|
||||
final ProfileController controller = Get.put(ProfileController());
|
||||
|
||||
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();
|
||||
},
|
||||
)
|
||||
],
|
||||
)),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
// NOTE: The CountryPicker and CountryPickerFromSetting widgets were not part of the main
|
||||
// profile page UI, so they are excluded here to keep the file focused.
|
||||
// If they are needed elsewhere, they should be moved to their own files.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:Intaleq/controller/home/home_page_controller.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:Intaleq/views/lang/languages.dart';
|
||||
@@ -8,109 +7,207 @@ import 'HomePage/about_page.dart';
|
||||
import 'HomePage/frequentlyQuestionsPage.dart';
|
||||
import 'HomePage/share_app_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 {
|
||||
const SettingPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(HomePageController());
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text('Setting'.tr),
|
||||
leading: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
child: const Icon(CupertinoIcons.back),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
// Using lazyPut to ensure the controller is available when needed.
|
||||
Get.lazyPut(() => HomePageController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor:
|
||||
const Color(0xFFF5F5F7), // A slightly off-white background
|
||||
appBar: AppBar(
|
||||
title: Text('Setting'.tr,
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||
children: [
|
||||
CupertinoListTile(
|
||||
onTap: () {
|
||||
Get.to(() => const Language());
|
||||
},
|
||||
leading: const Icon(CupertinoIcons.globe,
|
||||
color: CupertinoColors.activeBlue),
|
||||
title: Text('Language'.tr),
|
||||
subtitle: Text('To change Language the App'.tr),
|
||||
trailing: const CupertinoListTileChevron(),
|
||||
_buildSectionHeader('General'.tr),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
_buildSettingsTile(
|
||||
icon: Icons.language,
|
||||
color: Colors.blue,
|
||||
title: 'Language'.tr,
|
||||
subtitle: 'To change Language the App'.tr,
|
||||
onTap: () => Get.to(() => const Language()),
|
||||
),
|
||||
// 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(
|
||||
onTap: () {
|
||||
Get.to(() => CountryPickerFromSetting());
|
||||
},
|
||||
leading: const Icon(CupertinoIcons.location,
|
||||
color: CupertinoColors.activeBlue),
|
||||
title: Text('Change Country'.tr),
|
||||
subtitle:
|
||||
Text('You can change the Country to get all features'.tr),
|
||||
trailing: const CupertinoListTileChevron(),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionHeader('Preferences'.tr),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
GetBuilder<HomePageController>(
|
||||
builder: (controller) {
|
||||
return _buildSettingsSwitchTile(
|
||||
icon: Icons.vibration,
|
||||
color: Colors.purple,
|
||||
title: 'Vibration'.tr,
|
||||
subtitle: 'Vibration feedback for all buttons'.tr,
|
||||
value: controller.isVibrate,
|
||||
onChanged: controller.changeVibrateOption,
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||
_buildSettingsTile(
|
||||
icon: Icons.mic_none,
|
||||
color: Colors.orange,
|
||||
title: 'Trips recorded'.tr,
|
||||
subtitle: 'Here recorded trips audio'.tr,
|
||||
onTap: () => Get.to(() => const TripsRecordedPage()),
|
||||
),
|
||||
],
|
||||
),
|
||||
CupertinoListTile(
|
||||
onTap: () {
|
||||
Get.to(() => const FrequentlyQuestionsPage());
|
||||
},
|
||||
leading: const Icon(CupertinoIcons.question,
|
||||
color: CupertinoColors.activeBlue),
|
||||
title: Text('Frequently Questions'.tr),
|
||||
subtitle: Text('Find answers to common questions'.tr),
|
||||
trailing: const CupertinoListTileChevron(),
|
||||
),
|
||||
CupertinoListTile(
|
||||
leading: const Icon(Icons.vibration,
|
||||
color: CupertinoColors.activeBlue),
|
||||
title: Text('Vibration'.tr),
|
||||
trailing: GetBuilder<HomePageController>(
|
||||
builder: (controller) {
|
||||
return CupertinoSwitch(
|
||||
value: controller.isVibrate,
|
||||
onChanged: controller.changeVibrateOption,
|
||||
);
|
||||
},
|
||||
),
|
||||
subtitle: Text(
|
||||
'You can change the vibration feedback for all buttons'.tr),
|
||||
),
|
||||
CupertinoListTile(
|
||||
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(),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionHeader('Support & Info'.tr),
|
||||
_buildSettingsCard(
|
||||
children: [
|
||||
_buildSettingsTile(
|
||||
icon: Icons.help_outline,
|
||||
color: Colors.cyan,
|
||||
title: 'Frequently Questions'.tr,
|
||||
subtitle: 'Find answers to common questions'.tr,
|
||||
onTap: () => Get.to(() => const FrequentlyQuestionsPage()),
|
||||
),
|
||||
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||
_buildSettingsTile(
|
||||
icon: Icons.info_outline,
|
||||
color: Colors.indigo,
|
||||
title: 'About Us'.tr,
|
||||
subtitle: 'Learn more about our app and mission'.tr,
|
||||
onTap: () => Get.to(() => const AboutPage()),
|
||||
),
|
||||
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||
_buildSettingsTile(
|
||||
icon: Icons.share_outlined,
|
||||
color: Colors.redAccent,
|
||||
title: 'Share App'.tr,
|
||||
subtitle: 'Share with friends and earn rewards'.tr,
|
||||
onTap: () => Get.to(() => ShareAppPage()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0, left: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class Language extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Select your preferred language for the app interface.',
|
||||
"Select your preferred language for the app interface.".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: CupertinoColors.secondaryLabel,
|
||||
|
||||
@@ -50,7 +50,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
|
||||
|
||||
@@ -74,6 +74,7 @@ dependencies:
|
||||
dotted_line: ^3.2.3
|
||||
shimmer: ^3.0.0
|
||||
share_plus: ^11.0.0
|
||||
asn1lib: ^1.6.5
|
||||
# home_widget: ^0.7.0+1
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
Reference in New Issue
Block a user