flutter(rider): rebuild lib on Cubit/Bloc + Tripz backend (core + auth slice)
- keep store identity (com.mobileapp.store.ride, shorebird, firebase, fonts); old lib -> lib_old_getx - core: config/di/router(go_router)/theme/api_client(dio+tenant+bearer)/token_store(secure) - auth: repo + AuthCubit + OTP phone/otp screens; home: flutter_map (OSM placeholder, انطلق TODO) - android usesCleartextTraffic=true for http API; maps=flutter_map (no Google), state=Cubit Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/links.dart';
|
||||
import '../../views/widgets/elevated_btn.dart';
|
||||
import '../functions/crud.dart';
|
||||
|
||||
class BlinkingController extends GetxController {
|
||||
final promoFormKey = GlobalKey<FormState>();
|
||||
|
||||
final promo = TextEditingController();
|
||||
bool promoTaken = false;
|
||||
void applyPromoCodeToPassenger() async {
|
||||
//TAWJIHI
|
||||
if (promoFormKey.currentState!.validate()) {
|
||||
CRUD().get(link: AppLink.getPassengersPromo, payload: {
|
||||
'promo_code': promo.text,
|
||||
}).then((value) {
|
||||
if (value == 'failure') {
|
||||
Get.defaultDialog(
|
||||
title: 'Promo End !'.tr,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Back',
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
},
|
||||
));
|
||||
}
|
||||
var decode = jsonDecode(value);
|
||||
|
||||
// if (decode["status"] == "success") {
|
||||
// var firstElement = decode["message"][0];
|
||||
// if (double.parse(box.read(BoxName.passengerWalletTotal)) < 0) {
|
||||
// totalPassenger = totalCostPassenger -
|
||||
// (totalCostPassenger * int.parse(firstElement['amount']) / 100);
|
||||
// update();
|
||||
// } else {
|
||||
// totalPassenger = totalCostPassenger -
|
||||
// (totalCostPassenger * int.parse(firstElement['amount']) / 100);
|
||||
// update();
|
||||
// }
|
||||
|
||||
// totalDriver = totalDriver -
|
||||
// (totalDriver * int.parse(firstElement['amount']) / 100);
|
||||
// promoTaken = true;
|
||||
// update();
|
||||
// Get.back();
|
||||
// }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reactive variable for blinking (on/off)
|
||||
var isLightOn = false.obs;
|
||||
|
||||
// To animate the border color
|
||||
var borderColor = Colors.black.obs;
|
||||
|
||||
Timer? _blinkingTimer;
|
||||
|
||||
// Method to start blinking for 5 seconds
|
||||
void startBlinking() {
|
||||
int count = 0;
|
||||
|
||||
_blinkingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
// Toggle light on/off
|
||||
isLightOn.value = !isLightOn.value;
|
||||
borderColor.value = isLightOn.value
|
||||
? Colors.yellow
|
||||
: Colors.black; // Animate border color
|
||||
|
||||
count++;
|
||||
|
||||
// Stop blinking after 5 seconds
|
||||
if (count >= 35) {
|
||||
timer.cancel();
|
||||
isLightOn.value = false; // Ensure light turns off
|
||||
borderColor.value = Colors.black; // Reset the border color
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_blinkingTimer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../functions/launch.dart';
|
||||
|
||||
class ContactUsController extends GetxController {
|
||||
/// WORKING HOURS (10:00 → 16:00)
|
||||
final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0);
|
||||
final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0);
|
||||
|
||||
bool _isWithinWorkTime(TimeOfDay now) {
|
||||
return (now.hour > workStartTime.hour ||
|
||||
(now.hour == workStartTime.hour &&
|
||||
now.minute >= workStartTime.minute)) &&
|
||||
(now.hour < workEndTime.hour ||
|
||||
(now.hour == workEndTime.hour && now.minute <= workEndTime.minute));
|
||||
}
|
||||
|
||||
/// PHONE LIST (USED FOR CALLS + WHATSAPP)
|
||||
final List<String> phoneNumbers = [
|
||||
'+963952475734',
|
||||
'+963952475740',
|
||||
'+963952475742'
|
||||
];
|
||||
|
||||
/// RANDOM PHONE SELECTOR
|
||||
String getRandomPhone() {
|
||||
final random = Random();
|
||||
return phoneNumbers[random.nextInt(phoneNumbers.length)];
|
||||
}
|
||||
|
||||
/// SHOW DIALOG
|
||||
void showContactDialog(BuildContext context) {
|
||||
TimeOfDay now = TimeOfDay.now();
|
||||
bool withinHours = _isWithinWorkTime(now);
|
||||
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
title: Text('Contact Us'.tr),
|
||||
message: Text('Choose a contact option'.tr),
|
||||
actions: <Widget>[
|
||||
/// 📞 CALL (RANDOM) — ONLY DURING WORK HOURS
|
||||
if (withinHours)
|
||||
CupertinoActionSheetAction(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Icon(CupertinoIcons.phone),
|
||||
Text('Call Support'.tr),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
final phone = getRandomPhone();
|
||||
makePhoneCall(phone);
|
||||
},
|
||||
),
|
||||
|
||||
/// ⛔ OUTSIDE WORK HOURS — SHOW INFO
|
||||
if (!withinHours)
|
||||
CupertinoActionSheetAction(
|
||||
child: Text(
|
||||
'Work time is from 10:00 AM to 16:00 PM.\nYou can send a WhatsApp message or email.'
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
|
||||
/// 💬 WHATSAPP (RANDOM)
|
||||
CupertinoActionSheetAction(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const Icon(
|
||||
FontAwesome.whatsapp,
|
||||
color: AppColor.greenColor,
|
||||
),
|
||||
Text('Send WhatsApp Message'.tr),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
final phone = getRandomPhone();
|
||||
launchCommunication('whatsapp', phone, 'Hello'.tr);
|
||||
},
|
||||
),
|
||||
|
||||
/// 📧 EMAIL
|
||||
CupertinoActionSheetAction(
|
||||
child: Text('Send Email'.tr),
|
||||
onPressed: () => launchCommunication(
|
||||
'email', 'support@intaleqapp.com', 'Hello'.tr),
|
||||
),
|
||||
],
|
||||
|
||||
/// ❌ CANCEL BUTTON
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
child: Text('Cancel'.tr),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
List<LatLng> decodePolylineIsolate(String encoded) {
|
||||
List<LatLng> points = [];
|
||||
int index = 0, len = encoded.length;
|
||||
int lat = 0, lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
int b, shift = 0, result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
|
||||
points.add(LatLng(lat / 1E5, lng / 1E5));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
// Helper method for decoding polyline (if not already defined)
|
||||
// List<LatLng> decodePolyline(String encoded) {
|
||||
// List<LatLng> points = [];
|
||||
// int index = 0, len = encoded.length;
|
||||
// int lat = 0, lng = 0;
|
||||
|
||||
// while (index < len) {
|
||||
// int b, shift = 0, result = 0;
|
||||
// do {
|
||||
// b = encoded.codeUnitAt(index++) - 63;
|
||||
// result |= (b & 0x1f) << shift;
|
||||
// shift += 5;
|
||||
// } while (b >= 0x20);
|
||||
// int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
// lat += dlat;
|
||||
|
||||
// shift = 0;
|
||||
// result = 0;
|
||||
// do {
|
||||
// b = encoded.codeUnitAt(index++) - 63;
|
||||
// result |= (b & 0x1f) << shift;
|
||||
// shift += 5;
|
||||
// } while (b >= 0x20);
|
||||
// int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
// lng += dlng;
|
||||
|
||||
// points.add(LatLng(lat / 1E5, lng / 1E5));
|
||||
// }
|
||||
|
||||
// return points;
|
||||
// }
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class DeepLinkController extends GetxController {
|
||||
final _appLinks = AppLinks();
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
|
||||
// تخزين الرابط الخام (URL) ليتم معالجته لاحقاً في MapPassengerController
|
||||
final Rx<String?> rawDeepLink = Rx<String?>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initDeepLinks();
|
||||
}
|
||||
|
||||
Future<void> initDeepLinks() async {
|
||||
// الاستماع للروابط والتطبيق يعمل في الخلفية
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
print('🔗 Received deep link (Stream): $uri');
|
||||
rawDeepLink.value = uri.toString();
|
||||
});
|
||||
|
||||
// الاستماع للروابط إذا كان التطبيق مغلقاً تماماً (Cold Start)
|
||||
try {
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
print('🔗 Received initial deep link (Cold Start): $initialUri');
|
||||
rawDeepLink.value = initialUri.toString();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error getting initial link: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_linkSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
class DevicePerformanceManager {
|
||||
/// القائمة البيضاء لموديلات الهواتف القوية (Flagships Only)
|
||||
/// أي هاتف يبدأ موديله بأحد هذه الرموز سيعتبر قوياً
|
||||
static const List<String> _highEndSamsungModels = [
|
||||
'SM-S', // سلسلة Galaxy S21, S22, S23, S24 (S901, S908, S911...)
|
||||
'SM-F', // سلسلة Fold و Flip (Z Fold, Z Flip)
|
||||
'SM-N9', // سلسلة Note 9, Note 10, Note 20
|
||||
'SM-G9', // سلسلة S10, S20 (G970, G980...)
|
||||
];
|
||||
|
||||
static const List<String> _highEndGoogleModels = [
|
||||
'Pixel 6',
|
||||
'Pixel 7',
|
||||
'Pixel 8',
|
||||
'Pixel 9',
|
||||
'Pixel Fold'
|
||||
];
|
||||
|
||||
static const List<String> _highEndHuaweiModels = [
|
||||
'ELS-', // P40 Pro
|
||||
'ANA-', // P40
|
||||
'HMA-', // Mate 20
|
||||
'LYA-', // Mate 20 Pro
|
||||
'VOG-', // P30 Pro
|
||||
'ELE-', // P30
|
||||
'NOH-', // Mate 40 Pro
|
||||
'AL00', // Mate X series (some)
|
||||
];
|
||||
|
||||
static const List<String> _highEndXiaomiModels = [
|
||||
'2201122', // Xiaomi 12 series patterns often look like this
|
||||
'2210132', // Xiaomi 13
|
||||
'2304FPN', // Xiaomi 13 Ultra
|
||||
'M2007J1', // Mi 10 series
|
||||
'M2102K1', // Mi 11 Ultra
|
||||
];
|
||||
|
||||
static const List<String> _highEndOnePlusModels = [
|
||||
'GM19', // OnePlus 7
|
||||
'HD19', // OnePlus 7T
|
||||
'IN20', // OnePlus 8
|
||||
'KB20', // OnePlus 8T
|
||||
'LE21', // OnePlus 9
|
||||
'NE22', // OnePlus 10
|
||||
'PHB110', // OnePlus 11
|
||||
'CPH', // Newer OnePlus models
|
||||
];
|
||||
|
||||
/// دالة الفحص الرئيسية
|
||||
static Future<bool> isHighEndDevice() async {
|
||||
// 1. الآيفون دائماً قوي (نظام الرسوميات فيه متفوق حتى في الموديلات القديمة)
|
||||
if (Platform.isIOS) return true;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
try {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
|
||||
String manufacturer = androidInfo.manufacturer.toLowerCase();
|
||||
String model =
|
||||
androidInfo.model.toUpperCase(); // نحوله لحروف كبيرة للمقارنة
|
||||
String hardware = androidInfo.hardware.toLowerCase(); // المعالج
|
||||
|
||||
// --- الفحص العكسي (الحظر المباشر) ---
|
||||
// إذا كان المعالج من الفئات الضعيفة جداً المشهورة في الهواتف المقلدة
|
||||
// mt65xx, mt6735, sc77xx هي معالجات رخيصة جداً
|
||||
if (hardware.contains('mt65') ||
|
||||
hardware.contains('mt6735') ||
|
||||
hardware.contains('sc77')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- فحص القائمة البيضاء (Whitelist) ---
|
||||
|
||||
// 1. Samsung Flagships
|
||||
if (manufacturer.contains('samsung')) {
|
||||
for (var prefix in _highEndSamsungModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Google Pixel (6 and above)
|
||||
if (manufacturer.contains('google')) {
|
||||
for (var prefix in _highEndGoogleModels) {
|
||||
if (model.contains(prefix.toUpperCase())) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Huawei Flagships
|
||||
if (manufacturer.contains('huawei')) {
|
||||
for (var prefix in _highEndHuaweiModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. OnePlus Flagships
|
||||
if (manufacturer.contains('oneplus')) {
|
||||
for (var prefix in _highEndOnePlusModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Xiaomi Flagships
|
||||
if (manufacturer.contains('xiaomi') ||
|
||||
manufacturer.contains('redmi') ||
|
||||
manufacturer.contains('poco')) {
|
||||
// شاومي تسميتها معقدة، لذا سنعتمد على فحص الرام كعامل مساعد هنا فقط
|
||||
// لأن هواتف شاومي القوية عادة لا تزور الرام
|
||||
// الرام يجب أن يكون أكبر من 6 جيجا (بايت)
|
||||
double ramGB = (androidInfo.availableRamSize) / (1024 * 1024 * 1024);
|
||||
if (ramGB > 7.5)
|
||||
return true; // 8GB RAM or more is usually safe for Xiaomi high-end
|
||||
}
|
||||
|
||||
// إذا لم يكن من ضمن القوائم أعلاه، نعتبره جهازاً متوسطاً/ضعيفاً ونعرض الرسم البسيط
|
||||
return false;
|
||||
} catch (e) {
|
||||
// في حال حدوث خطأ في الفحص، نعود للوضع الآمن (الرسم البسيط)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
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';
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
|
||||
class HomePageController extends GetxController {
|
||||
late bool isVibrate = box.read(BoxName.isvibrate) ?? true;
|
||||
|
||||
void changeVibrateOption(bool value) {
|
||||
isVibrate = box.read(BoxName.isvibrate) ?? true;
|
||||
isVibrate = value;
|
||||
box.write(BoxName.isvibrate, value);
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:live_activities/live_activities.dart';
|
||||
|
||||
class IosLiveActivityService {
|
||||
static final _liveActivitiesPlugin = LiveActivities();
|
||||
static String? _activityId;
|
||||
|
||||
static void init() {
|
||||
if (Platform.isIOS) {
|
||||
_liveActivitiesPlugin.init(
|
||||
appGroupId: "group.com.Intaleq.intaleq",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> startRideActivity({
|
||||
required String rideId,
|
||||
required String driverName,
|
||||
required String carDetails,
|
||||
required String etaText,
|
||||
required double progress,
|
||||
}) async {
|
||||
if (!Platform.isIOS) return;
|
||||
|
||||
try {
|
||||
await _liveActivitiesPlugin.endAllActivities();
|
||||
|
||||
// استخدام dynamic يسمح للحزمة بحفظ النصوص والأرقام في الذاكرة المشتركة
|
||||
final Map<String, dynamic> activityModel = {
|
||||
'status': 'waiting',
|
||||
'driverName': driverName,
|
||||
'carDetails': carDetails,
|
||||
'etaText': etaText,
|
||||
'progress': progress,
|
||||
};
|
||||
|
||||
// الدالة هنا تأخذ المعاملات مباشرة
|
||||
_activityId = await _liveActivitiesPlugin.createActivity(
|
||||
rideId,
|
||||
activityModel,
|
||||
);
|
||||
} catch (e) {
|
||||
print("❌ Live Activity Start Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> updateRideActivity({
|
||||
required String status,
|
||||
required String driverName,
|
||||
required String carDetails,
|
||||
required String etaText,
|
||||
required double progress,
|
||||
}) async {
|
||||
if (!Platform.isIOS || _activityId == null) return;
|
||||
|
||||
final Map<String, dynamic> updatedModel = {
|
||||
'status': status,
|
||||
'driverName': driverName,
|
||||
'carDetails': carDetails,
|
||||
'etaText': etaText,
|
||||
'progress': progress,
|
||||
};
|
||||
|
||||
await _liveActivitiesPlugin.updateActivity(_activityId!, updatedModel);
|
||||
}
|
||||
|
||||
static Future<void> endRideActivity() async {
|
||||
if (!Platform.isIOS || _activityId == null) return;
|
||||
await _liveActivitiesPlugin.endActivity(_activityId!);
|
||||
_activityId = null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MyMenuController extends GetxController {
|
||||
bool isDrawerOpen = true;
|
||||
|
||||
void getDrawerMenu() {
|
||||
if (isDrawerOpen == true) {
|
||||
isDrawerOpen = false;
|
||||
} else {
|
||||
isDrawerOpen = true;
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:Tripz/main.dart';
|
||||
|
||||
class CaptainWalletController extends GetxController {
|
||||
bool isLoading = false;
|
||||
Map walletDate = {};
|
||||
Map walletDateVisa = {};
|
||||
Map walletDriverPointsDate = {};
|
||||
final formKey = GlobalKey<FormState>();
|
||||
String totalAmount = '0';
|
||||
String totalAmountVisa = '0';
|
||||
String totalPoints = '0';
|
||||
final amountFromBudgetController = TextEditingController();
|
||||
|
||||
payFromBudget() async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
var pointFromBudget = box.read(BoxName.countryCode) == 'Jordan'
|
||||
? int.parse((amountFromBudgetController.text)) * 100
|
||||
: int.parse((amountFromBudgetController.text));
|
||||
|
||||
await addDriverPayment('fromBudgetToPoints',
|
||||
int.parse((amountFromBudgetController.text)) * -1);
|
||||
Future.delayed(const Duration(seconds: 2));
|
||||
await addDriverWallet('fromBudget', pointFromBudget.toString());
|
||||
update();
|
||||
Get.back();
|
||||
// getCaptainWalletFromRide();
|
||||
// getCaptainWalletFromBuyPoints();
|
||||
// checkAccountCaptainBank();
|
||||
}
|
||||
}
|
||||
|
||||
// Future getCaptainWalletFromRide() async {
|
||||
// isLoading = true;
|
||||
// update();
|
||||
// var res = await CRUD().get(
|
||||
// link: AppLink.getAllPaymentFromRide,
|
||||
// payload: {'driverID': box.read(BoxName.driverID)},
|
||||
// );
|
||||
// walletDate = jsonDecode(res);
|
||||
// totalAmount = walletDate['message'][0]['total_amount'].toString() == null
|
||||
// ? '0'
|
||||
// : walletDate['message'][0]['total_amount'];
|
||||
|
||||
// var res1 = await CRUD().get(
|
||||
// link: AppLink.getAllPaymentVisa,
|
||||
// payload: {'driverID': box.read(BoxName.driverID)});
|
||||
// walletDateVisa = jsonDecode(res1);
|
||||
// totalAmountVisa = walletDateVisa['message'][0]['diff'].toString() == null
|
||||
// ? '0'
|
||||
// : walletDateVisa['message'][0]['diff'];
|
||||
// isLoading = false;
|
||||
// update();
|
||||
// }
|
||||
|
||||
// Future getCaptainWalletFromBuyPoints() async {
|
||||
// isLoading = true;
|
||||
// update();
|
||||
// var res = await CRUD().get(
|
||||
// link: AppLink.getDriverPaymentPoints,
|
||||
// payload: {'driverID': box.read(BoxName.driverID)},
|
||||
// );
|
||||
// walletDriverPointsDate = jsonDecode(res);
|
||||
// if (walletDriverPointsDate['message'][0]['driverID'].toString() ==
|
||||
// box.read(BoxName.driverID)) {
|
||||
// double totalPointsDouble = double.parse(
|
||||
// walletDriverPointsDate['message'][0]['total_amount'].toString());
|
||||
// totalPoints = totalPointsDouble.toStringAsFixed(0);
|
||||
// } else {
|
||||
// totalPoints = '0';
|
||||
// }
|
||||
|
||||
// isLoading = false;
|
||||
// update();
|
||||
// }
|
||||
|
||||
late String paymentID;
|
||||
Future addDriverPayment(String paymentMethod, amount) async {
|
||||
var res =
|
||||
await CRUD().postWallet(link: AppLink.addDriverPaymentPoints, payload: {
|
||||
'driverID': box.read(BoxName.driverID).toString(),
|
||||
'amount': amount.toString(),
|
||||
'payment_method': paymentMethod.toString(),
|
||||
});
|
||||
var d = jsonDecode(res);
|
||||
paymentID = d['message'].toString();
|
||||
}
|
||||
|
||||
Future addDriverWallet(String paymentMethod, point) async {
|
||||
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
|
||||
'driverID': box.read(BoxName.driverID).toString(),
|
||||
'paymentID': paymentID.toString(),
|
||||
'amount': point,
|
||||
'paymentMethod': paymentMethod.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
//check if account bank is created or not
|
||||
Future checkAccountCaptainBank() async {
|
||||
isLoading = false;
|
||||
update();
|
||||
if (box.read(BoxName.accountIdStripeConnect).toString().isEmpty) {
|
||||
var res = await CRUD().getWallet(link: AppLink.getAccount, payload: {
|
||||
'id': box.read(BoxName.driverID).toString(),
|
||||
});
|
||||
var d = jsonDecode(res);
|
||||
if (d['status'] != 'failure') {
|
||||
box.write(BoxName.accountIdStripeConnect,
|
||||
d['message'][0]['accountBank'].toString());
|
||||
}
|
||||
}
|
||||
isLoading = true;
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
// getCaptainWalletFromRide();
|
||||
// getCaptainWalletFromBuyPoints();
|
||||
// checkAccountCaptainBank();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../functions/digit_obsecur_formate.dart';
|
||||
import '../../functions/secure_storage.dart';
|
||||
|
||||
class CreditCardController extends GetxController {
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
final TextEditingController cardNumberController = TextEditingController();
|
||||
final TextEditingController cardHolderNameController =
|
||||
TextEditingController();
|
||||
final TextEditingController expiryDateController = TextEditingController();
|
||||
final TextEditingController cvvCodeController = TextEditingController();
|
||||
openPayment() async {
|
||||
String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
|
||||
String? cardHolderName =
|
||||
await SecureStorage().readData(BoxName.cardHolderName);
|
||||
String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
|
||||
String? cvvCode = await SecureStorage().readData(BoxName.cvvCode);
|
||||
|
||||
// if (cvvCode != null && cvvCode.isNotEmpty) {
|
||||
// final maskedCardNumber = DigitObscuringFormatter()
|
||||
// .formatEditUpdate(
|
||||
// TextEditingValue.empty,
|
||||
// TextEditingValue(text: cardNumber ?? ''),
|
||||
// )
|
||||
// .text;
|
||||
|
||||
// cardNumberController.text = maskedCardNumber;
|
||||
// cardHolderNameController.text = cardHolderName ?? '';
|
||||
// expiryDateController.text = expiryDate ?? '';
|
||||
// cvvCodeController.text = cvvCode;
|
||||
// }
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
openPayment();
|
||||
// String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
|
||||
// String? cardHolderName =
|
||||
// await SecureStorage().readData(BoxName.cardHolderName);
|
||||
// String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
|
||||
// String? cvvCode = await SecureStorage().readData(BoxName.cvvCode);
|
||||
|
||||
// if (cvvCode != null && cvvCode.isNotEmpty) {
|
||||
// final maskedCardNumber = DigitObscuringFormatter()
|
||||
// .formatEditUpdate(
|
||||
// TextEditingValue.empty,
|
||||
// TextEditingValue(text: cardNumber ?? ''),
|
||||
// )
|
||||
// .text;
|
||||
|
||||
// cardNumberController.text = maskedCardNumber;
|
||||
// cardHolderNameController.text = cardHolderName ?? '';
|
||||
// expiryDateController.text = expiryDate ?? '';
|
||||
// cvvCodeController.text = cvvCode;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
class CreditCardModel {
|
||||
String cardNumber;
|
||||
String cardHolderName;
|
||||
String expiryDate;
|
||||
String cvvCode;
|
||||
|
||||
CreditCardModel({
|
||||
required this.cardNumber,
|
||||
required this.cardHolderName,
|
||||
required this.expiryDate,
|
||||
required this.cvvCode,
|
||||
});
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:Tripz/constant/style.dart';
|
||||
import 'package:Tripz/controller/home/map_passenger_controller.dart';
|
||||
|
||||
import '../../constant/api_key.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../functions/crud.dart';
|
||||
import '../functions/location_controller.dart';
|
||||
|
||||
class PointsForRiderController extends GetxController {
|
||||
List<String> locations = [];
|
||||
String hintTextDestinationPoint = 'Search for your destination'.tr;
|
||||
TextEditingController placeStartController = TextEditingController();
|
||||
|
||||
void addLocation(String location) {
|
||||
locations.add(location);
|
||||
update();
|
||||
}
|
||||
|
||||
void getTextFromList(String location) {
|
||||
locations.add(location);
|
||||
update();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
void removeLocation(int index) {
|
||||
locations.removeAt(index);
|
||||
update();
|
||||
}
|
||||
|
||||
void onReorder(int oldIndex, int newIndex) {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
update();
|
||||
}
|
||||
|
||||
final item = locations.removeAt(oldIndex);
|
||||
locations.insert(newIndex, item);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
class LocationModel {
|
||||
String name;
|
||||
double lat, lon;
|
||||
|
||||
LocationModel({required this.name, required this.lat, required this.lon});
|
||||
}
|
||||
|
||||
class WayPointController extends GetxController {
|
||||
// A list of text editing controllers for each text field
|
||||
// final textFields = [TextEditingController()].obs;
|
||||
List<String> wayPoints = [];
|
||||
List<List<dynamic>> placeListResponse = [];
|
||||
double wayPointHeight = 400;
|
||||
String hintTextDestinationPoint = 'Search for your destination'.tr;
|
||||
TextEditingController textSearchCotroller = TextEditingController();
|
||||
// A list of places corresponding to each text field
|
||||
final places = <String>[];
|
||||
|
||||
final hintTextPointList = <String>[];
|
||||
late LatLng myLocation;
|
||||
|
||||
void addWayPoints() {
|
||||
String wayPoint = 'Add a Stop'.tr;
|
||||
|
||||
if (wayPoints.length < 5) {
|
||||
wayPoints.add(wayPoint);
|
||||
update();
|
||||
} else {
|
||||
Get.defaultDialog(
|
||||
title: 'This is most WayPoints',
|
||||
titleStyle: AppStyle.title,
|
||||
middleText: '');
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void removeTextField(int index) {
|
||||
wayPoints.removeAt(index);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
// A method to reorder the text fields and the places
|
||||
void reorderTextFields(int oldIndex, int newIndex) {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final wayPoint = wayPoints.removeAt(oldIndex);
|
||||
wayPoints.insert(newIndex, wayPoint);
|
||||
update();
|
||||
}
|
||||
|
||||
void updatePlace(int index, String input) async {
|
||||
var url =
|
||||
'${AppLink.googleMapsLink}place/nearbysearch/json?keyword=$input&location=${myLocation.latitude},${myLocation.longitude}&radius=50000&language=en&key=${AK.mapAPIKEY.toString()}';
|
||||
var response = await CRUD().getGoogleApi(link: url, payload: {});
|
||||
// final place = input;
|
||||
// if (index == 0) {
|
||||
List<dynamic> newList = [];
|
||||
placeListResponse.add(newList);
|
||||
newList = response['results'];
|
||||
placeListResponse[index].add(newList);
|
||||
update();
|
||||
// }
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
// Get.put(LocationController());
|
||||
addWayPoints();
|
||||
myLocation = Get.find<MapPassengerController>().passengerLocation;
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceList extends StatelessWidget {
|
||||
// Get the controller instance
|
||||
final controller = Get.put(WayPointController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Use the Obx widget to rebuild the widget when the controller changes
|
||||
return Obx(() {
|
||||
// Use the ListView widget to display the list of places
|
||||
return ListView(
|
||||
// The children of the list are the places
|
||||
children: [
|
||||
// Loop through the places in the controller
|
||||
for (final place in controller.places)
|
||||
// Create a text widget for each place
|
||||
Text(
|
||||
// Use the place as the text
|
||||
place,
|
||||
|
||||
// Add some style and padding
|
||||
style: const TextStyle(fontSize: 18.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/constant/colors.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:Tripz/main.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
import '../../../env/env.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/widgets/mydialoug.dart';
|
||||
import '../../functions/encrypt_decrypt.dart';
|
||||
|
||||
class ComplaintController extends GetxController {
|
||||
bool isLoading = false;
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final complaintController = TextEditingController();
|
||||
|
||||
List feedBack = [];
|
||||
Map<String, dynamic>? passengerReport;
|
||||
Map<String, dynamic>? driverReport;
|
||||
|
||||
var isUploading = false.obs;
|
||||
var uploadSuccess = false.obs;
|
||||
String audioLink = ''; // سيتم تخزين رابط الصوت هنا بعد الرفع
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
getLatestRidesForPassengers();
|
||||
}
|
||||
|
||||
// --- دالة مخصصة لعرض إشعارات Snackbar بشكل جميل ---
|
||||
void _showCustomSnackbar(String title, String message,
|
||||
{bool isError = false}) {
|
||||
Get.snackbar(
|
||||
'', // العنوان سيتم التعامل معه عبر titleText
|
||||
'', // الرسالة سيتم التعامل معها عبر messageText
|
||||
titleText: Text(title.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
messageText: Text(message.tr,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14)),
|
||||
backgroundColor: isError
|
||||
? AppColor.redColor.withOpacity(0.95)
|
||||
: const Color.fromARGB(255, 6, 148, 79).withOpacity(0.95),
|
||||
icon: Icon(isError ? Icons.error_outline : Icons.check_circle_outline,
|
||||
color: Colors.white, size: 28),
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(15),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 4),
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
// --- هذه الدالة تبقى كما هي لجلب بيانات الرحلة ---
|
||||
getLatestRidesForPassengers() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
var res = await CRUD().get(link: AppLink.getFeedBack, payload: {
|
||||
'passengerId': box.read(BoxName.passengerID).toString(),
|
||||
});
|
||||
if (res != 'failure') {
|
||||
var d = jsonDecode(res)['message'];
|
||||
feedBack = d;
|
||||
}
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
// --- تم تحديث الهيدر في هذه الدالة ---
|
||||
Future<void> uploadAudioFile(File audioFile) async {
|
||||
try {
|
||||
isUploading.value = true;
|
||||
update();
|
||||
|
||||
var uri = Uri.parse('${AppLink.server}/upload_audio.php');
|
||||
var request = http.MultipartRequest('POST', uri);
|
||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||
|
||||
var mimeType = lookupMimeType(audioFile.path);
|
||||
// ** التعديل: تم استخدام نفس هيدر التوثيق **
|
||||
request.headers.addAll({
|
||||
'Authorization': 'Bearer $token',
|
||||
});
|
||||
request.files.add(
|
||||
await http.MultipartFile.fromPath(
|
||||
'audio',
|
||||
audioFile.path,
|
||||
contentType: mimeType != null ? MediaType.parse(mimeType) : null,
|
||||
),
|
||||
);
|
||||
|
||||
var response = await request.send();
|
||||
var responseBody = await http.Response.fromStream(response);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var jsonResponse = jsonDecode(responseBody.body);
|
||||
if (jsonResponse['status'] == 'Audio file uploaded successfully.') {
|
||||
uploadSuccess.value = true;
|
||||
audioLink = jsonResponse['link']; // تخزين الرابط في المتغير
|
||||
Get.back();
|
||||
// استخدام الـ Snackbar الجديد
|
||||
_showCustomSnackbar('Success', 'Audio uploaded successfully.');
|
||||
} else {
|
||||
uploadSuccess.value = false;
|
||||
_showCustomSnackbar('Error', 'Failed to upload audio file.',
|
||||
isError: true);
|
||||
}
|
||||
} else {
|
||||
uploadSuccess.value = false;
|
||||
_showCustomSnackbar('Error', 'Server error: ${response.statusCode}',
|
||||
isError: true);
|
||||
}
|
||||
} catch (e) {
|
||||
uploadSuccess.value = false;
|
||||
_showCustomSnackbar(
|
||||
'Error', 'An application error occurred during upload.',
|
||||
isError: true);
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// --- الدالة الجديدة التي تتصل بالسكريبت الجديد ---
|
||||
Future<void> submitComplaintToServer() async {
|
||||
// 1. التحقق من صحة الفورم
|
||||
if (!formKey.currentState!.validate() || complaintController.text.isEmpty) {
|
||||
// استخدام الـ Snackbar الجديد
|
||||
_showCustomSnackbar(
|
||||
'Error', 'Please describe your issue before submitting.',
|
||||
isError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. التحقق من وجود بيانات الرحلة
|
||||
if (feedBack.isEmpty) {
|
||||
// استخدام الـ Snackbar الجديد
|
||||
_showCustomSnackbar(
|
||||
'Error', 'Ride information not found. Please refresh the page.',
|
||||
isError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
update();
|
||||
|
||||
try {
|
||||
// 3. استخراج البيانات المطلوبة
|
||||
final rideId = feedBack[0]['id'].toString(); // ! تأكد أن اسم حقل ID صحيح
|
||||
final complaint = complaintController.text;
|
||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||
|
||||
// 4. استدعاء سكربت PHP الجديد باستخدام http.post
|
||||
final response = await http.post(
|
||||
Uri.parse(AppLink.add_solve_all),
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
body: {
|
||||
'ride_id': rideId,
|
||||
'complaint_text': complaint,
|
||||
'audio_link': audioLink,
|
||||
},
|
||||
);
|
||||
|
||||
Log.print('Server Response: ${response.body}');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_showCustomSnackbar(
|
||||
'Error', 'Failed to connect to the server. Please try again.',
|
||||
isError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. التعامل مع محتوى الرد من الخادم
|
||||
final responseData = jsonDecode(response.body);
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
passengerReport = responseData['data']['passenger_response'];
|
||||
driverReport = responseData['data']['driver_response'];
|
||||
update();
|
||||
|
||||
MyDialogContent().getDialog(
|
||||
'Success'.tr, Text('Your complaint has been submitted.'.tr), () {
|
||||
Get.back();
|
||||
complaintController.clear();
|
||||
audioLink = '';
|
||||
formKey.currentState?.reset();
|
||||
});
|
||||
} else {
|
||||
String errorMessage =
|
||||
responseData['message'] ?? 'An unknown server error occurred'.tr;
|
||||
_showCustomSnackbar('Submission Failed', errorMessage, isError: true);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Submit Complaint Error: $e");
|
||||
_showCustomSnackbar('Error', 'An application error occurred.'.tr,
|
||||
isError: true);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/constant/colors.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/widgets/error_snakbar.dart';
|
||||
import '../../../views/widgets/mydialoug.dart';
|
||||
import '../../functions/launch.dart';
|
||||
import '../../notification/notification_captain_controller.dart';
|
||||
import '../../payment/payment_controller.dart';
|
||||
|
||||
class InviteController extends GetxController {
|
||||
final TextEditingController invitePhoneController = TextEditingController();
|
||||
List driverInvitationData = [];
|
||||
List driverInvitationDataToPassengers = [];
|
||||
String? couponCode;
|
||||
String? driverCouponCode;
|
||||
|
||||
int selectedTab = 0;
|
||||
PassengerStats passengerStats = PassengerStats();
|
||||
|
||||
List<Contact> contacts = <Contact>[];
|
||||
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// It's good practice to fetch initial data in onInit or onReady
|
||||
// fetchDriverStats();
|
||||
// fetchDriverStatsPassengers();
|
||||
}
|
||||
|
||||
void updateSelectedTab(int index) {
|
||||
selectedTab = index;
|
||||
update();
|
||||
}
|
||||
|
||||
// --- Sharing Methods ---
|
||||
|
||||
Future<void> shareDriverCode() async {
|
||||
if (driverCouponCode != null) {
|
||||
final String shareText = '''
|
||||
${'Join Tripz as a driver using my referral code!'.tr}
|
||||
${'Use code:'.tr} $driverCouponCode
|
||||
${'Download the Tripz Driver app now and earn rewards!'.tr}
|
||||
''';
|
||||
await Share.share(shareText);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sharePassengerCode() async {
|
||||
if (couponCode != null) {
|
||||
final String shareText = '''
|
||||
${'Get a discount on your first Tripz ride!'.tr}
|
||||
${'Use my referral code:'.tr} $couponCode
|
||||
${'Download the Tripz app now and enjoy your ride!'.tr}
|
||||
''';
|
||||
await Share.share(shareText);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data Fetching ---
|
||||
|
||||
void fetchDriverStats() async {
|
||||
try {
|
||||
var response = await CRUD().get(link: AppLink.getInviteDriver, payload: {
|
||||
"driverId": box.read(BoxName.driverID),
|
||||
});
|
||||
if (response != 'failure') {
|
||||
var data = jsonDecode(response);
|
||||
driverInvitationData = data['message'];
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error fetching driver stats: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void fetchDriverStatsPassengers() async {
|
||||
try {
|
||||
var response = await CRUD()
|
||||
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
|
||||
"driverId": box.read(BoxName.passengerID),
|
||||
});
|
||||
if (response != 'failure') {
|
||||
var data = jsonDecode(response);
|
||||
driverInvitationDataToPassengers = data['message'];
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error fetching passenger stats: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Contact Handling ---
|
||||
|
||||
/// **IMPROVEMENT**: This function now filters out contacts without any phone numbers.
|
||||
/// This is the fix for the `RangeError` you were seeing, which happened when the UI
|
||||
/// tried to access the first phone number of a contact that had none.
|
||||
Future<void> pickContacts() async {
|
||||
try {
|
||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
||||
final List<Contact> allContacts =
|
||||
await FlutterContacts.getContacts(withProperties: true);
|
||||
final int totalContactsOnDevice = allContacts.length;
|
||||
|
||||
// **FIX**: Filter contacts to only include those with at least one phone number.
|
||||
contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
|
||||
final int contactsWithPhones = contacts.length;
|
||||
|
||||
if (contactsWithPhones > 0) {
|
||||
Log.print('Found $contactsWithPhones contacts with phone numbers.');
|
||||
contactMaps.value = contacts.map((contact) {
|
||||
return {
|
||||
'name': contact.displayName,
|
||||
'phones': contact.phones.map((p) => p.number).toList(),
|
||||
'emails': contact.emails.map((e) => e.address).toList(),
|
||||
};
|
||||
}).toList();
|
||||
update();
|
||||
|
||||
// **IMPROVEMENT**: Provide feedback if some contacts were filtered out.
|
||||
if (contactsWithPhones < totalContactsOnDevice) {
|
||||
Get.snackbar('Contacts Loaded'.tr,
|
||||
'${'Showing'.tr} $contactsWithPhones ${'of'.tr} $totalContactsOnDevice ${'contacts. Others were hidden because they don\'t have a phone number.'.tr}',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('No contacts found'.tr,
|
||||
'No contacts with phone numbers were found on your device.'.tr);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('Permission denied'.tr,
|
||||
'Contact permission is required to pick contacts'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error picking contacts: $e');
|
||||
Get.snackbar(
|
||||
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
void selectPhone(String phone) {
|
||||
invitePhoneController.text = phone;
|
||||
update();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
/// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
|
||||
/// It handles various user inputs gracefully to produce a standardized international format.
|
||||
String _formatSyrianPhoneNumber(String phone) {
|
||||
// 1. Remove all non-digit characters to clean the input.
|
||||
String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
|
||||
|
||||
// 2. If it already starts with the country code, we assume it's correct.
|
||||
if (digitsOnly.startsWith('963')) {
|
||||
return '$digitsOnly';
|
||||
}
|
||||
|
||||
// 3. If it starts with '09' (common local format), remove the leading '0'.
|
||||
if (digitsOnly.startsWith('09')) {
|
||||
digitsOnly = digitsOnly.substring(1);
|
||||
}
|
||||
|
||||
// 4. Prepend the Syrian country code.
|
||||
return '963$digitsOnly';
|
||||
}
|
||||
|
||||
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and
|
||||
/// sends a much-improved, user-friendly WhatsApp message.
|
||||
void sendInviteToPassenger() async {
|
||||
if (invitePhoneController.text.isEmpty ||
|
||||
invitePhoneController.text.length < 9) {
|
||||
mySnackeBarError('Please enter a correct phone'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the new formatting function to ensure the number is correct.
|
||||
String formattedPhoneNumber =
|
||||
_formatSyrianPhoneNumber(invitePhoneController.text);
|
||||
|
||||
var response =
|
||||
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
||||
"driverId": box.read(BoxName.passengerID),
|
||||
"inviterPassengerPhone": formattedPhoneNumber,
|
||||
});
|
||||
|
||||
if (response != 'failure') {
|
||||
var d = response;
|
||||
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
|
||||
backgroundColor: Colors.green, colorText: Colors.white);
|
||||
|
||||
String expirationTime = d['message']['expirationTime'].toString();
|
||||
String inviteCode = d['message']['inviteCode'].toString();
|
||||
|
||||
// New and improved WhatsApp message for better user engagement.
|
||||
String message = "👋 ${'Hello! I\'m inviting you to try Tripz.'.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.Tripz.Tripz\n"
|
||||
"• *iOS:* https://apps.apple.com/st/app/Tripz-rider/id6748075179\n\n"
|
||||
"${'See you on the road!'.tr} 🚗";
|
||||
|
||||
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
||||
invitePhoneController.clear();
|
||||
update();
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Error'.tr, "This phone number has already been invited.".tr,
|
||||
backgroundColor: AppColor.redColor,
|
||||
duration: const Duration(seconds: 4));
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error sending invite: $e");
|
||||
Get.snackbar(
|
||||
'Error'.tr, 'An unexpected error occurred. Please try again.'.tr,
|
||||
backgroundColor: AppColor.redColor);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is dependent on the `pickContacts` method filtering out contacts without phones.
|
||||
savePhoneToServer() async {
|
||||
for (var contactMap in contactMaps) {
|
||||
// The `pickContacts` function ensures the 'phones' list is not empty here.
|
||||
var phones = contactMap['phones'] as List<String>;
|
||||
var res = await CRUD().post(link: AppLink.savePhones, payload: {
|
||||
"name": contactMap['name'] ?? 'No Name',
|
||||
"phones": phones.first, // Safely access the first phone number
|
||||
"phones2": phones.join(', '),
|
||||
});
|
||||
if (res == 'failure') {
|
||||
Log.print('Failed to save contact: ${contactMap['name']}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onSelectPassengerInvitation(int index) async {
|
||||
try {
|
||||
final invitation = driverInvitationDataToPassengers[index];
|
||||
final tripCount =
|
||||
int.tryParse(invitation['countOfInvitDriver'].toString()) ?? 0;
|
||||
final passengerName = invitation['passengerName'].toString();
|
||||
final isGiftTaken = invitation['isGiftToken'].toString() == '1';
|
||||
|
||||
if (tripCount >= 2) {
|
||||
// Gift can be claimed
|
||||
if (!isGiftTaken) {
|
||||
MyDialog().getDialog(
|
||||
'You deserve the gift'.tr,
|
||||
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
|
||||
() async {
|
||||
Get.back(); // Close dialog first
|
||||
await Get.find<PaymentController>().addPassengersWallet('20');
|
||||
await CRUD().post(
|
||||
link: AppLink.updatePassengerGift,
|
||||
payload: {'id': invitation['id']},
|
||||
);
|
||||
NotificationCaptainController().addNotificationCaptain(
|
||||
invitation['passengerInviterId'].toString(),
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have earned 20".tr} ${'LE'}',
|
||||
false,
|
||||
);
|
||||
fetchDriverStatsPassengers(); // Refresh list
|
||||
},
|
||||
);
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
"Gift Already Claimed".tr,
|
||||
"You have already received your gift for inviting $passengerName."
|
||||
.tr,
|
||||
() => Get.back(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Gift not yet earned
|
||||
MyDialog().getDialog(
|
||||
'${'Keep it up!'.tr}',
|
||||
'$passengerName ${'has completed'.tr} $tripCount / 2 ${'trips'.tr}. ${"You can claim your gift once they complete 2 trips.".tr}',
|
||||
() => Get.back(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error in onSelectPassengerInvitation: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PassengerStats {
|
||||
final int totalInvites;
|
||||
final int activeUsers;
|
||||
final double totalEarnings;
|
||||
|
||||
PassengerStats({
|
||||
this.totalInvites = 0,
|
||||
this.activeUsers = 0,
|
||||
this.totalEarnings = 0.0,
|
||||
});
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:Tripz/main.dart';
|
||||
|
||||
class OrderHistoryController extends GetxController {
|
||||
List<dynamic> orderHistoryListPassenger = [];
|
||||
bool isloading = true;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
getOrderHistoryByPassenger();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future getOrderHistoryByPassenger() async {
|
||||
var res = await CRUD().get(link: AppLink.getRides, payload: {
|
||||
'passenger_id': box.read(BoxName.passengerID).toString(),
|
||||
});
|
||||
if (res.toString() == 'failure') {
|
||||
// Get.snackbar('failure', 'message');
|
||||
isloading = false;
|
||||
update();
|
||||
} else {
|
||||
var jsonDecoded = jsonDecode(res);
|
||||
|
||||
orderHistoryListPassenger = jsonDecoded['data'];
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
|
||||
import '../../../main.dart';
|
||||
|
||||
class PromosController extends GetxController {
|
||||
List<dynamic> promoList = [];
|
||||
bool isLoading = true;
|
||||
late String promos;
|
||||
@override
|
||||
void onInit() {
|
||||
getPromoByToday();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future getPromoByToday() async {
|
||||
var res = await CRUD().get(link: AppLink.getPromoBytody, payload: {
|
||||
'passengerID': box.read(BoxName.passengerID).toString(),
|
||||
});
|
||||
if (res.toString() == 'failure') {
|
||||
// Get.defaultDialog(
|
||||
// title: 'No Promo for today .'.tr,
|
||||
// middleText: '',
|
||||
// titleStyle: AppStyle.title,
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'Back'.tr,
|
||||
// onPressed: () {
|
||||
// Get.back();
|
||||
// Get.back();
|
||||
// }));
|
||||
isLoading = false;
|
||||
update();
|
||||
} else {
|
||||
var jsonDecoded = jsonDecode(res);
|
||||
|
||||
promoList = jsonDecoded['message'];
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:Tripz/controller/functions/add_error.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/onbording_page.dart';
|
||||
import 'package:Tripz/views/auth/login_page.dart';
|
||||
import 'package:Tripz/controller/auth/login_controller.dart';
|
||||
import 'package:Tripz/controller/functions/secure_storage.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
import '../../../constant/notification.dart';
|
||||
import '../../../main.dart';
|
||||
import '../firebase/firbase_messge.dart';
|
||||
import '../firebase/local_notification.dart';
|
||||
import '../functions/add_error.dart';
|
||||
import '../functions/encrypt_decrypt.dart';
|
||||
|
||||
class SplashScreenController extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> titleFadeAnimation,
|
||||
titleScaleAnimation,
|
||||
taglineFadeAnimation,
|
||||
footerFadeAnimation;
|
||||
late Animation<Offset> taglineSlideAnimation;
|
||||
|
||||
final progress = 0.0.obs;
|
||||
Timer? _progressTimer;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_animationController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 2000));
|
||||
|
||||
// Animation definitions
|
||||
titleFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
|
||||
titleScaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
|
||||
taglineFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
|
||||
taglineSlideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
|
||||
footerFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0.5, 1.0, curve: Curves.easeOut)));
|
||||
|
||||
_animationController.forward();
|
||||
_initializeApp();
|
||||
}
|
||||
|
||||
Future<void> _initializeApp() async {
|
||||
_startProgressAnimation();
|
||||
|
||||
// Run navigation logic and background services in parallel.
|
||||
final logicFuture = _performNavigationLogic();
|
||||
final backgroundServicesFuture = _initializeBackgroundServices();
|
||||
|
||||
// Ensure the splash screen is visible for a minimum duration.
|
||||
final minTimeFuture = Future.delayed(const Duration(seconds: 3));
|
||||
|
||||
// Wait for all tasks to complete.
|
||||
await Future.wait([logicFuture, backgroundServicesFuture, minTimeFuture]);
|
||||
}
|
||||
|
||||
void _startProgressAnimation() {
|
||||
// Visual timer for the progress bar.
|
||||
const totalTime = 2800; // ms
|
||||
const interval = 50; // ms
|
||||
int elapsed = 0;
|
||||
|
||||
_progressTimer =
|
||||
Timer.periodic(const Duration(milliseconds: interval), (timer) {
|
||||
elapsed += interval;
|
||||
progress.value = (elapsed / totalTime).clamp(0.0, 1.0);
|
||||
if (elapsed >= totalTime) timer.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/// Initializes all heavy background services while the splash animation is running.
|
||||
Future<void> _initializeBackgroundServices() async {
|
||||
try {
|
||||
await EncryptionHelper.initialize();
|
||||
|
||||
// --- [LAZY LOADING IN ACTION] ---
|
||||
// This `Get.find()` call will create the NotificationController instance
|
||||
// for the first time because it was defined with `lazyPut`.
|
||||
final notificationController = Get.find<NotificationController>();
|
||||
await notificationController.initNotifications();
|
||||
|
||||
// The same happens for FirebaseMessagesController.
|
||||
final fcm = Get.find<FirebaseMessagesController>();
|
||||
await fcm.requestFirebaseMessagingPermission();
|
||||
|
||||
// _scheduleDailyNotifications(notificationController);
|
||||
_initializeQuickActions();
|
||||
} catch (e, st) {
|
||||
addError('background_init_error: $e', st.toString(), );
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the next screen based on user's login state.
|
||||
Future<void> _performNavigationLogic() async {
|
||||
await _getPackageInfo();
|
||||
SecureStorage().saveData('iss', 'mobile-app:');
|
||||
|
||||
if (box.read(BoxName.onBoarding) == null) {
|
||||
Get.off(() => OnBoardingPage());
|
||||
} else if (box.read(BoxName.email) != null &&
|
||||
box.read(BoxName.phone) != null &&
|
||||
box.read(BoxName.isVerified) == '1') {
|
||||
// `Get.find()` creates the LoginController instance here.
|
||||
final loginController = Get.find<LoginController>();
|
||||
// The loginController itself will handle navigation via Get.offAll() upon success.
|
||||
await loginController.loginUsingCredentials(
|
||||
box.read(BoxName.passengerID).toString(),
|
||||
box.read(BoxName.email).toString(),
|
||||
);
|
||||
} else {
|
||||
Get.off(() => LoginPage());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getPackageInfo() async {
|
||||
try {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
box.write(BoxName.packagInfo, info.version);
|
||||
update();
|
||||
} catch (e) {
|
||||
print("Could not get package info: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleDailyNotifications(NotificationController controller) {
|
||||
try {
|
||||
final List<String> msgs = passengerMessages ?? const [];
|
||||
if (msgs.isEmpty) {
|
||||
controller.scheduleNotificationsForSevenDays(
|
||||
'Tripz', 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.', "tone1");
|
||||
} else {
|
||||
final rnd = Random();
|
||||
final raw = msgs[rnd.nextInt(msgs.length)];
|
||||
final parts = raw.split(':');
|
||||
final title = parts.isNotEmpty ? parts.first.trim() : 'Tripz';
|
||||
final body = parts.length > 1
|
||||
? parts.sublist(1).join(':').trim()
|
||||
: 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.';
|
||||
controller.scheduleNotificationsForSevenDays(
|
||||
title.isEmpty ? 'Tripz' : title,
|
||||
body.isEmpty ? 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.' : body,
|
||||
"tone1");
|
||||
}
|
||||
} catch (e, st) {
|
||||
addError('notif_init: $e', st.toString(), );
|
||||
}
|
||||
}
|
||||
|
||||
void _initializeQuickActions() {
|
||||
final QuickActions quickActions = QuickActions();
|
||||
quickActions.initialize((String shortcutType) {
|
||||
Get.toNamed('/$shortcutType');
|
||||
});
|
||||
|
||||
quickActions.setShortcutItems(<ShortcutItem>[
|
||||
ShortcutItem(
|
||||
type: 'shareApp', localizedTitle: 'Share App'.tr, icon: 'icon_share'),
|
||||
ShortcutItem(
|
||||
type: 'wallet', localizedTitle: 'Wallet'.tr, icon: 'icon_wallet'),
|
||||
ShortcutItem(
|
||||
type: 'profile', localizedTitle: 'Profile'.tr, icon: 'icon_user'),
|
||||
ShortcutItem(
|
||||
type: 'contactSupport',
|
||||
localizedTitle: 'Contact Support'.tr,
|
||||
icon: 'icon_support'),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_progressTimer?.cancel();
|
||||
_animationController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/controller/functions/crud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
class TripMonitorController extends GetxController {
|
||||
bool isLoading = false;
|
||||
Map tripData = {};
|
||||
late String rideId;
|
||||
late String driverId;
|
||||
GoogleMapController? mapController;
|
||||
List myListString = [];
|
||||
late Timer timer;
|
||||
late LatLng parentLocation;
|
||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor motoIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor ladyIcon = BitmapDescriptor.defaultMarker;
|
||||
double rotation = 0;
|
||||
double speed = 0;
|
||||
|
||||
getLocationParent() async {
|
||||
var res = await CRUD().get(
|
||||
link: AppLink.getLocationParents, payload: {"driver_id": driverId});
|
||||
if (res != 'failure') {
|
||||
tripData = jsonDecode(res);
|
||||
parentLocation = LatLng(
|
||||
double.parse(tripData['message'][0]['latitude'].toString()),
|
||||
double.parse(tripData['message'][0]['longitude'].toString()));
|
||||
rotation = double.parse(tripData['message'][0]['heading'].toString());
|
||||
speed = double.parse(tripData['message'][0]['speed'].toString());
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void onMapCreated(GoogleMapController controller) async {
|
||||
mapController = controller;
|
||||
controller.getVisibleRegion();
|
||||
controller.animateCamera(
|
||||
CameraUpdate.newLatLng(parentLocation),
|
||||
);
|
||||
update();
|
||||
// Set up a timer or interval to trigger the marker update every 3 seconds.
|
||||
timer = Timer.periodic(const Duration(seconds: 10), (_) async {
|
||||
await getLocationParent();
|
||||
mapController?.animateCamera(CameraUpdate.newLatLng(parentLocation));
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
// init() async {
|
||||
// final arguments = Get.arguments;
|
||||
// driverId = arguments['driverId'];
|
||||
// rideId = arguments['rideId'];
|
||||
// await getLocationParent();
|
||||
// }
|
||||
|
||||
Future<void> init({String? rideId, String? driverId}) async {
|
||||
this.driverId = driverId!;
|
||||
this.rideId = rideId!;
|
||||
await getLocationParent();
|
||||
update();
|
||||
}
|
||||
|
||||
void addCustomCarIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.fromAssetImage(config, 'assets/images/car.png',
|
||||
mipmaps: false)
|
||||
.then((value) {
|
||||
carIcon = value;
|
||||
update();
|
||||
});
|
||||
void addCustomMotoIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.fromAssetImage(config, 'assets/images/moto1.png',
|
||||
mipmaps: false)
|
||||
.then((value) {
|
||||
motoIcon = value;
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
void addCustomLadyIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.fromAssetImage(config, 'assets/images/lady1.png',
|
||||
mipmaps: false)
|
||||
.then((value) {
|
||||
ladyIcon = value;
|
||||
update();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
addCustomCarIcon();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
timer.cancel();
|
||||
mapController?.dispose();
|
||||
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:Tripz/constant/box_name.dart';
|
||||
import 'package:Tripz/constant/colors.dart';
|
||||
import 'package:Tripz/constant/links.dart';
|
||||
import 'package:Tripz/constant/style.dart';
|
||||
import 'package:Tripz/controller/home/map_passenger_controller.dart';
|
||||
import 'package:Tripz/main.dart';
|
||||
import 'package:Tripz/views/widgets/elevated_btn.dart';
|
||||
import 'package:Tripz/views/widgets/my_scafold.dart';
|
||||
import 'package:Tripz/views/widgets/mycircular.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_font_icons/flutter_font_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../functions/crud.dart';
|
||||
import '../functions/encrypt_decrypt.dart';
|
||||
|
||||
class VipOrderController extends GetxController {
|
||||
RxBool isLoading = false.obs;
|
||||
final arguments = Get.arguments;
|
||||
RxList<dynamic> tripData = <dynamic>[].obs;
|
||||
RxBool isButtonVisible = false.obs;
|
||||
RxInt countdown = 60.obs;
|
||||
Timer? _countdownTimer;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchOrder();
|
||||
startCountdown();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_countdownTimer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> fetchOrder() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
var mapPassengerController = Get.find<MapPassengerController>();
|
||||
|
||||
var res = await CRUD().get(
|
||||
link: AppLink.getMishwari,
|
||||
payload: {
|
||||
// 'driverId': mapPassengerController.driverIdVip.toString(),
|
||||
'driverId': box.read(BoxName.passengerID).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (res != 'failure') {
|
||||
var decodedResponse = jsonDecode(res);
|
||||
if (decodedResponse['message'] is List) {
|
||||
tripData.value = decodedResponse['message'];
|
||||
} else {
|
||||
tripData.clear(); // Ensure empty list if no data
|
||||
// mySnackeBarError('No trip data found');
|
||||
}
|
||||
} else {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('Failed to fetch trip data');
|
||||
}
|
||||
} catch (e) {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('An error occurred: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void startCountdown() {
|
||||
_countdownTimer?.cancel(); // Cancel any existing timer
|
||||
countdown.value = 60;
|
||||
|
||||
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--;
|
||||
} else {
|
||||
timer.cancel();
|
||||
isButtonVisible.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void sendToDriverAgain() {
|
||||
// Reset states
|
||||
isButtonVisible.value = false;
|
||||
fetchOrder(); // Refresh order
|
||||
startCountdown(); // Restart countdown
|
||||
}
|
||||
}
|
||||
|
||||
class VipWaittingPage extends StatelessWidget {
|
||||
VipWaittingPage({super.key});
|
||||
final VipOrderController vipOrderController = Get.put(VipOrderController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScafolld(
|
||||
title: "Waiting VIP".tr,
|
||||
body: [
|
||||
Obx(() {
|
||||
// Loading state
|
||||
if (vipOrderController.isLoading.value) {
|
||||
return const Center(child: MyCircularProgressIndicator());
|
||||
}
|
||||
|
||||
// No data state
|
||||
if (vipOrderController.tripData.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'No trip data available'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Data available
|
||||
var data = vipOrderController.tripData[0];
|
||||
|
||||
// Function to get the localized status string
|
||||
String getLocalizedStatus(String status) {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'pending'.tr;
|
||||
case 'accepted':
|
||||
return 'accepted'.tr;
|
||||
case 'begin':
|
||||
return 'begin'.tr;
|
||||
case 'rejected':
|
||||
return 'rejected'.tr;
|
||||
case 'cancelled':
|
||||
return 'cancelled'.tr;
|
||||
default:
|
||||
return 'unknown'.tr;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the appropriate status color
|
||||
Color getStatusColor(String status) {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return Colors.yellow;
|
||||
case 'accepted':
|
||||
return Colors.green;
|
||||
case 'begin':
|
||||
return Colors.green;
|
||||
case 'rejected':
|
||||
return Colors.red;
|
||||
case 'cancelled':
|
||||
return Colors.red;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${'Driver Name:'.tr} ${data['name']}",
|
||||
style: AppStyle.title,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${'Car Plate:'.tr} ${data['car_plate']}",
|
||||
style: AppStyle.title,
|
||||
),
|
||||
Text(
|
||||
"${'Car Make:'.tr} ${data['make']}",
|
||||
style: AppStyle.title,
|
||||
),
|
||||
Text(
|
||||
"${'Car Model:'.tr} ${data['model']}",
|
||||
style: AppStyle.title,
|
||||
),
|
||||
Text(
|
||||
"${"Car Color:".tr} ${data['color']}",
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: Icon(
|
||||
Fontisto.car,
|
||||
size: 80,
|
||||
color: Color(
|
||||
int.parse(
|
||||
data['color_hex'].replaceFirst('#', '0xff'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
color: getStatusColor(data['status']),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"${'Trip Status:'.tr} ${getLocalizedStatus(data['status'])}",
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${'Scheduled Time:'.tr} ${DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.parse(data['timeSelected']))}",
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
data['status'].toString() != 'begin'
|
||||
? MyElevatedButton(
|
||||
title: "Cancel Trip".tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () {
|
||||
Get.find<MapPassengerController>().cancelVip(
|
||||
data['token'].toString(),
|
||||
data['id'].toString(),
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Obx(() {
|
||||
// If countdown is still running, show countdown
|
||||
if (!vipOrderController.isButtonVisible.value) {
|
||||
return Column(
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: 1 -
|
||||
(vipOrderController.countdown.value / 60),
|
||||
strokeWidth: 6.0,
|
||||
color: AppColor.greenColor,
|
||||
backgroundColor: AppColor.accentColor,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
"${vipOrderController.countdown.value}s ${'remaining'.tr}",
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Once countdown is complete, show "Send to Driver Again" button
|
||||
return MyElevatedButton(
|
||||
title: "Send to Driver Again".tr,
|
||||
kolor: AppColor.greenColor,
|
||||
onPressed: () {
|
||||
Get.find<MapPassengerController>()
|
||||
.sendToDriverAgain(data['token']);
|
||||
vipOrderController.fetchOrder();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
data['status'].toString() == 'begin'
|
||||
? MyElevatedButton(
|
||||
title: "Click here to begin your trip\n\nGood luck, "
|
||||
.tr +
|
||||
(box.read(BoxName.name).toString().split(' ')[0])
|
||||
.toString(),
|
||||
kolor: AppColor.greenColor,
|
||||
onPressed: () {
|
||||
final mapPassengerController =
|
||||
Get.find<MapPassengerController>();
|
||||
mapPassengerController.make = data['make'];
|
||||
mapPassengerController.licensePlate =
|
||||
data['car_plate'];
|
||||
mapPassengerController.model = data['model'];
|
||||
mapPassengerController.driverId = data['driverId'];
|
||||
mapPassengerController.carColor = data['color'];
|
||||
mapPassengerController.driverRate = data['rating'];
|
||||
mapPassengerController.colorHex = data['color_hex'];
|
||||
mapPassengerController.driverPhone = data['phone'];
|
||||
mapPassengerController.driverToken = data['token'];
|
||||
mapPassengerController.driverName =
|
||||
data['name'].toString().split(' ')[0];
|
||||
|
||||
Get.back();
|
||||
|
||||
mapPassengerController.begiVIPTripFromPassenger();
|
||||
},
|
||||
)
|
||||
: const SizedBox()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
isleading: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user