change to map-saas api key and env - 2026-04-12

This commit is contained in:
Hamza-Ayed
2026-04-12 02:25:44 +03:00
parent 454276d1e0
commit 0aa1f15f25
16 changed files with 14974 additions and 13857 deletions

2
.env
View File

@@ -113,4 +113,4 @@ W=T
X=D X=D
Y=S Y=S
Z=M Z=M
mapSaasKey=intaleq_secret_2026 mapSaasKey=zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX

View File

@@ -48,8 +48,8 @@ android {
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdkVersion = 24 minSdkVersion = 24
targetSdk = 36 targetSdk = 36
versionCode = 63 versionCode = 65
versionName = '1.1.63' versionName = '1.1.65'
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"

View File

@@ -4,6 +4,8 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@@ -42,6 +42,11 @@ post_install do |installer|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.1' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.1'
# Fix for "non-modular header" issues with some pods. # Fix for "non-modular header" issues with some pods.
config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES' config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CONTACTS=1',
]
end end
end end
end end

View File

@@ -486,6 +486,6 @@ SPEC CHECKSUMS:
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
PODFILE CHECKSUM: 095099247b81b4f879b2736241d2072ab1875fad PODFILE CHECKSUM: 8a0b04ec79a0d49122ae6c10242e7cb023122802
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -12,7 +12,8 @@ class ContactUsController extends GetxController {
final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0); final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0); final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0);
bool _isWithinWorkTime(TimeOfDay now) { bool get isWorkTime {
final now = TimeOfDay.now();
return (now.hour > workStartTime.hour || return (now.hour > workStartTime.hour ||
(now.hour == workStartTime.hour && (now.hour == workStartTime.hour &&
now.minute >= workStartTime.minute)) && now.minute >= workStartTime.minute)) &&
@@ -20,6 +21,11 @@ class ContactUsController extends GetxController {
(now.hour == workEndTime.hour && now.minute <= workEndTime.minute)); (now.hour == workEndTime.hour && now.minute <= workEndTime.minute));
} }
/// Helper to format working hours for UI
String get workHoursString =>
'${workStartTime.hour.toString().padLeft(2, '0')}:${workStartTime.minute.toString().padLeft(2, '0')} - '
'${workEndTime.hour.toString().padLeft(2, '0')}:${workEndTime.minute.toString().padLeft(2, '0')}';
/// PHONE LIST (USED FOR CALLS + WHATSAPP) /// PHONE LIST (USED FOR CALLS + WHATSAPP)
final List<String> phoneNumbers = [ final List<String> phoneNumbers = [
'+963952475734', '+963952475734',
@@ -33,10 +39,24 @@ class ContactUsController extends GetxController {
return phoneNumbers[random.nextInt(phoneNumbers.length)]; return phoneNumbers[random.nextInt(phoneNumbers.length)];
} }
/// SHOW DIALOG /// DIRECT ACTIONS
void makeCall() {
if (isWorkTime) {
makePhoneCall(getRandomPhone());
}
}
void sendWhatsApp() {
launchCommunication('whatsapp', getRandomPhone(), 'Hello'.tr);
}
void sendEmail() {
launchCommunication('email', 'support@intaleqapp.com', 'Hello'.tr);
}
/// SHOW DIALOG (Optional legacy support)
void showContactDialog(BuildContext context) { void showContactDialog(BuildContext context) {
TimeOfDay now = TimeOfDay.now(); bool withinHours = isWorkTime;
bool withinHours = _isWithinWorkTime(now);
showCupertinoModalPopup( showCupertinoModalPopup(
context: context, context: context,
@@ -44,7 +64,6 @@ class ContactUsController extends GetxController {
title: Text('Contact Us'.tr), title: Text('Contact Us'.tr),
message: Text('Choose a contact option'.tr), message: Text('Choose a contact option'.tr),
actions: <Widget>[ actions: <Widget>[
/// 📞 CALL (RANDOM) — ONLY DURING WORK HOURS
if (withinHours) if (withinHours)
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Row( child: Row(
@@ -55,12 +74,10 @@ class ContactUsController extends GetxController {
], ],
), ),
onPressed: () { onPressed: () {
final phone = getRandomPhone(); Navigator.pop(context);
makePhoneCall(phone); makeCall();
}, },
), ),
/// ⛔ OUTSIDE WORK HOURS — SHOW INFO
if (!withinHours) if (!withinHours)
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Text( child: Text(
@@ -70,8 +87,6 @@ class ContactUsController extends GetxController {
), ),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
/// 💬 WHATSAPP (RANDOM)
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@@ -84,20 +99,18 @@ class ContactUsController extends GetxController {
], ],
), ),
onPressed: () { onPressed: () {
final phone = getRandomPhone(); Navigator.pop(context);
launchCommunication('whatsapp', phone, 'Hello'.tr); sendWhatsApp();
}, },
), ),
/// 📧 EMAIL
CupertinoActionSheetAction( CupertinoActionSheetAction(
child: Text('Send Email'.tr), child: Text('Send Email'.tr),
onPressed: () => launchCommunication( onPressed: () {
'email', 'support@intaleqapp.com', 'Hello'.tr), Navigator.pop(context);
sendEmail();
},
), ),
], ],
/// ❌ CANCEL BUTTON
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr), child: Text('Cancel'.tr),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),

View File

@@ -611,53 +611,56 @@ class MapPassengerController extends GetxController {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseData = json.decode(response.body); final responseData = json.decode(response.body);
if (responseData['code'] == 'Ok' || responseData['routes'] != null) { // Support both old format (routes[0]) and new SaaS format (top-level)
var routeData = responseData['routes'][0]; var routeData = responseData['routes'] != null
? responseData['routes'][0]
: responseData;
// 2. تحديث المتغيرات (المسافة والوقت) // 2. تحديث المتغيرات (المسافة والوقت)
double durationSecondsRaw = (routeData['duration'] as num).toDouble(); double durationSecondsRaw = (routeData['duration'] as num).toDouble();
int finalDurationSeconds = int finalDurationSeconds =
(durationSecondsRaw * kDurationScalar).toInt(); (durationSecondsRaw * kDurationScalar).toInt();
double distanceMeters = (routeData['distance'] as num).toDouble(); double distanceMeters = (routeData['distance'] as num).toDouble();
timeToPassengerFromDriverAfterApplied = finalDurationSeconds; timeToPassengerFromDriverAfterApplied = finalDurationSeconds;
remainingTimeToPassengerFromDriverAfterApplied = finalDurationSeconds; remainingTimeToPassengerFromDriverAfterApplied = finalDurationSeconds;
distanceByPassenger = distanceMeters.toStringAsFixed(0); distanceByPassenger = distanceMeters.toStringAsFixed(0);
// تحديث نصوص الواجهة // تحديث نصوص الواجهة
int minutes = (finalDurationSeconds / 60).floor(); int minutes = (finalDurationSeconds / 60).floor();
int seconds = finalDurationSeconds % 60; int seconds = finalDurationSeconds % 60;
stringRemainingTimeToPassenger = stringRemainingTimeToPassenger =
'$minutes:${seconds.toString().padLeft(2, '0')}'; '$minutes:${seconds.toString().padLeft(2, '0')}';
Log.print( Log.print(
'✅ Driver Route Info: $minutes min, ${distanceMeters.toInt()} m'); '✅ Driver Route Info: $minutes min, ${distanceMeters.toInt()} m');
// 3. معالجة الرسم (Polyline) // 3. معالجة الرسم (Polyline)
String pointsString = routeData['geometry'] ?? ""; // SaaS uses 'points', OSRM uses 'geometry'
if (pointsString.isNotEmpty) { String pointsString =
List<LatLng> decodedPoints = routeData['points'] ?? routeData['geometry'] ?? "";
await compute(decodePolylineIsolate, pointsString); if (pointsString.isNotEmpty) {
// حفظ نسخة للمقارنة List<LatLng> decodedPoints =
_currentDriverRoutePoints = decodedPoints; await compute(decodePolylineIsolate, pointsString);
// إزالة خط مسار السائق القديم فقط // حفظ نسخة للمقارنة
polyLines = polyLines.where((p) => p.lineOpacity != 0.999).toList(); _currentDriverRoutePoints = decodedPoints;
// إزالة خط مسار السائق القديم فقط
polyLines = polyLines.where((p) => p.lineOpacity != 0.999).toList();
// إضافة الخط الجديد (بستايل مميز للسائق) // إضافة الخط الجديد (بستايل مميز للسائق)
polyLines.add(LineOptions( polyLines.add(LineOptions(
geometry: decodedPoints, geometry: decodedPoints,
lineColor: '#333333', // لون مختلف عن مسار الرحلة الأساسي lineColor: '#333333', // لون مختلف عن مسار الرحلة الأساسي
lineWidth: 5, lineWidth: 5,
lineOpacity: 0.999, // acting as ID lineOpacity: 0.999, // acting as ID
)); ));
refreshMapElements(); refreshMapElements();
}
// 4. ضبط الكاميرا لتشمل السائق والراكب
_fitCameraToPoints(driverPos, passengerPos);
update(); // تحديث واحد للكل
} }
// 4. ضبط الكاميرا لتشمل السائق والراكب
_fitCameraToPoints(driverPos, passengerPos);
update(); // تحديث واحد للكل
} }
} catch (e) { } catch (e) {
Log.print('❌ Error calculating driver route: $e'); Log.print('❌ Error calculating driver route: $e');
@@ -6478,30 +6481,29 @@ Intaleq Team''';
double lngDest = double.parse(coordDestination[1]); double lngDest = double.parse(coordDestination[1]);
myDestination = LatLng(latDest, lngDest); myDestination = LatLng(latDest, lngDest);
// ── 2. Routing Decision: Hybrid Strategy ────────────────────────── // ── 2. Unified SaaS Routing Strategy ──────────────────────────
final bool isSaaSRequest = activeMenuWaypointCount == 0; final bool isSaaSRequest = true;
Uri uri; Uri uri;
if (isSaaSRequest) { var originCoords = origin.split(',');
// Mapping SaaS format: Query Parameters final Map<String, String> queryParams = {
var originCoords = origin.split(','); 'fromLat': originCoords[0].trim(),
final Map<String, String> queryParams = { 'fromLng': originCoords[1].trim(),
'fromLat': originCoords[0].trim(), 'toLat': latDest.toString(),
'fromLng': originCoords[1].trim(), 'toLng': lngDest.toString(),
'toLat': latDest.toString(), };
'toLng': lngDest.toString(),
}; // Add multi-stop waypoints to the query parameters
uri = for (int i = 0; i < activeMenuWaypointCount; i++) {
Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams); final wp = menuWaypoints[i];
} else { if (wp != null) {
// Legacy OSRM format for multi-waypoint support queryParams['stop${i + 1}Lat'] = wp.latitude.toString();
var originCoords = origin.split(','); queryParams['stop${i + 1}Lng'] = wp.longitude.toString();
String waypointCoords = _buildOsrmWaypointCoords(); }
String osrmUrl =
'${AppLink.routesOsm}/route/v1/driving/${originCoords[1]},${originCoords[0]}$waypointCoords;$lngDest,$latDest';
uri = Uri.parse('$osrmUrl?steps=false&overview=full');
} }
uri = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);
Log.print( Log.print(
'Requesting Route URI (${isSaaSRequest ? "SaaS" : "OSRM"}, Attempt: ${attemptCount + 1}): $uri'); 'Requesting Route URI (${isSaaSRequest ? "SaaS" : "OSRM"}, Attempt: ${attemptCount + 1}): $uri');

View File

@@ -7,6 +7,7 @@ import 'package:Intaleq/controller/functions/crud.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';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../main.dart'; import '../../../main.dart';
@@ -107,7 +108,37 @@ ${'Download the Intaleq app now and enjoy your ride!'.tr}
/// tried to access the first phone number of a contact that had none. /// tried to access the first phone number of a contact that had none.
Future<void> pickContacts() async { Future<void> pickContacts() async {
try { try {
if (await FlutterContacts.requestPermission(readonly: true)) { // 1. Check current permission status using permission_handler for better control
PermissionStatus status = await Permission.contacts.status;
// 2. If status is permanently denied, direct user to settings
if (status.isPermanentlyDenied) {
Get.defaultDialog(
title: 'Permission Required'.tr,
middleText:
'Contact permission is permanently denied. Please enable it in settings to continue.'
.tr,
textConfirm: 'Settings'.tr,
textCancel: 'Cancel'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
openAppSettings();
Get.back();
},
);
return;
}
// 3. Request permission if not already granted
if (!status.isGranted) {
status = await Permission.contacts.request();
}
// 4. Proceed if granted
if (status.isGranted) {
// Also call flutter_contacts requestPermission to ensure it's synced (internal state)
await FlutterContacts.requestPermission(readonly: true);
final List<Contact> allContacts = final List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true); await FlutterContacts.getContacts(withProperties: true);
final int totalContactsOnDevice = allContacts.length; final int totalContactsOnDevice = allContacts.length;

View File

@@ -4,6 +4,18 @@ class MyTranslation extends Translations {
@override @override
Map<String, Map<String, String>> get keys => { Map<String, Map<String, String>> get keys => {
"ar": { "ar": {
"About Intaleq": "حول انطلق",
"Chat with us anytime": "دردش معنا في أي وقت",
"Direct talk with our team": "تحدث مباشرة مع فريقنا",
"Email Support": "الدعم عبر البريد الإلكتروني",
"For official inquiries": "للاستفسارات الرسمية",
"Intaleq Support": "دعم انطلق",
"Reach out to us via": "تواصل معنا عبر",
"Support is Away": "الدعم غير متاح حالياً",
"Support is currently Online": "الدعم متاح حالياً",
"Voice Call": "اتصال صوتي",
"We're here to help you 24/7": "نحن هنا لمساعدتكم على مدار الساعة",
"Working Hours:": "ساعات العمل:",
"1 Passenger": "راكب واحد", "1 Passenger": "راكب واحد",
"2 Passengers": "راكبين", "2 Passengers": "راكبين",
"3 Passengers": "٣ ركاب", "3 Passengers": "٣ ركاب",
@@ -7263,6 +7275,18 @@ class MyTranslation extends Translations {
"Change Country": "تبديل الدولة", "Change Country": "تبديل الدولة",
}, },
"tr": { "tr": {
"About Intaleq": "Intaleq Hakkında",
"Chat with us anytime": "İstediğiniz zaman bizimle sohbet edin",
"Direct talk with our team": "Ekibimizle doğrudan görüşün",
"Email Support": "E-posta Desteği",
"For official inquiries": "Resmi sorular için",
"Intaleq Support": "Intaleq Destek",
"Reach out to us via": "Bize şuradan ulaşın",
"Support is Away": "Destek şu an uzakta",
"Support is currently Online": "Destek şu an çevrimiçi",
"Voice Call": "Sesli Arama",
"We're here to help you 24/7": "7/24 size yardımcı olmaya hazırız",
"Working Hours:": "Çalışma Saatleri:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",
@@ -13840,6 +13864,18 @@ class MyTranslation extends Translations {
"Change Country": "Cambiar país", "Change Country": "Cambiar país",
}, },
"fa": { "fa": {
"About Intaleq": "درباره انطلق",
"Chat with us anytime": "هر زمان با ما چت کنید",
"Direct talk with our team": "صحبت مستقیم با تیم ما",
"Email Support": "پشتیبانی ایمیلی",
"For official inquiries": "برای استعلام‌های رسمی",
"Intaleq Support": "پشتیبانی انطلق",
"Reach out to us via": "با ما در تماس باشید از طریق",
"Support is Away": "پشتیبانی در حال حاضر در دسترس نیست",
"Support is currently Online": "پشتیبانی در حال حاضر آنلاین است",
"Voice Call": "تماس صوتی",
"We're here to help you 24/7": "ما ۷/۲۴ برای کمک به شما آماده‌ایم",
"Working Hours:": "ساعات کاری:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",
@@ -16826,6 +16862,18 @@ class MyTranslation extends Translations {
"Change Country": "Αλλαγή Χώρας", "Change Country": "Αλλαγή Χώρας",
}, },
"ur": { "ur": {
"About Intaleq": "انطلق کے بارے میں",
"Chat with us anytime": "کسی भी समय हमसे चैट करें",
"Direct talk with our team": "ہماری ٹیم سے براہ راست بات کریں",
"Email Support": "ای میل سپورٹ",
"For official inquiries": "سرکاری استفسارات کے لیے",
"Intaleq Support": "انطلق سپورٹ",
"Reach out to us via": "ہم سے رابطہ کریں بذریعہ",
"Support is Away": "سپورٹ اب دستیاب نہیں ہے",
"Support is currently Online": "سپورٹ اب آن لائن ہے",
"Voice Call": "صوتی کال",
"We're here to help you 24/7": "ہم چوبیس گھنٹے آپ کی مدد کے لیے حاضر ہیں",
"Working Hours:": "کام کے اوقات:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",
@@ -18375,6 +18423,18 @@ class MyTranslation extends Translations {
"Change Country": "ملک تبدیل کریں", "Change Country": "ملک تبدیل کریں",
}, },
"hi": { "hi": {
"About Intaleq": "Intaleq के बारे में",
"Chat with us anytime": "हमसे कभी भी चैट करें",
"Direct talk with our team": "हमारी टीम से सीधे बात करें",
"Email Support": "ईमेल सहायता",
"For official inquiries": "आधिकारिक पूछताछ के लिए",
"Intaleq Support": "Intaleq सहायता",
"Reach out to us via": "हमसे संपर्क करें",
"Support is Away": "सहायता अभी उपलब्ध नहीं है",
"Support is currently Online": "सहायता अभी ऑनलाइन है",
"Voice Call": "वॉइस कॉल",
"We're here to help you 24/7": "हम आपकी सहायता के लिए 24/7 उपलब्ध हैं",
"Working Hours:": "कार्य समय:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",
@@ -19923,6 +19983,18 @@ class MyTranslation extends Translations {
"Change Country": "देश बदलें", "Change Country": "देश बदलें",
}, },
"ru": { "ru": {
"About Intaleq": "Об Intaleq",
"Chat with us anytime": "Пишите нам в любое время",
"Direct talk with our team": "Прямой разговор с нашей командой",
"Email Support": "Поддержка по электронной почте",
"For official inquiries": "Для официальных запросов",
"Intaleq Support": "Поддержка Intaleq",
"Reach out to us via": "Свяжитесь с нами через",
"Support is Away": "Поддержка сейчас недоступна",
"Support is currently Online": "Поддержка сейчас онлайн",
"Voice Call": "Голосовой звонок",
"We're here to help you 24/7": "Мы готовы помочь вам 24/7",
"Working Hours:": "Рабочие часы:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",
@@ -21375,6 +21447,18 @@ class MyTranslation extends Translations {
"Change Country": "Сменить страну", "Change Country": "Сменить страну",
}, },
"it": { "it": {
"About Intaleq": "Informazioni su Intaleq",
"Chat with us anytime": "Chatta con noi in qualsiasi momento",
"Direct talk with our team": "Parla direttamente con il nostro team",
"Email Support": "Supporto via email",
"For official inquiries": "Per richieste ufficiali",
"Intaleq Support": "Supporto Intaleq",
"Reach out to us via": "Contattaci tramite",
"Support is Away": "Il supporto è attualmente assente",
"Support is currently Online": "Il supporto è attualmente online",
"Voice Call": "Chiamata vocale",
"We're here to help you 24/7": "Siamo qui per aiutarti 24/7",
"Working Hours:": "Orario di lavoro:",
"1 Passenger": "1 Passenger", "1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers", "2 Passengers": "2 Passengers",
"3 Passengers": "3 Passengers", "3 Passengers": "3 Passengers",

1
lib/env/env.dart vendored
View File

@@ -1,3 +1,4 @@
// Force rebuild to pick up late coordinates from .env
import 'package:envied/envied.dart'; import 'package:envied/envied.dart';
part 'env.g.dart'; part 'env.g.dart';

26290
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ class Log {
Log._(); Log._();
static void print(String value, {StackTrace? stackTrace}) { static void print(String value, {StackTrace? stackTrace}) {
developer.log(value, name: 'LOG', stackTrace: stackTrace); // developer.log(value, name: 'LOG', stackTrace: stackTrace);
} }
static Object? inspect(Object? object) { static Object? inspect(Object? object) {

View File

@@ -35,187 +35,675 @@ class LoginPage extends StatelessWidget {
if (box.read(BoxName.agreeTerms) != 'agreed') if (box.read(BoxName.agreeTerms) != 'agreed')
_buildAgreementPage(context, controller) _buildAgreementPage(context, controller)
else if (box.read(BoxName.locationPermission) != 'true') else if (box.read(BoxName.locationPermission) != 'true')
_buildLocationPermissionDialog(controller) _buildLocationPermissionDialog(context, controller)
// else if (box.read(BoxName.isTest).toString() == '0')
// buildEmailPasswordForm(controller)
else else
// _buildLoginContent(controller, authController),
PhoneNumberScreen() PhoneNumberScreen()
], ],
), ),
); );
} }
Widget _buildAgreementPage(BuildContext context, LoginController controller) { // ─────────────────────────────────────────────────────────────────────────
// This UI can be identical to the one in LoginPage for consistency. // SHARED HELPERS
// I am reusing the improved design from the previous request. // ─────────────────────────────────────────────────────────────────────────
return Padding(
padding: const EdgeInsets.all(24.0), /// Subtle geometric background — two soft circles, no heavy blur needed.
child: Column( Widget _buildBackground(BuildContext context) {
mainAxisAlignment: MainAxisAlignment.center, final isDark = Theme.of(context).brightness == Brightness.dark;
children: [ return Stack(
Icon(Icons.policy_outlined, size: 80, color: AppColor.primaryColor), children: [
const SizedBox(height: 20), // Base gradient
Text("passenger agreement".tr, Container(
textAlign: TextAlign.center, style: AppStyle.headTitle2), decoration: BoxDecoration(
const SizedBox(height: 30), gradient: LinearGradient(
RichText( begin: Alignment.topLeft,
textAlign: TextAlign.center, end: Alignment.bottomRight,
text: TextSpan( colors: isDark
style: AppStyle.title.copyWith(height: 1.5), ? [const Color(0xFF0D0D14), const Color(0xFF161622)]
children: [ : [const Color(0xFFF8F9FF), const Color(0xFFEFF1FB)],
TextSpan(
text:
"To become a passenger, you must review and agree to the "
.tr),
TextSpan(
text: 'Terms of Use'.tr,
style: TextStyle(
decoration: TextDecoration.underline,
color: AppColor.cyanBlue,
fontWeight: FontWeight.bold),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(Uri.parse(
'https://intaleq.xyz/intaleq/privacy_policy.php'));
}),
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
],
), ),
), ),
Expanded( ),
child: Container( // Top-right accent circle
decoration: BoxDecoration( Positioned(
border: Border.all(color: Colors.grey.shade300), top: -80,
borderRadius: BorderRadius.circular(8), right: -60,
), child: Container(
child: SingleChildScrollView( width: 260,
padding: const EdgeInsets.all(12), height: 260,
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar' decoration: BoxDecoration(
? AppInformation.privacyPolicyArabic shape: BoxShape.circle,
: AppInformation.privacyPolicy), gradient: RadialGradient(
colors: [
AppColor.primaryColor.withOpacity(isDark ? 0.18 : 0.12),
AppColor.primaryColor.withOpacity(0.0),
],
), ),
), ),
), ),
CheckboxListTile( ),
title: Text('I Agree'.tr, style: AppStyle.title), // Bottom-left accent circle
value: controller.isAgreeTerms, Positioned(
onChanged: (value) => controller.changeAgreeTerm(), bottom: -100,
activeColor: AppColor.primaryColor, left: -80,
controlAffinity: ListTileControlAffinity.leading, child: Container(
), width: 320,
const SizedBox(height: 16), height: 320,
SizedBox( decoration: BoxDecoration(
width: double.infinity, shape: BoxShape.circle,
child: MyElevatedButton( gradient: RadialGradient(
title: 'Continue'.tr, colors: [
onPressed: controller.isAgreeTerms AppColor.primaryColor.withOpacity(isDark ? 0.12 : 0.08),
? () => controller.saveAgreementTerms() AppColor.primaryColor.withOpacity(0.0),
: () {}, ],
),
), ),
), ),
], ),
), ],
); );
} }
Widget buildEmailPasswordForm(LoginController controller) { /// Glassy card container used across screens.
Widget _glassCard({
required Widget child,
required bool isDark,
EdgeInsets padding = const EdgeInsets.all(20),
double radius = 20,
}) {
return Container( return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(radius),
borderRadius: BorderRadius.circular(12), color: isDark
? Colors.white.withOpacity(0.05)
: Colors.white.withOpacity(0.75),
border: Border.all(
color: isDark
? Colors.white.withOpacity(0.08)
: Colors.white.withOpacity(0.9),
width: 1,
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.grey.withOpacity(0.2), color: isDark
spreadRadius: 2, ? Colors.black.withOpacity(0.3)
blurRadius: 5, : Colors.black.withOpacity(0.06),
offset: const Offset(0, 3), blurRadius: 24,
offset: const Offset(0, 8),
), ),
], ],
), ),
child: Form( padding: padding,
key: controller.formKey, child: child,
child: Column( );
crossAxisAlignment: CrossAxisAlignment.stretch, }
children: [
TextFormField( /// Pill-shaped icon badge with gradient background.
keyboardType: TextInputType.emailAddress, Widget _iconBadge(IconData icon, bool isDark) {
controller: controller.emailController, return Container(
decoration: InputDecoration( width: 80,
labelText: 'Email'.tr, height: 80,
hintText: 'Your email address'.tr, decoration: BoxDecoration(
border: const OutlineInputBorder(), shape: BoxShape.circle,
), gradient: LinearGradient(
validator: (value) => value == null || begin: Alignment.topLeft,
value.isEmpty || end: Alignment.bottomRight,
!value.contains('@') || colors: [
!value.contains('.') AppColor.primaryColor,
? 'Enter a valid email'.tr AppColor.primaryColor.withOpacity(0.7),
: null,
),
const SizedBox(height: 16),
TextFormField(
obscureText: true,
controller: controller.passwordController,
decoration: InputDecoration(
labelText: 'Password'.tr,
hintText: 'Your password'.tr,
border: const OutlineInputBorder(),
),
validator: (value) => value == null || value.isEmpty
? 'Enter your password'.tr
: null,
),
const SizedBox(height: 24),
GetBuilder<LoginController>(
builder: (controller) => controller.isloading
? const Center(child: CircularProgressIndicator())
: ElevatedButton(
onPressed: () {
if (controller.formKey.currentState!.validate()) {
controller.login();
}
},
child: Text('Submit'.tr),
),
),
], ],
), ),
boxShadow: [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.35),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Icon(icon, color: Colors.white, size: 36),
);
}
/// Section divider line.
Widget _divider(bool isDark) => Container(
height: 1,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
isDark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.08),
Colors.transparent,
],
),
),
);
// ─────────────────────────────────────────────────────────────────────────
// AGREEMENT PAGE
// ─────────────────────────────────────────────────────────────────────────
Widget _buildAgreementPage(BuildContext context, LoginController controller) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
return Stack(
children: [
_buildBackground(context),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Column(
children: [
// ── Header ──────────────────────────────────────────────
const SizedBox(height: 20),
_iconBadge(Icons.policy_outlined, isDark),
const SizedBox(height: 16),
Text(
"passenger agreement".tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle2.copyWith(
color: textMain,
fontSize: 22,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
),
),
const SizedBox(height: 10),
// Subtitle with link
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: AppStyle.title.copyWith(
height: 1.6,
color: textSub,
fontSize: 13.5,
),
children: [
TextSpan(
text:
"To become a passenger, you must review and agree to the "
.tr,
),
TextSpan(
text: 'Terms of Use'.tr,
style: TextStyle(
decoration: TextDecoration.underline,
decorationColor: AppColor.primaryColor,
color: AppColor.primaryColor,
fontWeight: FontWeight.w600,
),
recognizer: TapGestureRecognizer()
..onTap = () {
launchUrl(Uri.parse(
'https://intaleq.xyz/intaleq/privacy_policy.php'));
},
),
TextSpan(text: " and acknowledge our Privacy Policy.".tr),
],
),
),
const SizedBox(height: 16),
_divider(isDark),
const SizedBox(height: 12),
// ── Policy scroll area ──────────────────────────────────
Expanded(
child: _glassCard(
isDark: isDark,
padding: EdgeInsets.zero,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(18),
child: HtmlWidget(
box.read(BoxName.lang).toString() == 'ar'
? AppInformation.privacyPolicyArabic
: AppInformation.privacyPolicy,
textStyle: TextStyle(
color: textSub,
fontSize: 13,
height: 1.7,
),
),
),
),
),
),
const SizedBox(height: 12),
_divider(isDark),
// ── Checkbox row ────────────────────────────────────────
_glassCard(
isDark: isDark,
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: CheckboxListTile(
title: Text(
'I Agree'.tr,
style: AppStyle.title.copyWith(
color: textMain,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
value: controller.isAgreeTerms,
onChanged: (value) => controller.changeAgreeTerm(),
activeColor: AppColor.primaryColor,
checkColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6)),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
dense: true,
),
),
const SizedBox(height: 12),
// ── CTA Button ──────────────────────────────────────────
_buildPrimaryButton(
label: 'Continue'.tr,
enabled: controller.isAgreeTerms,
onPressed: controller.isAgreeTerms
? () => controller.saveAgreementTerms()
: () {},
isDark: isDark,
),
const SizedBox(height: 8),
],
),
),
),
],
);
}
// ─────────────────────────────────────────────────────────────────────────
// EMAIL / PASSWORD FORM
// ─────────────────────────────────────────────────────────────────────────
Widget buildEmailPasswordForm(
BuildContext context, LoginController controller) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
return Stack(
children: [
_buildBackground(context),
SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Logo / badge
Center(child: _iconBadge(Icons.lock_outline_rounded, isDark)),
const SizedBox(height: 20),
Text(
'Welcome Back'.tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle2.copyWith(
color: textMain,
fontSize: 26,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
Text(
'Sign in to continue'.tr,
textAlign: TextAlign.center,
style:
AppStyle.title.copyWith(color: textSub, fontSize: 14),
),
const SizedBox(height: 32),
_glassCard(
isDark: isDark,
padding: const EdgeInsets.all(24),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Email field
_buildTextField(
controller: controller.emailController,
label: 'Email'.tr,
hint: 'Your email address'.tr,
icon: Icons.email_outlined,
isDark: isDark,
keyboardType: TextInputType.emailAddress,
validator: (value) => value == null ||
value.isEmpty ||
!value.contains('@') ||
!value.contains('.')
? 'Enter a valid email'.tr
: null,
),
const SizedBox(height: 16),
// Password field
_buildTextField(
controller: controller.passwordController,
label: 'Password'.tr,
hint: 'Your password'.tr,
icon: Icons.lock_outline,
isDark: isDark,
obscureText: true,
validator: (value) => value == null || value.isEmpty
? 'Enter your password'.tr
: null,
),
const SizedBox(height: 28),
GetBuilder<LoginController>(
builder: (controller) => controller.isloading
? Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
color: AppColor.primaryColor,
strokeWidth: 2.5,
),
),
)
: _buildPrimaryButton(
label: 'Submit'.tr,
enabled: true,
isDark: isDark,
onPressed: () {
if (controller.formKey.currentState!
.validate()) {
controller.login();
}
},
),
),
],
),
),
),
],
),
),
),
),
],
);
}
// ─────────────────────────────────────────────────────────────────────────
// LOCATION PERMISSION PAGE
// ─────────────────────────────────────────────────────────────────────────
Widget _buildLocationPermissionDialog(
BuildContext context, LoginController controller) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textMain = isDark ? Colors.white : const Color(0xFF1A1A2E);
final textSub = isDark ? Colors.white60 : const Color(0xFF6B7280);
return Stack(
children: [
_buildBackground(context),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(flex: 2),
// Animated-look stacked circles around icon
Stack(
alignment: Alignment.center,
children: [
// Outer glow ring
Container(
width: 140,
height: 140,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.primaryColor.withOpacity(0.08),
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2),
width: 1,
),
),
),
// Mid ring
Container(
width: 108,
height: 108,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.primaryColor.withOpacity(0.12),
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.3),
width: 1,
),
),
),
// Core badge
_iconBadge(Icons.location_on_outlined, isDark),
],
),
const SizedBox(height: 36),
Text(
'Enable Location Access'.tr,
style: AppStyle.headTitle2.copyWith(
color: textMain,
fontSize: 24,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 14),
Text(
'We need your location to find nearby drivers for pickups and drop-offs.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
color: textSub,
fontSize: 14.5,
height: 1.6,
),
),
const SizedBox(height: 40),
// Feature chips row
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_featureChip(
Icons.speed_outlined, 'Fast matching'.tr, isDark),
const SizedBox(width: 10),
_featureChip(Icons.shield_outlined, 'Secure'.tr, isDark),
const SizedBox(width: 10),
_featureChip(Icons.near_me_outlined, 'Nearby'.tr, isDark),
],
),
const Spacer(flex: 3),
_buildPrimaryButton(
label: 'Next'.tr,
enabled: true,
isDark: isDark,
onPressed: () async =>
await controller.getLocationPermission(),
),
const SizedBox(height: 16),
],
),
),
),
],
);
}
// ─────────────────────────────────────────────────────────────────────────
// SHARED SMALL WIDGETS
// ─────────────────────────────────────────────────────────────────────────
/// Reusable styled text field.
Widget _buildTextField({
required TextEditingController controller,
required String label,
required String hint,
required IconData icon,
required bool isDark,
bool obscureText = false,
TextInputType? keyboardType,
String? Function(String?)? validator,
}) {
final fill = isDark ? Colors.white.withOpacity(0.05) : Colors.white;
final border =
isDark ? Colors.white.withOpacity(0.1) : const Color(0xFFE5E7EB);
return TextFormField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
style: TextStyle(
color: isDark ? Colors.white : const Color(0xFF1A1A2E),
fontSize: 14.5,
),
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: Icon(icon,
size: 20, color: isDark ? Colors.white38 : const Color(0xFF9CA3AF)),
labelStyle: TextStyle(
color: isDark ? Colors.white38 : const Color(0xFF9CA3AF),
fontSize: 13.5,
),
hintStyle: TextStyle(
color: isDark ? Colors.white24 : const Color(0xFFD1D5DB),
),
filled: true,
fillColor: fill,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: border, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: AppColor.primaryColor, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: Color(0xFFEF4444), width: 1.5),
),
),
validator: validator,
);
}
/// Full-width gradient primary button.
Widget _buildPrimaryButton({
required String label,
required bool enabled,
required VoidCallback onPressed,
required bool isDark,
}) {
return SizedBox(
width: double.infinity,
height: 54,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: enabled
? LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
AppColor.primaryColor,
AppColor.primaryColor.withOpacity(0.8),
],
)
: null,
color: enabled
? null
: (isDark ? Colors.white12 : const Color(0xFFE5E7EB)),
boxShadow: enabled
? [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.32),
blurRadius: 16,
offset: const Offset(0, 6),
)
]
: null,
),
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
child: Text(
label,
style: TextStyle(
color: enabled
? Colors.white
: (isDark ? Colors.white38 : const Color(0xFF9CA3AF)),
fontWeight: FontWeight.w600,
fontSize: 15,
letterSpacing: 0.4,
),
),
),
), ),
); );
} }
Widget _buildLocationPermissionDialog(LoginController controller) { /// Small chip used on location page.
return Padding( Widget _featureChip(IconData icon, String text, bool isDark) {
padding: const EdgeInsets.all(32), return Container(
child: Column( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
mainAxisAlignment: MainAxisAlignment.center, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: isDark
? Colors.white.withOpacity(0.06)
: Colors.white.withOpacity(0.8),
border: Border.all(
color: isDark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.07),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.location_on, size: 60, color: AppColor.primaryColor), Icon(icon, size: 14, color: AppColor.primaryColor),
const SizedBox(height: 20), const SizedBox(width: 5),
Text( Text(
'Enable Location Access'.tr, text,
style: AppStyle.headTitle2, style: TextStyle(
textAlign: TextAlign.center, fontSize: 12,
fontWeight: FontWeight.w500,
color: isDark ? Colors.white70 : const Color(0xFF374151),
),
), ),
const SizedBox(height: 10),
Text(
'We need your location to find nearby drivers for pickups and drop-offs.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async => await controller.getLocationPermission(),
child: Text('Next'.tr),
// child: Text('Allow Location Access'.tr),
),
// TextButton(
// onPressed: () => openAppSettings(),
// child: Text('Open Settings'.tr),
// ),
], ],
), ),
); );

File diff suppressed because it is too large Load Diff

View File

@@ -1,102 +1,360 @@
import 'package:Intaleq/constant/colors.dart'; import 'package:Intaleq/constant/colors.dart';
import 'package:Intaleq/constant/style.dart'; import 'package:Intaleq/constant/style.dart';
// ignore: unused_import
import 'package:Intaleq/controller/functions/launch.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart'; 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: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 '../../../controller/functions/tts.dart'; import '../../../controller/functions/tts.dart';
import '../../../controller/home/contact_us_controller.dart'; import '../../../controller/home/contact_us_controller.dart';
import '../../widgets/elevated_btn.dart';
class ContactUsPage extends StatelessWidget { class ContactUsPage extends StatelessWidget {
ContactUsPage({super.key}); ContactUsPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(ContactUsController()); final controller = Get.put(ContactUsController());
return GetBuilder<ContactUsController>(builder: (controller) { final isDark = Theme.of(context).brightness == Brightness.dark;
return MyScafolld(
title: "Contact Us".tr, return MyScafolld(
body: [ title: "Contact Us".tr,
Padding( isleading: true,
padding: const EdgeInsets.all(8.0), body: [
child: ListView( // Background subtle gradient/shape
// crossAxisAlignment: CrossAxisAlignment.center, Positioned.fill(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Container(
children: [ decoration: BoxDecoration(
Container( gradient: LinearGradient(
decoration: AppStyle.boxDecoration1, begin: Alignment.topLeft,
child: Column( end: Alignment.bottomRight,
children: [ colors: isDark
ClipRRect( ? [const Color(0xFF0D0D14), const Color(0xFF161622)]
borderRadius: BorderRadius.circular(15), : [const Color(0xFFF8F9FF), const Color(0xFFEFF1FB)],
child: Image.asset('assets/images/logo.gif')),
IconButton(
onPressed: () async {
Get.find<TextToSpeechController>().speakText(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr);
},
icon: const Icon(Icons.headphones),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
],
),
),
const SizedBox(
height: 30,
),
Container(
decoration: AppStyle.boxDecoration1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"You can contact us during working hours from 10:00 - 16:00."
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
),
InkWell(
onTap: () => controller.showContactDialog(context),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(
Icons.phone,
color: AppColor.blueColor,
),
Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
Icon(
Icons.email,
color: AppColor.redColor,
),
],
),
),
const SizedBox(
height: 30,
)
],
), ),
) ),
], ),
isleading: true); ),
}); SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Hero Section ──────────────────────────────────────────
_buildHeroSection(isDark),
const SizedBox(height: 24),
// ── Availability Status ────────────────────────────────────
_buildAvailabilityStatus(controller, isDark),
const SizedBox(height: 24),
// ── Support Actions ────────────────────────────────────────
Text(
"Reach out to us via".tr,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold,
fontSize: 16,
color: isDark ? Colors.white70 : Colors.black54,
),
),
const SizedBox(height: 12),
_buildContactCards(controller, isDark),
const SizedBox(height: 32),
// ── About Section ──────────────────────────────────────────
_buildAboutSection(isDark),
const SizedBox(height: 40),
],
),
),
),
],
);
}
Widget _buildHeroSection(bool isDark) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: isDark ? Colors.white12 : Colors.black.withOpacity(0.05),
),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black26 : Colors.black.withOpacity(0.03),
blurRadius: 20,
offset: const Offset(0, 10),
)
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isDark ? Colors.white10 : Colors.white,
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/logo.gif', height: 80),
),
),
const SizedBox(height: 16),
Text(
"Intaleq Support".tr,
style: AppStyle.headTitle2.copyWith(
fontSize: 22,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : AppColor.primaryColor,
),
),
const SizedBox(height: 4),
Text(
"We're here to help you 24/7".tr,
style: TextStyle(
color: isDark ? Colors.white54 : Colors.black45,
fontSize: 14,
),
),
],
),
);
}
Widget _buildAvailabilityStatus(ContactUsController controller, bool isDark) {
final isOpen = controller.isWorkTime;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: (isOpen ? AppColor.greenColor : Colors.orange).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: (isOpen ? AppColor.greenColor : Colors.orange).withOpacity(0.2),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isOpen ? AppColor.greenColor : Colors.orange,
),
child: Icon(
isOpen ? Icons.check_circle_outline : Icons.access_time,
color: Colors.white,
size: 18,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isOpen ? "Support is currently Online".tr : "Support is Away".tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isOpen ? AppColor.greenColor : Colors.orange,
),
),
Text(
"${"Working Hours:".tr} ${controller.workHoursString}",
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.white60 : Colors.black54,
),
),
],
),
),
],
),
);
}
Widget _buildContactCards(ContactUsController controller, bool isDark) {
return Column(
children: [
_ContactCard(
title: "Voice Call".tr,
subtitle: "Direct talk with our team".tr,
icon: Icons.phone_in_talk_outlined,
color: AppColor.primaryColor,
isDark: isDark,
enabled: controller.isWorkTime,
onTap: controller.makeCall,
trailing: controller.isWorkTime ? null : Icon(Icons.lock_clock_outlined, size: 20, color: isDark ? Colors.white24 : Colors.black26),
),
const SizedBox(height: 12),
_ContactCard(
title: "WhatsApp".tr,
subtitle: "Chat with us anytime".tr,
icon: FontAwesome.whatsapp,
color: AppColor.greenColor,
isDark: isDark,
onTap: controller.sendWhatsApp,
),
const SizedBox(height: 12),
_ContactCard(
title: "Email Support".tr,
subtitle: "For official inquiries".tr,
icon: Icons.alternate_email_outlined,
color: AppColor.redColor,
isDark: isDark,
onTap: controller.sendEmail,
),
],
);
}
Widget _buildAboutSection(bool isDark) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.02) : Colors.black.withOpacity(0.02),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: isDark ? Colors.white10 : Colors.black12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"About Intaleq".tr,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: isDark ? Colors.white : Colors.black87,
),
),
IconButton(
onPressed: () {
Get.find<TextToSpeechController>().speakText(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience.'
.tr,
);
},
icon: Icon(Icons.volume_up_outlined, color: AppColor.primaryColor),
),
],
),
const SizedBox(height: 12),
Text(
'Intaleq is the safest and most reliable ride-sharing app designed especially for passengers in Syria. We provide a comfortable, respectful, and affordable riding experience with features that prioritize your safety and convenience. Our trusted captains are verified, insured, and supported by regular car maintenance carried out by top engineers. We also offer on-road support services to make sure every trip is smooth and worry-free. With Intaleq, you enjoy quality, safety, and peace of mind—every time you ride.'
.tr,
style: TextStyle(
color: isDark ? Colors.white70 : Colors.black54,
fontSize: 14,
height: 1.6,
),
),
],
),
);
}
}
class _ContactCard extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final bool isDark;
final bool enabled;
final VoidCallback onTap;
final Widget? trailing;
const _ContactCard({
required this.title,
required this.subtitle,
required this.icon,
required this.color,
required this.isDark,
this.enabled = true,
required this.onTap,
this.trailing,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: enabled ? onTap : null,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: enabled
? (isDark ? Colors.white.withOpacity(0.05) : Colors.white)
: (isDark ? Colors.white.withOpacity(0.02) : Colors.grey.withOpacity(0.05)),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: enabled
? (isDark ? Colors.white12 : Colors.black.withOpacity(0.05))
: Colors.transparent,
),
boxShadow: enabled ? [
BoxShadow(
color: isDark ? Colors.black12 : Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
)
] : null,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: (enabled ? color : Colors.grey).withOpacity(0.12),
borderRadius: BorderRadius.circular(14),
),
child: Icon(icon, color: enabled ? color : Colors.grey, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: enabled
? (isDark ? Colors.white : Colors.black87)
: Colors.grey,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white38 : Colors.black38,
),
),
],
),
),
trailing ?? Icon(
Icons.arrow_forward_ios,
size: 14,
color: isDark ? Colors.white24 : Colors.black12,
),
],
),
),
),
);
} }
} }

View File

@@ -14,6 +14,7 @@ import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart'; import '../../../controller/functions/crud.dart';
import '../../../controller/functions/tts.dart'; import '../../../controller/functions/tts.dart';
import '../../../controller/home/decode_polyline_isolate.dart'; import '../../../controller/home/decode_polyline_isolate.dart';
import '../../../env/env.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../print.dart'; import '../../../print.dart';
import 'dart:ui'; import 'dart:ui';
@@ -719,7 +720,7 @@ class NavigationController extends GetxController
try { try {
// 1. Try SaaS first // 1. Try SaaS first
http.Response response = await http.get(saasUri, headers: { http.Response response = await http.get(saasUri, headers: {
'x-api-key': 'intaleq_secret_2026', 'x-api-key': Env.mapSaasKey,
}); });
bool useSaaS = response.statusCode == 200; bool useSaaS = response.statusCode == 200;
@@ -947,7 +948,8 @@ class NavigationController extends GetxController
final destBytes = await rootBundle.load('assets/images/b.png'); final destBytes = await rootBundle.load('assets/images/b.png');
await mapController!.addImage('car_icon', carBytes.buffer.asUint8List()); await mapController!.addImage('car_icon', carBytes.buffer.asUint8List());
await mapController!.addImage('start_icon', startBytes.buffer.asUint8List()); await mapController!
.addImage('start_icon', startBytes.buffer.asUint8List());
await mapController!.addImage('dest_icon', destBytes.buffer.asUint8List()); await mapController!.addImage('dest_icon', destBytes.buffer.asUint8List());
} }