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.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 7
|
versionCode = 11
|
||||||
versionName = '1.0.7'
|
versionName = '1.0.11'
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<network-security-config>
|
<network-security-config>
|
||||||
<!-- منع أي اتصال HTTP غير مشفّر -->
|
|
||||||
<base-config cleartextTrafficPermitted="false">
|
<base-config cleartextTrafficPermitted="false">
|
||||||
<trust-anchors>
|
<trust-anchors>
|
||||||
<!-- الاعتماد على شهادات النظام فقط -->
|
|
||||||
<certificates src="system" />
|
<certificates src="system" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</base-config>
|
</base-config>
|
||||||
|
|
||||||
<!-- اسمح بالاتصال بدوميناتك فقط -->
|
|
||||||
<domain-config cleartextTrafficPermitted="false">
|
<domain-config cleartextTrafficPermitted="false">
|
||||||
|
|
||||||
<domain includeSubdomains="true">intaleq.xyz</domain>
|
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||||
<!-- أضف أي ساب دومين تحتاجه هنا -->
|
|
||||||
<!-- <domain includeSubdomains="true">walletintaleq.intaleq.xyz</domain>
|
<pin-set expiration="2027-01-01">
|
||||||
<domain includeSubdomains="true">syria.intaleq.xyz</domain> -->
|
<pin digest="SHA-256">pXmP2hTQLxDEvlTVmP5N7xpiA32sycBsxB6hBFT2uL4=</pin>
|
||||||
|
|
||||||
|
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
|
||||||
|
</pin-set>
|
||||||
</domain-config>
|
</domain-config>
|
||||||
|
|
||||||
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
|
|
||||||
<debug-overrides>
|
<debug-overrides>
|
||||||
<trust-anchors>
|
<trust-anchors>
|
||||||
<certificates src="system" />
|
<certificates src="system" />
|
||||||
<certificates src="user" />
|
<certificates src="user" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</debug-overrides>
|
</debug-overrides>
|
||||||
|
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
BIN
assets/images/bus.png
Normal file
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 = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -339,10 +343,14 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5</string>
|
<string>6</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<dict/>
|
<dict/>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0.5</string>
|
<string>1.0.6</string>
|
||||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||||
<string>NO</string>
|
<string>NO</string>
|
||||||
<key>GMSApiKey</key>
|
<key>GMSApiKey</key>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class BoxName {
|
|||||||
static const String serverChosen = "serverChosen";
|
static const String serverChosen = "serverChosen";
|
||||||
static const String gender = "gender";
|
static const String gender = "gender";
|
||||||
static const String jwt = "jwt";
|
static const String jwt = "jwt";
|
||||||
|
static const String lowEndMode = "lowEndMode";
|
||||||
|
static const String appVersionChecked = "appVersionChecked";
|
||||||
static const String lastName = "lastName";
|
static const String lastName = "lastName";
|
||||||
static const String fingerPrint = "fingerPrint";
|
static const String fingerPrint = "fingerPrint";
|
||||||
static const String payMobApikey = "payMobApikey";
|
static const String payMobApikey = "payMobApikey";
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
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,
|
link: _sendOtpUrl,
|
||||||
payload: {'receiver': phoneNumber},
|
payload: {'receiver': phoneNumber},
|
||||||
);
|
);
|
||||||
// Log.print('response: ${response}');
|
Log.print('response: ${response}');
|
||||||
if (response != 'failure') {
|
if (response != 'failure') {
|
||||||
final data = (response);
|
final data = (response);
|
||||||
// if (data['status'] == 'success') {
|
// if (data['status'] == 'success') {
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class FirebaseMessagesController extends GetxController {
|
|||||||
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
|
'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
} else if (message.notification!.title! == 'Hi ,I Arrive your site') {
|
} else if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
notificationController.showNotification(
|
notificationController.showNotification(
|
||||||
'Hi ,I Arrive your site'.tr, ''.tr, 'ding');
|
'Hi ,I Arrive your site'.tr, ''.tr, 'ding');
|
||||||
|
|||||||
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/colors.dart';
|
||||||
import 'package:Intaleq/constant/links.dart';
|
import 'package:Intaleq/constant/links.dart';
|
||||||
import 'package:Intaleq/controller/functions/crud.dart';
|
import 'package:Intaleq/controller/functions/crud.dart';
|
||||||
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
|
|
||||||
import 'package:Intaleq/controller/payment/payment_controller.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -17,6 +15,7 @@ import '../../../views/widgets/error_snakbar.dart';
|
|||||||
import '../../../views/widgets/mydialoug.dart';
|
import '../../../views/widgets/mydialoug.dart';
|
||||||
import '../../functions/launch.dart';
|
import '../../functions/launch.dart';
|
||||||
import '../../notification/notification_captain_controller.dart';
|
import '../../notification/notification_captain_controller.dart';
|
||||||
|
import '../../payment/payment_controller.dart';
|
||||||
|
|
||||||
class InviteController extends GetxController {
|
class InviteController extends GetxController {
|
||||||
final TextEditingController invitePhoneController = TextEditingController();
|
final TextEditingController invitePhoneController = TextEditingController();
|
||||||
@@ -27,21 +26,31 @@ class InviteController extends GetxController {
|
|||||||
|
|
||||||
int selectedTab = 0;
|
int selectedTab = 0;
|
||||||
PassengerStats passengerStats = PassengerStats();
|
PassengerStats passengerStats = PassengerStats();
|
||||||
|
|
||||||
|
List<Contact> contacts = <Contact>[];
|
||||||
|
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
// It's good practice to fetch initial data in onInit or onReady
|
||||||
|
// fetchDriverStats();
|
||||||
|
// fetchDriverStatsPassengers();
|
||||||
|
}
|
||||||
|
|
||||||
void updateSelectedTab(int index) {
|
void updateSelectedTab(int index) {
|
||||||
selectedTab = index;
|
selectedTab = index;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> shareCouponCode() async {
|
// --- Sharing Methods ---
|
||||||
// TODO: Implement sharing functionality
|
|
||||||
// You can use share_plus package to share the coupon code
|
|
||||||
}
|
|
||||||
Future<void> shareDriverCode() async {
|
Future<void> shareDriverCode() async {
|
||||||
if (driverCouponCode != null) {
|
if (driverCouponCode != null) {
|
||||||
final String shareText = '''
|
final String shareText = '''
|
||||||
Join Intaleq as a driver using my referral code!
|
${'Join Intaleq as a driver using my referral code!'.tr}
|
||||||
Use code: $driverCouponCode
|
${'Use code:'.tr} $driverCouponCode
|
||||||
Download the Intaleq Driver app now and earn rewards!
|
${'Download the Intaleq Driver app now and earn rewards!'.tr}
|
||||||
''';
|
''';
|
||||||
await Share.share(shareText);
|
await Share.share(shareText);
|
||||||
}
|
}
|
||||||
@@ -50,19 +59,15 @@ Download the Intaleq Driver app now and earn rewards!
|
|||||||
Future<void> sharePassengerCode() async {
|
Future<void> sharePassengerCode() async {
|
||||||
if (couponCode != null) {
|
if (couponCode != null) {
|
||||||
final String shareText = '''
|
final String shareText = '''
|
||||||
Get a discount on your first Intaleq ride!
|
${'Get a discount on your first Intaleq ride!'.tr}
|
||||||
Use my referral code: $couponCode
|
${'Use my referral code:'.tr} $couponCode
|
||||||
Download the Intaleq app now and enjoy your ride!
|
${'Download the Intaleq app now and enjoy your ride!'.tr}
|
||||||
''';
|
''';
|
||||||
await Share.share(shareText);
|
await Share.share(shareText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
// --- Data Fetching ---
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
// fetchDriverStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchDriverStats() async {
|
void fetchDriverStats() async {
|
||||||
try {
|
try {
|
||||||
@@ -74,7 +79,9 @@ Download the Intaleq app now and enjoy your ride!
|
|||||||
driverInvitationData = data['message'];
|
driverInvitationData = data['message'];
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
Log.print("Error fetching driver stats: $e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchDriverStatsPassengers() async {
|
void fetchDriverStatsPassengers() async {
|
||||||
@@ -88,216 +95,207 @@ Download the Intaleq app now and enjoy your ride!
|
|||||||
driverInvitationDataToPassengers = data['message'];
|
driverInvitationDataToPassengers = data['message'];
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectPhone(String phone) {
|
|
||||||
if (box.read(BoxName.countryCode) == 'Egypt') {
|
|
||||||
invitePhoneController.text = phone;
|
|
||||||
update();
|
|
||||||
Get.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveContactsToServer() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implement the actual server upload logic here
|
|
||||||
// Simulating a server request
|
|
||||||
await Future.delayed(Duration(seconds: 2));
|
|
||||||
Get.snackbar('Success'.tr,
|
|
||||||
'${selectedContacts.length} contacts saved to server'.tr);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar('Error'.tr,
|
Log.print("Error fetching passenger stats: $e");
|
||||||
'An error occurred while saving contacts to server: $e'.tr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Contact> contacts = <Contact>[];
|
// --- Contact Handling ---
|
||||||
List<Contact> selectedContacts = <Contact>[];
|
|
||||||
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
|
||||||
|
|
||||||
|
/// **IMPROVEMENT**: This function now filters out contacts without any phone numbers.
|
||||||
|
/// This is the fix for the `RangeError` you were seeing, which happened when the UI
|
||||||
|
/// tried to access the first phone number of a contact that had none.
|
||||||
Future<void> pickContacts() async {
|
Future<void> pickContacts() async {
|
||||||
try {
|
try {
|
||||||
// Request contacts permission
|
|
||||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
if (await FlutterContacts.requestPermission(readonly: true)) {
|
||||||
// Fetch all contacts with full properties
|
final List<Contact> allContacts =
|
||||||
final List<Contact> allContacts = await FlutterContacts.getContacts(
|
await FlutterContacts.getContacts(withProperties: true);
|
||||||
withProperties: true,
|
final int totalContactsOnDevice = allContacts.length;
|
||||||
withThumbnail: false,
|
|
||||||
withPhoto: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if contacts are available
|
// **FIX**: Filter contacts to only include those with at least one phone number.
|
||||||
if (allContacts.isNotEmpty) {
|
contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
|
||||||
// Store the contacts
|
final int contactsWithPhones = contacts.length;
|
||||||
contacts = allContacts;
|
|
||||||
Log.print('contacts: $contacts');
|
|
||||||
|
|
||||||
// Convert contacts to a list of maps
|
if (contactsWithPhones > 0) {
|
||||||
contactMaps.value = await Future.wait(contacts.map((contact) async {
|
Log.print('Found $contactsWithPhones contacts with phone numbers.');
|
||||||
Log.print('Contact name: ${contact.displayName}');
|
contactMaps.value = contacts.map((contact) {
|
||||||
|
|
||||||
// Fetch phone numbers separately
|
|
||||||
final phones = await contact.phones;
|
|
||||||
Log.print('Contact phones: $phones');
|
|
||||||
|
|
||||||
// Fetch email addresses separately
|
|
||||||
final emails = await contact.emails;
|
|
||||||
Log.print('Contact emails: $emails');
|
|
||||||
|
|
||||||
// Handle empty or null values
|
|
||||||
return {
|
return {
|
||||||
'name': contact.displayName ?? '',
|
'name': contact.displayName,
|
||||||
'phones': phones
|
'phones': contact.phones.map((p) => p.number).toList(),
|
||||||
.where((phone) => phone.normalizedNumber != null)
|
'emails': contact.emails.map((e) => e.address).toList(),
|
||||||
.map((phone) => phone.normalizedNumber ?? 'No number')
|
|
||||||
.toList(),
|
|
||||||
'emails': emails
|
|
||||||
.where((email) => email.address != null)
|
|
||||||
.map((email) => email.address ?? 'No email')
|
|
||||||
.toList(),
|
|
||||||
};
|
};
|
||||||
}).toList());
|
}).toList();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
// **IMPROVEMENT**: Provide feedback if some contacts were filtered out.
|
||||||
|
if (contactsWithPhones < totalContactsOnDevice) {
|
||||||
|
Get.snackbar('Contacts Loaded'.tr,
|
||||||
|
'${'Showing'.tr} $contactsWithPhones ${'of'.tr} $totalContactsOnDevice ${'contacts. Others were hidden because they don\'t have a phone number.'.tr}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar('No contacts available'.tr,
|
Get.snackbar('No contacts found'.tr,
|
||||||
'Please add contacts to your phone.'.tr);
|
'No contacts with phone numbers were found on your device.'.tr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar('Permission denied'.tr,
|
Get.snackbar('Permission denied'.tr,
|
||||||
'Contact permission is required to pick contacts'.tr);
|
'Contact permission is required to pick contacts'.tr);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Log.print('Error picking contacts: $e');
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSelectPassengerInvitation(int index) async {
|
void selectPhone(String phone) {
|
||||||
MyDialog().getDialog(
|
invitePhoneController.text = phone;
|
||||||
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver']
|
update();
|
||||||
.toString()) <
|
Get.back();
|
||||||
2
|
|
||||||
? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} '
|
|
||||||
: 'You deserve the gift'.tr,
|
|
||||||
'${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver'].toString()} / 2 ${'Trip'.tr}',
|
|
||||||
() async {
|
|
||||||
if (int.parse(driverInvitationDataToPassengers[index]
|
|
||||||
['countOfInvitDriver']
|
|
||||||
.toString()) <
|
|
||||||
2) {
|
|
||||||
Get.back();
|
|
||||||
} else {
|
|
||||||
// Claim the gift if 100 trips are completed
|
|
||||||
if (driverInvitationDataToPassengers[index]['isGiftToken']
|
|
||||||
.toString() ==
|
|
||||||
'0') {
|
|
||||||
Get.back();
|
|
||||||
// Add wallet to the inviter
|
|
||||||
await Get.find<PaymentController>().addPassengersWallet('20');
|
|
||||||
// add for invitor too
|
|
||||||
// await Get.find<CaptainWalletController>().addDriverWalletToInvitor(
|
|
||||||
// 'paymentMethod',
|
|
||||||
// driverInvitationData[index]['driverInviterId'],
|
|
||||||
// '50');
|
|
||||||
// Update invitation as claimed
|
|
||||||
await CRUD().post(
|
|
||||||
link: AppLink.updatePassengerGift,
|
|
||||||
payload: {'id': driverInvitationDataToPassengers[index]['id']},
|
|
||||||
);
|
|
||||||
// Notify the inviter
|
|
||||||
NotificationCaptainController().addNotificationCaptain(
|
|
||||||
driverInvitationDataToPassengers[index]['passengerInviterId']
|
|
||||||
.toString(),
|
|
||||||
"You have got a gift for invitation".tr,
|
|
||||||
'${"You have 20".tr} ${'LE'}',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Get.back();
|
|
||||||
MyDialog().getDialog(
|
|
||||||
"You have got a gift".tr,
|
|
||||||
"Share the app with another new passenger".tr,
|
|
||||||
() {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
savePhoneToServer() async {
|
/// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
|
||||||
for (var i = 0; i < contactMaps.length; i++) {
|
/// It handles various user inputs gracefully to produce a standardized international format.
|
||||||
var phones = contactMaps[i]['phones'];
|
String _formatSyrianPhoneNumber(String phone) {
|
||||||
if (phones != null && phones.isNotEmpty && phones[0].isNotEmpty) {
|
// 1. Remove all non-digit characters to clean the input.
|
||||||
var res = await CRUD().post(link: AppLink.savePhones, payload: {
|
String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
|
||||||
"name": contactMaps[i]['name'] ?? 'none',
|
|
||||||
"phones": phones[0] ?? 'none',
|
// 2. If it already starts with the country code, we assume it's correct.
|
||||||
"phones2": phones.join(', ') ??
|
if (digitsOnly.startsWith('963')) {
|
||||||
'none', // Convert List<String> to a comma-separated string
|
return '+$digitsOnly';
|
||||||
});
|
|
||||||
if (res != 'failure') {}
|
|
||||||
} else {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String formatPhoneNumber(String input) {
|
// 3. If it starts with '09' (common local format), remove the leading '0'.
|
||||||
// Remove any non-digit characters
|
if (digitsOnly.startsWith('09')) {
|
||||||
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
|
|
||||||
|
|
||||||
// Ensure the number starts with the country code
|
|
||||||
if (digitsOnly.startsWith('20')) {
|
|
||||||
digitsOnly = digitsOnly.substring(1);
|
digitsOnly = digitsOnly.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return digitsOnly;
|
// 4. Prepend the Syrian country code.
|
||||||
|
return '+963$digitsOnly';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and
|
||||||
|
/// sends a much-improved, user-friendly WhatsApp message.
|
||||||
void sendInviteToPassenger() async {
|
void sendInviteToPassenger() async {
|
||||||
if (invitePhoneController.text.isEmpty ||
|
if (invitePhoneController.text.isEmpty ||
|
||||||
invitePhoneController.text.length < 11) {
|
invitePhoneController.text.length < 9) {
|
||||||
mySnackeBarError('Please enter a correct phone'.tr);
|
mySnackeBarError('Please enter a correct phone'.tr);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
String phoneNumber = formatPhoneNumber(invitePhoneController.text);
|
// Use the new formatting function to ensure the number is correct.
|
||||||
|
String formattedPhoneNumber =
|
||||||
|
_formatSyrianPhoneNumber(invitePhoneController.text);
|
||||||
|
|
||||||
var response =
|
var response =
|
||||||
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
||||||
"driverId": box.read(BoxName.passengerID),
|
"driverId": box.read(BoxName.passengerID),
|
||||||
"inviterPassengerPhone": ('+2$phoneNumber')
|
"inviterPassengerPhone": formattedPhoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response != 'failure') {
|
if (response != 'failure') {
|
||||||
var d = response;
|
var d = response;
|
||||||
Get.snackbar('Success', 'Invite sent successfully'.tr);
|
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
|
||||||
|
backgroundColor: Colors.green, colorText: Colors.white);
|
||||||
|
|
||||||
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
|
String expirationTime = d['message']['expirationTime'].toString();
|
||||||
'${"Use this code in registration".tr}\n'
|
String inviteCode = d['message']['inviteCode'].toString();
|
||||||
'${"To get a gift for both".tr}\n\n'
|
|
||||||
'${"The period of this code is 1 hour".tr}\n\n'
|
|
||||||
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
|
|
||||||
'_*${d['message']['inviteCode'].toString()}*_\n\n'
|
|
||||||
'${"Install our app:".tr}\n'
|
|
||||||
'*Android:* https://play.google.com/store/apps/details?id=com.mobileapp.store.ride\n\n\n'
|
|
||||||
'*iOS:* https://apps.apple.com/us/app/sefer/id6458734951';
|
|
||||||
|
|
||||||
launchCommunication('whatsapp', '+2$phoneNumber', message);
|
// New and improved WhatsApp message for better user engagement.
|
||||||
|
String message =
|
||||||
|
"👋 ${'Hello! I\'m inviting you to try Intaleq.'.tr}\n\n"
|
||||||
|
"🎁 ${'Use my invitation code to get a special gift on your first ride!'.tr}\n\n"
|
||||||
|
"${'Your personal invitation code is:'.tr}\n"
|
||||||
|
"*$inviteCode*\n\n"
|
||||||
|
"⏳ ${'Be sure to use it quickly! This code expires at'.tr} *$expirationTime*.\n\n"
|
||||||
|
"📲 ${'Download the app now:'.tr}\n"
|
||||||
|
"• *Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n"
|
||||||
|
"• *iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179\n\n"
|
||||||
|
"${'See you on the road!'.tr} 🚗";
|
||||||
|
|
||||||
invitePhoneController.clear();
|
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
||||||
} else {
|
invitePhoneController.clear();
|
||||||
Get.snackbar('Error'.tr, "Invite code already used".tr,
|
update();
|
||||||
backgroundColor: AppColor.redColor,
|
} else {
|
||||||
duration: const Duration(seconds: 4));
|
Get.snackbar(
|
||||||
|
'Error'.tr, "This phone number has already been invited.".tr,
|
||||||
|
backgroundColor: AppColor.redColor,
|
||||||
|
duration: const Duration(seconds: 4));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("Error sending invite: $e");
|
||||||
|
Get.snackbar(
|
||||||
|
'Error'.tr, 'An unexpected error occurred. Please try again.'.tr,
|
||||||
|
backgroundColor: AppColor.redColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is dependent on the `pickContacts` method filtering out contacts without phones.
|
||||||
|
savePhoneToServer() async {
|
||||||
|
for (var contactMap in contactMaps) {
|
||||||
|
// The `pickContacts` function ensures the 'phones' list is not empty here.
|
||||||
|
var phones = contactMap['phones'] as List<String>;
|
||||||
|
var res = await CRUD().post(link: AppLink.savePhones, payload: {
|
||||||
|
"name": contactMap['name'] ?? 'No Name',
|
||||||
|
"phones": phones.first, // Safely access the first phone number
|
||||||
|
"phones2": phones.join(', '),
|
||||||
|
});
|
||||||
|
if (res == 'failure') {
|
||||||
|
Log.print('Failed to save contact: ${contactMap['name']}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSelectPassengerInvitation(int index) async {
|
||||||
|
try {
|
||||||
|
final invitation = driverInvitationDataToPassengers[index];
|
||||||
|
final tripCount =
|
||||||
|
int.tryParse(invitation['countOfInvitDriver'].toString()) ?? 0;
|
||||||
|
final passengerName = invitation['passengerName'].toString();
|
||||||
|
final isGiftTaken = invitation['isGiftToken'].toString() == '1';
|
||||||
|
|
||||||
|
if (tripCount >= 2) {
|
||||||
|
// Gift can be claimed
|
||||||
|
if (!isGiftTaken) {
|
||||||
|
MyDialog().getDialog(
|
||||||
|
'You deserve the gift'.tr,
|
||||||
|
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
|
||||||
|
() async {
|
||||||
|
Get.back(); // Close dialog first
|
||||||
|
await Get.find<PaymentController>().addPassengersWallet('20');
|
||||||
|
await CRUD().post(
|
||||||
|
link: AppLink.updatePassengerGift,
|
||||||
|
payload: {'id': invitation['id']},
|
||||||
|
);
|
||||||
|
NotificationCaptainController().addNotificationCaptain(
|
||||||
|
invitation['passengerInviterId'].toString(),
|
||||||
|
"You have got a gift for invitation".tr,
|
||||||
|
'${"You have earned 20".tr} ${'LE'}',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
fetchDriverStatsPassengers(); // Refresh list
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
MyDialog().getDialog(
|
||||||
|
"Gift Already Claimed".tr,
|
||||||
|
"You have already received your gift for inviting $passengerName."
|
||||||
|
.tr,
|
||||||
|
() => Get.back(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Gift not yet earned
|
||||||
|
MyDialog().getDialog(
|
||||||
|
'${'Keep it up!'.tr}',
|
||||||
|
'$passengerName ${'has completed'.tr} $tripCount / 2 ${'trips'.tr}. ${"You can claim your gift once they complete 2 trips.".tr}',
|
||||||
|
() => Get.back(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("Error in onSelectPassengerInvitation: $e");
|
||||||
}
|
}
|
||||||
// } catch (e) {
|
|
||||||
// Get.snackbar('Error', 'An error occurred'.tr);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@ import 'package:Intaleq/constant/colors.dart';
|
|||||||
import 'package:Intaleq/constant/style.dart';
|
import 'package:Intaleq/constant/style.dart';
|
||||||
import 'package:Intaleq/main.dart';
|
import 'package:Intaleq/main.dart';
|
||||||
import 'package:Intaleq/views/widgets/my_scafold.dart';
|
import 'package:Intaleq/views/widgets/my_scafold.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../constant/info.dart';
|
import '../../constant/info.dart';
|
||||||
import '../../controller/auth/apple_signin_controller.dart';
|
import '../../controller/auth/apple_signin_controller.dart';
|
||||||
@@ -73,7 +75,11 @@ class LoginPage extends StatelessWidget {
|
|||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
color: AppColor.blueColor,
|
color: AppColor.blueColor,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
recognizer: TapGestureRecognizer()..onTap = () {}),
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
launchUrl(Uri.parse(
|
||||||
|
'https://intaleq.xyz/intaleq/privacy_policy.php'));
|
||||||
|
}),
|
||||||
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
|
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -88,7 +94,7 @@ class LoginPage extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar'
|
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar'
|
||||||
? AppInformation.privacyPolicyArabic
|
? AppInformation.privacyPolicyArabic
|
||||||
: AppInformation.privacyPolicyEnglish),
|
: AppInformation.privacyPolicy),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ class AuthScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Image.asset('assets/images/logo.gif', height: 120),
|
Image.asset('assets/images/logo.gif', height: 120),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// Get.find<LoginController>().getJWT();
|
||||||
|
// },
|
||||||
|
// icon: const Icon(Icons.add),
|
||||||
|
// ),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ class AboutPage extends StatelessWidget {
|
|||||||
|
|
||||||
// Company Name and Introduction
|
// Company Name and Introduction
|
||||||
Text(
|
Text(
|
||||||
'Tripz LLC',
|
'Intaleq LLC',
|
||||||
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Egypt\'s pioneering ride-sharing service, proudly developed by Arabian and local owners. We prioritize being near you – both our valued passengers and our dedicated captains.'
|
"Syria's pioneering ride-sharing service, proudly developed by Arabian and local owners. We prioritize being near you – both our valued passengers and our dedicated captains."
|
||||||
.tr,
|
.tr,
|
||||||
style: CupertinoTheme.of(context).textTheme.textStyle,
|
style: CupertinoTheme.of(context).textTheme.textStyle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -43,7 +43,7 @@ class AboutPage extends StatelessWidget {
|
|||||||
|
|
||||||
// Key Features Section
|
// Key Features Section
|
||||||
Text(
|
Text(
|
||||||
'Why Choose Tripz?'.tr,
|
'Why Choose Intaleq?'.tr,
|
||||||
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -432,8 +432,7 @@ class ShareAppPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
controller.formatPhoneNumber(
|
(contact['phones'][0].toString()),
|
||||||
contact['phones'][0].toString()),
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: CupertinoColors.secondaryLabel,
|
color: CupertinoColors.secondaryLabel,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:Intaleq/views/widgets/my_scafold.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
@@ -8,205 +6,258 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
import '../../../controller/functions/audio_record1.dart';
|
import '../../../controller/functions/audio_record1.dart';
|
||||||
|
|
||||||
class TripsRecordedPage extends StatelessWidget {
|
class TripsRecordedPage extends StatelessWidget {
|
||||||
const TripsRecordedPage({
|
const TripsRecordedPage({super.key});
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MyScafolld(
|
// Ensure the controller is available.
|
||||||
title: 'Trips recorded'.tr,
|
// If it's not initialized elsewhere, you can use Get.put() or Get.lazyPut() here.
|
||||||
body: [
|
// Get.lazyPut(() => AudioRecorderController());
|
||||||
GetBuilder<AudioRecorderController>(builder: (audio) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
FutureBuilder<List<String>>(
|
|
||||||
future: audio.getRecordedFiles(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(
|
|
||||||
child: CupertinoActivityIndicator());
|
|
||||||
} else if (snapshot.hasData) {
|
|
||||||
final recordedFiles = snapshot.data!;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: CupertinoButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: () async {
|
|
||||||
String? selectedFile =
|
|
||||||
await showCupertinoModalPopup<String>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CupertinoActionSheet(
|
|
||||||
title: Text('Select a File'.tr),
|
|
||||||
actions: recordedFiles
|
|
||||||
.map(
|
|
||||||
(file) => CupertinoActionSheetAction(
|
|
||||||
child: Text(path.basename(file)),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(file);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (selectedFile != null) {
|
|
||||||
audio.selectedFilePath = selectedFile;
|
|
||||||
audio.playRecordedFile(selectedFile);
|
|
||||||
audio.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
audio.selectedFilePath != null
|
|
||||||
? path.basename(audio.selectedFilePath!)
|
|
||||||
: 'Select a File'.tr,
|
|
||||||
style: CupertinoTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.actionTextStyle
|
|
||||||
.copyWith(color: CupertinoColors.activeBlue),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text('Error: ${snapshot.error}'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
// Cupertino-style slider for seeking audio
|
return Scaffold(
|
||||||
Padding(
|
appBar: AppBar(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
title: Text('Trips recorded'.tr),
|
||||||
child: CupertinoSlider(
|
backgroundColor: Colors.white,
|
||||||
value: audio.totalDuration > 0
|
elevation: 1,
|
||||||
? audio.currentPosition / audio.totalDuration
|
actions: [
|
||||||
: 0.0, // Normalize to a value between 0.0 and 1.0
|
GetBuilder<AudioRecorderController>(
|
||||||
min: 0.0,
|
builder: (controller) => IconButton(
|
||||||
max: 1.0, // Maximum value is now 1.0
|
tooltip: 'Delete All'.tr,
|
||||||
activeColor: CupertinoColors.activeBlue,
|
icon: const Icon(Icons.delete_sweep_outlined),
|
||||||
onChanged: (value) {
|
onPressed: () {
|
||||||
final newPosition = value * audio.totalDuration;
|
_showDeleteConfirmation(context, controller, isDeleteAll: true);
|
||||||
audio.currentPosition = newPosition;
|
},
|
||||||
audio.audioPlayer
|
),
|
||||||
.seek(Duration(seconds: newPosition.toInt()));
|
)
|
||||||
audio.update();
|
],
|
||||||
},
|
),
|
||||||
),
|
body: GetBuilder<AudioRecorderController>(
|
||||||
),
|
builder: (controller) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildRecordingsList(controller),
|
||||||
|
),
|
||||||
|
// Show player controls only when a file is selected
|
||||||
|
if (controller.selectedFilePath != null)
|
||||||
|
_buildPlayerControls(context, controller),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// iOS-style playback controls
|
/// Builds the list of recorded audio files.
|
||||||
Padding(
|
Widget _buildRecordingsList(AudioRecorderController controller) {
|
||||||
padding: const EdgeInsets.symmetric(
|
return FutureBuilder<List<String>>(
|
||||||
vertical: 16.0, horizontal: 16.0),
|
future: controller.getRecordedFiles(),
|
||||||
child: Row(
|
builder: (context, snapshot) {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
children: [
|
return const Center(child: CircularProgressIndicator());
|
||||||
CupertinoButton(
|
}
|
||||||
padding: EdgeInsets.zero,
|
if (snapshot.hasError) {
|
||||||
child: Icon(
|
return Center(child: Text('Error: ${snapshot.error}'.tr));
|
||||||
audio.isPlaying
|
}
|
||||||
? CupertinoIcons.pause
|
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
: CupertinoIcons.play_arrow,
|
return Center(
|
||||||
color: CupertinoColors.activeBlue,
|
child: Column(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onPressed: () {
|
children: [
|
||||||
if (audio.isPlaying) {
|
Icon(Icons.mic_off_outlined, size: 80, color: Colors.grey[400]),
|
||||||
audio.pausePlayback();
|
const SizedBox(height: 16),
|
||||||
} else {
|
Text(
|
||||||
audio.resumePlayback();
|
'No Recordings Found'.tr,
|
||||||
}
|
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
|
||||||
audio.update();
|
),
|
||||||
},
|
const SizedBox(height: 8),
|
||||||
),
|
Padding(
|
||||||
CupertinoButton(
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
||||||
padding: EdgeInsets.zero,
|
child: Text(
|
||||||
child: const Icon(CupertinoIcons.stop,
|
'Record your trips to see them here.'.tr,
|
||||||
color: CupertinoColors.destructiveRed),
|
textAlign: TextAlign.center,
|
||||||
onPressed: () {
|
style: TextStyle(color: Colors.grey[500]),
|
||||||
audio.stopPlayback();
|
|
||||||
audio.update();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
CupertinoButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
child: const Icon(CupertinoIcons.delete,
|
|
||||||
color: CupertinoColors.destructiveRed),
|
|
||||||
onPressed: () async {
|
|
||||||
showCupertinoModalPopup(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CupertinoActionSheet(
|
|
||||||
title: Text('Are you sure?'.tr),
|
|
||||||
message: Text(
|
|
||||||
'This will delete all recorded files from your device.'
|
|
||||||
.tr,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
isDestructiveAction: true,
|
|
||||||
onPressed: () async {
|
|
||||||
await audio.deleteAllRecordedFiles();
|
|
||||||
Navigator.pop(context);
|
|
||||||
audio.update();
|
|
||||||
},
|
|
||||||
child: Text('Delete'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
cancelButton: CupertinoActionSheetAction(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: Text('Cancel'.tr),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// File selection and sharing
|
final recordedFiles = snapshot.data!;
|
||||||
if (audio.selectedFilePath != null)
|
return ListView.builder(
|
||||||
Align(
|
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
||||||
alignment: Alignment.bottomCenter,
|
itemCount: recordedFiles.length,
|
||||||
child: Container(
|
itemBuilder: (context, index) {
|
||||||
padding: const EdgeInsets.all(16.0),
|
final file = recordedFiles[index];
|
||||||
color: CupertinoColors.systemGrey6,
|
final fileName = path.basename(file);
|
||||||
child: Row(
|
final isSelected = controller.selectedFilePath == file;
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
return Card(
|
||||||
Text(
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
'Selected file: ${path.basename(audio.selectedFilePath!)}',
|
elevation: isSelected ? 4 : 1,
|
||||||
style: CupertinoTheme.of(context)
|
shape: RoundedRectangleBorder(
|
||||||
.textTheme
|
borderRadius: BorderRadius.circular(12)),
|
||||||
.textStyle,
|
child: ListTile(
|
||||||
),
|
leading: Icon(
|
||||||
CupertinoButton(
|
isSelected && controller.isPlaying
|
||||||
padding: EdgeInsets.zero,
|
? Icons.pause_circle_filled
|
||||||
child: const Icon(CupertinoIcons.share),
|
: Icons.play_circle_fill,
|
||||||
onPressed: () {
|
color:
|
||||||
Share.shareXFiles(
|
isSelected ? Theme.of(context).primaryColor : Colors.grey,
|
||||||
[XFile(audio.selectedFilePath!)]);
|
size: 40,
|
||||||
},
|
),
|
||||||
),
|
title: Text(fileName,
|
||||||
],
|
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||||
),
|
subtitle: Text("Audio Recording".tr),
|
||||||
),
|
onTap: () {
|
||||||
),
|
if (isSelected) {
|
||||||
],
|
controller.isPlaying
|
||||||
|
? controller.pausePlayback()
|
||||||
|
: controller.resumePlayback();
|
||||||
|
} else {
|
||||||
|
controller.playRecordedFile(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: isSelected,
|
||||||
|
selectedTileColor:
|
||||||
|
Theme.of(context).primaryColor.withOpacity(0.1),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
})
|
},
|
||||||
],
|
);
|
||||||
isleading: true);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the player UI at the bottom of the screen.
|
||||||
|
Widget _buildPlayerControls(
|
||||||
|
BuildContext context, AudioRecorderController controller) {
|
||||||
|
final fileName = path.basename(controller.selectedFilePath!);
|
||||||
|
final positionText = Duration(seconds: controller.currentPosition.toInt())
|
||||||
|
.toString()
|
||||||
|
.split('.')
|
||||||
|
.first
|
||||||
|
.padLeft(8, '0')
|
||||||
|
.substring(3);
|
||||||
|
final durationText = Duration(seconds: controller.totalDuration.toInt())
|
||||||
|
.toString()
|
||||||
|
.split('.')
|
||||||
|
.first
|
||||||
|
.padLeft(8, '0')
|
||||||
|
.substring(3);
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
elevation: 10,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(fileName,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(positionText),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: (controller.totalDuration > 0)
|
||||||
|
? controller.currentPosition / controller.totalDuration
|
||||||
|
: 0.0,
|
||||||
|
onChanged: (value) {
|
||||||
|
final newPosition = value * controller.totalDuration;
|
||||||
|
controller.audioPlayer
|
||||||
|
.seek(Duration(seconds: newPosition.toInt()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(durationText),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.share_outlined),
|
||||||
|
tooltip: 'Share'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
Share.shareXFiles([XFile(controller.selectedFilePath!)]);
|
||||||
|
},
|
||||||
|
iconSize: 28,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(controller.isPlaying
|
||||||
|
? Icons.pause_circle_filled
|
||||||
|
: Icons.play_circle_filled),
|
||||||
|
onPressed: () {
|
||||||
|
controller.isPlaying
|
||||||
|
? controller.pausePlayback()
|
||||||
|
: controller.resumePlayback();
|
||||||
|
},
|
||||||
|
iconSize: 56,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon:
|
||||||
|
const Icon(Icons.delete_outline, color: Colors.redAccent),
|
||||||
|
tooltip: 'Delete'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
_showDeleteConfirmation(context, controller,
|
||||||
|
isDeleteAll: false);
|
||||||
|
},
|
||||||
|
iconSize: 28,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a confirmation dialog for deleting one or all files.
|
||||||
|
void _showDeleteConfirmation(
|
||||||
|
BuildContext context,
|
||||||
|
AudioRecorderController controller, {
|
||||||
|
required bool isDeleteAll,
|
||||||
|
}) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(isDeleteAll
|
||||||
|
? 'Delete All Recordings?'.tr
|
||||||
|
: 'Delete Recording?'.tr),
|
||||||
|
content: Text(isDeleteAll
|
||||||
|
? 'This action cannot be undone.'.tr
|
||||||
|
: 'Are you sure you want to delete this file?'.tr),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('Cancel'.tr),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child:
|
||||||
|
Text('Delete'.tr, style: const TextStyle(color: Colors.red)),
|
||||||
|
onPressed: () async {
|
||||||
|
if (isDeleteAll) {
|
||||||
|
await controller.deleteAllRecordedFiles();
|
||||||
|
} else {
|
||||||
|
// NOTE: You may need to add this method to your controller
|
||||||
|
// if it doesn't exist.
|
||||||
|
// await controller.deleteFile(controller.selectedFilePath!);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class MapPagePassenger extends StatelessWidget {
|
|||||||
const RideFromStartApp(),
|
const RideFromStartApp(),
|
||||||
|
|
||||||
// cancelRidePage(),
|
// cancelRidePage(),
|
||||||
const MenuIconMapPageWidget(),
|
// const MenuIconMapPageWidget(),
|
||||||
PointsPageForRider()
|
PointsPageForRider()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,15 +40,19 @@ List<CarType> carTypes = [
|
|||||||
carType: 'Electric',
|
carType: 'Electric',
|
||||||
carDetail: 'Quiet & Eco-Friendly'.tr,
|
carDetail: 'Quiet & Eco-Friendly'.tr,
|
||||||
image:
|
image:
|
||||||
'assets/images/electric_car.jpg'), // Third choice - NOTE: Use your actual image path
|
'assets/images/electric.png'), // Third choice - NOTE: Use your actual image path
|
||||||
CarType(
|
CarType(
|
||||||
carType: 'Lady',
|
carType: 'Lady',
|
||||||
carDetail: 'Lady Captain for girls'.tr,
|
carDetail: 'Lady Captain for girls'.tr,
|
||||||
image: 'assets/images/lady.png'),
|
image: 'assets/images/lady.png'),
|
||||||
CarType(
|
CarType(
|
||||||
carType: 'Scooter',
|
carType: 'Van',
|
||||||
carDetail: 'Delivery service'.tr,
|
carDetail: 'Van for familly'.tr,
|
||||||
image: 'assets/images/moto.png'),
|
image: 'assets/images/bus.png'),
|
||||||
|
// CarType(
|
||||||
|
// carType: 'Scooter',
|
||||||
|
// carDetail: 'Delivery service'.tr,
|
||||||
|
// image: 'assets/images/moto.png'),
|
||||||
CarType(
|
CarType(
|
||||||
carType: 'Rayeh Gai',
|
carType: 'Rayeh Gai',
|
||||||
carDetail: "Best choice for cities".tr,
|
carDetail: "Best choice for cities".tr,
|
||||||
@@ -365,6 +369,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1);
|
return mapPassengerController.totalPassengerBalash.toStringAsFixed(1);
|
||||||
case 'Scooter':
|
case 'Scooter':
|
||||||
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
|
return mapPassengerController.totalPassengerScooter.toStringAsFixed(1);
|
||||||
|
case 'Van':
|
||||||
|
return mapPassengerController.totalPassengerVan.toStringAsFixed(1);
|
||||||
case 'Lady':
|
case 'Lady':
|
||||||
return mapPassengerController.totalPassengerLady.toStringAsFixed(1);
|
return mapPassengerController.totalPassengerLady.toStringAsFixed(1);
|
||||||
case 'Pink Bike':
|
case 'Pink Bike':
|
||||||
@@ -476,6 +482,9 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.'
|
return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.'
|
||||||
.tr;
|
.tr;
|
||||||
case 'Scooter':
|
case 'Scooter':
|
||||||
|
case 'Van':
|
||||||
|
return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together."
|
||||||
|
.tr;
|
||||||
case 'Pink Bike':
|
case 'Pink Bike':
|
||||||
return 'This is for delivery or a motorcycle.'.tr;
|
return 'This is for delivery or a motorcycle.'.tr;
|
||||||
case 'Mishwar Vip':
|
case 'Mishwar Vip':
|
||||||
@@ -556,6 +565,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
|
|||||||
return mapPassengerController.totalPassengerElectric;
|
return mapPassengerController.totalPassengerElectric;
|
||||||
case 'Awfar Car':
|
case 'Awfar Car':
|
||||||
return mapPassengerController.totalPassengerBalash;
|
return mapPassengerController.totalPassengerBalash;
|
||||||
|
case 'Van':
|
||||||
|
return mapPassengerController.totalPassengerVan;
|
||||||
case 'Lady':
|
case 'Lady':
|
||||||
return mapPassengerController.totalPassengerLady;
|
return mapPassengerController.totalPassengerLady;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class CashConfirmPageShown extends StatelessWidget {
|
|||||||
// بطاقة المحفظة
|
// بطاقة المحفظة
|
||||||
_buildPaymentOptionCard(
|
_buildPaymentOptionCard(
|
||||||
icon: Icons.account_balance_wallet_outlined,
|
icon: Icons.account_balance_wallet_outlined,
|
||||||
title: '${AppInformation.appName} Wallet'.tr,
|
title: '${AppInformation.appName} Balance'.tr,
|
||||||
subtitle:
|
subtitle:
|
||||||
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
|
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
|
||||||
isSelected: paymentCtrl.isWalletChecked,
|
isSelected: paymentCtrl.isWalletChecked,
|
||||||
@@ -115,7 +115,7 @@ class CashConfirmPageShown extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
MyElevatedButton(
|
MyElevatedButton(
|
||||||
title: 'Top up Wallet to continue'.tr,
|
title: 'Top up Balance to continue'.tr,
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Get.to(() => const PassengerWallet()),
|
Get.to(() => const PassengerWallet()),
|
||||||
kolor: AppColor.redColor,
|
kolor: AppColor.redColor,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
@@ -6,15 +8,15 @@ import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
|
|||||||
import '../../../constant/colors.dart';
|
import '../../../constant/colors.dart';
|
||||||
import '../../../constant/style.dart';
|
import '../../../constant/style.dart';
|
||||||
import '../../../controller/functions/location_controller.dart';
|
import '../../../controller/functions/location_controller.dart';
|
||||||
|
import '../../../controller/home/device_tier.dart';
|
||||||
import '../../../controller/home/map_passenger_controller.dart';
|
import '../../../controller/home/map_passenger_controller.dart';
|
||||||
import '../../widgets/mycircular.dart';
|
import '../../widgets/mycircular.dart';
|
||||||
import '../../widgets/mydialoug.dart';
|
import '../../widgets/mydialoug.dart';
|
||||||
|
|
||||||
class GoogleMapPassengerWidget extends StatelessWidget {
|
class GoogleMapPassengerWidget extends StatelessWidget {
|
||||||
GoogleMapPassengerWidget({
|
GoogleMapPassengerWidget({super.key});
|
||||||
super.key,
|
|
||||||
});
|
final WayPointController wayPointController = Get.put(WayPointController());
|
||||||
WayPointController wayPointController = Get.put(WayPointController());
|
|
||||||
final LocationController locationController = Get.find<LocationController>();
|
final LocationController locationController = Get.find<LocationController>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,19 +32,25 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
|||||||
child: GoogleMap(
|
child: GoogleMap(
|
||||||
onMapCreated: controller.onMapCreated,
|
onMapCreated: controller.onMapCreated,
|
||||||
|
|
||||||
|
// ✅ حدود الكاميرا كما هي
|
||||||
cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
|
cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
|
||||||
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
|
|
||||||
|
// ✅ Zoom أهدأ للأجهزة الضعيفة
|
||||||
|
minMaxZoomPreference: controller.lowPerf
|
||||||
|
? const MinMaxZoomPreference(6, 17)
|
||||||
|
: const MinMaxZoomPreference(6, 18),
|
||||||
|
|
||||||
onLongPress: (argument) {
|
onLongPress: (argument) {
|
||||||
MyDialog().getDialog('Are you want to go to this site'.tr, '',
|
MyDialog().getDialog('Are you want to go to this site'.tr, '',
|
||||||
() async {
|
() async {
|
||||||
controller.clearPolyline();
|
controller.clearPolyline();
|
||||||
if (controller.dataCarsLocationByPassenger != null) {
|
if (controller.dataCarsLocationByPassenger != null) {
|
||||||
await controller.getDirectionMap(
|
await controller.getDirectionMap(
|
||||||
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
||||||
'${argument.latitude.toString()},${argument.longitude.toString()}');
|
'${argument.latitude},${argument.longitude}',
|
||||||
|
);
|
||||||
Get.back();
|
Get.back();
|
||||||
controller.bottomSheet();
|
await controller.bottomSheet();
|
||||||
controller.showBottomSheet1();
|
controller.showBottomSheet1();
|
||||||
} else {
|
} else {
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -54,15 +62,12 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
|||||||
duration: const Duration(seconds: 11),
|
duration: const Duration(seconds: 11),
|
||||||
instantInit: true,
|
instantInit: true,
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
titleText: Text(
|
titleText: Text('Error'.tr,
|
||||||
'Error'.tr,
|
style: const TextStyle(color: AppColor.redColor)),
|
||||||
style: const TextStyle(color: AppColor.redColor),
|
|
||||||
),
|
|
||||||
messageText: Text(
|
messageText: Text(
|
||||||
'We Are Sorry That we dont have cars in your Location!'
|
'We Are Sorry That we dont have cars in your Location!'
|
||||||
.tr,
|
.tr,
|
||||||
style: AppStyle.title,
|
style: AppStyle.title),
|
||||||
),
|
|
||||||
icon: const Icon(Icons.error),
|
icon: const Icon(Icons.error),
|
||||||
shouldIconPulse: true,
|
shouldIconPulse: true,
|
||||||
maxWidth: double.infinity,
|
maxWidth: double.infinity,
|
||||||
@@ -86,370 +91,98 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
|||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
// mainButton: TextButton(
|
|
||||||
// onPressed: () {
|
|
||||||
// controller.getCarsLocationByPassenger();
|
|
||||||
// },
|
|
||||||
// child: Text(
|
|
||||||
// 'Try Again'.tr,
|
|
||||||
// style: const TextStyle(
|
|
||||||
// color: AppColor.secondaryColor),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
onTap: (GetSnackBar snackBar) {
|
|
||||||
// Do something when the snackbar is tapped.
|
|
||||||
},
|
|
||||||
isDismissible: true,
|
isDismissible: true,
|
||||||
showProgressIndicator: false,
|
showProgressIndicator: false,
|
||||||
dismissDirection: DismissDirection.up,
|
dismissDirection: DismissDirection.up,
|
||||||
progressIndicatorController: null,
|
|
||||||
progressIndicatorBackgroundColor: Colors.transparent,
|
|
||||||
progressIndicatorValueColor: null,
|
|
||||||
snackStyle: SnackStyle.GROUNDED,
|
snackStyle: SnackStyle.GROUNDED,
|
||||||
forwardAnimationCurve: Curves.easeInToLinear,
|
forwardAnimationCurve: Curves.easeInToLinear,
|
||||||
reverseAnimationCurve: Curves.easeInOut,
|
reverseAnimationCurve: Curves.easeInOut,
|
||||||
animationDuration: const Duration(milliseconds: 4000),
|
animationDuration: const Duration(milliseconds: 4000),
|
||||||
barBlur: 8,
|
barBlur: 8,
|
||||||
overlayBlur: 0,
|
|
||||||
snackbarStatus: null,
|
|
||||||
overlayColor: AppColor.primaryColor.withOpacity(0.5),
|
overlayColor: AppColor.primaryColor.withOpacity(0.5),
|
||||||
userInputForm: null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
});
|
});
|
||||||
// Get.defaultDialog(
|
|
||||||
// title: 'Are you want to go to this site'.tr,
|
|
||||||
// content: Column(
|
|
||||||
// children: [
|
|
||||||
// Text('${argument.latitude},${argument.longitude}'),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// confirm: MyElevatedButton(
|
|
||||||
// title: 'Ok'.tr,
|
|
||||||
// onPressed: () async {
|
|
||||||
// controller.clearPolyline();
|
|
||||||
// if (controller.dataCarsLocationByPassenger != null) {
|
|
||||||
// await controller.getMap(
|
|
||||||
// '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
|
||||||
// '${argument.latitude.toString()},${argument.longitude.toString()}');
|
|
||||||
|
|
||||||
// Get.back();
|
|
||||||
// controller.bottomSheet();
|
|
||||||
// controller.showBottomSheet1();
|
|
||||||
// } else {
|
|
||||||
// Get.back();
|
|
||||||
// Get.snackbar(
|
|
||||||
// 'We Are Sorry That we dont have cars in your Location!'
|
|
||||||
// .tr,
|
|
||||||
// '',
|
|
||||||
// colorText: AppColor.redColor,
|
|
||||||
// duration: const Duration(seconds: 11),
|
|
||||||
// instantInit: true,
|
|
||||||
// snackPosition: SnackPosition.TOP,
|
|
||||||
// titleText: Text(
|
|
||||||
// 'Error'.tr,
|
|
||||||
// style:
|
|
||||||
// const TextStyle(color: AppColor.redColor),
|
|
||||||
// ),
|
|
||||||
// messageText: Text(
|
|
||||||
// 'We Are Sorry That we dont have cars in your Location!'
|
|
||||||
// .tr,
|
|
||||||
// style: AppStyle.title,
|
|
||||||
// ),
|
|
||||||
// icon: const Icon(Icons.error),
|
|
||||||
// shouldIconPulse: true,
|
|
||||||
// maxWidth: double.infinity,
|
|
||||||
// margin: const EdgeInsets.all(16),
|
|
||||||
// padding: const EdgeInsets.all(16),
|
|
||||||
// borderRadius: 8,
|
|
||||||
// borderColor: AppColor.redColor,
|
|
||||||
// borderWidth: 2,
|
|
||||||
// backgroundColor: AppColor.secondaryColor,
|
|
||||||
// leftBarIndicatorColor: AppColor.redColor,
|
|
||||||
// boxShadows: [
|
|
||||||
// BoxShadow(
|
|
||||||
// color: Colors.black.withOpacity(0.25),
|
|
||||||
// blurRadius: 4,
|
|
||||||
// spreadRadius: 2,
|
|
||||||
// offset: const Offset(0, 4),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// backgroundGradient: const LinearGradient(
|
|
||||||
// colors: [
|
|
||||||
// AppColor.redColor,
|
|
||||||
// AppColor.accentColor
|
|
||||||
// ],
|
|
||||||
// begin: Alignment.topLeft,
|
|
||||||
// end: Alignment.bottomRight,
|
|
||||||
// ),
|
|
||||||
// // mainButton: TextButton(
|
|
||||||
// // onPressed: () {
|
|
||||||
// // controller.getCarsLocationByPassenger();
|
|
||||||
// // },
|
|
||||||
// // child: Text(
|
|
||||||
// // 'Try Again'.tr,
|
|
||||||
// // style: const TextStyle(
|
|
||||||
// // color: AppColor.secondaryColor),
|
|
||||||
// // ),
|
|
||||||
// // ),
|
|
||||||
// onTap: (GetSnackBar snackBar) {
|
|
||||||
// // Do something when the snackbar is tapped.
|
|
||||||
// },
|
|
||||||
// isDismissible: true,
|
|
||||||
// showProgressIndicator: false,
|
|
||||||
// dismissDirection: DismissDirection.up,
|
|
||||||
// progressIndicatorController: null,
|
|
||||||
// progressIndicatorBackgroundColor:
|
|
||||||
// Colors.transparent,
|
|
||||||
// progressIndicatorValueColor: null,
|
|
||||||
// snackStyle: SnackStyle.GROUNDED,
|
|
||||||
// forwardAnimationCurve: Curves.easeInToLinear,
|
|
||||||
// reverseAnimationCurve: Curves.easeInOut,
|
|
||||||
// animationDuration:
|
|
||||||
// const Duration(milliseconds: 4000),
|
|
||||||
// barBlur: 8,
|
|
||||||
// overlayBlur: 0,
|
|
||||||
// snackbarStatus: null,
|
|
||||||
// overlayColor:
|
|
||||||
// AppColor.primaryColor.withOpacity(0.5),
|
|
||||||
// userInputForm: null,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onTap: (argument) {
|
onTap: (argument) {
|
||||||
controller.hidePlaces();
|
controller.hidePlaces();
|
||||||
|
|
||||||
// controller.changeBottomSheetShown();
|
|
||||||
// controller.bottomSheet();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
target: controller.passengerLocation,
|
target: controller.passengerLocation,
|
||||||
zoom: 15,
|
zoom: controller.lowPerf ? 14.5 : 15,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ✅ ماركرز (احرص أن الأيقونات محجّمة ومخزّنة Cache في الكنترولر)
|
||||||
markers: controller.markers.toSet(),
|
markers: controller.markers.toSet(),
|
||||||
// {
|
|
||||||
// if (controller.statusRide != 'Apply' ||
|
|
||||||
// !controller.rideTimerBegin)
|
|
||||||
// for (var carLocation in controller.carLocationsModels)
|
|
||||||
|
|
||||||
// // Marker(
|
// ✅ بوليغونز كما هي
|
||||||
// // // anchor: const Offset(4, 4),
|
|
||||||
// // position: LatLng(
|
|
||||||
// // carLocation.latitude,
|
|
||||||
// // carLocation.longitude,
|
|
||||||
// // ),
|
|
||||||
// // icon: controller.carIcon,
|
|
||||||
// // markerId: MarkerId(carLocation.toString()),
|
|
||||||
// // rotation: carLocation.heading,
|
|
||||||
// // ),
|
|
||||||
// // controller.carMarrkerAplied,
|
|
||||||
// if (controller.statusRide == 'Apply')
|
|
||||||
// // for (var carLocation
|
|
||||||
// // in controller.driverCarsLocationToPassengerAfterApplied)
|
|
||||||
// Marker(
|
|
||||||
// // anchor: const Offset(4, 4),
|
|
||||||
// position: LatLng(
|
|
||||||
// double.parse(
|
|
||||||
// controller
|
|
||||||
// .datadriverCarsLocationToPassengerAfterApplied[
|
|
||||||
// 'message'][0]['latitude'],
|
|
||||||
// ),
|
|
||||||
// double.parse(
|
|
||||||
// controller
|
|
||||||
// .datadriverCarsLocationToPassengerAfterApplied[
|
|
||||||
// 'message'][0]['longitude'],
|
|
||||||
// ),
|
|
||||||
// ), //carLocation,
|
|
||||||
// icon: controller.carIcon,
|
|
||||||
// rotation: double.parse(controller
|
|
||||||
// .datadriverCarsLocationToPassengerAfterApplied[
|
|
||||||
// 'message'][0]['heading']),
|
|
||||||
// markerId: MarkerId(controller
|
|
||||||
// .datadriverCarsLocationToPassengerAfterApplied[
|
|
||||||
// 'message'][0]['longitude']
|
|
||||||
// .toString())),
|
|
||||||
// for (int i = 1;
|
|
||||||
// i < controller.coordinatesWithoutEmpty.length - 1;
|
|
||||||
// i++)
|
|
||||||
// Marker(
|
|
||||||
// // anchor: const Offset(4, 4),
|
|
||||||
// position: LatLng(
|
|
||||||
// double.parse(controller.coordinatesWithoutEmpty[i]
|
|
||||||
// .split(',')[0]),
|
|
||||||
// double.parse(controller.coordinatesWithoutEmpty[i]
|
|
||||||
// .split(',')[1])),
|
|
||||||
// icon: controller.tripIcon,
|
|
||||||
// markerId: MarkerId(
|
|
||||||
// controller.coordinatesWithoutEmpty[i].toString())),
|
|
||||||
// if (controller.isMarkersShown)
|
|
||||||
// Marker(
|
|
||||||
// markerId: MarkerId('MyLocation'.tr),
|
|
||||||
// position: controller.newStartPointLocation,
|
|
||||||
// draggable: true,
|
|
||||||
// icon: controller.startIcon,
|
|
||||||
// ),
|
|
||||||
// if (controller.isMarkersShown)
|
|
||||||
// Marker(
|
|
||||||
// markerId: MarkerId('Destination'.tr),
|
|
||||||
// position: controller.myDestination,
|
|
||||||
// draggable: true,
|
|
||||||
// icon: controller.endIcon,
|
|
||||||
// ),
|
|
||||||
// if (controller.haveSteps)
|
|
||||||
// Marker(
|
|
||||||
// markerId: MarkerId('StartSteps'.tr),
|
|
||||||
// position: LatLng(
|
|
||||||
// double.parse(
|
|
||||||
// controller.placesCoordinate[0].split(',')[0]),
|
|
||||||
// double.parse(
|
|
||||||
// controller.placesCoordinate[0].split(',')[1])),
|
|
||||||
// draggable: true,
|
|
||||||
// icon: controller.startIcon,
|
|
||||||
// ),
|
|
||||||
// if (controller.haveSteps)
|
|
||||||
// Marker(
|
|
||||||
// markerId: MarkerId('EndSteps'.tr),
|
|
||||||
// position: controller.latestPosition,
|
|
||||||
// draggable: true,
|
|
||||||
// icon: controller.endIcon,
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
polygons: controller.polygons,
|
polygons: controller.polygons,
|
||||||
polylines: controller.polyLines.toSet(),
|
|
||||||
// {
|
|
||||||
// Polyline(
|
|
||||||
// polylineId: const PolylineId('route'),
|
|
||||||
// points: controller.polylineCoordinates,
|
|
||||||
// color: AppColor.primaryColor,
|
|
||||||
// width: 4,
|
|
||||||
// // patterns: [
|
|
||||||
// // PatternItem.dot,
|
|
||||||
// // PatternItem.gap(10),
|
|
||||||
// // ],
|
|
||||||
// endCap: Cap.roundCap,
|
|
||||||
// startCap: Cap.roundCap,
|
|
||||||
// geodesic: true,
|
|
||||||
// ),
|
|
||||||
|
|
||||||
// Polyline(
|
// ✅ Polyline مُبسّطة للأجهزة الضعيفة (الكنترولر يجهّز مجموعة مبسطة عند lowPerf)
|
||||||
// zIndex: 1,
|
polylines: controller.lowPerf
|
||||||
// consumeTapEvents: true,
|
? controller.polyLinesLight
|
||||||
// geodesic: true,
|
.toSet() // <- استخدم مجموعة خفيفة
|
||||||
// endCap: Cap.buttCap,
|
: controller.polyLines.toSet(),
|
||||||
// startCap: Cap.buttCap,
|
|
||||||
// visible: true,
|
// ✅ دوائر خفيفة على الأجهزة الضعيفة
|
||||||
// polylineId: const PolylineId('route0'),
|
|
||||||
// points: controller.polylineCoordinatesPointsAll[0],
|
|
||||||
// color: AppColor.blueColor,
|
|
||||||
// width: 5,
|
|
||||||
// ),
|
|
||||||
// Polyline(
|
|
||||||
// zIndex: 2,
|
|
||||||
// consumeTapEvents: true,
|
|
||||||
// geodesic: true,
|
|
||||||
// endCap: Cap.buttCap,
|
|
||||||
// startCap: Cap.buttCap,
|
|
||||||
// visible: true,
|
|
||||||
// polylineId: const PolylineId('route1'),
|
|
||||||
// points: controller.polylineCoordinatesPointsAll[1],
|
|
||||||
// color: AppColor.yellowColor,
|
|
||||||
// width: 5,
|
|
||||||
// ),
|
|
||||||
// Polyline(
|
|
||||||
// zIndex: 2,
|
|
||||||
// consumeTapEvents: true,
|
|
||||||
// geodesic: true,
|
|
||||||
// endCap: Cap.buttCap,
|
|
||||||
// startCap: Cap.buttCap,
|
|
||||||
// visible: true,
|
|
||||||
// polylineId: const PolylineId('route2'),
|
|
||||||
// points: controller.polylineCoordinatesPointsAll[2],
|
|
||||||
// color: AppColor.greenColor,
|
|
||||||
// width: 5,
|
|
||||||
// ),
|
|
||||||
// Polyline(
|
|
||||||
// zIndex: 2,
|
|
||||||
// consumeTapEvents: true,
|
|
||||||
// geodesic: true,
|
|
||||||
// endCap: Cap.buttCap,
|
|
||||||
// startCap: Cap.buttCap,
|
|
||||||
// visible: true,
|
|
||||||
// polylineId: const PolylineId('route3'),
|
|
||||||
// points: controller.polylineCoordinatesPointsAll[2],
|
|
||||||
// color: AppColor.deepPurpleAccent,
|
|
||||||
// width: 5,
|
|
||||||
// ),
|
|
||||||
// // Polyline(
|
|
||||||
// // zIndex: 2,
|
|
||||||
// // consumeTapEvents: true,
|
|
||||||
// // geodesic: true,
|
|
||||||
// // endCap: Cap.buttCap,
|
|
||||||
// // startCap: Cap.buttCap,
|
|
||||||
// // visible: true,
|
|
||||||
// // polylineId: PolylineId('g'),
|
|
||||||
// // points: [
|
|
||||||
// // LatLng(controller.southwest.latitude,
|
|
||||||
// // controller.southwest.longitude),
|
|
||||||
// // LatLng(controller.northeast.latitude,
|
|
||||||
// // controller.northeast.longitude)
|
|
||||||
// // ],
|
|
||||||
// // color: AppColor.primaryColor,
|
|
||||||
// // width: 5,
|
|
||||||
// // ),
|
|
||||||
// },
|
|
||||||
// circles: {
|
// circles: {
|
||||||
// Circle(
|
// Circle(
|
||||||
// circleId: const CircleId('kk'),
|
// circleId: const CircleId('circle_id'),
|
||||||
// center: controller.mylocation,
|
// center: controller.passengerLocation,
|
||||||
// radius: 60,
|
// radius: controller.lowPerf ? 80 : 100,
|
||||||
// fillColor: AppColor.primaryColor,)
|
// fillColor:
|
||||||
|
// Colors.blue.withOpacity(controller.lowPerf ? 0.2 : 0.3),
|
||||||
|
// strokeColor: Colors.blue,
|
||||||
|
// strokeWidth: controller.lowPerf ? 1 : 2,
|
||||||
|
// ),
|
||||||
// },
|
// },
|
||||||
|
|
||||||
circles: <Circle>{
|
// ✅ الوضع الخفيف: liteMode + تعطيل الطبقات المكلفة + خريطة Normal
|
||||||
Circle(
|
mapType: controller.lowPerf
|
||||||
circleId: const CircleId('circle_id'),
|
? MapType.normal
|
||||||
center: controller.passengerLocation,
|
: (controller.mapType
|
||||||
radius: 100,
|
? MapType.satellite
|
||||||
fillColor: Colors.blue.withOpacity(0.3),
|
: MapType.terrain),
|
||||||
strokeColor: Colors.blue,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
mapType:
|
myLocationButtonEnabled: false,
|
||||||
controller.mapType ? MapType.satellite : MapType.terrain,
|
|
||||||
myLocationButtonEnabled: true,
|
|
||||||
// liteModeEnabled: true, tiltGesturesEnabled: false,
|
|
||||||
|
|
||||||
// indoorViewEnabled: true,
|
// ⚠️ liteMode (Android فقط): فعّله على الأجهزة الضعيفة
|
||||||
trafficEnabled: controller.mapTrafficON,
|
// liteModeEnabled: controller.lowPerf,
|
||||||
buildingsEnabled: true,
|
liteModeEnabled: Platform.isAndroid ? isLowEnd() : false,
|
||||||
mapToolbarEnabled: true,
|
trafficEnabled: controller.mapTrafficON && !isLowEnd(),
|
||||||
|
buildingsEnabled: !isLowEnd(),
|
||||||
|
// ✅ تقليل الكلفة الرسومية
|
||||||
|
|
||||||
|
mapToolbarEnabled: false,
|
||||||
|
rotateGesturesEnabled: isLowEnd() ? false : true,
|
||||||
|
tiltGesturesEnabled: false, // تعطيل الميلان لتقليل الحمل
|
||||||
|
|
||||||
|
// ✅ Throttle لحركة الكاميرا على الأجهزة الضعيفة
|
||||||
onCameraMove: (position) {
|
onCameraMove: (position) {
|
||||||
int waypointsLength =
|
if (controller.lowPerf) {
|
||||||
Get.find<WayPointController>().wayPoints.length;
|
controller.onCameraMoveThrottled(position);
|
||||||
int index = controller.wayPointIndex;
|
} else {
|
||||||
if (waypointsLength > 0) {
|
// منطقك الحالي
|
||||||
controller.placesCoordinate[index] =
|
int waypointsLength =
|
||||||
'${position.target.latitude.toString()},${position.target.longitude}';
|
Get.find<WayPointController>().wayPoints.length;
|
||||||
|
int index = controller.wayPointIndex;
|
||||||
|
if (waypointsLength > 0) {
|
||||||
|
controller.placesCoordinate[index] =
|
||||||
|
'${position.target.latitude},${position.target.longitude}';
|
||||||
|
}
|
||||||
|
if (controller.startLocationFromMap == true) {
|
||||||
|
controller.newStartPointLocation = position.target;
|
||||||
|
} else if (controller.passengerStartLocationFromMap ==
|
||||||
|
true) {
|
||||||
|
controller.newStartPointLocation = position.target;
|
||||||
|
}
|
||||||
|
controller.newMyLocation = position.target;
|
||||||
}
|
}
|
||||||
if (controller.startLocationFromMap == true) {
|
|
||||||
controller.newStartPointLocation = position.target;
|
|
||||||
} else if (controller.passengerStartLocationFromMap == true) {
|
|
||||||
controller.newStartPointLocation = position.target;
|
|
||||||
}
|
|
||||||
controller.newMyLocation = position.target;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
myLocationEnabled: true,
|
myLocationEnabled: true,
|
||||||
// liteModeEnabled: true,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
import 'package:Intaleq/constant/box_name.dart';
|
|
||||||
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
|
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
|
||||||
import 'package:Intaleq/env/env.dart';
|
|
||||||
import 'package:Intaleq/main.dart';
|
|
||||||
import 'package:Intaleq/views/auth/login_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||||
|
|
||||||
import '../../../constant/char_map.dart';
|
|
||||||
import '../../../constant/colors.dart';
|
import '../../../constant/colors.dart';
|
||||||
import '../../../controller/auth/login_controller.dart';
|
import '../../../controller/auth/login_controller.dart';
|
||||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
|
||||||
import '../../../controller/functions/tts.dart';
|
import '../../../controller/functions/tts.dart';
|
||||||
import '../../../controller/home/map_passenger_controller.dart';
|
import '../../../controller/home/map_passenger_controller.dart';
|
||||||
import '../../../controller/home/vip_waitting_page.dart';
|
import '../../../controller/home/vip_waitting_page.dart';
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../auth/otp_page.dart';
|
import '../../auth/otp_page.dart';
|
||||||
import '../../auth/otp_token_page.dart';
|
|
||||||
|
|
||||||
// --- الدالة الرئيسية بالتصميم الجديد ---
|
// --- الدالة الرئيسية بالتصميم الجديد ---
|
||||||
GetBuilder<MapPassengerController> leftMainMenuIcons() {
|
GetBuilder<MapPassengerController> leftMainMenuIcons() {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
||||||
|
|
||||||
import 'package:Intaleq/constant/box_name.dart';
|
import 'package:Intaleq/constant/box_name.dart';
|
||||||
import 'package:Intaleq/main.dart';
|
import 'package:Intaleq/main.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -10,12 +12,13 @@ import 'package:Intaleq/views/home/profile/complaint_page.dart';
|
|||||||
import 'package:Intaleq/views/home/profile/order_history.dart';
|
import 'package:Intaleq/views/home/profile/order_history.dart';
|
||||||
import 'package:Intaleq/views/home/profile/promos_passenger_page.dart';
|
import 'package:Intaleq/views/home/profile/promos_passenger_page.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
|
|
||||||
|
|
||||||
import '../../../constant/colors.dart';
|
import '../../../constant/colors.dart';
|
||||||
|
import '../../../constant/links.dart';
|
||||||
import '../../../controller/home/map_passenger_controller.dart';
|
import '../../../controller/home/map_passenger_controller.dart';
|
||||||
import '../../notification/notification_page.dart';
|
import '../../notification/notification_page.dart';
|
||||||
import '../HomePage/contact_us.dart';
|
import '../HomePage/contact_us.dart';
|
||||||
|
import '../HomePage/share_app_page.dart';
|
||||||
import '../setting_page.dart';
|
import '../setting_page.dart';
|
||||||
import '../profile/passenger_profile_page.dart';
|
import '../profile/passenger_profile_page.dart';
|
||||||
|
|
||||||
@@ -25,17 +28,25 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// استخدام Get.lazyPut لضمان وجود الكنترولر
|
|
||||||
Get.lazyPut(() => MapPassengerController());
|
Get.lazyPut(() => MapPassengerController());
|
||||||
|
|
||||||
return GetBuilder<MapPassengerController>(
|
return GetBuilder<MapPassengerController>(
|
||||||
builder: (controller) => Stack(
|
builder: (controller) => Stack(
|
||||||
children: [
|
children: [
|
||||||
|
// --- خلفية معتمة عند فتح القائمة ---
|
||||||
|
if (controller.widthMenu > 0)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: controller.getDrawerMenu,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// --- القائمة الجانبية المنزلقة ---
|
// --- القائمة الجانبية المنزلقة ---
|
||||||
_buildSideMenu(controller),
|
_buildSideMenu(controller),
|
||||||
|
|
||||||
// --- زر القائمة العائم ---
|
// --- زر القائمة العائم ---
|
||||||
// _buildMenuButton(controller),
|
_buildMenuButton(controller),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -48,7 +59,7 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
left: 16,
|
left: 16,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: controller.getDrawerMenu, // نفس دالتك القديمة
|
onTap: controller.getDrawerMenu,
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
@@ -64,9 +75,7 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
Border.all(color: AppColor.writeColor.withOpacity(0.2)),
|
Border.all(color: AppColor.writeColor.withOpacity(0.2)),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
controller.widthMenu > 0
|
controller.widthMenu > 0 ? Icons.close : Icons.menu,
|
||||||
? Icons.close_rounded
|
|
||||||
: Icons.menu_rounded,
|
|
||||||
color: AppColor.writeColor,
|
color: AppColor.writeColor,
|
||||||
size: 26,
|
size: 26,
|
||||||
),
|
),
|
||||||
@@ -85,63 +94,100 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
curve: Curves.fastOutSlowIn,
|
curve: Curves.fastOutSlowIn,
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
// تحريك القائمة من خارج الشاشة إلى داخلها
|
|
||||||
left: controller.widthMenu > 0 ? 0 : -Get.width,
|
left: controller.widthMenu > 0 ? 0 : -Get.width,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: Get.width * 0.75, // عرض القائمة
|
width: Get.width * 0.8,
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
constraints: const BoxConstraints(maxWidth: 320),
|
||||||
color: AppColor.secondaryColor.withOpacity(0.9),
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.secondaryColor.withOpacity(0.95),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
blurRadius: 20,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// --- 1. رأس القائمة (معلومات المستخدم) ---
|
|
||||||
_buildMenuHeader(),
|
_buildMenuHeader(),
|
||||||
|
|
||||||
// --- 2. الأزرار السريعة المدمجة ---
|
|
||||||
_buildQuickActionButtons(),
|
_buildQuickActionButtons(),
|
||||||
const Divider(
|
const Divider(
|
||||||
color: AppColor.writeColor,
|
color: Colors.white24,
|
||||||
indent: 16,
|
indent: 16,
|
||||||
endIndent: 16,
|
endIndent: 16,
|
||||||
height: 1),
|
height: 24),
|
||||||
|
|
||||||
// --- 3. قائمة الخيارات الرئيسية ---
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
children: [
|
children: [
|
||||||
IconMainPageMap(
|
MenuListItem(
|
||||||
title: 'My Wallet'.tr,
|
title: 'My Balance'.tr,
|
||||||
icon: Icons.account_balance_wallet_outlined,
|
icon: Icons.account_balance_wallet_outlined,
|
||||||
onTap: () => Get.to(() => const PassengerWallet())),
|
onTap: () => Get.to(() => const PassengerWallet())),
|
||||||
IconMainPageMap(
|
MenuListItem(
|
||||||
title: 'Order History'.tr,
|
title: 'Order History'.tr,
|
||||||
icon: Icons.history_edu_rounded,
|
icon: Icons.history_rounded,
|
||||||
onTap: () => Get.to(() => const OrderHistory())),
|
onTap: () => Get.to(() => const OrderHistory())),
|
||||||
IconMainPageMap(
|
MenuListItem(
|
||||||
title: 'Contact Us'.tr,
|
|
||||||
icon: Icons.contact_support_outlined,
|
|
||||||
onTap: () => Get.to(() => ContactUsPage())),
|
|
||||||
IconMainPageMap(
|
|
||||||
title: 'Driver'.tr,
|
|
||||||
icon: Ionicons.car_sport_outline,
|
|
||||||
onTap: () => _launchDriverAppUrl()),
|
|
||||||
IconMainPageMap(
|
|
||||||
title: 'Complaint'.tr,
|
|
||||||
icon: Icons.feedback_outlined,
|
|
||||||
onTap: () => Get.to(() => ComplaintPage())),
|
|
||||||
IconMainPageMap(
|
|
||||||
title: 'Promos'.tr,
|
title: 'Promos'.tr,
|
||||||
icon: Icons.local_offer_outlined,
|
icon: Icons.local_offer_outlined,
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
Get.to(() => const PromosPassengerPage())),
|
Get.to(() => const PromosPassengerPage())),
|
||||||
|
MenuListItem(
|
||||||
|
title: 'Contact Us'.tr,
|
||||||
|
icon: Icons.contact_support_outlined,
|
||||||
|
onTap: () => Get.to(() => ContactUsPage())),
|
||||||
|
MenuListItem(
|
||||||
|
title: 'Complaint'.tr,
|
||||||
|
icon: Icons.flag_outlined,
|
||||||
|
onTap: () => Get.to(() => ComplaintPage())),
|
||||||
|
MenuListItem(
|
||||||
|
title: 'Driver'.tr,
|
||||||
|
icon: Ionicons.car_sport_outline,
|
||||||
|
onTap: () => _launchDriverAppUrl()),
|
||||||
|
MenuListItem(
|
||||||
|
title: 'Share App'.tr,
|
||||||
|
icon: Icons.share_outlined,
|
||||||
|
onTap: () => Get.to(() => ShareAppPage())),
|
||||||
|
MenuListItem(
|
||||||
|
title: 'Privacy Policy'.tr,
|
||||||
|
icon: Icons.shield_outlined,
|
||||||
|
onTap: () => launchUrl(Uri.parse(
|
||||||
|
'${AppLink.server}/privacy_policy.php')),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Divider(
|
||||||
|
color: Colors.white24,
|
||||||
|
indent: 16,
|
||||||
|
endIndent: 16,
|
||||||
|
height: 1),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: MenuListItem(
|
||||||
|
title: 'Logout'.tr,
|
||||||
|
icon: Icons.logout_rounded,
|
||||||
|
onTap: () {
|
||||||
|
Get.defaultDialog(
|
||||||
|
title: "Logout".tr,
|
||||||
|
middleText: "Are you sure you want to logout?".tr,
|
||||||
|
textConfirm: "Logout".tr,
|
||||||
|
textCancel: "Cancel".tr,
|
||||||
|
onConfirm: () {
|
||||||
|
// controller.logout();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
color: Colors.red.shade300,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,49 +197,57 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ويدجت مساعدة لرأس القائمة ---
|
// --- ويدجت رأس القائمة ---
|
||||||
Widget _buildMenuHeader() {
|
Widget _buildMenuHeader() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.fromLTRB(20, 30, 20, 16),
|
||||||
child: Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
// CircleAvatar(
|
const CircleAvatar(
|
||||||
// radius: 30,
|
radius: 30,
|
||||||
// backgroundColor: AppColor.primaryColor,
|
backgroundColor: AppColor.primaryColor,
|
||||||
// child:
|
child: Icon(Icons.person, color: AppColor.writeColor, size: 35),
|
||||||
// const Icon(Icons.person, color: AppColor.writeColor, size: 35),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
"Welcome Back!".tr, // يمكنك تغييرها لاسم المستخدم
|
|
||||||
style: AppStyle.title
|
|
||||||
.copyWith(color: AppColor.writeColor.withOpacity(0.7)),
|
|
||||||
),
|
),
|
||||||
Text(
|
const SizedBox(width: 16),
|
||||||
box.read(BoxName.name), // يمكنك تغييرها لاسم المستخدم
|
Expanded(
|
||||||
style: AppStyle.headTitle.copyWith(fontSize: 22),
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
box.read(BoxName.name) ?? 'Guest',
|
||||||
|
style: AppStyle.headTitle.copyWith(fontSize: 20),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
"Intaleq Passenger".tr,
|
||||||
|
style: AppStyle.title.copyWith(
|
||||||
|
color: AppColor.writeColor.withOpacity(0.7),
|
||||||
|
fontSize: 14),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ويدجت مساعدة للأزرار السريعة ---
|
// --- ويدجت الأزرار السريعة ---
|
||||||
Widget _buildQuickActionButtons() {
|
Widget _buildQuickActionButtons() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
_buildSmallActionButton(
|
|
||||||
icon: Icons.notifications_none_rounded,
|
|
||||||
label: 'Alerts'.tr,
|
|
||||||
onTap: () => Get.to(() => const NotificationPage())),
|
|
||||||
_buildSmallActionButton(
|
_buildSmallActionButton(
|
||||||
icon: Icons.person_outline_rounded,
|
icon: Icons.person_outline_rounded,
|
||||||
label: 'Profile'.tr,
|
label: 'Profile'.tr,
|
||||||
onTap: () => Get.to(() => PassengerProfilePage())),
|
onTap: () => Get.to(() => PassengerProfilePage())),
|
||||||
|
_buildSmallActionButton(
|
||||||
|
icon: Icons.notifications_none_rounded,
|
||||||
|
label: 'Alerts'.tr,
|
||||||
|
onTap: () => Get.to(() => const NotificationPage())),
|
||||||
_buildSmallActionButton(
|
_buildSmallActionButton(
|
||||||
icon: Icons.settings_outlined,
|
icon: Icons.settings_outlined,
|
||||||
label: 'Settings'.tr,
|
label: 'Settings'.tr,
|
||||||
@@ -209,29 +263,31 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
required VoidCallback onTap}) {
|
required VoidCallback onTap}) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColor.writeColor, size: 24),
|
Icon(icon, color: AppColor.writeColor.withOpacity(0.9), size: 24),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 6),
|
||||||
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
Text(label,
|
||||||
|
style: AppStyle.subtitle.copyWith(
|
||||||
|
fontSize: 12, color: AppColor.writeColor.withOpacity(0.9))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- نفس دالتك القديمة لفتح رابط تطبيق السائق ---
|
|
||||||
void _launchDriverAppUrl() async {
|
void _launchDriverAppUrl() async {
|
||||||
final String driverAppUrl;
|
final String driverAppUrl;
|
||||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||||
driverAppUrl =
|
driverAppUrl =
|
||||||
'https://play.google.com/store/apps/details?id=com.sefer_driver';
|
'https://play.google.com/store/apps/details?id=com.intaleq_driver';
|
||||||
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
|
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
|
||||||
driverAppUrl = 'https://apps.apple.com/eg/app/tripz-driver/id6502189302';
|
driverAppUrl =
|
||||||
|
'https://apps.apple.com/st/app/intaleq-driver/id6482995159';
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -248,28 +304,39 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- كلاس عناصر القائمة بالتصميم الجديد (يستخدم ListTile) ---
|
// --- ويدجت عناصر القائمة بتصميم محسن ---
|
||||||
class IconMainPageMap extends StatelessWidget {
|
class MenuListItem extends StatelessWidget {
|
||||||
const IconMainPageMap({
|
const MenuListItem({
|
||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
leading:
|
leading: Icon(
|
||||||
Icon(icon, size: 26, color: AppColor.writeColor.withOpacity(0.8)),
|
icon,
|
||||||
|
size: 26,
|
||||||
|
color: color ?? AppColor.writeColor.withOpacity(0.8),
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
title.tr,
|
title.tr,
|
||||||
style: AppStyle.title.copyWith(fontSize: 16),
|
style: AppStyle.title.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
color: color ?? AppColor.writeColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
splashColor: AppColor.primaryColor.withOpacity(0.2),
|
splashColor: AppColor.primaryColor.withOpacity(0.2),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class RideBeginPassenger extends StatelessWidget {
|
|||||||
box.write(BoxName.sosPhonePassenger,
|
box.write(BoxName.sosPhonePassenger,
|
||||||
profileController.prfoileData['sosPhone']);
|
profileController.prfoileData['sosPhone']);
|
||||||
} else {
|
} else {
|
||||||
makePhoneCall('122');
|
makePhoneCall('112');
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
_buildActionButton(
|
_buildActionButton(
|
||||||
@@ -211,18 +211,17 @@ class RideBeginPassenger extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
final phoneNumber =
|
final phoneNumber =
|
||||||
box.read(BoxName.sosPhonePassenger).toString();
|
box.read(BoxName.sosPhonePassenger).toString();
|
||||||
final phone = box.read(BoxName.countryCode) == 'Egypt'
|
|
||||||
? '+2$phoneNumber'
|
final phone = controller.formatSyrianPhoneNumber(phoneNumber);
|
||||||
: '+962$phoneNumber';
|
|
||||||
controller.sendWhatsapp(phone);
|
controller.sendWhatsapp(phone);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
_buildActionButton(
|
_buildActionButton(
|
||||||
icon: Foundation.video,
|
icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
|
||||||
label: 'Video Call'.tr,
|
label: 'Share'.tr, // اسم جديد وواضح
|
||||||
color: AppColor.blueColor,
|
color: AppColor.blueColor,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// --- نفس منطقك القديم ---
|
// نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
|
||||||
await controller.getTokenForParent();
|
await controller.getTokenForParent();
|
||||||
}),
|
}),
|
||||||
_buildActionButton(
|
_buildActionButton(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class PassengerWallet extends StatelessWidget {
|
|||||||
Get.put(CreditCardController());
|
Get.put(CreditCardController());
|
||||||
|
|
||||||
return MyScafolld(
|
return MyScafolld(
|
||||||
title: 'My Wallet'.tr,
|
title: 'My Balance'.tr,
|
||||||
isleading: true,
|
isleading: true,
|
||||||
body: [
|
body: [
|
||||||
// استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة
|
// استخدام Stack فقط لعرض الـ Dialog فوق المحتوى عند الحاجة
|
||||||
@@ -53,7 +53,7 @@ class PassengerWallet extends StatelessWidget {
|
|||||||
// --- 2. قائمة الخيارات المنظمة ---
|
// --- 2. قائمة الخيارات المنظمة ---
|
||||||
_buildActionTile(
|
_buildActionTile(
|
||||||
icon: Icons.add_card_rounded,
|
icon: Icons.add_card_rounded,
|
||||||
title: 'Top up Wallet'.tr,
|
title: 'Top up Balance'.tr,
|
||||||
subtitle: 'Add funds using our secure methods'.tr,
|
subtitle: 'Add funds using our secure methods'.tr,
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
showPaymentBottomSheet(context), // نفس دالتك القديمة
|
showPaymentBottomSheet(context), // نفس دالتك القديمة
|
||||||
@@ -68,7 +68,7 @@ class PassengerWallet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_buildActionTile(
|
_buildActionTile(
|
||||||
icon: Icons.phone_iphone_rounded,
|
icon: Icons.phone_iphone_rounded,
|
||||||
title: 'Set Wallet Phone Number'.tr,
|
title: 'Set Phone Number'.tr,
|
||||||
subtitle: 'Link a phone number for transfers'.tr,
|
subtitle: 'Link a phone number for transfers'.tr,
|
||||||
onTap: () => _showWalletPhoneDialog(context,
|
onTap: () => _showWalletPhoneDialog(context,
|
||||||
Get.find<PaymentController>()), // نفس دالتك القديمة
|
Get.find<PaymentController>()), // نفس دالتك القديمة
|
||||||
@@ -132,7 +132,7 @@ class PassengerWallet extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${AppInformation.appName} Wallet'.tr,
|
'${AppInformation.appName} ${'Balance'.tr}',
|
||||||
style: AppStyle.headTitle.copyWith(
|
style: AppStyle.headTitle.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:Intaleq/controller/functions/encrypt_decrypt.dart';
|
|
||||||
import 'package:Intaleq/views/auth/login_page.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -8,8 +6,8 @@ import 'package:Intaleq/constant/colors.dart';
|
|||||||
import 'package:Intaleq/constant/style.dart';
|
import 'package:Intaleq/constant/style.dart';
|
||||||
import 'package:Intaleq/controller/profile/profile_controller.dart';
|
import 'package:Intaleq/controller/profile/profile_controller.dart';
|
||||||
import 'package:Intaleq/main.dart';
|
import 'package:Intaleq/main.dart';
|
||||||
|
import 'package:Intaleq/views/auth/login_page.dart';
|
||||||
import 'package:Intaleq/views/widgets/elevated_btn.dart';
|
import 'package:Intaleq/views/widgets/elevated_btn.dart';
|
||||||
import 'package:Intaleq/views/widgets/my_scafold.dart';
|
|
||||||
import 'package:Intaleq/views/widgets/my_textField.dart';
|
import 'package:Intaleq/views/widgets/my_textField.dart';
|
||||||
import 'package:Intaleq/views/widgets/mycircular.dart';
|
import 'package:Intaleq/views/widgets/mycircular.dart';
|
||||||
|
|
||||||
@@ -18,239 +16,319 @@ import '../../../controller/functions/log_out.dart';
|
|||||||
|
|
||||||
class PassengerProfilePage extends StatelessWidget {
|
class PassengerProfilePage extends StatelessWidget {
|
||||||
PassengerProfilePage({super.key});
|
PassengerProfilePage({super.key});
|
||||||
LogOutController logOutController = Get.put(LogOutController());
|
|
||||||
|
final LogOutController logOutController = Get.put(LogOutController());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Get.put(ProfileController());
|
Get.put(ProfileController());
|
||||||
|
|
||||||
return MyScafolld(
|
return Scaffold(
|
||||||
isleading: true,
|
backgroundColor: Colors.grey[100],
|
||||||
title: 'My Profile'.tr,
|
appBar: AppBar(
|
||||||
body: [
|
title: Text('My Profile'.tr,
|
||||||
GetBuilder<ProfileController>(
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
builder: (controller) => controller.isloading
|
backgroundColor: Colors.grey[100],
|
||||||
? const MyCircularProgressIndicator()
|
elevation: 0,
|
||||||
: Padding(
|
centerTitle: true,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
),
|
||||||
child: SizedBox(
|
body: GetBuilder<ProfileController>(
|
||||||
height: Get.height,
|
builder: (controller) {
|
||||||
child: SingleChildScrollView(
|
if (controller.isloading) {
|
||||||
child: Column(
|
return const MyCircularProgressIndicator();
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
}
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return ListView(
|
||||||
children: [
|
padding:
|
||||||
Text(
|
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
|
||||||
'Edit Profile'.tr,
|
children: [
|
||||||
style: AppStyle.headTitle2,
|
_buildProfileHeader(controller),
|
||||||
),
|
const SizedBox(height: 24),
|
||||||
ListTile(
|
_buildSectionCard(
|
||||||
title: Text(
|
'Personal Information'.tr,
|
||||||
'Name'.tr,
|
[
|
||||||
style: AppStyle.title,
|
_buildProfileTile(
|
||||||
),
|
icon: Icons.person_outline,
|
||||||
leading: const Icon(
|
color: Colors.blue,
|
||||||
Icons.person_pin_rounded,
|
title: 'Name'.tr,
|
||||||
size: 35,
|
subtitle:
|
||||||
),
|
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}',
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
onTap: () =>
|
||||||
subtitle: Text(
|
controller.updatField('first_name', TextInputType.name),
|
||||||
'${(controller.prfoileData['first_name'])} ${(controller.prfoileData['last_name'])}'),
|
),
|
||||||
onTap: () {
|
_buildProfileTile(
|
||||||
controller.updatField(
|
icon: Icons.wc_outlined,
|
||||||
'first_name', TextInputType.name);
|
color: Colors.pink,
|
||||||
},
|
title: 'Gender'.tr,
|
||||||
),
|
subtitle: controller.prfoileData['gender']?.toString() ??
|
||||||
ListTile(
|
'Not set'.tr,
|
||||||
title: Text(
|
onTap: () => _showGenderDialog(controller),
|
||||||
'Gender'.tr,
|
),
|
||||||
style: AppStyle.title,
|
_buildProfileTile(
|
||||||
),
|
icon: Icons.school_outlined,
|
||||||
leading: Image.asset(
|
color: Colors.orange,
|
||||||
'assets/images/gender.png',
|
title: 'Education'.tr,
|
||||||
width: 35,
|
subtitle: controller.prfoileData['education']?.toString() ??
|
||||||
),
|
'Not set'.tr,
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
onTap: () => _showEducationDialog(controller),
|
||||||
subtitle: Text((controller.prfoileData['gender']
|
),
|
||||||
.toString())),
|
],
|
||||||
onTap: () {
|
),
|
||||||
Get.defaultDialog(
|
const SizedBox(height: 24),
|
||||||
title: 'Update Gender'.tr,
|
_buildSectionCard(
|
||||||
content: Column(
|
'Work & Contact'.tr,
|
||||||
children: [
|
[
|
||||||
GenderPicker(),
|
_buildProfileTile(
|
||||||
MyElevatedButton(
|
icon: Icons.work_outline,
|
||||||
title: 'Update'.tr,
|
color: Colors.green,
|
||||||
onPressed: () {
|
title: 'Employment Type'.tr,
|
||||||
controller.updateColumn({
|
subtitle:
|
||||||
'id': controller.prfoileData['id']
|
controller.prfoileData['employmentType']?.toString() ??
|
||||||
.toString(),
|
'Not set'.tr,
|
||||||
'gender': (controller.gender),
|
onTap: () => controller.updatField(
|
||||||
});
|
'employmentType', TextInputType.name),
|
||||||
Get.back();
|
),
|
||||||
},
|
_buildProfileTile(
|
||||||
)
|
icon: Icons.favorite_border,
|
||||||
],
|
color: Colors.purple,
|
||||||
));
|
title: 'Marital Status'.tr,
|
||||||
// controller.updatField('gender');
|
subtitle:
|
||||||
},
|
controller.prfoileData['maritalStatus']?.toString() ??
|
||||||
),
|
'Not set'.tr,
|
||||||
ListTile(
|
onTap: () => controller.updatField(
|
||||||
title: Text(
|
'maritalStatus', TextInputType.name),
|
||||||
'Education'.tr,
|
),
|
||||||
style: AppStyle.title,
|
_buildProfileTile(
|
||||||
),
|
icon: Icons.sos_outlined,
|
||||||
leading: Image.asset(
|
color: Colors.red,
|
||||||
'assets/images/education.png',
|
title: 'SOS Phone'.tr,
|
||||||
width: 35,
|
subtitle: controller.prfoileData['sosPhone']?.toString() ??
|
||||||
),
|
'Not set'.tr,
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
onTap: () async {
|
||||||
subtitle: Text(controller.prfoileData['education']
|
await controller.updatField(
|
||||||
.toString()),
|
'sosPhone', TextInputType.phone);
|
||||||
onTap: () {
|
box.write(BoxName.sosPhonePassenger,
|
||||||
Get.defaultDialog(
|
controller.prfoileData['sosPhone']);
|
||||||
barrierDismissible: true,
|
},
|
||||||
title: 'Update Education'.tr,
|
),
|
||||||
content: SizedBox(
|
],
|
||||||
height: 200,
|
),
|
||||||
child: Column(
|
const SizedBox(height: 32),
|
||||||
children: [
|
_buildAccountActions(context, logOutController),
|
||||||
EducationDegreePicker(),
|
],
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
confirm: MyElevatedButton(
|
);
|
||||||
title: 'Update Education'.tr,
|
}
|
||||||
onPressed: () {
|
|
||||||
controller.updateColumn({
|
Widget _buildProfileHeader(ProfileController controller) {
|
||||||
'id': controller.prfoileData['id']
|
String fullName =
|
||||||
.toString(),
|
'${controller.prfoileData['first_name'] ?? ''} ${controller.prfoileData['last_name'] ?? ''}';
|
||||||
'education':
|
String initials = (fullName.isNotEmpty && fullName.contains(" "))
|
||||||
controller.selectedDegree,
|
? fullName.split(" ").map((e) => e.isNotEmpty ? e[0] : "").join()
|
||||||
});
|
: (fullName.isNotEmpty ? fullName[0] : "");
|
||||||
Get.back();
|
|
||||||
},
|
// Logic to hide email if it contains 'intaleqapp.com'
|
||||||
));
|
String email = box.read(BoxName.email) ?? '';
|
||||||
},
|
if (email.contains('intaleqapp.com')) {
|
||||||
),
|
email = ''; // Clear the email if it contains the domain
|
||||||
ListTile(
|
}
|
||||||
title: Text(
|
|
||||||
'Employment Type'.tr,
|
return Center(
|
||||||
style: AppStyle.title,
|
child: Column(
|
||||||
),
|
children: [
|
||||||
leading: Image.asset(
|
CircleAvatar(
|
||||||
'assets/images/employmentType.png',
|
radius: 50,
|
||||||
width: 35,
|
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
|
||||||
),
|
child: Text(
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
initials,
|
||||||
subtitle: Text(controller
|
style:
|
||||||
.prfoileData['employmentType']
|
const TextStyle(fontSize: 40, color: AppColor.primaryColor),
|
||||||
.toString()),
|
),
|
||||||
onTap: () {
|
),
|
||||||
controller.updatField(
|
const SizedBox(height: 12),
|
||||||
'employmentType', TextInputType.name);
|
Text(
|
||||||
},
|
fullName,
|
||||||
),
|
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||||
ListTile(
|
),
|
||||||
title: Text(
|
if (email
|
||||||
'Marital Status'.tr,
|
.isNotEmpty) // Only show the Text widget if the email is not empty
|
||||||
style: AppStyle.title,
|
Text(
|
||||||
),
|
email,
|
||||||
leading: Image.asset(
|
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||||
'assets/images/maritalStatus.png',
|
),
|
||||||
width: 35,
|
],
|
||||||
),
|
),
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
);
|
||||||
subtitle: Text(controller
|
}
|
||||||
.prfoileData['maritalStatus']
|
|
||||||
.toString()),
|
Widget _buildSectionCard(String title, List<Widget> children) {
|
||||||
onTap: () {
|
return Column(
|
||||||
controller.updatField(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
'maritalStatus', TextInputType.name);
|
children: [
|
||||||
},
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
|
||||||
ListTile(
|
child: Text(
|
||||||
title: Text(
|
title,
|
||||||
'SOS Phone'.tr,
|
style: TextStyle(
|
||||||
style: AppStyle.title,
|
fontSize: 16,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
leading: const Icon(
|
color: Colors.grey[700]),
|
||||||
Icons.sos,
|
),
|
||||||
color: AppColor.redColor,
|
),
|
||||||
size: 35,
|
Container(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
color: Colors.white,
|
||||||
subtitle: Text(
|
borderRadius: BorderRadius.circular(12),
|
||||||
(controller.prfoileData['sosPhone'])
|
),
|
||||||
.toString()),
|
child: Column(
|
||||||
onTap: () async {
|
children: children,
|
||||||
await controller.updatField(
|
),
|
||||||
'sosPhone', TextInputType.phone);
|
),
|
||||||
box.write(BoxName.sosPhonePassenger,
|
|
||||||
controller.prfoileData['sosPhone']);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// const Spacer(),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Sign Out'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
LogOutController().logOutPassenger();
|
|
||||||
}),
|
|
||||||
GetBuilder<LogOutController>(
|
|
||||||
builder: (logOutController) {
|
|
||||||
return MyElevatedButton(
|
|
||||||
title: 'Delete My Account'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title:
|
|
||||||
'Are you sure to delete your account?'
|
|
||||||
.tr,
|
|
||||||
content: Form(
|
|
||||||
key: logOutController.formKey1,
|
|
||||||
child: MyTextForm(
|
|
||||||
controller: logOutController
|
|
||||||
.emailTextController,
|
|
||||||
label: 'Type your Email'.tr,
|
|
||||||
hint: 'Type your Email'.tr,
|
|
||||||
type:
|
|
||||||
TextInputType.emailAddress,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Delete My Account'.tr,
|
|
||||||
kolor: AppColor.redColor,
|
|
||||||
onPressed: () async {
|
|
||||||
await logOutController
|
|
||||||
.deletePassengerAccount();
|
|
||||||
}),
|
|
||||||
cancel: MyElevatedButton(
|
|
||||||
title: 'No I want'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
logOutController
|
|
||||||
.emailTextController
|
|
||||||
.clear();
|
|
||||||
logOutController.update();
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileTile({
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: onTap,
|
||||||
|
leading: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 24),
|
||||||
|
),
|
||||||
|
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||||
|
subtitle: Text(subtitle, style: TextStyle(color: Colors.grey[600])),
|
||||||
|
trailing:
|
||||||
|
Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey[400]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAccountActions(
|
||||||
|
BuildContext context, LogOutController logOutController) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: TextButton.icon(
|
||||||
|
icon: const Icon(Icons.logout),
|
||||||
|
label: Text('Sign Out'.tr),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blueGrey,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
logOutController.logOutPassenger();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: TextButton.icon(
|
||||||
|
icon: const Icon(Icons.delete_forever_outlined),
|
||||||
|
label: Text('Delete My Account'.tr),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
_showDeleteAccountDialog(context, logOutController),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showGenderDialog(ProfileController controller) {
|
||||||
|
Get.defaultDialog(
|
||||||
|
title: 'Update Gender'.tr,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
GenderPicker(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
MyElevatedButton(
|
||||||
|
title: 'Update'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
controller.updateColumn({
|
||||||
|
'id': controller.prfoileData['id'].toString(),
|
||||||
|
'gender': controller.gender,
|
||||||
|
});
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEducationDialog(ProfileController controller) {
|
||||||
|
Get.defaultDialog(
|
||||||
|
title: 'Update Education'.tr,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
EducationDegreePicker(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
MyElevatedButton(
|
||||||
|
title: 'Update'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
controller.updateColumn({
|
||||||
|
'id': controller.prfoileData['id'].toString(),
|
||||||
|
'education': controller.selectedDegree,
|
||||||
|
});
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDeleteAccountDialog(
|
||||||
|
BuildContext context, LogOutController logOutController) {
|
||||||
|
Get.defaultDialog(
|
||||||
|
title: 'Delete My Account'.tr,
|
||||||
|
middleText: 'Are you sure? This action cannot be undone.'.tr,
|
||||||
|
content: Form(
|
||||||
|
key: logOutController.formKey1,
|
||||||
|
child: MyTextForm(
|
||||||
|
controller: logOutController.emailTextController,
|
||||||
|
label: 'Confirm your Email'.tr,
|
||||||
|
hint: 'Type your Email'.tr,
|
||||||
|
type: TextInputType.emailAddress,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
confirm: MyElevatedButton(
|
||||||
|
title: 'Delete Permanently'.tr,
|
||||||
|
kolor: AppColor.redColor,
|
||||||
|
onPressed: () async {
|
||||||
|
await logOutController.deletePassengerAccount();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cancel: TextButton(
|
||||||
|
child: Text('Cancel'.tr),
|
||||||
|
onPressed: () {
|
||||||
|
logOutController.emailTextController.clear();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenderPicker extends StatelessWidget {
|
// --- Helper Widgets for Pickers ---
|
||||||
final ProfileController controller = Get.put(ProfileController());
|
|
||||||
|
|
||||||
|
class GenderPicker extends StatelessWidget {
|
||||||
|
final ProfileController controller = Get.find<ProfileController>();
|
||||||
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
|
final List<String> genderOptions = ['Male'.tr, 'Female'.tr, 'Other'.tr];
|
||||||
|
|
||||||
GenderPicker({super.key});
|
GenderPicker({super.key});
|
||||||
@@ -258,14 +336,14 @@ class GenderPicker extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 100,
|
height: 150,
|
||||||
child: CupertinoPicker(
|
child: CupertinoPicker(
|
||||||
itemExtent: 32.0,
|
itemExtent: 40.0,
|
||||||
onSelectedItemChanged: (int index) {
|
onSelectedItemChanged: (int index) {
|
||||||
controller.setGender(genderOptions[index]);
|
controller.setGender(genderOptions[index]);
|
||||||
},
|
},
|
||||||
children: genderOptions.map((String value) {
|
children: genderOptions.map((String value) {
|
||||||
return Text(value);
|
return Center(child: Text(value));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -273,216 +351,34 @@ class GenderPicker extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EducationDegreePicker extends StatelessWidget {
|
class EducationDegreePicker extends StatelessWidget {
|
||||||
final ProfileController controller = Get.put(ProfileController());
|
final ProfileController controller = Get.find<ProfileController>();
|
||||||
|
|
||||||
final List<String> degreeOptions = [
|
final List<String> degreeOptions = [
|
||||||
'High School Diploma'.tr,
|
'High School Diploma'.tr,
|
||||||
'Associate Degree'.tr,
|
'Associate Degree'.tr,
|
||||||
'Bachelor\'s Degree'.tr,
|
"Bachelor's Degree".tr,
|
||||||
'Master\'s Degree'.tr,
|
"Master's Degree".tr,
|
||||||
'Doctoral Degree'.tr,
|
'Doctoral Degree'.tr,
|
||||||
];
|
];
|
||||||
|
|
||||||
EducationDegreePicker({Key? key}) : super(key: key);
|
EducationDegreePicker({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 180,
|
||||||
child: CupertinoPicker(
|
child: CupertinoPicker(
|
||||||
// backgroundColor: AppColor.accentColor,
|
itemExtent: 40.0,
|
||||||
// looping: true,
|
|
||||||
squeeze: 2,
|
|
||||||
// diameterRatio: 5,
|
|
||||||
itemExtent: 32,
|
|
||||||
onSelectedItemChanged: (int index) {
|
onSelectedItemChanged: (int index) {
|
||||||
controller.setDegree(degreeOptions[index]);
|
controller.setDegree(degreeOptions[index]);
|
||||||
},
|
},
|
||||||
children: degreeOptions.map((String value) {
|
children: degreeOptions.map((String value) {
|
||||||
return Text(value);
|
return Center(child: Text(value));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CountryPicker extends StatelessWidget {
|
// NOTE: The CountryPicker and CountryPickerFromSetting widgets were not part of the main
|
||||||
final ProfileController controller = Get.put(ProfileController());
|
// profile page UI, so they are excluded here to keep the file focused.
|
||||||
|
// If they are needed elsewhere, they should be moved to their own files.
|
||||||
final List<String> countryOptions = [
|
|
||||||
'Jordan',
|
|
||||||
'Syria',
|
|
||||||
'Egypt',
|
|
||||||
'Turkey',
|
|
||||||
'Saudi Arabia',
|
|
||||||
'Qatar',
|
|
||||||
'Bahrain',
|
|
||||||
'Kuwait',
|
|
||||||
'USA'
|
|
||||||
];
|
|
||||||
|
|
||||||
CountryPicker({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GetBuilder<ProfileController>(builder: (controller) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Select Your Country".tr,
|
|
||||||
style: AppStyle.headTitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
// const SizedBox(
|
|
||||||
// height: 20,
|
|
||||||
// ),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
"To ensure you receive the most accurate information for your location, please select your country below. This will help tailor the app experience and content to your country."
|
|
||||||
.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: CupertinoPicker(
|
|
||||||
itemExtent: 32,
|
|
||||||
onSelectedItemChanged: (int index) {
|
|
||||||
controller.setCountry(countryOptions[index]);
|
|
||||||
box.write(BoxName.countryCode,
|
|
||||||
countryOptions[index]); // Save in English
|
|
||||||
},
|
|
||||||
children: List.generate(
|
|
||||||
countryOptions.length,
|
|
||||||
(index) => Center(
|
|
||||||
child: Text(
|
|
||||||
countryOptions[index]
|
|
||||||
.tr, // Display translated if not English
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Select Country'.tr, // Use translated text for button
|
|
||||||
onPressed: () {
|
|
||||||
Get.find<LoginController>().saveCountryCode(controller
|
|
||||||
.selectedCountry
|
|
||||||
.toString()); // No conversion needed
|
|
||||||
box.write(
|
|
||||||
BoxName.countryCode, //
|
|
||||||
controller.selectedCountry); // Already saved in English
|
|
||||||
if (controller.selectedCountry == null) {
|
|
||||||
Get.snackbar("You should select your country".tr, '');
|
|
||||||
} else {
|
|
||||||
Get.snackbar(controller.selectedCountry.toString().tr, '');
|
|
||||||
Get.off(LoginPage());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CountryPickerFromSetting extends StatelessWidget {
|
|
||||||
final ProfileController controller = Get.put(ProfileController());
|
|
||||||
final LoginController loginController = Get.put(LoginController());
|
|
||||||
|
|
||||||
final List<String> countryOptions = [
|
|
||||||
'Jordan',
|
|
||||||
'USA',
|
|
||||||
'Egypt',
|
|
||||||
'Turkey',
|
|
||||||
'Saudi Arabia',
|
|
||||||
'Qatar',
|
|
||||||
'Bahrain',
|
|
||||||
'Kuwait',
|
|
||||||
];
|
|
||||||
|
|
||||||
CountryPickerFromSetting({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GetBuilder<ProfileController>(builder: (controller) {
|
|
||||||
return CupertinoPageScaffold(
|
|
||||||
navigationBar: CupertinoNavigationBar(
|
|
||||||
middle: Text('Select Your Country'.tr),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
// Text(
|
|
||||||
// "Select Your Country".tr,
|
|
||||||
// style: AppStyle.headTitle2,
|
|
||||||
// textAlign: TextAlign.center,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(
|
|
||||||
// height: 20,
|
|
||||||
// ),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
"To ensure you receive the most accurate information for your location, please select your country below. This will help tailor the app experience and content to your country."
|
|
||||||
.tr,
|
|
||||||
style: AppStyle.headTitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: CupertinoPicker(
|
|
||||||
itemExtent: 32,
|
|
||||||
onSelectedItemChanged: (int index) {
|
|
||||||
controller.setCountry(countryOptions[index]);
|
|
||||||
box.write(BoxName.countryCode,
|
|
||||||
countryOptions[index]); // Save in English
|
|
||||||
},
|
|
||||||
children: List.generate(
|
|
||||||
countryOptions.length,
|
|
||||||
(index) => Center(
|
|
||||||
child: Text(
|
|
||||||
countryOptions[index]
|
|
||||||
.tr, // Display translated if not English
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Select Country'.tr, // Use translated text for button
|
|
||||||
onPressed: () async {
|
|
||||||
loginController.saveCountryCode(controller.selectedCountry
|
|
||||||
.toString()); // No conversion needed
|
|
||||||
box.write(
|
|
||||||
BoxName.countryCode, //
|
|
||||||
controller.selectedCountry); // Already saved in English
|
|
||||||
Get.snackbar(controller.selectedCountry.toString().tr, '',
|
|
||||||
backgroundColor: AppColor.greenColor);
|
|
||||||
// Get.back();//
|
|
||||||
// Get.back();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:Intaleq/controller/home/home_page_controller.dart';
|
import 'package:Intaleq/controller/home/home_page_controller.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:Intaleq/views/lang/languages.dart';
|
import 'package:Intaleq/views/lang/languages.dart';
|
||||||
@@ -8,109 +7,207 @@ import 'HomePage/about_page.dart';
|
|||||||
import 'HomePage/frequentlyQuestionsPage.dart';
|
import 'HomePage/frequentlyQuestionsPage.dart';
|
||||||
import 'HomePage/share_app_page.dart';
|
import 'HomePage/share_app_page.dart';
|
||||||
import 'HomePage/trip_record_page.dart';
|
import 'HomePage/trip_record_page.dart';
|
||||||
import 'profile/passenger_profile_page.dart';
|
|
||||||
|
// NOTE: This is a placeholder for your actual CountryPickerFromSetting widget.
|
||||||
|
// You should remove this and import your own widget.
|
||||||
|
class CountryPickerFromSetting extends StatelessWidget {
|
||||||
|
const CountryPickerFromSetting({super.key});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text('Change Country'.tr)),
|
||||||
|
body: Center(
|
||||||
|
child: Text('Country Picker Page Placeholder'.tr),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingPage extends StatelessWidget {
|
class SettingPage extends StatelessWidget {
|
||||||
const SettingPage({super.key});
|
const SettingPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Get.put(HomePageController());
|
// Using lazyPut to ensure the controller is available when needed.
|
||||||
return CupertinoPageScaffold(
|
Get.lazyPut(() => HomePageController());
|
||||||
navigationBar: CupertinoNavigationBar(
|
|
||||||
middle: Text('Setting'.tr),
|
return Scaffold(
|
||||||
leading: CupertinoButton(
|
backgroundColor:
|
||||||
padding: EdgeInsets.zero,
|
const Color(0xFFF5F5F7), // A slightly off-white background
|
||||||
child: const Icon(CupertinoIcons.back),
|
appBar: AppBar(
|
||||||
onPressed: () {
|
title: Text('Setting'.tr,
|
||||||
Navigator.pop(context);
|
style: const TextStyle(
|
||||||
},
|
color: Colors.black87, fontWeight: FontWeight.bold)),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
elevation: 0.5,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black87),
|
||||||
|
onPressed: () => Get.back(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||||
children: [
|
children: [
|
||||||
CupertinoListTile(
|
_buildSectionHeader('General'.tr),
|
||||||
onTap: () {
|
_buildSettingsCard(
|
||||||
Get.to(() => const Language());
|
children: [
|
||||||
},
|
_buildSettingsTile(
|
||||||
leading: const Icon(CupertinoIcons.globe,
|
icon: Icons.language,
|
||||||
color: CupertinoColors.activeBlue),
|
color: Colors.blue,
|
||||||
title: Text('Language'.tr),
|
title: 'Language'.tr,
|
||||||
subtitle: Text('To change Language the App'.tr),
|
subtitle: 'To change Language the App'.tr,
|
||||||
trailing: const CupertinoListTileChevron(),
|
onTap: () => Get.to(() => const Language()),
|
||||||
|
),
|
||||||
|
// const Divider(height: 1, indent: 68, endIndent: 16),
|
||||||
|
// _buildSettingsTile(
|
||||||
|
// icon: Icons.map_outlined,
|
||||||
|
// color: Colors.green,
|
||||||
|
// title: 'Change Country'.tr,
|
||||||
|
// subtitle: 'You can change the Country to get all features'.tr,
|
||||||
|
// onTap: () => Get.to(() => const CountryPickerFromSetting()),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
CupertinoListTile(
|
const SizedBox(height: 24),
|
||||||
onTap: () {
|
_buildSectionHeader('Preferences'.tr),
|
||||||
Get.to(() => CountryPickerFromSetting());
|
_buildSettingsCard(
|
||||||
},
|
children: [
|
||||||
leading: const Icon(CupertinoIcons.location,
|
GetBuilder<HomePageController>(
|
||||||
color: CupertinoColors.activeBlue),
|
builder: (controller) {
|
||||||
title: Text('Change Country'.tr),
|
return _buildSettingsSwitchTile(
|
||||||
subtitle:
|
icon: Icons.vibration,
|
||||||
Text('You can change the Country to get all features'.tr),
|
color: Colors.purple,
|
||||||
trailing: const CupertinoListTileChevron(),
|
title: 'Vibration'.tr,
|
||||||
|
subtitle: 'Vibration feedback for all buttons'.tr,
|
||||||
|
value: controller.isVibrate,
|
||||||
|
onChanged: controller.changeVibrateOption,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||||
|
_buildSettingsTile(
|
||||||
|
icon: Icons.mic_none,
|
||||||
|
color: Colors.orange,
|
||||||
|
title: 'Trips recorded'.tr,
|
||||||
|
subtitle: 'Here recorded trips audio'.tr,
|
||||||
|
onTap: () => Get.to(() => const TripsRecordedPage()),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
CupertinoListTile(
|
const SizedBox(height: 24),
|
||||||
onTap: () {
|
_buildSectionHeader('Support & Info'.tr),
|
||||||
Get.to(() => const FrequentlyQuestionsPage());
|
_buildSettingsCard(
|
||||||
},
|
children: [
|
||||||
leading: const Icon(CupertinoIcons.question,
|
_buildSettingsTile(
|
||||||
color: CupertinoColors.activeBlue),
|
icon: Icons.help_outline,
|
||||||
title: Text('Frequently Questions'.tr),
|
color: Colors.cyan,
|
||||||
subtitle: Text('Find answers to common questions'.tr),
|
title: 'Frequently Questions'.tr,
|
||||||
trailing: const CupertinoListTileChevron(),
|
subtitle: 'Find answers to common questions'.tr,
|
||||||
),
|
onTap: () => Get.to(() => const FrequentlyQuestionsPage()),
|
||||||
CupertinoListTile(
|
),
|
||||||
leading: const Icon(Icons.vibration,
|
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||||
color: CupertinoColors.activeBlue),
|
_buildSettingsTile(
|
||||||
title: Text('Vibration'.tr),
|
icon: Icons.info_outline,
|
||||||
trailing: GetBuilder<HomePageController>(
|
color: Colors.indigo,
|
||||||
builder: (controller) {
|
title: 'About Us'.tr,
|
||||||
return CupertinoSwitch(
|
subtitle: 'Learn more about our app and mission'.tr,
|
||||||
value: controller.isVibrate,
|
onTap: () => Get.to(() => const AboutPage()),
|
||||||
onChanged: controller.changeVibrateOption,
|
),
|
||||||
);
|
const Divider(height: 1, indent: 68, endIndent: 16),
|
||||||
},
|
_buildSettingsTile(
|
||||||
),
|
icon: Icons.share_outlined,
|
||||||
subtitle: Text(
|
color: Colors.redAccent,
|
||||||
'You can change the vibration feedback for all buttons'.tr),
|
title: 'Share App'.tr,
|
||||||
),
|
subtitle: 'Share with friends and earn rewards'.tr,
|
||||||
CupertinoListTile(
|
onTap: () => Get.to(() => ShareAppPage()),
|
||||||
onTap: () {
|
),
|
||||||
Get.to(() => const TripsRecordedPage());
|
],
|
||||||
},
|
|
||||||
leading: const Icon(CupertinoIcons.mic_circle,
|
|
||||||
color: CupertinoColors.activeBlue),
|
|
||||||
title: Text('Trips recorded'.tr),
|
|
||||||
subtitle: Text('Here recorded trips audio'.tr),
|
|
||||||
trailing: const CupertinoListTileChevron(),
|
|
||||||
),
|
|
||||||
CupertinoListTile(
|
|
||||||
onTap: () {
|
|
||||||
Get.to(() => const AboutPage());
|
|
||||||
},
|
|
||||||
leading: const Icon(CupertinoIcons.info_circle,
|
|
||||||
color: CupertinoColors.activeBlue),
|
|
||||||
title: Text('About Us'.tr),
|
|
||||||
subtitle: Text('Learn more about our app and mission'.tr),
|
|
||||||
trailing: const CupertinoListTileChevron(),
|
|
||||||
),
|
|
||||||
CupertinoListTile(
|
|
||||||
onTap: () {
|
|
||||||
Get.to(() => ShareAppPage());
|
|
||||||
},
|
|
||||||
leading: const Icon(CupertinoIcons.share,
|
|
||||||
color: CupertinoColors.activeBlue),
|
|
||||||
title: Text('Share App'.tr),
|
|
||||||
subtitle: Text(
|
|
||||||
'You can share the Intaleq App with your friends and earn rewards for rides they take using your code'
|
|
||||||
.tr),
|
|
||||||
trailing: const CupertinoListTileChevron(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSectionHeader(String title) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12.0, left: 8.0),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[700],
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsCard({required List<Widget> children}) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsTile({
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: onTap,
|
||||||
|
leading: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 22),
|
||||||
|
),
|
||||||
|
title: Text(title,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
|
||||||
|
subtitle: Text(subtitle,
|
||||||
|
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
|
||||||
|
trailing: Icon(Icons.chevron_right, color: Colors.grey[400]),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsSwitchTile({
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required bool value,
|
||||||
|
required ValueChanged<bool> onChanged,
|
||||||
|
}) {
|
||||||
|
return SwitchListTile(
|
||||||
|
secondary: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 22),
|
||||||
|
),
|
||||||
|
title: Text(title,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16)),
|
||||||
|
subtitle: Text(subtitle,
|
||||||
|
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
|
||||||
|
value: value,
|
||||||
|
onChanged: onChanged,
|
||||||
|
activeColor: const Color(0xFF007AFF), // iOS-like blue
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class Language extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Select your preferred language for the app interface.',
|
"Select your preferred language for the app interface.".tr,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: CupertinoColors.secondaryLabel,
|
color: CupertinoColors.secondaryLabel,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.7.0"
|
||||||
asn1lib:
|
asn1lib:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
|
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ dependencies:
|
|||||||
dotted_line: ^3.2.3
|
dotted_line: ^3.2.3
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
|
asn1lib: ^1.6.5
|
||||||
# home_widget: ^0.7.0+1
|
# home_widget: ^0.7.0+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user