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

View File

@@ -47,8 +47,8 @@ android {
// For more information, see: https://flutter.dev/to/review-gradle-config.
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"

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
assets/images/electric.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -322,10 +322,14 @@
inputFileListPaths = (
"${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";

View File

@@ -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>

View File

@@ -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

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ class FirebaseMessagesController extends GetxController {
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
}
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');

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,6 @@ import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/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

View File

@@ -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),
),
),
),

View File

@@ -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(

View File

@@ -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,
),

View File

@@ -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,

View File

@@ -1,5 +1,3 @@
import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package: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();
},
),
],
);
},
);
}
}

View File

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

View File

@@ -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:

View File

@@ -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,

View File

@@ -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,
),
),
);

View File

@@ -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() {

View File

@@ -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),
);

View File

@@ -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(

View File

@@ -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,

View File

@@ -1,5 +1,3 @@
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/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.

View File

@@ -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),
);
}
}

View File

@@ -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,

View File

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

View File

@@ -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: