feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند: - تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً، وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً. - OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع. - N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر. N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)، ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird. فلاتر: - docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام). - السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب. - طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط. - الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3ab74a13a8
commit
de4bbd00ac
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'core/config.dart';
|
||||||
|
import 'core/di.dart';
|
||||||
|
import 'core/router.dart';
|
||||||
|
import 'core/theme.dart';
|
||||||
|
import 'features/auth/cubit/auth_cubit.dart';
|
||||||
|
import 'features/auth/data/auth_repository.dart';
|
||||||
|
|
||||||
|
class TripzDriverApp extends StatelessWidget {
|
||||||
|
const TripzDriverApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => AuthCubit(getIt<AuthRepository>())..checkAuth(),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final router = buildRouter(context.read<AuthCubit>());
|
||||||
|
return MaterialApp.router(
|
||||||
|
title: AppConfig.appName,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: buildTheme(),
|
||||||
|
locale: const Locale('ar'),
|
||||||
|
supportedLocales: const [Locale('ar'), Locale('en')],
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
builder: (context, child) => Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: child!,
|
||||||
|
),
|
||||||
|
routerConfig: router,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
|
|
||||||
import '../env/env.dart';
|
|
||||||
import 'char_map.dart';
|
|
||||||
|
|
||||||
class AK {
|
|
||||||
static final String sss_pass = X.r(X.r(X.r(Env.sss_pass, cn), cC), cs);
|
|
||||||
static final String allowed = Env.allowed;
|
|
||||||
static final String allowedWallet = Env.allowedWallet;
|
|
||||||
static final String passnpassenger = X
|
|
||||||
.r(X.r(X.r(Env.passnpassenger, cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(Env.addd)[0];
|
|
||||||
static final String newId = Env.newId;
|
|
||||||
static final String sss_encryptionSalt =
|
|
||||||
X.r(X.r(X.r(Env.sss_encryptionSalt, cn), cC), cs);
|
|
||||||
|
|
||||||
static final String publishableKeyStripe =
|
|
||||||
X.r(X.r(X.r(Env.stripePublishableKe, cn), cC), cs);
|
|
||||||
static final String secretKeyStripe =
|
|
||||||
X.r(X.r(X.r(Env.secretKey, cn), cC), cs);
|
|
||||||
static final String basicAuthCredentials =
|
|
||||||
X.r(X.r(X.r(Env.basicAuthCredentials, cn), cC), cs);
|
|
||||||
static final String basicCompareFaces =
|
|
||||||
X.r(X.r(X.r(Env.basicCompareFaces, cn), cC), cs);
|
|
||||||
static final String accountSIDTwillo =
|
|
||||||
X.r(X.r(X.r(Env.accountSIDTwillo, cn), cC), cs);
|
|
||||||
static final String serverAPI = X.r(X.r(X.r(Env.serverAPI, cn), cC), cs);
|
|
||||||
static final String mapAPIKEY = Env.mapAPIKEY;
|
|
||||||
static final String twilloRecoveryCode =
|
|
||||||
X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs);
|
|
||||||
static final String authTokenTwillo =
|
|
||||||
X.r(X.r(X.r(Env.authTokenTwillo, cn), cC), cs);
|
|
||||||
static final String chatGPTkey = X.r(X.r(X.r(Env.chatGPTkey, cn), cC), cs);
|
|
||||||
static final String transactionCloude =
|
|
||||||
X.r(X.r(X.r(Env.transactionCloude, cn), cC), cs);
|
|
||||||
static final String visionApi = X.r(X.r(X.r(Env.visionApi, cn), cC), cs);
|
|
||||||
static final String chatGPTkeySefer =
|
|
||||||
X.r(X.r(X.r(Env.chatGPTkeySefer, cn), cC), cs);
|
|
||||||
static final String chatGPTkeySeferNew =
|
|
||||||
X.r(X.r(X.r(Env.chatGPTkeySeferNew, cn), cC), cs);
|
|
||||||
static final String serverPHP = Env.serverPHP;
|
|
||||||
static final String llamaKey = X.r(X.r(X.r(Env.llamaKey, cn), cC), cs);
|
|
||||||
static final String cohere = X.r(X.r(X.r(Env.cohere, cn), cC), cs);
|
|
||||||
static final String claudeAiAPI = X.r(X.r(X.r(Env.claudeAiAPI, cn), cC), cs);
|
|
||||||
static final String payPalClientId =
|
|
||||||
X.r(X.r(X.r(Env.payPalClientId, cn), cC), cs);
|
|
||||||
static final String payPalSecret =
|
|
||||||
X.r(X.r(X.r(Env.payPalSecret, cn), cC), cs);
|
|
||||||
static final String geminiApi = X.r(X.r(X.r(Env.geminiApi, cn), cC), cs);
|
|
||||||
static final String agoraAppId = X.r(X.r(X.r(Env.agoraAppId, cn), cC), cs);
|
|
||||||
static final String agoraAppCertificate =
|
|
||||||
X.r(X.r(X.r(Env.agoraAppCertificate, cn), cC), cs);
|
|
||||||
static final String payPalClientIdLive =
|
|
||||||
X.r(X.r(X.r(Env.payPalClientIdLive, cn), cC), cs);
|
|
||||||
static final String payPalSecretLive =
|
|
||||||
X.r(X.r(X.r(Env.payPalSecretLive, cn), cC), cs);
|
|
||||||
static final String integrationIdPayMob =
|
|
||||||
X.r(X.r(X.r(Env.integrationIdPayMob, cn), cC), cs);
|
|
||||||
static final String passwordPayMob =
|
|
||||||
X.r(X.r(X.r(Env.passwordPayMob, cn), cC), cs);
|
|
||||||
static final String usernamePayMob =
|
|
||||||
X.r(X.r(X.r(Env.usernamePayMob, cn), cC), cs);
|
|
||||||
static final String payMobApikey =
|
|
||||||
X.r(X.r(X.r(Env.payMobApikey, cn), cC), cs);
|
|
||||||
static final String integrationIdPayMobWallet =
|
|
||||||
X.r(X.r(X.r(Env.integrationIdPayMobWallet, cn), cC), cs);
|
|
||||||
static final String smsPasswordEgypt =
|
|
||||||
X.r(X.r(X.r(Env.smsPasswordEgypt, cn), cC), cs);
|
|
||||||
static final String ocpApimSubscriptionKey = Env.ocpApimSubscriptionKey;
|
|
||||||
static final String chatGPTkeySeferNew4 =
|
|
||||||
X.r(X.r(X.r(Env.chatGPTkeySeferNew4, cn), cC), cs);
|
|
||||||
static final String anthropicAIkeySeferNew =
|
|
||||||
X.r(X.r(X.r(Env.anthropicAIkeySeferNew, cn), cC), cs);
|
|
||||||
static final String llama3Key = X.r(X.r(X.r(Env.llama3Key, cn), cC), cs);
|
|
||||||
|
|
||||||
static final String payMobOutPassword =
|
|
||||||
X.r(X.r(X.r(Env.payMobOutPassword, cn), cC), cs);
|
|
||||||
// static final String privateKeyFCM = Env.privateKeyFCM;
|
|
||||||
static final String payMobOutUserName =
|
|
||||||
X.r(X.r(X.r(Env.payMobOutUserName, cn), cC), cs);
|
|
||||||
|
|
||||||
///////////
|
|
||||||
static final String keyOfApp = X.r(X.r(X.r(Env.keyOfApp, cn), cC), cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
class KN {
|
|
||||||
static const String pmobsec = 'PAYMOBOUTCLIENTSECRET';
|
|
||||||
static const String pmobid = 'PAYMOBOUTCLIENT_ID';
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
class BoxName {
|
|
||||||
static const String driverID = "driverID";
|
|
||||||
static const String countryCode = "countryCode";
|
|
||||||
static const String googlaMapApp = "googlaMapApp";
|
|
||||||
static const String keyOfApp = 'keyOfApp';
|
|
||||||
static const String initializationVector = 'initializationVector';
|
|
||||||
static const String firstTimeLoadKey = 'firstTimeLoadKey';
|
|
||||||
static const String jwt = "jwt";
|
|
||||||
static const String blockUntilDate = "blockUntilDate";
|
|
||||||
static const String rideId = "rideId";
|
|
||||||
static const String rideArgumentsFromBackground =
|
|
||||||
"rideArgumentsFromBackground";
|
|
||||||
static const String FCM_PRIVATE_KEY = "FCM_PRIVATE_KEY";
|
|
||||||
static const String hmac = "hmac";
|
|
||||||
static const String ttsEnabled = "ttsEnabled";
|
|
||||||
static const String security_check = "security_check";
|
|
||||||
static const String rideType = "rideType";
|
|
||||||
static const String walletType = "walletType";
|
|
||||||
static const String fingerPrint = "fingerPrint";
|
|
||||||
static const String updateInterval = "updateInterval";
|
|
||||||
static const String payMobApikey = "payMobApikey";
|
|
||||||
static const String refreshToken = "refreshToken";
|
|
||||||
static const String lang = "lang";
|
|
||||||
static const String isvibrate = "isvibrate";
|
|
||||||
static const String myListString = "myListString";
|
|
||||||
static const String myList = "myList";
|
|
||||||
static const String bodyOrder = "bodyOrder";
|
|
||||||
static const String gender = "gender";
|
|
||||||
static const String phoneWallet = "phoneWallet";
|
|
||||||
static const String locationName = "locationName";
|
|
||||||
static const String basicLink = "basicLink";
|
|
||||||
static const String paymentLink = "paymentLink";
|
|
||||||
static const String isSavedPhones = "IsSavedPhones";
|
|
||||||
static const String isTest = "isTest";
|
|
||||||
static const String carType = "carType";
|
|
||||||
static const String carTypeOfDriver = "carTypeOfDriver";
|
|
||||||
static const String isFirstTime = "isFirstTime";
|
|
||||||
static const String deviceInfo = "deviceInfo";
|
|
||||||
static const String packagInfo = "packagInfo";
|
|
||||||
static const String phoneVerified = "phoneVerified";
|
|
||||||
static const String carPlate = "carPlate";
|
|
||||||
static const String statusDriverLocation = "statusDriverLocation";
|
|
||||||
static const String isAppInForeground = "isAppInForeground";
|
|
||||||
static const String rideStatus = "rideStatus";
|
|
||||||
static const String nameArabic = "nameArabic";
|
|
||||||
static const String carYear = "carYear";
|
|
||||||
static const String password = "password";
|
|
||||||
static const String isVerified = '0';
|
|
||||||
static const String arrivalTime = "arrivalTime";
|
|
||||||
static const String locationPermission = "locationPermission";
|
|
||||||
static const String passwordDriver = "passwordDriver";
|
|
||||||
static const String agreeTerms = "agreeTerms";
|
|
||||||
static const String addWork = 'addWork';
|
|
||||||
static const String addHome = 'addHome';
|
|
||||||
static const String rideArguments = 'rideArguments';
|
|
||||||
static const String durationData = 'durationData';
|
|
||||||
static const String rideStaticDriverData = 'rideStaticDriverData';
|
|
||||||
static const String lastTimeStaticThrottle = 'lastTimeStaticThrottle';
|
|
||||||
static const String lastTimeCaptainWalletCashOut =
|
|
||||||
'lastTimeCaptainWalletCashOut';
|
|
||||||
static const String tipPercentage = 'tipPercentage';
|
|
||||||
static const String accountIdStripeConnect = "accountIdStripeConnect";
|
|
||||||
static const String faceDetectTimes = "faceDetectTimes";
|
|
||||||
static const String sosPhonePassenger = "sosPhonePassenger";
|
|
||||||
static const String sosPhoneDriver = "sosPhoneDriver";
|
|
||||||
static const String passengerID = "pasengerID";
|
|
||||||
static const String phone = "phone";
|
|
||||||
static const String phoneDriver = "phoneDriver";
|
|
||||||
static const String is_claimed = "is_claimed";
|
|
||||||
static const String isInstall = "isInstall";
|
|
||||||
static const String isGiftToken = "isGiftToken";
|
|
||||||
static const String lastOtpTime = "lastOtpTime";
|
|
||||||
static const String bankCodeDriver = "bankCodeDriver";
|
|
||||||
static const String accountBankNumberDriver = "accountBankNumberDriver";
|
|
||||||
static const String dobDriver = "dobDriver";
|
|
||||||
static const String sexDriver = "sexDriver";
|
|
||||||
static const String lastNameDriver = "lastNameDriver";
|
|
||||||
static const String name = "name";
|
|
||||||
static const String nameDriver = "nameDriver";
|
|
||||||
static const String driverPhotoUrl = "driverPhotoUrl";
|
|
||||||
static const String email = "email";
|
|
||||||
static const String emailDriver = "emailDriver";
|
|
||||||
static const String tokens = "tokens";
|
|
||||||
static const String tokenFCM = "tokenFCM";
|
|
||||||
static const String tokenDriver = "tokenDriver";
|
|
||||||
static const String cardNumber = "cardNumber";
|
|
||||||
static const String cardNumberDriver = "cardNumberDriver";
|
|
||||||
static const String cardHolderName = "cardHolderName";
|
|
||||||
static const String cardHolderNameDriver = "cardHolderNameDriver";
|
|
||||||
static const String expiryDate = "expiryDate";
|
|
||||||
static const String expiryDateDriver = "expiryDateDriver";
|
|
||||||
static const String cvvCode = "cvvCode";
|
|
||||||
static const String cvvCodeDriver = "cvvCodeDriver";
|
|
||||||
static const String passengerWalletDetails = "passengerWalletDetails";
|
|
||||||
static const String passengerWalletTotal = "passengerWalletTotal";
|
|
||||||
static const String passengerWalletFound = "passengerWalletFound";
|
|
||||||
static const String periods = 'periods';
|
|
||||||
static const String onBoarding = 'onBoarding';
|
|
||||||
static const String stripePublishableKey = 'stripe_publishableKe';
|
|
||||||
static const String apiKeyRun = 'apiKeyRun';
|
|
||||||
static const String serverAPI = 'serverAPI';
|
|
||||||
static const String serverChosen = 'serverChosen';
|
|
||||||
static const String secretKey = 'secretKey';
|
|
||||||
static const String basicAuthCredentials = 'basicAuthCredentials';
|
|
||||||
static const String mapAPIKEY = 'mapAPIKEY';
|
|
||||||
static const String twilloRecoveryCode = 'twilloRecoveryCode';
|
|
||||||
static const String accountSIDTwillo = 'accountSIDTwillo';
|
|
||||||
static const String authTokenTwillo = 'authTokenTwillo';
|
|
||||||
static const String chatGPTkey = 'chatGPTkey';
|
|
||||||
static const String chatGPTkeySefer = 'chatGPTkeySefer';
|
|
||||||
static const String transactionCloude = 'transactionCloude';
|
|
||||||
static const String visionApi = 'visionApi';
|
|
||||||
static const String vin = "vin";
|
|
||||||
static const String make = "make";
|
|
||||||
static const String model = "model";
|
|
||||||
static const String year = "year";
|
|
||||||
static const String expirationDate = "expirationDate";
|
|
||||||
static const String color = "color";
|
|
||||||
static const String owner = "owner";
|
|
||||||
static const String registrationDate = "registrationDate";
|
|
||||||
static const String recentLocations = 'recentLocations';
|
|
||||||
static const String tripData = 'tripData';
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import '../env/env.dart';
|
|
||||||
|
|
||||||
Map<String, String> cs = {
|
|
||||||
"a": Env.a,
|
|
||||||
"b": Env.b,
|
|
||||||
"c": Env.c,
|
|
||||||
"d": Env.d,
|
|
||||||
"e": Env.e,
|
|
||||||
"f": Env.f,
|
|
||||||
"g": Env.g,
|
|
||||||
"h": Env.h,
|
|
||||||
"i": Env.i,
|
|
||||||
"j": Env.j,
|
|
||||||
"k": Env.k,
|
|
||||||
"l": Env.l,
|
|
||||||
"m": Env.m,
|
|
||||||
"n": Env.n,
|
|
||||||
"o": Env.o,
|
|
||||||
"p": Env.p,
|
|
||||||
"q": Env.q,
|
|
||||||
"r": Env.r,
|
|
||||||
"s": Env.s,
|
|
||||||
"t": Env.t,
|
|
||||||
"u": Env.u,
|
|
||||||
"v": Env.v,
|
|
||||||
"w": Env.w,
|
|
||||||
"x": Env.x,
|
|
||||||
"y": Env.y,
|
|
||||||
"z": Env.z,
|
|
||||||
};
|
|
||||||
Map<String, String> cC = {
|
|
||||||
"A": Env.A,
|
|
||||||
"B": Env.B,
|
|
||||||
"C": Env.C,
|
|
||||||
"D": Env.D,
|
|
||||||
"E": Env.E,
|
|
||||||
"F": Env.F,
|
|
||||||
"G": Env.G,
|
|
||||||
"H": Env.H,
|
|
||||||
"I": Env.I,
|
|
||||||
"J": Env.J,
|
|
||||||
"K": Env.K,
|
|
||||||
"L": Env.L,
|
|
||||||
"M": Env.M,
|
|
||||||
"N": Env.N,
|
|
||||||
"O": Env.O,
|
|
||||||
"P": Env.P,
|
|
||||||
"Q": Env.Q,
|
|
||||||
"R": Env.R,
|
|
||||||
"S": Env.S,
|
|
||||||
"T": Env.T,
|
|
||||||
"U": Env.U,
|
|
||||||
"V": Env.V,
|
|
||||||
"W": Env.W,
|
|
||||||
"X": Env.X,
|
|
||||||
"Y": Env.Y,
|
|
||||||
"Z": Env.Z
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
Map<String, String> cn = {
|
|
||||||
"0": "3",
|
|
||||||
"1": "7",
|
|
||||||
"2": "1",
|
|
||||||
"3": "9",
|
|
||||||
"4": "0",
|
|
||||||
"5": "5",
|
|
||||||
"6": "2",
|
|
||||||
"7": "6",
|
|
||||||
"8": "4",
|
|
||||||
"9": "8"
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppColor {
|
|
||||||
static const Color primaryColor = Color(0xFF1DA1F2);
|
|
||||||
static const Color writeColor = Color(0xff222359);
|
|
||||||
|
|
||||||
static const Color bronze = Color(0xFFCD7F32);
|
|
||||||
static const Color goldenBronze = Color(0xFFB87333); // Golden bronze color
|
|
||||||
static const Color gold = Color(0xFFD4AF37);
|
|
||||||
static const Color secondaryColor = Colors.white;
|
|
||||||
static const Color accentColor = Colors.grey;
|
|
||||||
static const Color greyColor = Colors.grey;
|
|
||||||
static const Color twitterColor = Color(0xFF1DA1F2); // Twitter blue
|
|
||||||
|
|
||||||
static const Color redColor = Color(0xFFEA4335); // Google Red
|
|
||||||
static const Color greenColor = Color(0xFF34A853); // Google Green
|
|
||||||
static const Color blueColor = Color(0xFF1DA1F2); // Google Blue
|
|
||||||
static const Color yellowColor = Color(0xFFFBBC05); // Google Yellow
|
|
||||||
static Color deepPurpleAccent =
|
|
||||||
const Color.fromARGB(255, 123, 76, 254).withOpacity(0.3);
|
|
||||||
|
|
||||||
// For dynamic elements like gradients
|
|
||||||
static List<Color> gradientStartEnd = [
|
|
||||||
Color.fromARGB(255, 40, 158, 232), // Start with primary color
|
|
||||||
Color.fromARGB(
|
|
||||||
255, 44, 63, 75), // End with a slightly darker shade of Twitter blue
|
|
||||||
];
|
|
||||||
|
|
||||||
static List<Color> secondaryGradientStartEnd = [
|
|
||||||
const Color(0xFF1DA1F2), // Start with Twitter blue
|
|
||||||
const Color(0xFF0C7ABF), // End with a slightly darker shade of Twitter blue
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
// في ملف: constant/country_polygons.dart
|
|
||||||
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
|
|
||||||
class CountryPolygons {
|
|
||||||
// ==========================================================
|
|
||||||
// 1. الأردن: تغطية الممر الحضري الرئيسي (من إربد شمالاً حتى العقبة جنوباً)
|
|
||||||
// حوالي 12 نقطة
|
|
||||||
// ==========================================================
|
|
||||||
static final List<LatLng> jordanBoundary = [
|
|
||||||
// شمال إربد (قرب الحدود)
|
|
||||||
const LatLng(32.65, 35.80),
|
|
||||||
// شمال شرق المفرق
|
|
||||||
const LatLng(32.35, 37.00),
|
|
||||||
// شرق الزرقاء / الأزرق
|
|
||||||
const LatLng(31.85, 36.80),
|
|
||||||
// جنوب شرق (نهاية الزحف السكاني)
|
|
||||||
const LatLng(31.00, 36.50),
|
|
||||||
// جنوب / معان
|
|
||||||
const LatLng(30.30, 35.75),
|
|
||||||
// العقبة
|
|
||||||
const LatLng(29.50, 35.00),
|
|
||||||
// البحر الأحمر / الحدود الغربية
|
|
||||||
const LatLng(29.50, 34.85),
|
|
||||||
// غرب وادي عربة
|
|
||||||
const LatLng(30.80, 35.25),
|
|
||||||
// منطقة البحر الميت / السلط
|
|
||||||
const LatLng(32.00, 35.50),
|
|
||||||
// العودة عبر وادي الأردن إلى الشمال
|
|
||||||
const LatLng(32.45, 35.60),
|
|
||||||
// العودة لنقطة إربد
|
|
||||||
const LatLng(32.65, 35.80),
|
|
||||||
];
|
|
||||||
|
|
||||||
// ==========================================================
|
|
||||||
// 2. سوريا: تغطية الممر الغربي والساحلي (درعا، دمشق، حمص، حماة، حلب، الساحل)
|
|
||||||
// حوالي 14 نقطة
|
|
||||||
// ==========================================================
|
|
||||||
static final List<LatLng> syriaBoundary = [
|
|
||||||
// درعا / الجنوب
|
|
||||||
const LatLng(32.65, 35.95),
|
|
||||||
// شرق السويداء (حدود المنطقة المأهولة)
|
|
||||||
const LatLng(32.85, 37.10),
|
|
||||||
// أطراف دمشق الشرقية
|
|
||||||
const LatLng(33.50, 36.65),
|
|
||||||
// تدمر (أقصى امتداد شرقي للمضلع)
|
|
||||||
const LatLng(34.50, 38.30),
|
|
||||||
// الرقة (شمال شرق)
|
|
||||||
const LatLng(35.95, 38.80),
|
|
||||||
// حلب (الشمال)
|
|
||||||
const LatLng(36.45, 37.15),
|
|
||||||
// الحدود الشمالية الغربية (إدلب / تركيا)
|
|
||||||
const LatLng(36.50, 36.50),
|
|
||||||
// اللاذقية (الساحل)
|
|
||||||
const LatLng(35.50, 35.75),
|
|
||||||
// طرطوس (الساحل)
|
|
||||||
const LatLng(34.80, 35.85),
|
|
||||||
// حمص
|
|
||||||
const LatLng(34.70, 36.70),
|
|
||||||
// حماة
|
|
||||||
const LatLng(35.10, 36.70),
|
|
||||||
// العودة إلى منطقة دمشق
|
|
||||||
const LatLng(33.40, 36.30),
|
|
||||||
// العودة إلى درعا
|
|
||||||
const LatLng(32.65, 35.95),
|
|
||||||
];
|
|
||||||
|
|
||||||
// ==========================================================
|
|
||||||
// 3. مصر: تغطية القاهرة الكبرى، الدلتا، والإسكندرية والإسماعيلية
|
|
||||||
// حوالي 10 نقاط
|
|
||||||
// ==========================================================
|
|
||||||
static final List<LatLng> egyptBoundary = [
|
|
||||||
// جنوب الفيوم (أقصى امتداد جنوبي غربي)
|
|
||||||
const LatLng(29.20, 30.60),
|
|
||||||
// جنوب القاهرة (العياط)
|
|
||||||
const LatLng(29.80, 31.30),
|
|
||||||
// شرق السويس
|
|
||||||
const LatLng(29.95, 32.70),
|
|
||||||
// الإسماعيلية / القناة
|
|
||||||
const LatLng(30.60, 32.25),
|
|
||||||
// بورسعيد / أطراف الدلتا الشمالية الشرقية
|
|
||||||
const LatLng(31.30, 31.80),
|
|
||||||
// دمياط / ساحل الدلتا
|
|
||||||
const LatLng(31.50, 31.25),
|
|
||||||
// الإسكندرية (أقصى الشمال الغربي)
|
|
||||||
const LatLng(31.20, 29.80),
|
|
||||||
// غرب الدلتا
|
|
||||||
const LatLng(30.50, 30.20),
|
|
||||||
// العودة لنقطة البداية
|
|
||||||
const LatLng(29.20, 30.60),
|
|
||||||
];
|
|
||||||
|
|
||||||
// دالة تُرجع رابط API بناءً على اسم الدولة
|
|
||||||
static String getRoutingApiUrl(String countryName) {
|
|
||||||
switch (countryName) {
|
|
||||||
case 'Jordan':
|
|
||||||
return 'https://routec.intaleq.xyz/route-jo';
|
|
||||||
case 'Syria':
|
|
||||||
return 'https://routec.intaleq.xyz/route';
|
|
||||||
case 'Egypt':
|
|
||||||
return 'https://routec.intaleq.xyz/route-eg';
|
|
||||||
default:
|
|
||||||
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
|
|
||||||
return 'https://routec.intaleq.xyz/route';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
|
|
||||||
import '../controller/functions/crud.dart';
|
|
||||||
import '../main.dart';
|
|
||||||
import 'box_name.dart';
|
|
||||||
import 'char_map.dart';
|
|
||||||
import 'links.dart';
|
|
||||||
|
|
||||||
class AC {
|
|
||||||
gAK() async {
|
|
||||||
if (box.read(BoxName.apiKeyRun).toString() != 'run') {
|
|
||||||
var res = await CRUD().get(link: AppLink.getApiKey, payload: {});
|
|
||||||
var decod = jsonDecode(res);
|
|
||||||
// print(decod);
|
|
||||||
|
|
||||||
Map<String, dynamic> jsonData = {};
|
|
||||||
for (var i = 0; i < decod['message'].length; i++) {
|
|
||||||
String h = decod['message'][i]['hashed_key'].toString();
|
|
||||||
String retrievedString = X.r(X.r(X.r(h, cn), cC), cs);
|
|
||||||
|
|
||||||
await storage.write(
|
|
||||||
key: decod['message'][i]['name'].toString(),
|
|
||||||
value: retrievedString.toString(),
|
|
||||||
);
|
|
||||||
//
|
|
||||||
String name = decod['message'][i]['name'].toString();
|
|
||||||
String value = decod['message'][i]['hashed_key'].toString();
|
|
||||||
|
|
||||||
jsonData[name] = value;
|
|
||||||
}
|
|
||||||
String jsonString = json.encode(jsonData);
|
|
||||||
print(jsonString);
|
|
||||||
box.write(BoxName.apiKeyRun, 'run');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String q(String b, String c) {
|
|
||||||
final d = utf8.encode(c);
|
|
||||||
final e = utf8.encode(b);
|
|
||||||
|
|
||||||
final f = Hmac(sha256, d);
|
|
||||||
final g = f.convert(e);
|
|
||||||
|
|
||||||
final h = g.bytes;
|
|
||||||
final i = base64Url.encode(h);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
String j(String k, String l) {
|
|
||||||
final m = utf8.encode(l);
|
|
||||||
final n = base64Url.decode(k);
|
|
||||||
|
|
||||||
final o = Hmac(sha256, m);
|
|
||||||
final p = o.convert(n);
|
|
||||||
|
|
||||||
final q = utf8.decode(p.bytes);
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
String a(String b, String c) {
|
|
||||||
int d = b.length;
|
|
||||||
int e = d ~/ 4;
|
|
||||||
|
|
||||||
List<String> f = [];
|
|
||||||
for (int g = 0; g < d; g += e) {
|
|
||||||
int h = g + e;
|
|
||||||
if (h > d) {
|
|
||||||
h = d;
|
|
||||||
}
|
|
||||||
String i = b.substring(g, h);
|
|
||||||
f.add(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f);
|
|
||||||
Map<String, String> j = {};
|
|
||||||
j['birinci'] = f[4];
|
|
||||||
j['ikinci'] = f[2];
|
|
||||||
j['üçüncü'] = c + f[1];
|
|
||||||
j['dördüncü'] = f[0];
|
|
||||||
j['beş'] = f[3];
|
|
||||||
|
|
||||||
String k = '';
|
|
||||||
j.forEach((l, m) {
|
|
||||||
k += m;
|
|
||||||
});
|
|
||||||
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> n(String o, String c) {
|
|
||||||
String p = o.replaceAll(c, '');
|
|
||||||
|
|
||||||
Map<String, String> q = {};
|
|
||||||
q['birinci'] = p[p.length - 5] + p[p.length - 3];
|
|
||||||
q['ikinci'] = p[p.length - 1] + p[p.length - 15];
|
|
||||||
q['üçüncü'] = p[p.length - 9] + p[p.length - 12];
|
|
||||||
q['dördüncü'] = p[p.length - 11] + p[p.length - 6];
|
|
||||||
q['beş'] = p[p.length - 2] + p[p.length - 8];
|
|
||||||
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
class AppInformation {
|
|
||||||
static const String companyName = 'Tripz';
|
|
||||||
static const String appName = 'Tripz DRIVER';
|
|
||||||
static const String appVersion = 'Tripz DRIVER';
|
|
||||||
static const String phoneNumber = '962798583052';
|
|
||||||
static const String linkedInProfile =
|
|
||||||
'https://www.linkedin.com/in/hamza-ayed/';
|
|
||||||
static const String website = 'https://tripz-egypt.com';
|
|
||||||
static const String email = 'hamzaayed@tripz-egypt.com';
|
|
||||||
static const String complaintPrompt =
|
|
||||||
'for this data for complaint from driver or passenger i collect all data i want you analyze this complaint and show what is reason and what is solution .this data collected from many table to find solution if payment in visa not complete and if ride status is finished it will be paymnet in payment table if ride status is not finished there is no need to pay and payment table is null for this ride and if paymentFromPaymentTable not null and visa type not cash the payment sucssessed . if ratingpassenger is low or passengr rating drivers low grade then dont mine of this passenger ,look at driver too like passengerratingdriver with rating or ratingtopassenger .in json add status of complaint and message to passenger and message to driver and message to call center write in arabic in json output with key in english .for output please just json i want';
|
|
||||||
static const String addd = 'BlBlNl';
|
|
||||||
static const String privacyPolicy = '''
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Tripz - Privacy Policy & Terms of Use</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: #333;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
h1, h2, h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 1.8em;
|
|
||||||
border-bottom: 2px solid #3498db;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: #2980b9;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #16a085;
|
|
||||||
}
|
|
||||||
strong {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding-left: 20px;
|
|
||||||
list-style-position: outside;
|
|
||||||
}
|
|
||||||
.highlight {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-left: 4px solid #3498db;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Privacy Policy & Terms of Use</h1>
|
|
||||||
|
|
||||||
<div class="highlight">
|
|
||||||
<p><strong>Effective Date:</strong> August 9, 2025</p>
|
|
||||||
<p><strong>Last Updated:</strong> August 9, 2025</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>1. Introduction and Acceptance</h2>
|
|
||||||
<p>By downloading, registering, or using the Tripz application ("App"), you agree to be bound by this Privacy Policy and our Terms of Use. If you do not agree, you must stop using the App immediately. Your continued use constitutes acceptance of these terms and any future updates.</p>
|
|
||||||
|
|
||||||
<h2>2. Definitions</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>"Tripz", "we", "us":</strong> Refers to the Tripz for Ride Hailing company, Damascus – Syria (Owner & operator), which provides the technology platform.</li>
|
|
||||||
<li><strong>"Driver":</strong> An independent service provider who uses the App to offer transportation services.</li>
|
|
||||||
<li><strong>"Passenger", "you":</strong> An individual who uses the App to request transportation services.</li>
|
|
||||||
<li><strong>"Services":</strong> The connection between Passengers and Drivers facilitated by our App.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>3. Privacy Policy</h2>
|
|
||||||
|
|
||||||
<h3>3.1 Information We Collect</h3>
|
|
||||||
<p>We collect information necessary to provide and improve our Services.</p>
|
|
||||||
|
|
||||||
<h4>A. Information You Provide:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>For Drivers:</strong> To ensure safety and compliance, we collect identity information, including your full name, phone number, personal photo, and official documents (e.g., driver's license, vehicle registration).</li>
|
|
||||||
<li><strong>For Passengers:</strong> We only require a phone number for registration and communication. We are not authorized to request or view official identity documents for passengers.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>B. Information Collected Automatically:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Location Data:</strong> We collect precise location data when the App is in use to facilitate ride matching, navigation, and for safety purposes.</li>
|
|
||||||
<li><strong>Device Data:</strong> We collect information about your device, such as model, operating system, and unique identifiers, to ensure App functionality and for security verification.</li>
|
|
||||||
<li><strong>Usage Data:</strong> We log how you interact with our App, including trip history and features used, to improve our services.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.2 Payment Information</h3>
|
|
||||||
<p><strong>We do not collect, process, or store any sensitive payment information like credit/debit card numbers.</strong> We facilitate payments by connecting you to licensed, local third-party providers:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Mobile Carrier Billing:</strong> Payments via MTN and Syriatel are processed directly by them based on your registered phone number. A one-time password (OTP) sent by the carrier is required to confirm the transaction.</li>
|
|
||||||
<li><strong>Bank Card Payments:</strong> We connect you with the Syrian company "eCash" to process card payments. They handle the transaction, and your bank will send an OTP to your phone to authorize it.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.3 How We Use Your Information</h3>
|
|
||||||
<ul>
|
|
||||||
<li>To operate and maintain the Services (e.g., connect Drivers and Passengers).</li>
|
|
||||||
<li>To verify Driver identity and eligibility.</li>
|
|
||||||
<li>To improve App security and prevent fraud.</li>
|
|
||||||
<li>To provide customer support.</li>
|
|
||||||
<li>To comply with legal obligations.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.4 Data Sharing</h3>
|
|
||||||
<p>We do not sell your personal data. We only share it in the following limited circumstances:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Between Passenger and Driver:</strong> To facilitate a ride, we share necessary information like name, photo, and real-time location.</li>
|
|
||||||
<li><strong>With Service Providers:</strong> For services like payment processing and mapping. These providers are contractually obligated to protect your data.</li>
|
|
||||||
<li><strong>For Legal Reasons:</strong> If required by law or a valid legal order.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.5 Policy for Minors</h3>
|
|
||||||
<p>Our services are intended for individuals over the age of 18.
|
|
||||||
<strong>For Drivers:</strong> We strictly verify the identity and age of all drivers to ensure no minors are operating on our platform.
|
|
||||||
<strong>For Passengers:</strong> While we do not verify passenger identity, the service is not directed at children under 18. If a parent or guardian becomes aware that their child has provided us with information without their consent, they should contact us immediately.</p>
|
|
||||||
|
|
||||||
<h2>4. User Obligations & Conduct</h2>
|
|
||||||
<ul>
|
|
||||||
<li>You must provide accurate and current information during registration.</li>
|
|
||||||
<li>You are responsible for maintaining the security of your account.</li>
|
|
||||||
<li>You agree not to use the App for any illegal activities, to harass others, or to cause damage to a Driver's vehicle.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>5. Disclaimer of Liability</h2>
|
|
||||||
<p>The App is provided "as is". Tripz is an intermediary platform and is not liable for the actions of Drivers or Passengers, accidents, delays, or any disputes between users. Our liability is limited to the fullest extent permitted by law.</p>
|
|
||||||
|
|
||||||
<h2>6. Policy Updates</h2>
|
|
||||||
<p>We may update these terms. If we make significant changes, we will notify you within the App. You will be required to review and accept the new terms to continue using the Services, ensuring your consent is active and informed.</p>
|
|
||||||
|
|
||||||
<h2>7. Account Deletion & Contact</h2>
|
|
||||||
<p>You have the right to request the deletion of your account and personal data. To do so, or for any other questions, please contact us. We will respond to deletion requests within 30 days.</p>
|
|
||||||
<p><strong>Email:</strong> <a href="mailto:support@Tripzapp.com">support@Tripzapp.com</a></p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
''';
|
|
||||||
|
|
||||||
static const String privacyPolicyArabic = '''
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ar" dir="rtl">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Tripz - سياسة الخصوصية وشروط الاستخدام</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: #333;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
h1, h2, h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 1.8em;
|
|
||||||
border-bottom: 2px solid #3498db;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: #2980b9;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #16a085;
|
|
||||||
}
|
|
||||||
strong {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding-right: 20px;
|
|
||||||
list-style-position: outside;
|
|
||||||
}
|
|
||||||
.highlight {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-right: 4px solid #3498db;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>سياسة الخصوصية وشروط الاستخدام</h1>
|
|
||||||
|
|
||||||
<div class="highlight">
|
|
||||||
<p><strong>تاريخ النفاذ:</strong> 9 أغسطس 2025</p>
|
|
||||||
<p><strong>آخر تحديث:</strong> 9 أغسطس 2025</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>1. المقدمة والقبول</h2>
|
|
||||||
<p>عبر تحميل أو تسجيل أو استخدام تطبيق "Tripz" ("التطبيق")، فإنك توافق على الالتزام بسياسة الخصوصية وشروط الاستخدام هذه. إذا كنت لا توافق، يجب عليك التوقف فورًا عن استخدام التطبيق. استمرارك في الاستخدام يُعد قبولاً لهذه الشروط وأي تحديثات مستقبلية لها.</p>
|
|
||||||
|
|
||||||
<h2>2. التعريفات</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>"Tripz"، "نحن":</strong> تشير إلى شركة Tripz لنقل الركاب، دمشق – سوريا (مالك ومشغل التطبيق)، التي توفر المنصة التقنية.</li>
|
|
||||||
<li><strong>"السائق":</strong> مقدم خدمة مستقل يستخدم التطبيق لتقديم خدمات النقل.</li>
|
|
||||||
<li><strong>"الراكب"، "أنت":</strong> الفرد الذي يستخدم التطبيق لطلب خدمات النقل.</li>
|
|
||||||
<li><strong>"الخدمات":</strong> عملية الربط بين الركاب والسائقين التي يسهلها تطبيقنا.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>3. سياسة الخصوصية</h2>
|
|
||||||
|
|
||||||
<h3>3.1 المعلومات التي نجمعها</h3>
|
|
||||||
<p>نحن نجمع المعلومات الضرورية لتقديم خدماتنا وتحسينها.</p>
|
|
||||||
|
|
||||||
<h4>أ. المعلومات التي تقدمها بنفسك:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>بالنسبة للسائقين:</strong> لضمان السلامة والامتثال للقوانين، نجمع بيانات الهوية الشخصية، بما في ذلك الاسم الكامل، رقم الهاتف، صورة شخصية، والوثائق الرسمية (مثل رخصة القيادة وتسجيل المركبة).</li>
|
|
||||||
<li><strong>بالنسبة للركاب:</strong> نطلب فقط رقم هاتف للتسجيل والتواصل. نحن غير مخولين بطلب أو الاطلاع على وثائق الهوية الرسمية للركاب.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>ب. المعلومات التي تُجمع تلقائيًا:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>بيانات الموقع:</strong> نجمع بيانات الموقع الجغرافي الدقيقة عند استخدام التطبيق لتسهيل تحديد أماكن الانطلاق والوصول، الملاحة، ولأغراض السلامة.</li>
|
|
||||||
<li><strong>بيانات الجهاز:</strong> نجمع معلومات عن جهازك (طراز، نظام تشغيل، معرفات فريدة) لضمان عمل التطبيق وللتحقق الأمني.</li>
|
|
||||||
<li><strong>بيانات الاستخدام:</strong> نسجل كيفية تفاعلك مع التطبيق، بما في ذلك سجل الرحلات والميزات المستخدمة، بهدف تحسين خدماتنا.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.2 معلومات الدفع</h3>
|
|
||||||
<p><strong>نحن لا نجمع أو نعالج أو نخزن أي معلومات دفع حساسة</strong> مثل أرقام بطاقات الائتمان/الخصم. نحن نسهل عمليات الدفع عبر ربطك بمزودي خدمات محليين مرخصين:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>الدفع عبر رصيد الهاتف المحمول:</strong> تتم معالجة الدفعات عبر شركتي MTN و Syriatel مباشرة من خلالهما بناءً على رقم هاتفك المسجل لديهم. يتطلب تأكيد العملية إدخال رمز تحقق (OTP) يُرسل من قبل شركة الاتصالات.</li>
|
|
||||||
<li><strong>الدفع عبر البطاقات البنكية:</strong> نربطك بشركة "eCash" السورية لمعالجة الدفعات بالبطاقات. هي التي تتولى المعاملة، وسيقوم البنك الذي تتعامل معه بإرسال رمز تحقق (OTP) إلى هاتفك لتفويض العملية.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.3 كيف نستخدم معلوماتك</h3>
|
|
||||||
<ul>
|
|
||||||
<li>لتشغيل وصيانة الخدمات (مثل الربط بين السائقين والركاب).</li>
|
|
||||||
<li>للتحقق من هوية السائقين وأهليتهم.</li>
|
|
||||||
<li>لتحسين أمان التطبيق ومنع الاحتيال.</li>
|
|
||||||
<li>لتقديم الدعم الفني للعملاء.</li>
|
|
||||||
<li>للامتثال للالتزامات القانونية.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.4 مشاركة البيانات</h3>
|
|
||||||
<p>نحن لا نبيع بياناتك الشخصية. نشاركها فقط في الحالات المحدودة التالية:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>بين الراكب والسائق:</strong> لتسهيل الرحلة، نشارك المعلومات الضرورية مثل الاسم، الصورة، والموقع المباشر.</li>
|
|
||||||
<li><strong>مع مزودي الخدمات:</strong> مثل معالجي الدفع وخدمات الخرائط. هؤلاء المزودون ملزمون تعاقديًا بحماية بياناتك.</li>
|
|
||||||
<li><strong>لأسباب قانونية:</strong> إذا طُلب ذلك بموجب القانون أو أمر قضائي ساري المفعول.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>3.5 سياسة القاصرين</h3>
|
|
||||||
<p>خدماتنا موجهة للأفراد الذين تزيد أعمارهم عن 18 عامًا.
|
|
||||||
<strong>بالنسبة للسائقين:</strong> نحن نتحقق بدقة من هوية وعمر جميع السائقين لضمان عدم وجود قاصرين يعملون على منصتنا.
|
|
||||||
<strong>بالنسبة للركاب:</strong> على الرغم من أننا لا نتحقق من هوية الركاب، فإن الخدمة غير موجهة للأطفال دون سن 18. إذا علم ولي الأمر أن طفله قد زودنا بمعلومات دون موافقته، فيجب عليه الاتصال بنا على الفور.</p>
|
|
||||||
|
|
||||||
<h2>4. التزامات المستخدم وسلوكه</h2>
|
|
||||||
<ul>
|
|
||||||
<li>يجب عليك تقديم معلومات دقيقة وحديثة عند التسجيل.</li>
|
|
||||||
<li>أنت مسؤول عن الحفاظ على أمان حسابك.</li>
|
|
||||||
<li>أنت توافق على عدم استخدام التطبيق لأي أنشطة غير قانونية، أو لمضايقة الآخرين، أو التسبب في ضرر لمركبة السائق.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>5. إخلاء المسؤولية</h2>
|
|
||||||
<p>يتم تقديم التطبيق "كما هو". "Tripz" هي منصة وسيطة وليست مسؤولة عن تصرفات السائقين أو الركاب، أو الحوادث، أو التأخير، أو أي نزاعات بين المستخدمين. مسؤوليتنا محدودة إلى أقصى حد يسمح به القانون.</p>
|
|
||||||
|
|
||||||
<h2>6. تحديثات السياسة</h2>
|
|
||||||
<p>قد نقوم بتحديث هذه الشروط. في حال إجراء تغييرات جوهرية، سنقوم بإعلامك داخل التطبيق. سيُطلب منك مراجعة الشروط الجديدة وقبولها لمواصلة استخدام الخدمات، لضمان أن موافقتك فعالة ومبنية على معرفة.</p>
|
|
||||||
|
|
||||||
<h2>7. حذف الحساب والتواصل</h2>
|
|
||||||
<p>لديك الحق في طلب حذف حسابك وبياناتك الشخصية. للقيام بذلك، أو لأي استفسارات أخرى، يرجى التواصل معنا. سنرد على طلبات الحذف في غضون 30 يومًا.</p>
|
|
||||||
<p><strong>البريد الإلكتروني:</strong> <a href="mailto:support@Tripzapp.com">support@Tripzapp.com</a></p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
''';
|
|
||||||
}
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
// import 'package:tripz_driver/env/env.dart';
|
|
||||||
|
|
||||||
import '../main.dart';
|
|
||||||
import 'box_name.dart';
|
|
||||||
|
|
||||||
class AppLink {
|
|
||||||
static String serverPHP = box.read('serverPHP');
|
|
||||||
|
|
||||||
///https://wl.tripz-egypt.com/v2/main
|
|
||||||
static String paymentServer = 'https://wl.tripz-egypt.com/v2/main';
|
|
||||||
|
|
||||||
static String locationServer =
|
|
||||||
'https://locationtripz.tripz-egypt.com/tripz/ride/location';
|
|
||||||
static const String routeApiBaseUrl =
|
|
||||||
"https://routesjo.intaleq.xyz/route/v1/driving";
|
|
||||||
static final String endPoint = 'https://api.tripz-egypt.com/v1/tripz';
|
|
||||||
|
|
||||||
///'https://api.tripz-egypt.com/v1/tripz'
|
|
||||||
static final String server = 'https://api.tripz-egypt.com/v1/tripz';
|
|
||||||
|
|
||||||
///=================ride==========================///
|
|
||||||
static String ride = '$server/ride';
|
|
||||||
static String rideServer = 'https://rides.tripz-egypt.com/tripz/ride';
|
|
||||||
|
|
||||||
static String tripzCairoServer = endPoint;
|
|
||||||
static String tripzGizaServer =
|
|
||||||
box.read('Giza') ?? box.read(BoxName.serverChosen);
|
|
||||||
static String tripzAlexandriaServer =
|
|
||||||
box.read('Alexandria') ?? box.read(BoxName.serverChosen);
|
|
||||||
// static final String server = Env.serverPHP;
|
|
||||||
|
|
||||||
static String loginJwtDriver = "$server/loginJwtDriver.php";
|
|
||||||
static String loginJwtWalletDriver = "$server/loginJwtWalletDriver.php";
|
|
||||||
static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php";
|
|
||||||
|
|
||||||
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
|
|
||||||
static String llama = 'https://api.llama-api.com/chat/completions';
|
|
||||||
static String gemini =
|
|
||||||
'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText';
|
|
||||||
|
|
||||||
static String test = "$server/test.php";
|
|
||||||
|
|
||||||
//===============contact==========================
|
|
||||||
static String savePhones = "$ride/egyptPhones/add.php";
|
|
||||||
static String savePhonesSyria = "$ride/egyptPhones/syrianAdd.php";
|
|
||||||
static String getPhones = "$ride/egyptPhones/get.php";
|
|
||||||
|
|
||||||
////===============firebase==========================
|
|
||||||
static String getTokens = "$ride/firebase/get.php";
|
|
||||||
|
|
||||||
static String getDriverToken = "$ride/firebase/getDriverToken.php";
|
|
||||||
static String addTokens = "$ride/firebase/add.php";
|
|
||||||
static String addTokensDriver = "$ride/firebase/addDriver.php";
|
|
||||||
static String addTokensDriverWallet =
|
|
||||||
"$paymentServer/ride/firebase/addDriver.php";
|
|
||||||
|
|
||||||
//=======================Wallet===================
|
|
||||||
static String wallet = '$paymentServer/ride/passengerWallet';
|
|
||||||
static String walletDriver = '$paymentServer/ride/driverWallet';
|
|
||||||
static String getAllPassengerTransaction =
|
|
||||||
"$wallet/getAllPassengerTransaction.php";
|
|
||||||
static String payWithMTNConfirm =
|
|
||||||
"$paymentServer/ride/mtn/driver/confirm_payment.php";
|
|
||||||
static String payWithMTNStart =
|
|
||||||
"$paymentServer/ride/mtn/driver/mtn_start.php";
|
|
||||||
static String payWithSyriatelConfirm =
|
|
||||||
"$paymentServer/ride/syriatel/driver/confirm_payment.php";
|
|
||||||
static String payWithSyriatelStart =
|
|
||||||
"$paymentServer/ride/syriatel/driver/start_payment.php";
|
|
||||||
static String payWithEcashDriver =
|
|
||||||
"$paymentServer/ride/ecash/driver/payWithEcash.php";
|
|
||||||
static String payWithEcashPassenger =
|
|
||||||
"$paymentServer/ride/ecash/passenger/payWithEcash.php";
|
|
||||||
// wl.tripz-egypt.com/v1/main/ride/ecash/driver
|
|
||||||
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
|
|
||||||
static String getPassengersWallet = "$wallet/get.php";
|
|
||||||
static String getPassengerWalletArchive =
|
|
||||||
"$wallet/getPassengerWalletArchive.php";
|
|
||||||
static String addPassengersWallet = "$wallet/add.php";
|
|
||||||
static String deletePassengersWallet = "$wallet/delete.php";
|
|
||||||
static String updatePassengersWallet = "$wallet/update.php";
|
|
||||||
|
|
||||||
static String getWalletByDriver = "$walletDriver/getWalletByDriver.php";
|
|
||||||
static String driverStatistic =
|
|
||||||
"$endPoint/ride/driverWallet/driverStatistic.php";
|
|
||||||
static String getDriverDetails =
|
|
||||||
"$tripzCairoServer/ride/driverWallet/getDriverDetails.php";
|
|
||||||
static String getDriverWeekPaymentMove =
|
|
||||||
"$walletDriver/getDriverWeekPaymentMove.php";
|
|
||||||
static String getDriversWallet = "$walletDriver/get.php";
|
|
||||||
static String addDriversWalletPoints = "$walletDriver/add.php";
|
|
||||||
static String addpromotionDriver = "$walletDriver/promotionDriver.php";
|
|
||||||
static String deleteDriversWallet = "$walletDriver/delete.php";
|
|
||||||
static String updateDriversWallet = "$walletDriver/update.php";
|
|
||||||
|
|
||||||
//=======================promo===================ride.mobile-app.store/ride/promo/get.php
|
|
||||||
static String promo = '$server/ride/promo';
|
|
||||||
static String getPassengersPromo = "$promo/get.php";
|
|
||||||
static String getPromoBytody = "$promo/getPromoBytody.php";
|
|
||||||
static String addPassengersPromo = "$promo/add.php";
|
|
||||||
static String deletePassengersPromo = "$promo/delete.php";
|
|
||||||
static String updatePassengersPromo = "$promo/update.php";
|
|
||||||
|
|
||||||
////=======================cancelRide===================
|
|
||||||
|
|
||||||
static String addCancelRideFromPassenger = "$rideServer/cancelRide/add.php";
|
|
||||||
static String addCancelTripFromDriverAfterApplied =
|
|
||||||
"$rideServer/cancelRide/addCancelTripFromDriverAfterApplied.php";
|
|
||||||
static String cancelRide = "$rideServer/cancelRide/get.php";
|
|
||||||
//-----------------ridessss------------------
|
|
||||||
static String addRides = "$rideServer/rides/add.php";
|
|
||||||
static String getRides = "$rideServer/rides/get.php";
|
|
||||||
static String getPlacesSyria = "$rideServer/places_syria/get.php";
|
|
||||||
static String getMishwari = "$rideServer/mishwari/get.php";
|
|
||||||
static String getMishwariDriver = "$rideServer/mishwari/getDriver.php";
|
|
||||||
static String getTripCountByCaptain =
|
|
||||||
"$rideServer/rides/getTripCountByCaptain.php";
|
|
||||||
static String getRideOrderID = "$rideServer/rides/getRideOrderID.php";
|
|
||||||
static String getRideStatus = "$rideServer/rides/getRideStatus.php";
|
|
||||||
static String getOverLayStatus = "$ride/overLay/get.php";
|
|
||||||
static String getArgumentAfterAppliedFromBackground =
|
|
||||||
"$ride/overLay/getArgumentAfterAppliedFromBackground.php";
|
|
||||||
static String addOverLayStatus = "$ride/overLay/add.php";
|
|
||||||
static String getapiKey = "$ride/apiKey/get.php";
|
|
||||||
|
|
||||||
static String getapiKeytripz = "$ride/apiKey/get.php";
|
|
||||||
static String getRideStatusBegin = "$rideServer/rides/getRideStatusBegin.php";
|
|
||||||
static String getRideStatusFromStartApp =
|
|
||||||
"$rideServer/rides/getRideStatusFromStartApp.php";
|
|
||||||
static String updateRides = "$rideServer/rides/update.php";
|
|
||||||
static String updateRideAndCheckIfApplied =
|
|
||||||
"$rideServer/rides/updateRideAndCheckIfApplied.php";
|
|
||||||
static String updateStausFromSpeed =
|
|
||||||
"$rideServer/rides/updateStausFromSpeed.php";
|
|
||||||
static String deleteRides = "$rideServer/rides/delete.php";
|
|
||||||
|
|
||||||
//-----------------DriverPayment------------------
|
|
||||||
static String addDriverScam = "$ride/driver_scam/add.php";
|
|
||||||
static String getDriverScam = "$ride/driver_scam/get.php";
|
|
||||||
|
|
||||||
/////////---getKazanPercent===////////////
|
|
||||||
static String getKazanPercent = "$ride/kazan/get.php";
|
|
||||||
static String get_home_init = "$ride/kazan/get_home_init.php.php";
|
|
||||||
static String addKazanPercent = "$ride/kazan/add.php";
|
|
||||||
|
|
||||||
////-----------------DriverPayment------------------
|
|
||||||
static String addDrivePayment = "$paymentServer/ride/payment/add.php";
|
|
||||||
static String payWithPayMobCardDriver =
|
|
||||||
"$paymentServer/ride/payMob/paymob_driver/payWithCard.php";
|
|
||||||
static String payWithWallet =
|
|
||||||
"$paymentServer/ride/payMob/paymob_driver/payWithWallet.php";
|
|
||||||
static String paymetVerifyDriver =
|
|
||||||
"$paymentServer/ride/payMob/paymob_driver/paymet_verfy.php";
|
|
||||||
static String updatePaymetToPaid =
|
|
||||||
"$paymentServer/ride/payment/updatePaymetToPaid.php";
|
|
||||||
static String paymobPayoutDriverWallet =
|
|
||||||
"$paymentServer/ride/payMob/paymob_driver/paymob_payout.php'";
|
|
||||||
|
|
||||||
static String addTripzWallet = "$paymentServer/ride/tripzWallet/add.php";
|
|
||||||
static String gettripzWallet = "$paymentServer/ride/tripzWallet/get.php";
|
|
||||||
static String addDriverPaymentPoints =
|
|
||||||
"$paymentServer/ride/driverPayment/add.php";
|
|
||||||
static String addPaymentTokenDriver =
|
|
||||||
"$paymentServer/ride/driverWallet/addPaymentToken.php"; //driverWallet/addPaymentToken.php
|
|
||||||
static String addPaymentTokenPassenger =
|
|
||||||
"$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
|
|
||||||
static String getDriverPaymentPoints =
|
|
||||||
"$paymentServer/ride/driverWallet/get.php";
|
|
||||||
static String getDriverPaymentToday = "$paymentServer/ride/payment/get.php";
|
|
||||||
static String getCountRide = "$rideServer/payment/getCountRide.php";
|
|
||||||
static String getAllPaymentFromRide =
|
|
||||||
"$paymentServer/ride/payment/getAllPayment.php";
|
|
||||||
static String getAllPaymentVisa =
|
|
||||||
"$paymentServer/ride/payment/getAllPaymentVisa.php";
|
|
||||||
|
|
||||||
//-----------------Passenger NotificationCaptain------------------
|
|
||||||
static String addNotificationPassenger =
|
|
||||||
"$ride/notificationPassenger/add.php";
|
|
||||||
static String getNotificationPassenger =
|
|
||||||
"$ride/notificationPassenger/get.php";
|
|
||||||
static String updateNotificationPassenger =
|
|
||||||
"$ride/notificationPassenger/update.php";
|
|
||||||
//-----------------Driver NotificationCaptain------------------
|
|
||||||
static String addNotificationCaptain = "$ride/notificationCaptain/add.php";
|
|
||||||
static String addWaitingRide = "$ride/notificationCaptain/addWaitingRide.php";
|
|
||||||
static String deleteAvailableRide =
|
|
||||||
"$ride/notificationCaptain/deleteAvailableRide.php";
|
|
||||||
static String updateWaitingRide =
|
|
||||||
"$ride/notificationCaptain/updateWaitingTrip.php";
|
|
||||||
static String getRideWaiting =
|
|
||||||
"$endPoint/ride/notificationCaptain/getRideWaiting.php";
|
|
||||||
static String getNotificationCaptain = "$ride/notificationCaptain/get.php";
|
|
||||||
static String updateNotificationCaptain =
|
|
||||||
"$ride/notificationCaptain/update.php";
|
|
||||||
static String deleteNotificationCaptain =
|
|
||||||
"$ride/notificationCaptain/delete.php";
|
|
||||||
//-----------------Api Key------------------
|
|
||||||
static String addApiKey = "$ride/apiKey/add.php";
|
|
||||||
static String getApiKey = "$ride/apiKey/get.php";
|
|
||||||
static String getCnMap = "$server/auth/cnMap.php";
|
|
||||||
static String getPromptDriverDocumentsEgypt =
|
|
||||||
"$server/auth/captin/getPromptDriverDocumentsEgypt.php";
|
|
||||||
|
|
||||||
static String updateApiKey = "$ride/apiKey/update.php";
|
|
||||||
static String deleteApiKey = "$ride/apiKey/delete.php";
|
|
||||||
static String checkPhoneNumberISVerfiedDriver =
|
|
||||||
"$auth/checkPhoneNumberISVerfiedDriver.php";
|
|
||||||
static String getTesterApp = "$auth/Tester/getTesterApp.php";
|
|
||||||
static String updateTesterApp = "$auth/Tester/updateTesterApp.php";
|
|
||||||
|
|
||||||
//-----------------healthInsuranceProvider------------------
|
|
||||||
static String addHealthInsuranceProvider = "$server/driver_assurance/add.php";
|
|
||||||
static String getHealthInsuranceProvider = "$server/driver_assurance/get.php";
|
|
||||||
|
|
||||||
//-----------------Feed Back------------------
|
|
||||||
static String addFeedBack = "$ride/feedBack/add.php";
|
|
||||||
static String getFeedBack = "$ride/feedBack/get.php";
|
|
||||||
static String updateFeedBack = "$ride/feedBack/updateFeedBack.php";
|
|
||||||
|
|
||||||
//-----------------Tips------------------
|
|
||||||
static String addTips = "$ride/tips/add.php";
|
|
||||||
static String getTips = "$ride/tips/get.php";
|
|
||||||
static String updateTips = "$ride/tips/update.php";
|
|
||||||
|
|
||||||
//-----------------Help Center------------------
|
|
||||||
static String addhelpCenter = "$ride/helpCenter/add.php";
|
|
||||||
static String gethelpCenter = "$ride/helpCenter/get.php";
|
|
||||||
static String getByIdhelpCenter = "$ride/helpCenter/getById.php";
|
|
||||||
static String updatehelpCenter = "$ride/helpCenter/update.php";
|
|
||||||
static String deletehelpCenter = "$ride/helpCenter/delete.php";
|
|
||||||
|
|
||||||
//-----------------license------------------
|
|
||||||
static String addLicense = "$ride/license/add.php";
|
|
||||||
static String getLicense = "$ride/license/get.php";
|
|
||||||
static String updateLicense = "$ride/license/updateFeedBack.php";
|
|
||||||
//-----------------RegisrationCar------------------
|
|
||||||
static String addRegisrationCar = "$ride/RegisrationCar/add.php";
|
|
||||||
static String getRegisrationCar = "$endPoint/ride/RegisrationCar/get.php";
|
|
||||||
static String updateRegisrationCar = "$ride/RegisrationCar/update.php";
|
|
||||||
static String makeDefaultCar = "$ride/RegisrationCar/makeDefaultCar.php";
|
|
||||||
|
|
||||||
//-----------------DriverOrder------------------
|
|
||||||
|
|
||||||
static String addDriverOrder = "$ride/driver_order/add.php";
|
|
||||||
static String getDriverOrder = "$ride/driver_order/get.php";
|
|
||||||
static String getOrderCancelStatus =
|
|
||||||
"$ride/driver_order/getOrderCancelStatus.php";
|
|
||||||
static String updateDriverOrder = "$ride/driver_order/update.php";
|
|
||||||
static String deleteDriverOrder = "$ride/driver_order/delete.php";
|
|
||||||
|
|
||||||
// =====================================
|
|
||||||
static String addRateToPassenger = "$ride/rate/add.php";
|
|
||||||
static String addRateToDriver = "$ride/rate/addRateToDriver.php";
|
|
||||||
static String addRateApp = "$ride/rate/add_rate_app.php";
|
|
||||||
static String sendEmailRateingApp = "$ride/rate/sendEmailRateingApp.php";
|
|
||||||
static String getDriverRate = "$ride/rate/getDriverRate.php";
|
|
||||||
static String getPassengerRate = "$ride/rate/getPassengerRate.php";
|
|
||||||
|
|
||||||
////////////////emails ============//
|
|
||||||
static String sendEmailToPassengerForTripDetails =
|
|
||||||
"$ride/rides/emailToPassengerTripDetail.php";
|
|
||||||
static String sendEmailToDrivertransaction =
|
|
||||||
"$server/Admin/sendEmailToDrivertransaction.php";
|
|
||||||
// ===========================================
|
|
||||||
static String pathImage = "$server/upload/types/";
|
|
||||||
static String uploadImage = "$server/uploadImage.php";
|
|
||||||
static String uploadImage1 = "$server/uploadImage1.php";
|
|
||||||
static String uploadImagePortrate = "$server/uploadImagePortrate.php";
|
|
||||||
static String uploadSyrianDocs = "$server/auth/syria/uploadSyrianDocs.php";
|
|
||||||
static String uploadImageType = "$server/uploadImageType.php";
|
|
||||||
//=============egypt documents ==============
|
|
||||||
static String uploadEgyptidFront =
|
|
||||||
"$server/EgyptDocuments/uploadEgyptidFront.php";
|
|
||||||
static String uploadEgypt = "$server/uploadEgypt.php";
|
|
||||||
static String uploadEgypt1 = "$server/uploadEgypt1.php";
|
|
||||||
|
|
||||||
//==================certifcate==========
|
|
||||||
// static String location = '$endPoint/ride/location';
|
|
||||||
|
|
||||||
static String getCarsLocationByPassenger = "$locationServer/get.php";
|
|
||||||
static String addpassengerLocation =
|
|
||||||
"$locationServer/addpassengerLocation.php";
|
|
||||||
static String getLocationAreaLinks =
|
|
||||||
"$locationServer/get_location_area_links.php";
|
|
||||||
static String getLatestLocationPassenger =
|
|
||||||
"$locationServer/getLatestLocationPassenger.php";
|
|
||||||
static String getFemalDriverLocationByPassenger =
|
|
||||||
"$locationServer/getFemalDriver.php";
|
|
||||||
static String getDriverCarsLocationToPassengerAfterApplied =
|
|
||||||
"$locationServer/getDriverCarsLocationToPassengerAfterApplied.php";
|
|
||||||
static String addCarsLocationByPassenger = "$locationServer/add.php";
|
|
||||||
static String saveBehavior = "$locationServer/save_behavior.php";
|
|
||||||
static String addCarsLocationGizaEndpoint = "$locationServer/add.php";
|
|
||||||
static String addCarsLocationAlexandriaEndpoint = "$locationServer/add.php";
|
|
||||||
static String addCarsLocationCairoEndpoint = "$locationServer/add.php";
|
|
||||||
static String deleteCarsLocationByPassenger = "$locationServer/delete.php";
|
|
||||||
static String updateCarsLocationByPassenger = "$locationServer/update.php";
|
|
||||||
static String getTotalDriverDuration =
|
|
||||||
"$locationServer/getTotalDriverDuration.php";
|
|
||||||
static String getRidesDriverByDay = "$locationServer/getRidesDriverByDay.php";
|
|
||||||
static String getTotalDriverDurationToday =
|
|
||||||
"$locationServer/getTotalDriverDurationToday.php";
|
|
||||||
|
|
||||||
//==================get_driver_behavior.php=============
|
|
||||||
static String get_driver_behavior =
|
|
||||||
'$ride/driver_behavior/get_driver_behavior.php';
|
|
||||||
|
|
||||||
//==================cars new drivers=============
|
|
||||||
static String addNewCarsDrivers = '$ride/carDrivers/add.php';
|
|
||||||
static String getNewCarsDrivers = '$ride/carDrivers/get.php';
|
|
||||||
static String deleteNewCarsDrivers = '$ride/carDrivers/delete.php';
|
|
||||||
|
|
||||||
//==================Blog=============
|
|
||||||
static String profile = '$ride/profile';
|
|
||||||
static String getprofile = "$profile/get.php";
|
|
||||||
static String getCaptainProfile = "$profile/getCaptainProfile.php";
|
|
||||||
static String addprofile = "$profile/add.php";
|
|
||||||
static String deleteprofile = "$profile/delete.php";
|
|
||||||
static String updateprofile = "$profile/update.php";
|
|
||||||
static String updateDriverEmail = "$profile/updateDriverEmail.php";
|
|
||||||
|
|
||||||
//===================Auth============
|
|
||||||
|
|
||||||
static String addInviteDriver = "$ride/invitor/add.php";
|
|
||||||
static String addInvitationPassenger =
|
|
||||||
"$ride/invitor/addInvitationPassenger.php";
|
|
||||||
static String getInviteDriver = "$ride/invitor/get.php";
|
|
||||||
static String getDriverInvitationToPassengers =
|
|
||||||
"$ride/invitor/getDriverInvitationToPassengers.php";
|
|
||||||
static String updateInviteDriver = "$ride/invitor/update.php";
|
|
||||||
static String updatePassengerGift = "$ride/invitor/updatePassengerGift.php";
|
|
||||||
static String updateInvitationCodeFromRegister =
|
|
||||||
"$ride/invitor/updateInvitationCodeFromRegister.php";
|
|
||||||
static String register_driver_and_car =
|
|
||||||
"$auth/syria/driver/register_driver_and_car.php";
|
|
||||||
static String updateDriverInvitationDirectly =
|
|
||||||
"$ride/invitor/updateDriverInvitationDirectly.php";
|
|
||||||
static String updatePassengersInvitation =
|
|
||||||
"$ride/invitor/updatePassengersInvitation.php";
|
|
||||||
|
|
||||||
//===================Auth============
|
|
||||||
|
|
||||||
static String auth = '$server/auth';
|
|
||||||
static String login = "$auth/login.php";
|
|
||||||
static String signUp = "$auth/signup.php";
|
|
||||||
static String updateDriverClaim = "$auth/captin/updateDriverClaim.php";
|
|
||||||
static String updateShamCashDriver = "$auth/captin/updateShamCashDriver.php";
|
|
||||||
static String sendVerifyEmail = "$auth/sendVerifyEmail.php";
|
|
||||||
static String passengerRemovedAccountEmail =
|
|
||||||
"$auth/passengerRemovedAccountEmail.php";
|
|
||||||
static String verifyEmail = "$auth/verifyEmail.php";
|
|
||||||
//===================Auth Captin============
|
|
||||||
static String authCaptin = '$server/auth/captin';
|
|
||||||
static String loginCaptin = "$authCaptin/login.php";
|
|
||||||
static String loginFromGoogleCaptin = "$authCaptin/loginFromGoogle.php";
|
|
||||||
static String loginUsingCredentialsWithoutGoogle =
|
|
||||||
"$authCaptin/loginUsingCredentialsWithoutGoogle.php";
|
|
||||||
static String packageInfo = "$server/auth/packageInfo.php";
|
|
||||||
static String signUpCaptin = "$authCaptin/register.php";
|
|
||||||
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
|
|
||||||
static String sendVerifyEmailCaptin = "$authCaptin/sendVerifyEmail.php";
|
|
||||||
static String sendVerifyOtpMessage =
|
|
||||||
"$server/auth/captin/sendOtpMessageDriver.php";
|
|
||||||
static String verifyOtpMessage = "$server/auth/verifyOtpMessage.php";
|
|
||||||
static String verifyOtpDriver = "$server/auth/captin/verifyOtpDriver.php";
|
|
||||||
static String verifyEmailCaptin = "$authCaptin/verifyEmail.php";
|
|
||||||
static String removeUser = "$authCaptin/removeAccount.php";
|
|
||||||
static String deletecaptainAccounr = "$authCaptin/deletecaptainAccounr.php";
|
|
||||||
static String updateAccountBank = "$authCaptin/updateAccountBank.php";
|
|
||||||
static String getAccount = "$authCaptin/getAccount.php";
|
|
||||||
static String uploadImageToAi = "$auth/document_syria/ai_document.php";
|
|
||||||
static String isPhoneVerified = "$auth/captin/isPhoneVerified.php";
|
|
||||||
|
|
||||||
//===================Admin Captin============
|
|
||||||
|
|
||||||
static String getPassengerDetailsByPassengerID =
|
|
||||||
"$server/Admin/getPassengerDetailsByPassengerID.php";
|
|
||||||
static String getPassengerDetails = "$server/Admin/getPassengerDetails.php";
|
|
||||||
static String getPassengerbyEmail = "$server/Admin/getPassengerbyEmail.php";
|
|
||||||
static String addAdminUser = "$server/Admin/adminUser/add.php";
|
|
||||||
static String addError = "$server/Admin/errorApp.php";
|
|
||||||
static String getAdminUser = "$server/Admin/adminUser/get.php";
|
|
||||||
static String getCaptainDetailsByEmailOrIDOrPhone =
|
|
||||||
"$server/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php";
|
|
||||||
static String getCaptainDetails = "$server/Admin/AdminCaptain/get.php";
|
|
||||||
static String getRidesPerMonth =
|
|
||||||
"$server/Admin/AdminRide/getRidesPerMonth.php";
|
|
||||||
static String getRidesDetails = "$server/Admin/AdminRide/get.php";
|
|
||||||
|
|
||||||
//////////Sms egypt///////////
|
|
||||||
static String sendSms = "https://sms.kazumi.me/api/sms/send-sms";
|
|
||||||
static String senddlr = "https://sms.kazumi.me/api/sms/send-dlr";
|
|
||||||
static String sendvalidity = "https://sms.kazumi.me/api/sms/send-validity";
|
|
||||||
static String sendmany = "https://sms.kazumi.me/api/sms/send-many";
|
|
||||||
static String checkCredit = "https://sms.kazumi.me/api/sms/check-credit";
|
|
||||||
static String checkStatus = "https://sms.kazumi.me/api/sms/check-status";
|
|
||||||
static String getSender = "$server/auth/sms/getSender.php";
|
|
||||||
static String updatePhoneInvalidSMS =
|
|
||||||
"$server/auth/sms/updatePhoneInvalidSMS.php";
|
|
||||||
|
|
||||||
//////////////service///////////
|
|
||||||
|
|
||||||
static String serviceApp = "$server/serviceapp";
|
|
||||||
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
|
|
||||||
static String getComplaintAllDataForDriver =
|
|
||||||
"$serviceApp/getComplaintAllDataForDriver.php";
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
List<String> syrianDriverMessages = [
|
|
||||||
// --- أوقات الذروة والطلبات الكتيرة ---
|
|
||||||
"وقت الذروة: البلد مولّعة مشاوير! 🌃 افتح التطبيق بسرعة وبلّش أرباحك تدبّل.",
|
|
||||||
"طلبات كتير حواليك: ⚠️ خليك أول كابتن بيلتقط الطلب وخلّي يومك رابح.",
|
|
||||||
"الناس طالعة من الشغل: 🌇 الكل راجع عالبيت، خليك جاهز لرحلات كتيرة!",
|
|
||||||
"زحمة المساء: ⏳ الزباين عم تزيد بهالوقت، لا تضيّع الفرصة واشتغل معنا!",
|
|
||||||
|
|
||||||
// --- التركيز عالربح والمصاري ---
|
|
||||||
"ضاعف ربحك: 💰 كل مشوار بيقربك من هدفك اليومي. جاهز تنطلق؟",
|
|
||||||
"مصاري أكتر: جزدانك ناطر تعبك، كل دقيقة شغل هي ربح مضمون. 💪",
|
|
||||||
"خليك بالواجهة: الطلب الجاي ممكن يكون إلك، كبسة زر بتجيبلك رزقة اليوم.",
|
|
||||||
"رزقتك جاهزة: 💸 لا تخلّي الزباين يستنّوا، افتح التطبيق وخليك متأهّب.",
|
|
||||||
|
|
||||||
// --- التقدير والشكر ---
|
|
||||||
"شكراً إلك: 🙏 إنت مو بس سائق، إنت أساس نجاح تطبيق انطلق.",
|
|
||||||
"نفتخر فيك: 🌟 الكباتن متلك هنن يلي رافعين اسمنا بالعالي.",
|
|
||||||
"أداء ممتاز: 👍 استمر بنفس الروح الحلوة، زباينك مبسوطين منك.",
|
|
||||||
"نجاح مشترك: 🗺️ كل توصيلة بتعملها بتكبر فيها شركتنا وانت كمان.",
|
|
||||||
|
|
||||||
// --- التحفيز والتشجيع ---
|
|
||||||
"صباح النشاط: ☀️ بلّش نهارك بطاقة إيجابية وانطلق لتكسب أكتر.",
|
|
||||||
"كل مشوار فرصة: 🏁 لا توقف، الطريق لإلك والنجاح ناطرك.",
|
|
||||||
"يوم مربح: 💼 السوق ناشط اليوم، لا تفوّت الفرصة!",
|
|
||||||
"جاهز للطلب الجاي: 🔔 الزبون الجاي ممكن يكون أوفر ممتاز، خليك مستعد.",
|
|
||||||
|
|
||||||
// --- نصايح ومعلومات ---
|
|
||||||
"نصيحة اليوم: روح صوب الأسواق والمطاعم، الطلب هناك عالي هالفترة. 🏙️",
|
|
||||||
"حافظ على تقييمك: 😊 الزبون بينجذب للكابتن يلي عنده تقييم عالي وابتسامة.",
|
|
||||||
"جو ممطر: 🌧️ المطر يعني طلبات أكتر، خليك شغّال بهالوقت!",
|
|
||||||
"حدث اليوم: 🎆 في فعالية بالبلد، المشاوير كتيرة بهالمنطقة، استغلها!"
|
|
||||||
];
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'colors.dart';
|
|
||||||
|
|
||||||
class AppStyle {
|
|
||||||
static TextStyle headTitle = TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 36,
|
|
||||||
color: AppColor.accentColor,
|
|
||||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
|
||||||
// ?GoogleFonts.markaziText().fontFamily
|
|
||||||
? GoogleFonts.markaziText().fontFamily
|
|
||||||
: GoogleFonts.inter().fontFamily);
|
|
||||||
static TextStyle headTitle2 = TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 24,
|
|
||||||
color: AppColor.writeColor,
|
|
||||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
|
||||||
? GoogleFonts.markaziText().fontFamily
|
|
||||||
: GoogleFonts.inter().fontFamily);
|
|
||||||
static TextStyle title = TextStyle(
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: 16,
|
|
||||||
color: AppColor.writeColor,
|
|
||||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
|
||||||
? GoogleFonts.markaziText().fontFamily
|
|
||||||
: GoogleFonts.inter().fontFamily);
|
|
||||||
static TextStyle subtitle = TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColor.writeColor,
|
|
||||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
|
||||||
? GoogleFonts.markaziText().fontFamily
|
|
||||||
: GoogleFonts.inter().fontFamily);
|
|
||||||
static TextStyle number = const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppColor.writeColor,
|
|
||||||
fontFamily: 'digit');
|
|
||||||
|
|
||||||
static BoxDecoration boxDecoration = const BoxDecoration(
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)),
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2))
|
|
||||||
],
|
|
||||||
color: AppColor.secondaryColor,
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.elliptical(15, 30),
|
|
||||||
));
|
|
||||||
static BoxDecoration boxDecoration1 = const BoxDecoration(
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromARGB(255, 237, 230, 230),
|
|
||||||
blurRadius: 5,
|
|
||||||
offset: Offset(2, 4)),
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromARGB(255, 242, 237, 237),
|
|
||||||
blurRadius: 5,
|
|
||||||
offset: Offset(-2, -2))
|
|
||||||
],
|
|
||||||
color: AppColor.secondaryColor,
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.elliptical(15, 30),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
class TableName {
|
|
||||||
static const String placesFavorite = "placesFavorite";
|
|
||||||
static const String recentLocations = "recentLocations";
|
|
||||||
static const String carLocations = "carLocations";
|
|
||||||
static const String driverOrdersRefuse = "driverOrdersRefuse";
|
|
||||||
static const String rideLocation = "rideLocation";
|
|
||||||
static const String faceDetectTimes = "faceDetectTimes";
|
|
||||||
static const String behavior = "behavior";
|
|
||||||
static const String captainNotification = "captainNotification";
|
|
||||||
static const String applyRideFromOverLay = "applyRideFromOverLay";
|
|
||||||
}
|
|
||||||
|
|
||||||
class Driver {
|
|
||||||
static const String driverPass = 'MG6DEJZSczBT6Rx0jOlehQ==';
|
|
||||||
static const String payMobApikey = 'payMobApikey';
|
|
||||||
static const String initializationVector = 'initializationVector';
|
|
||||||
static const String keyOfApp = 'keyOfApp';
|
|
||||||
static const String FCM_PRIVATE_KEY = 'FCM_PRIVATE_KEY';
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
|
||||||
|
|
||||||
class AuthController extends GetxController {
|
|
||||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
|
||||||
|
|
||||||
Future<User?> signInWithApple() async {
|
|
||||||
try {
|
|
||||||
final appleCredential = await SignInWithApple.getAppleIDCredential(
|
|
||||||
scopes: [
|
|
||||||
AppleIDAuthorizationScopes.email,
|
|
||||||
AppleIDAuthorizationScopes.fullName,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final oAuthProvider = OAuthProvider('apple.com');
|
|
||||||
final credential = oAuthProvider.credential(
|
|
||||||
idToken: appleCredential.identityToken,
|
|
||||||
accessToken: appleCredential.authorizationCode,
|
|
||||||
);
|
|
||||||
|
|
||||||
UserCredential userCredential =
|
|
||||||
await _auth.signInWithCredential(credential);
|
|
||||||
return userCredential.user;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void signOut() async {
|
|
||||||
await _auth.signOut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../views/home/Captin/history/history_details_page.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
import '../../functions/encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class HistoryCaptainController extends GetxController {
|
|
||||||
bool isloading = false;
|
|
||||||
Map historyData = {};
|
|
||||||
Map historyDetailsData = {};
|
|
||||||
late String orderID;
|
|
||||||
getOrderId(String orderId) {
|
|
||||||
orderID = orderId;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
getHistory() async {
|
|
||||||
isloading = true;
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.getDriverOrder,
|
|
||||||
payload: {'driver_id': box.read(BoxName.driverID)});
|
|
||||||
if (res != 'failure') {
|
|
||||||
historyData = jsonDecode(res);
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'No ride yet'.tr,
|
|
||||||
middleText: '',
|
|
||||||
barrierDismissible: false,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Back'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getHistoryDetails(String orderId) async {
|
|
||||||
isloading = true;
|
|
||||||
var res = await CRUD()
|
|
||||||
.get(link: AppLink.getRideOrderID, payload: {'id': (orderId)});
|
|
||||||
historyDetailsData = jsonDecode(res);
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
Get.to(() => HistoryDetailsPage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
getHistory();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_contacts/contact.dart';
|
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:local_auth/local_auth.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
|
|
||||||
import '../../firebase/local_notification.dart';
|
|
||||||
import '../../functions/launch.dart';
|
|
||||||
import '../../notification/notification_captain_controller.dart';
|
|
||||||
|
|
||||||
class InviteController extends GetxController {
|
|
||||||
final TextEditingController invitePhoneController = TextEditingController();
|
|
||||||
List driverInvitationData = [];
|
|
||||||
List driverInvitationDataToPassengers = [];
|
|
||||||
String? couponCode;
|
|
||||||
String? driverCouponCode;
|
|
||||||
|
|
||||||
// **FIX**: Added the missing 'contacts' and 'contactMaps' definitions.
|
|
||||||
List<Contact> contacts = [];
|
|
||||||
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
|
||||||
|
|
||||||
int selectedTab = 0;
|
|
||||||
PassengerStats passengerStats = PassengerStats();
|
|
||||||
void updateSelectedTab(int index) {
|
|
||||||
selectedTab = index;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> shareDriverCode() async {
|
|
||||||
if (driverCouponCode != null) {
|
|
||||||
final String shareText =
|
|
||||||
'''Join Intaleq as a driver using my referral code!
|
|
||||||
Use code: $driverCouponCode
|
|
||||||
Download the Intaleq Driver app now and earn rewards!
|
|
||||||
''';
|
|
||||||
await Share.share(shareText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sharePassengerCode() async {
|
|
||||||
if (couponCode != null) {
|
|
||||||
final String shareText = '''Get a discount on your first Intaleq ride!
|
|
||||||
Use my referral code: $couponCode
|
|
||||||
Download the Intaleq app now and enjoy your ride!
|
|
||||||
''';
|
|
||||||
await Share.share(shareText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
// **MODIFIED**: Sync contacts automatically on controller initialization.
|
|
||||||
syncContactsToServerOnce();
|
|
||||||
// fetchDriverStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- NEW LOGIC: ONE-TIME CONTACTS SYNC ---
|
|
||||||
|
|
||||||
/// **NEW**: Syncs all phone contacts to the server, but only runs once per user.
|
|
||||||
Future<void> syncContactsToServerOnce() async {
|
|
||||||
final String syncFlagKey = 'contactsSynced_${box.read(BoxName.driverID)}';
|
|
||||||
|
|
||||||
// 1. Check if contacts have already been synced for this user.
|
|
||||||
if (box.read(syncFlagKey) == true) {
|
|
||||||
print("Contacts have already been synced for this user.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 2. Request permission and fetch all contacts.
|
|
||||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
|
||||||
// mySnackbarSuccess('Starting contacts sync in background...'.tr);
|
|
||||||
final List<Contact> allContacts =
|
|
||||||
await FlutterContacts.getContacts(withProperties: true);
|
|
||||||
// **FIX**: Assign fetched contacts to the class variable.
|
|
||||||
contacts = allContacts;
|
|
||||||
contactMaps.value = contacts.map((contact) {
|
|
||||||
return {
|
|
||||||
'name': contact.displayName,
|
|
||||||
'phones':
|
|
||||||
contact.phones.map((phone) => phone.normalizedNumber).toList(),
|
|
||||||
'emails': contact.emails.map((email) => email.address).toList(),
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
update();
|
|
||||||
|
|
||||||
// 3. Loop through contacts and save them to the server.
|
|
||||||
for (var contact in allContacts) {
|
|
||||||
if (contact.phones.isNotEmpty) {
|
|
||||||
// Use the normalized phone number for consistency.
|
|
||||||
var phone = contact.phones.first.normalizedNumber;
|
|
||||||
if (phone.isNotEmpty) {
|
|
||||||
CRUD().post(link: AppLink.savePhonesSyria, payload: {
|
|
||||||
"driverId": box.read(BoxName.driverID), // Associate with driver
|
|
||||||
"name": contact.displayName ?? 'No Name',
|
|
||||||
"phone": phone,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. After a successful sync, set the flag to prevent future syncs.
|
|
||||||
await box.write(syncFlagKey, true);
|
|
||||||
// mySnackbarSuccess('Contacts sync completed successfully!'.tr);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// mySnackeBarError('An error occurred during contact sync: $e'.tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- NEW LOGIC: NATIVE CONTACT PICKER ---
|
|
||||||
|
|
||||||
/// **MODIFIED**: This function now opens the phone's native contact picker.
|
|
||||||
Future<void> pickContactFromNativeApp() async {
|
|
||||||
try {
|
|
||||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
|
||||||
// 1. Open the native contacts app to select a single contact.
|
|
||||||
final Contact? contact = await FlutterContacts.openExternalPick();
|
|
||||||
|
|
||||||
// 2. If a contact is selected and has a phone number...
|
|
||||||
if (contact != null && contact.phones.isNotEmpty) {
|
|
||||||
String selectedPhone = contact.phones.first.number;
|
|
||||||
|
|
||||||
// 3. Format the number and update the text field.
|
|
||||||
invitePhoneController.text = _formatSyrianPhoneNumber(selectedPhone);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('Contact permission is required to pick contacts'.tr);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// **FIX**: Added the missing 'selectPhone' method.
|
|
||||||
void selectPhone(String phone) {
|
|
||||||
// Format the selected phone number and update the text field.
|
|
||||||
invitePhoneController.text = _formatSyrianPhoneNumber(phone);
|
|
||||||
update();
|
|
||||||
Get.back(); // Close the contacts dialog after selection.
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Handle error gracefully
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchDriverStatsPassengers() async {
|
|
||||||
try {
|
|
||||||
var response = await CRUD()
|
|
||||||
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
|
|
||||||
"driverId": box.read(BoxName.driverID),
|
|
||||||
});
|
|
||||||
if (response != 'failure') {
|
|
||||||
var data = jsonDecode(response);
|
|
||||||
driverInvitationDataToPassengers = data['message'];
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Handle error gracefully
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSelectDriverInvitation(int index) async {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
int.parse((driverInvitationData[index]['countOfInvitDriver'])) < 100
|
|
||||||
? '${'When'.tr} ${(driverInvitationData[index]['invitorName'])} ${"complete, you can claim your gift".tr} '
|
|
||||||
: 'You deserve the gift'.tr,
|
|
||||||
'${(driverInvitationData[index]['invitorName'])} ${(driverInvitationData[index]['countOfInvitDriver'])} / 100 ${'Trip'.tr}',
|
|
||||||
() async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
if (int.parse((driverInvitationData[index]['countOfInvitDriver'])) <
|
|
||||||
100) {
|
|
||||||
Get.back();
|
|
||||||
} else if (isAvailable) {
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: const AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
if ((driverInvitationData[index]['isGiftToken']).toString() ==
|
|
||||||
'0') {
|
|
||||||
Get.back();
|
|
||||||
await CRUD().post(
|
|
||||||
link: AppLink.updateInviteDriver,
|
|
||||||
payload: {'id': (driverInvitationData[index]['id'])});
|
|
||||||
await Get.find<CaptainWalletController>().addDriverPayment(
|
|
||||||
'paymentMethod',
|
|
||||||
('50000'),
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
await Get.find<CaptainWalletController>()
|
|
||||||
.addDriverWalletToInvitor(
|
|
||||||
'paymentMethod',
|
|
||||||
(driverInvitationData[index]['driverInviterId']),
|
|
||||||
('50000'),
|
|
||||||
);
|
|
||||||
NotificationCaptainController().addNotificationCaptain(
|
|
||||||
driverInvitationData[index]['driverInviterId'].toString(),
|
|
||||||
"You have got a gift for invitation".tr,
|
|
||||||
'${"You have 50000".tr} ${'SYP'.tr}',
|
|
||||||
false);
|
|
||||||
NotificationController().showNotification(
|
|
||||||
"You have got a gift for invitation".tr,
|
|
||||||
'${"You have 50000".tr} ${'SYP'.tr}',
|
|
||||||
'tone1',
|
|
||||||
'');
|
|
||||||
} else {
|
|
||||||
Get.back();
|
|
||||||
MyDialog().getDialog("You have got a gift".tr,
|
|
||||||
"Share the app with another new driver".tr, () => Get.back());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog()
|
|
||||||
.getDialog('Authentication failed'.tr, '', () => Get.back());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
'Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr,
|
|
||||||
() => Get.back());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSelectPassengerInvitation(int index) async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
MyDialog().getDialog(
|
|
||||||
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver']
|
|
||||||
.toString()) <
|
|
||||||
3
|
|
||||||
? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} '
|
|
||||||
: 'You deserve the gift'.tr,
|
|
||||||
'${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver']} / 3 ${'Trip'.tr}',
|
|
||||||
() async {
|
|
||||||
if (int.parse(driverInvitationDataToPassengers[index]
|
|
||||||
['countOfInvitDriver']
|
|
||||||
.toString()) <
|
|
||||||
3) {
|
|
||||||
Get.back();
|
|
||||||
} else if (isAvailable) {
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: const AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
if (driverInvitationDataToPassengers[index]['isGiftToken']
|
|
||||||
.toString() ==
|
|
||||||
'0') {
|
|
||||||
Get.back();
|
|
||||||
await Get.find<CaptainWalletController>()
|
|
||||||
.addDriverWallet('paymentMethod', '20000', '20000');
|
|
||||||
await Get.find<CaptainWalletController>()
|
|
||||||
.addDriverWalletToInvitor('paymentMethod',
|
|
||||||
driverInvitationData[index]['driverInviterId'], '20000');
|
|
||||||
await CRUD().post(
|
|
||||||
link: AppLink.updatePassengerGift,
|
|
||||||
payload: {'id': driverInvitationDataToPassengers[index]['id']},
|
|
||||||
);
|
|
||||||
NotificationCaptainController().addNotificationCaptain(
|
|
||||||
driverInvitationDataToPassengers[index]['passengerInviterId']
|
|
||||||
.toString(),
|
|
||||||
"You have got a gift for invitation".tr,
|
|
||||||
'${"You have 20000".tr} ${'SYP'.tr}',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Get.back();
|
|
||||||
MyDialog().getDialog(
|
|
||||||
"You have got a gift".tr,
|
|
||||||
"Share the app with another new passenger".tr,
|
|
||||||
() => Get.back(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog()
|
|
||||||
.getDialog('Authentication failed'.tr, '', () => Get.back());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
'Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr,
|
|
||||||
() => Get.back());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Formats a phone number to the standard Syrian international format (+963...).
|
|
||||||
String _formatSyrianPhoneNumber(String input) {
|
|
||||||
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
|
|
||||||
|
|
||||||
if (digitsOnly.startsWith('09') && digitsOnly.length == 10) {
|
|
||||||
return '963${digitsOnly.substring(1)}';
|
|
||||||
}
|
|
||||||
if (digitsOnly.length == 9 && digitsOnly.startsWith('9')) {
|
|
||||||
return '963$digitsOnly';
|
|
||||||
}
|
|
||||||
return input; // Fallback for unrecognized formats
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalizeSyrianPhone(String input) {
|
|
||||||
String phone = input.trim();
|
|
||||||
|
|
||||||
// احذف كل شيء غير أرقام
|
|
||||||
phone = phone.replaceAll(RegExp(r'[^0-9]'), '');
|
|
||||||
|
|
||||||
// إذا يبدأ بـ 0 → احذفها
|
|
||||||
if (phone.startsWith('0')) {
|
|
||||||
phone = phone.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذا يبدأ بـ 963 مكررة → احذف التكرار
|
|
||||||
while (phone.startsWith('963963')) {
|
|
||||||
phone = phone.substring(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذا يبدأ بـ 963 ولكن داخله كمان 963 → خليه مرة واحدة فقط
|
|
||||||
if (phone.startsWith('963') && phone.length > 12) {
|
|
||||||
phone = phone.substring(phone.length - 9); // آخر 9 أرقام
|
|
||||||
}
|
|
||||||
|
|
||||||
// الآن إذا كان بلا 963 → أضفها
|
|
||||||
if (!phone.startsWith('963')) {
|
|
||||||
phone = '963' + phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends an invitation to a potential new driver.
|
|
||||||
void sendInvite() async {
|
|
||||||
if (invitePhoneController.text.isEmpty) {
|
|
||||||
mySnackeBarError('Please enter a phone number'.tr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Format Syrian phone number: remove leading 0 and add +963
|
|
||||||
String formattedPhoneNumber =
|
|
||||||
normalizeSyrianPhone(invitePhoneController.text);
|
|
||||||
if (formattedPhoneNumber.length != 12) {
|
|
||||||
mySnackeBarError('Please enter a correct phone'.tr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await CRUD().post(link: AppLink.addInviteDriver, payload: {
|
|
||||||
"driverId": box.read(BoxName.driverID),
|
|
||||||
"inviterDriverPhone": formattedPhoneNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
var d = (response);
|
|
||||||
mySnackbarSuccess('Invite sent successfully'.tr);
|
|
||||||
String message = '${'*Intaleq DRIVER CODE*'.tr}\n\n'
|
|
||||||
'${"Use this code in registration".tr}\n'
|
|
||||||
'${"To get a gift for both".tr}\n\n'
|
|
||||||
'${"The period of this code is 1 hour".tr}\n\n'
|
|
||||||
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
|
|
||||||
'_*${d['message']['inviteCode'].toString()}*_\n\n'
|
|
||||||
'${"Install our app:".tr}\n'
|
|
||||||
'*Android:* https://play.google.com/store/apps/details?id=com.intaleq_driver \n\n\n'
|
|
||||||
'*iOS:* https://apps.apple.com/st/app/intaleq-driver/id6482995159';
|
|
||||||
|
|
||||||
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
|
||||||
invitePhoneController.clear();
|
|
||||||
} else {
|
|
||||||
mySnackeBarError("Invite code already used".tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends an invitation to a potential new passenger.
|
|
||||||
void sendInviteToPassenger() async {
|
|
||||||
if (invitePhoneController.text.isEmpty) {
|
|
||||||
mySnackeBarError('Please enter a phone number'.tr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format Syrian phone number: remove leading 0 and add +963
|
|
||||||
String formattedPhoneNumber = invitePhoneController.text.trim();
|
|
||||||
if (formattedPhoneNumber.startsWith('0')) {
|
|
||||||
formattedPhoneNumber = formattedPhoneNumber.substring(1);
|
|
||||||
}
|
|
||||||
formattedPhoneNumber = '+963$formattedPhoneNumber';
|
|
||||||
|
|
||||||
if (formattedPhoneNumber.length < 12) {
|
|
||||||
// +963 + 9 digits = 12+
|
|
||||||
mySnackeBarError('Please enter a correct phone'.tr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await CRUD().post(
|
|
||||||
link: AppLink.addInvitationPassenger,
|
|
||||||
payload: {
|
|
||||||
"driverId": box.read(BoxName.driverID),
|
|
||||||
"inviterPassengerPhone": formattedPhoneNumber,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
var d = response;
|
|
||||||
mySnackbarSuccess('Invite sent successfully'.tr);
|
|
||||||
|
|
||||||
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
|
|
||||||
'${"Use this code in registration".tr}\n\n'
|
|
||||||
'${"To get a gift for both".tr}\n\n'
|
|
||||||
'${"The period of this code is 1 hour".tr}\n\n'
|
|
||||||
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
|
|
||||||
'_*${d['message']['inviteCode'].toString()}*_\n\n'
|
|
||||||
'${"Install our app:".tr}\n'
|
|
||||||
'*Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n\n\n'
|
|
||||||
'*iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179';
|
|
||||||
|
|
||||||
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
|
||||||
invitePhoneController.clear();
|
|
||||||
} else {
|
|
||||||
mySnackeBarError("Invite code already used".tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PassengerStats {
|
|
||||||
final int totalInvites;
|
|
||||||
final int activeUsers;
|
|
||||||
final double totalEarnings;
|
|
||||||
|
|
||||||
PassengerStats({
|
|
||||||
this.totalInvites = 0,
|
|
||||||
this.activeUsers = 0,
|
|
||||||
this.totalEarnings = 0.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,787 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
|
||||||
import 'package:location/location.dart';
|
|
||||||
|
|
||||||
import '../../../constant/api_key.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../../views/auth/captin/otp_page.dart';
|
|
||||||
import '../../../views/auth/captin/otp_token_page.dart';
|
|
||||||
import '../../../views/auth/syria/pending_driver_page.dart';
|
|
||||||
import '../../firebase/local_notification.dart';
|
|
||||||
import '../../firebase/notification_service.dart';
|
|
||||||
import '../../functions/encrypt_decrypt.dart';
|
|
||||||
import '../../functions/package_info.dart';
|
|
||||||
import '../../functions/security_checks.dart';
|
|
||||||
|
|
||||||
class LoginDriverController extends GetxController {
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
TextEditingController emailController = TextEditingController();
|
|
||||||
TextEditingController phoneController = TextEditingController();
|
|
||||||
TextEditingController passwordController = TextEditingController();
|
|
||||||
TextEditingController passwordController2 = TextEditingController();
|
|
||||||
bool isAgreeTerms = false;
|
|
||||||
bool isGoogleDashOpen = false;
|
|
||||||
bool isGoogleLogin = false;
|
|
||||||
bool isloading = false;
|
|
||||||
late int isTest = 1;
|
|
||||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
|
||||||
final location = Location();
|
|
||||||
void changeAgreeTerm() {
|
|
||||||
isAgreeTerms = !isAgreeTerms;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPasswordHidden = true;
|
|
||||||
|
|
||||||
void togglePasswordVisibility() {
|
|
||||||
isPasswordHidden = !isPasswordHidden;
|
|
||||||
update([
|
|
||||||
'passwordVisibility'
|
|
||||||
]); // Use a unique ID to only update the password field
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeGoogleDashOpen() {
|
|
||||||
isGoogleDashOpen = !isGoogleDashOpen;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() async {
|
|
||||||
box.write(BoxName.countryCode, 'Egypt');
|
|
||||||
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
|
|
||||||
// await getAppTester();
|
|
||||||
getJWT();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAppTester() async {
|
|
||||||
// var res = await CRUD().get(
|
|
||||||
// link: AppLink.getTesterApp,
|
|
||||||
// payload: {'appPlatform': AppInformation.appName});
|
|
||||||
// // Log.print('res: ${res}');
|
|
||||||
// if (res != 'failure') {
|
|
||||||
// var d = jsonDecode(res);
|
|
||||||
// isTest = d['message'][0]['isTest'];
|
|
||||||
// // Log.print('isTest: ${isTest}');
|
|
||||||
// box.write(BoxName.isTest, isTest);
|
|
||||||
|
|
||||||
// // Log.print('isTest: ${box.read(BoxName.isTest)}');
|
|
||||||
// update();
|
|
||||||
// } else {
|
|
||||||
// isTest = 0;
|
|
||||||
// box.write(BoxName.isTest, isTest);
|
|
||||||
// update();
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateAppTester(String appPlatform) async {
|
|
||||||
await CRUD().post(
|
|
||||||
link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
|
|
||||||
}
|
|
||||||
|
|
||||||
isPhoneVerified() async {
|
|
||||||
// 1. أولاً نتأكد هل يوجد رقم هاتف محفوظ محلياً (تجاوز الـ OTP)
|
|
||||||
String? savedPhone = box.read(BoxName.phoneDriver);
|
|
||||||
Log.print('savedPhone: ${savedPhone}');
|
|
||||||
|
|
||||||
if (savedPhone == null || savedPhone.isEmpty) {
|
|
||||||
// لا يوجد رقم محفوظ، نعيد المستخدم لصفحة إدخال الرقم
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: AppLink.isPhoneVerified, payload: {'phone_number': savedPhone});
|
|
||||||
|
|
||||||
Log.print('isPhoneVerified Res: $res');
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
// الحالة 1: السائق موجود ومفعل -> نذهب للصفحة التالية
|
|
||||||
Get.offAll(() => EgyptCardAI());
|
|
||||||
} else {
|
|
||||||
// الحالة 2: الرد فشل (السائق غير موجود في الداتا بيز)
|
|
||||||
if (savedPhone != null && savedPhone.isNotEmpty) {
|
|
||||||
// بما أن الرقم محفوظ، فهذا مستخدم جديد أكمل التحقق
|
|
||||||
// نوجهه لصفحة التسجيل/البطاقة بدلاً من العودة للبداية
|
|
||||||
Log.print('New User Detected, redirecting to Registration...');
|
|
||||||
Get.offAll(() => EgyptCardAI());
|
|
||||||
} else {
|
|
||||||
// لا يوجد رقم محفوظ ولا مستخدم في السيرفر -> عودة للبداية
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// isPhoneVerified() async {
|
|
||||||
// var res = await CRUD().post(
|
|
||||||
// link: AppLink.isPhoneVerified,
|
|
||||||
// payload: {'phone_number': box.read(BoxName.phoneDriver)});
|
|
||||||
// Log.print('isPhoneVerified: ${res}');
|
|
||||||
|
|
||||||
// if (res != 'failure') {
|
|
||||||
// // Get.offAll(() => SyrianCardAI());
|
|
||||||
// Get.offAll(() => EgyptCardAI());
|
|
||||||
|
|
||||||
// // isloading = false;
|
|
||||||
// // update();
|
|
||||||
// } else {
|
|
||||||
// Get.offAll(() => PhoneNumberScreen());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
void saveAgreementTerms() {
|
|
||||||
box.write(BoxName.agreeTerms, 'agreed');
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveCountryCode(String countryCode) {
|
|
||||||
box.write(BoxName.countryCode, countryCode);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
var dev = '';
|
|
||||||
getJwtWallet() async {
|
|
||||||
if (box.read(BoxName.security_check).toString() != 'passed') {
|
|
||||||
Log.print('Security check failed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.print('Security check passed');
|
|
||||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
|
||||||
// Log.print('fingerPrint: ${fingerPrint}');
|
|
||||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
|
||||||
var payload = {
|
|
||||||
'id': box.read(BoxName.driverID),
|
|
||||||
'password': AK.passnpassenger,
|
|
||||||
'aud': '${AK.allowedWallet}$dev',
|
|
||||||
'fingerPrint': fingerPrint
|
|
||||||
};
|
|
||||||
var response1 = await http.post(
|
|
||||||
Uri.parse(AppLink.loginJwtWalletDriver),
|
|
||||||
body: payload,
|
|
||||||
);
|
|
||||||
// Log.print('response.request: ${response1.request}');
|
|
||||||
// Log.print('response.JwtWallet: ${response1.body}');
|
|
||||||
// print(payload);
|
|
||||||
// Log.print('payment["jwt"]: ${jsonDecode(response1.body)['jwt']}');
|
|
||||||
await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']);
|
|
||||||
return jsonDecode(response1.body)['jwt'].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String shortHash(String password) {
|
|
||||||
var bytes = utf8.encode(password);
|
|
||||||
var digest = sha256.convert(bytes);
|
|
||||||
return base64UrlEncode(digest.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
getJWT() async {
|
|
||||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
|
||||||
Log.print(
|
|
||||||
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
|
|
||||||
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
|
|
||||||
var payload = {
|
|
||||||
'id': box.read(BoxName.driverID) ?? AK.newId,
|
|
||||||
'password': AK.passnpassenger,
|
|
||||||
'aud': '${AK.allowed}$dev',
|
|
||||||
};
|
|
||||||
// Log.print('payload: ${payload}');
|
|
||||||
|
|
||||||
var response0 = await http.post(
|
|
||||||
Uri.parse(AppLink.loginFirstTimeDriver),
|
|
||||||
body: payload,
|
|
||||||
);
|
|
||||||
// Log.print('response0: ${response0.body}');
|
|
||||||
Log.print('request: ${response0.request}');
|
|
||||||
if (response0.statusCode == 200) {
|
|
||||||
final decodedResponse1 = jsonDecode(response0.body);
|
|
||||||
// Log.print('decodedResponse1: ${decodedResponse1}');
|
|
||||||
|
|
||||||
final jwt = decodedResponse1['jwt'];
|
|
||||||
box.write(BoxName.jwt, c(jwt));
|
|
||||||
|
|
||||||
// ✅ بعد التأكد أن كل المفاتيح موجودة
|
|
||||||
await EncryptionHelper.initialize();
|
|
||||||
|
|
||||||
// await AppInitializer().getKey();
|
|
||||||
} else {}
|
|
||||||
} else {
|
|
||||||
await EncryptionHelper.initialize();
|
|
||||||
|
|
||||||
var payload = {
|
|
||||||
'id': box.read(BoxName.driverID),
|
|
||||||
'password': box.read(BoxName.emailDriver),
|
|
||||||
'aud': '${AK.allowed}$dev',
|
|
||||||
};
|
|
||||||
// print(payload);
|
|
||||||
var response1 = await http.post(
|
|
||||||
Uri.parse(AppLink.loginJwtDriver),
|
|
||||||
body: payload,
|
|
||||||
);
|
|
||||||
// print(response1.request);
|
|
||||||
// print(response1.body);
|
|
||||||
|
|
||||||
if (response1.statusCode == 200) {
|
|
||||||
final decodedResponse1 = jsonDecode(response1.body);
|
|
||||||
// Log.print('decodedResponse1: ${decodedResponse1}');
|
|
||||||
|
|
||||||
final jwt = decodedResponse1['jwt'];
|
|
||||||
await box.write(BoxName.jwt, c(jwt));
|
|
||||||
|
|
||||||
// await AppInitializer().getKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getLocationPermission() async {
|
|
||||||
var status = await Permission.locationAlways.status;
|
|
||||||
if (!status.isGranted) {
|
|
||||||
await Permission.locationAlways.request();
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateUniqueIdFromEmail(String email) {
|
|
||||||
// Step 1: Extract the local part of the email
|
|
||||||
String localPart = email.split('@')[0];
|
|
||||||
|
|
||||||
// Step 2: Replace invalid characters (if any)
|
|
||||||
String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
|
|
||||||
|
|
||||||
// Step 3: Ensure it does not exceed 24 characters
|
|
||||||
if (cleanLocalPart.length > 24) {
|
|
||||||
cleanLocalPart = cleanLocalPart.substring(0, 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Generate a random suffix if needed
|
|
||||||
String suffix = generateRandomSuffix(24 - cleanLocalPart.length);
|
|
||||||
|
|
||||||
return cleanLocalPart + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateRandomSuffix(int length) {
|
|
||||||
const String chars =
|
|
||||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
Random random = Random();
|
|
||||||
return List.generate(length, (index) => chars[random.nextInt(chars.length)])
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInviteDriverFound = false;
|
|
||||||
|
|
||||||
Future updateInvitationCodeFromRegister() async {
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: AppLink.updateDriverInvitationDirectly,
|
|
||||||
payload: {
|
|
||||||
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Log.print('invite: ${res}');
|
|
||||||
box.write(BoxName.isInstall, '1');
|
|
||||||
// ✅ تصحيح 1: التحقق من أن الرد ليس 'failure' وأنه Map قبل القراءة
|
|
||||||
if (res != 'failure' && res is Map && res['status'] == 'success') {
|
|
||||||
isInviteDriverFound = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
NotificationController().showNotification(
|
|
||||||
"Code approved".tr, "Code approved".tr, 'tone2', '');
|
|
||||||
|
|
||||||
// ✅ تصحيح 2: استخراج التوكن حسب هيكل PHP الجديد (مفتاح 'data')
|
|
||||||
String? targetToken;
|
|
||||||
|
|
||||||
// الهيكل الجديد: {status: success, data: {token: ...}}
|
|
||||||
if (res['data'] != null && res['data']['token'] != null) {
|
|
||||||
targetToken = res['data']['token'].toString();
|
|
||||||
}
|
|
||||||
// دعم الهيكل القديم للاحتياط: {status: success, message: [{token: ...}]}
|
|
||||||
else if (res['message'] is List && res['message'].isNotEmpty) {
|
|
||||||
targetToken = res['message'][0]['token'].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetToken != null) {
|
|
||||||
NotificationService.sendNotification(
|
|
||||||
target: targetToken,
|
|
||||||
title: 'You have received a gift token!'.tr,
|
|
||||||
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
|
|
||||||
isTopic: false,
|
|
||||||
tone: 'tone2',
|
|
||||||
driverList: [],
|
|
||||||
category: 'You have received a gift token!',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// فشل التحديث (الكود غير موجود أو خطأ سيرفر)
|
|
||||||
// لا تفعل شيئاً أو اعرض رسالة خطأ صامتة
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loginWithGoogleCredential(String driverID, String email) async {
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. تجهيز البيانات الإضافية للأمان
|
|
||||||
String platformName = Platform.isIOS ? 'ios' : 'android';
|
|
||||||
String storedPhone = box.read(BoxName.phoneDriver)?.toString() ?? '';
|
|
||||||
|
|
||||||
// 2. إرسال الطلب الموحد (Login + Token + PackageInfo)
|
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
|
||||||
'id': driverID,
|
|
||||||
'email': email, // ضروري للمطابقة الأمنية في السيرفر
|
|
||||||
'phone': storedPhone, // اختياري، لزيادة الموثوقية
|
|
||||||
'platform': platformName // لجلب معلومات التحديث المناسبة للجهاز
|
|
||||||
});
|
|
||||||
|
|
||||||
Log.print('loginWithGoogleCredential Response: $res');
|
|
||||||
|
|
||||||
if (res == 'failure') {
|
|
||||||
// فشل الدخول (البيانات غير موجودة أو غير مطابقة)
|
|
||||||
// نحاول التحقق من حالة الهاتف لتوجيهه للتسجيل إذا كان جديداً
|
|
||||||
await isPhoneVerified();
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
var jsonResponse = jsonDecode(res);
|
|
||||||
|
|
||||||
if (jsonResponse['status'] == 'success') {
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// A. معالجة بيانات السائق الأساسية
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
var d = jsonResponse['data'][0];
|
|
||||||
|
|
||||||
if (d['is_verified'].toString() == '1') {
|
|
||||||
// حفظ البيانات في التخزين المحلي
|
|
||||||
box.write(BoxName.emailDriver, d['email']);
|
|
||||||
box.write(BoxName.firstTimeLoadKey, 'false');
|
|
||||||
box.write(BoxName.driverID, d['id']);
|
|
||||||
box.write(BoxName.isTest, '1'); // أو حسب القيمة القادمة
|
|
||||||
box.write(BoxName.gender, d['gender']);
|
|
||||||
box.write(BoxName.phoneVerified, d['is_verified'].toString());
|
|
||||||
box.write(BoxName.phoneDriver, d['phone']);
|
|
||||||
box.write(BoxName.is_claimed, d['is_claimed']);
|
|
||||||
if (box.read(BoxName.isInstall).toString() == 'null' ||
|
|
||||||
box.read(BoxName.isInstall).toString() == '') {
|
|
||||||
box.write(BoxName.isInstall, d['isInstall']);
|
|
||||||
}
|
|
||||||
Log.print(
|
|
||||||
'isInstall for update invitation: ${box.read(BoxName.isInstall)}');
|
|
||||||
|
|
||||||
// box.write(BoxName.isInstall, d['isInstall']);
|
|
||||||
box.write(BoxName.nameArabic,
|
|
||||||
d['name_arabic']); // تأكد أن هذا الحقل موجود في الرد أو عدله
|
|
||||||
box.write(BoxName.bankCodeDriver, d['bankCode']);
|
|
||||||
box.write(BoxName.accountBankNumberDriver, d['accountBank']);
|
|
||||||
box.write(
|
|
||||||
BoxName.nameDriver, '${d['first_name']} ${d['last_name']}');
|
|
||||||
int categoryId =
|
|
||||||
int.tryParse(d['vehicle_category_id']?.toString() ?? '0') ?? 0;
|
|
||||||
|
|
||||||
// معالجة سنة السيارة بشكل آمن لتجنب الكراش
|
|
||||||
int carYear = int.tryParse(d['year']?.toString() ?? '0') ?? 0;
|
|
||||||
box.write(BoxName.carYear, carYear);
|
|
||||||
|
|
||||||
// منطق تحديد نوع السيارة
|
|
||||||
|
|
||||||
String gender = d['gender']?.toString() ?? '';
|
|
||||||
|
|
||||||
if (categoryId == 2) {
|
|
||||||
// 2 = دراجة / سكوتر (حسب قاعدة البيانات)
|
|
||||||
if (gender == 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
|
||||||
}
|
|
||||||
} else if (categoryId == 3) {
|
|
||||||
// 3 = فان (إذا كان لديكم فئة فان)
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Van');
|
|
||||||
} else if (carYear > 2017) {
|
|
||||||
if (gender != 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
|
||||||
}
|
|
||||||
} else if (carYear > 2002 && carYear <= 2017) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
|
||||||
} else if (carYear <= 2002 && carYear > 0) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// B. معالجة التحديثات (Package Info) من نفس الرد
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// if (jsonResponse['package_info'] != null &&
|
|
||||||
// (jsonResponse['package_info'] as List).isNotEmpty) {
|
|
||||||
// var pkg = jsonResponse['package_info'][0];
|
|
||||||
// // استدعاء دالة فحص التحديث (تأكد من وجودها أو مرر البيانات لها)
|
|
||||||
// // checkUpdateFromData(pkg);
|
|
||||||
// checkForUpdate(Get.context!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// C. معالجة كود الدعوة
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (box.read(BoxName.isInstall) == null ||
|
|
||||||
box.read(BoxName.isInstall).toString() == '0') {
|
|
||||||
updateInvitationCodeFromRegister();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// D. التحقق الأمني (Device Fingerprint & Token)
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (d['status'].toString() != 'yet') {
|
|
||||||
// استخراج التوكن من الرد المجمع (دون طلب جديد)
|
|
||||||
List? tokenList = jsonResponse['driver_token'];
|
|
||||||
|
|
||||||
// توليد بصمة الجهاز الحالية
|
|
||||||
String currentFingerPrint =
|
|
||||||
await DeviceHelper.getDeviceFingerprint();
|
|
||||||
await storage.write(
|
|
||||||
key: BoxName.fingerPrint, value: currentFingerPrint);
|
|
||||||
|
|
||||||
if (tokenList != null && tokenList.isNotEmpty) {
|
|
||||||
var tokenData = tokenList[0];
|
|
||||||
String serverToken = tokenData['token'].toString();
|
|
||||||
String serverFingerPrint = tokenData['fingerPrint'].toString();
|
|
||||||
String localToken =
|
|
||||||
box.read(BoxName.tokenDriver)?.toString() ?? "";
|
|
||||||
|
|
||||||
// التحقق من تطابق التوكن والبصمة
|
|
||||||
if (serverToken != localToken ||
|
|
||||||
serverFingerPrint != currentFingerPrint) {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
|
|
||||||
await Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'Device Change Detected'.tr,
|
|
||||||
middleText: 'Please verify your identity'.tr,
|
|
||||||
textConfirm: 'Verify'.tr,
|
|
||||||
confirmTextColor: Colors.white,
|
|
||||||
onConfirm: () {
|
|
||||||
Get.back(); // إغلاق الديالوج
|
|
||||||
// الانتقال لصفحة التحقق مع تمرير البيانات اللازمة
|
|
||||||
Get.to(() => OtpVerificationPage(
|
|
||||||
phone: d['phone'].toString(),
|
|
||||||
deviceToken: currentFingerPrint,
|
|
||||||
// نمرر التوكن القادم من السيرفر ليستعمل في التحقق لاحقاً
|
|
||||||
token: jsonEncode(
|
|
||||||
{'status': 'success', 'data': tokenList}),
|
|
||||||
ptoken: serverToken,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true; // إيقاف التنفيذ هنا (بانتظار التحقق)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذا كل شيء سليم، الدخول للرئيسية
|
|
||||||
Get.offAll(() => HomeCaptain());
|
|
||||||
} else {
|
|
||||||
// الحالة "yet" تعني أن السائق لم يكمل رفع الوثائق
|
|
||||||
Get.offAll(() => DriverVerificationScreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// الحساب موجود لكن رقم الهاتف غير مفعل
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// الرد من السيرفر ليس success (رغم أنه ليس failure في الاتصال)
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("Login Critical Error: $e");
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logintest(String driverID, email) async {
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
// await SecurityHelper.performSecurityChecks();
|
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
|
||||||
|
|
||||||
var res = await CRUD()
|
|
||||||
.get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
|
|
||||||
'email': email ?? 'yet',
|
|
||||||
'id': driverID,
|
|
||||||
});
|
|
||||||
|
|
||||||
// print('res is $res');
|
|
||||||
// if (res == 'failure') {
|
|
||||||
// await isPhoneVerified();
|
|
||||||
// // Get.snackbar('Failure', '', backgroundColor: Colors.red);
|
|
||||||
// } else
|
|
||||||
// {
|
|
||||||
var jsonDecoeded = jsonDecode(res);
|
|
||||||
var d = jsonDecoeded['data'][0];
|
|
||||||
if (jsonDecoeded.isNotEmpty) {
|
|
||||||
if (jsonDecoeded['status'] == 'success')
|
|
||||||
// &&
|
|
||||||
// d['is_verified'].toString() == '1')
|
|
||||||
{
|
|
||||||
box.write(BoxName.emailDriver, d['email']);
|
|
||||||
box.write(BoxName.firstTimeLoadKey, 'false');
|
|
||||||
box.write(BoxName.driverID, (d['id']));
|
|
||||||
box.write(BoxName.isTest, '1');
|
|
||||||
box.write(BoxName.gender, (d['gender']));
|
|
||||||
box.write(BoxName.phoneVerified, d['is_verified'].toString());
|
|
||||||
box.write(BoxName.phoneDriver, (d['phone']));
|
|
||||||
box.write(BoxName.is_claimed, d['is_claimed']);
|
|
||||||
box.write(BoxName.isInstall, d['isInstall']);
|
|
||||||
// box.write(
|
|
||||||
// BoxName.isGiftToken, d['isGiftToken']);
|
|
||||||
box.write(BoxName.nameArabic, (d['name_arabic']));
|
|
||||||
box.write(BoxName.carYear, d['year']);
|
|
||||||
box.write(BoxName.bankCodeDriver, (d['bankCode']));
|
|
||||||
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
|
|
||||||
box.write(
|
|
||||||
BoxName.nameDriver,
|
|
||||||
'${(d['first_name'])}'
|
|
||||||
' ${(d['last_name'])}');
|
|
||||||
if (((d['model']).toString().contains('دراجه') ||
|
|
||||||
d['make'].toString().contains('دراجه '))) {
|
|
||||||
if ((d['gender']).toString() == 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
|
||||||
}
|
|
||||||
} else if (int.parse(d['year'].toString()) > 2017) {
|
|
||||||
if (d['gender'].toString() != 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
|
||||||
}
|
|
||||||
} else if (int.parse(d['year'].toString()) > 2002 &&
|
|
||||||
int.parse(d['year'].toString()) < 2017) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
|
||||||
} else if (int.parse(d['year'].toString()) < 2002) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
|
||||||
}
|
|
||||||
// updateAppTester(AppInformation.appName);
|
|
||||||
|
|
||||||
// var token = await CRUD().get(
|
|
||||||
// link: AppLink.getDriverToken,
|
|
||||||
// payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
|
|
||||||
|
|
||||||
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
|
||||||
// await storage.write(
|
|
||||||
// key: BoxName.fingerPrint, value: fingerPrint.toString());
|
|
||||||
|
|
||||||
Get.off(() => HomeCaptain());
|
|
||||||
// } else {
|
|
||||||
// Get.offAll(() => PhoneNumberScreen());
|
|
||||||
|
|
||||||
// isloading = false;
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// mySnackbarSuccess('');
|
|
||||||
|
|
||||||
// isloading = false;
|
|
||||||
// update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loginUsingCredentialsWithoutGoogle(
|
|
||||||
String password, String email, String phone) async {
|
|
||||||
isloading = true;
|
|
||||||
// تحديد نوع العملية (ليس جوجل)
|
|
||||||
isGoogleLogin = false;
|
|
||||||
update();
|
|
||||||
|
|
||||||
try {
|
|
||||||
String platformName = Platform.isIOS ? 'ios' : 'android';
|
|
||||||
|
|
||||||
// 1. إرسال الطلب (إضافة Platform)
|
|
||||||
var res = await CRUD()
|
|
||||||
.get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
|
|
||||||
'email': email,
|
|
||||||
'password': password,
|
|
||||||
'phone': phone, // <-- يجب إضافة هذا السطر
|
|
||||||
'platform': platformName
|
|
||||||
});
|
|
||||||
|
|
||||||
// حفظ الإيميل لمحاولات الدخول اللاحقة
|
|
||||||
box.write(BoxName.emailDriver, email);
|
|
||||||
|
|
||||||
if (res == 'failure') {
|
|
||||||
// في حال الفشل (كلمة مرور خطأ أو مستخدم غير موجود)
|
|
||||||
// نقوم بفحص حالة الهاتف إذا كان مسجلاً من قبل لتوجيهه
|
|
||||||
await isPhoneVerified();
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
var jsonResponse = jsonDecode(res);
|
|
||||||
|
|
||||||
if (jsonResponse['status'] == 'success' &&
|
|
||||||
jsonResponse['data'].isNotEmpty) {
|
|
||||||
var d = jsonResponse['data'][0];
|
|
||||||
|
|
||||||
// 2. التحقق من تفعيل الهاتف
|
|
||||||
if (d['is_verified'].toString() == '1') {
|
|
||||||
// --- A. حفظ البيانات الأساسية ---
|
|
||||||
box.write(BoxName.emailDriver, d['email']);
|
|
||||||
box.write(BoxName.firstTimeLoadKey, 'false');
|
|
||||||
box.write(BoxName.driverID, d['id']);
|
|
||||||
box.write(BoxName.isTest, '1');
|
|
||||||
box.write(BoxName.gender, d['gender']);
|
|
||||||
box.write(BoxName.phoneVerified, d['is_verified'].toString());
|
|
||||||
box.write(BoxName.phoneDriver, d['phone']);
|
|
||||||
box.write(BoxName.is_claimed, d['is_claimed']);
|
|
||||||
box.write(BoxName.isInstall, d['isInstall']);
|
|
||||||
box.write(BoxName.nameArabic, d['name_arabic']);
|
|
||||||
box.write(BoxName.bankCodeDriver, d['bankCode']);
|
|
||||||
box.write(BoxName.accountBankNumberDriver, d['accountBank']);
|
|
||||||
box.write(
|
|
||||||
BoxName.nameDriver, '${d['first_name']} ${d['last_name']}');
|
|
||||||
|
|
||||||
// --- B. منطق نوع السيارة ---
|
|
||||||
int categoryId =
|
|
||||||
int.tryParse(d['vehicle_category_id']?.toString() ?? '0') ?? 0;
|
|
||||||
int carYear = int.tryParse(d['year']?.toString() ?? '0') ?? 0;
|
|
||||||
box.write(BoxName.carYear, carYear);
|
|
||||||
String gender = d['gender']?.toString() ?? '';
|
|
||||||
|
|
||||||
if (categoryId == 2) {
|
|
||||||
if (gender == 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
|
||||||
}
|
|
||||||
} else if (categoryId == 3) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Van');
|
|
||||||
} else if (carYear > 2017) {
|
|
||||||
if (gender != 'Male') {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
|
||||||
}
|
|
||||||
} else if (carYear > 2002 && carYear <= 2017) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
|
||||||
} else if (carYear <= 2002 && carYear > 0) {
|
|
||||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- C. كود الدعوة ---
|
|
||||||
if (box.read(BoxName.isInstall) == null ||
|
|
||||||
box.read(BoxName.isInstall).toString() == '0') {
|
|
||||||
updateInvitationCodeFromRegister();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- D. التحقق الأمني (Device Fingerprint) ---
|
|
||||||
if (d['status'].toString() != 'yet') {
|
|
||||||
// الـ PHP المعدل يعيد driver_token داخل object السائق
|
|
||||||
List? tokenList = d['driver_token'];
|
|
||||||
|
|
||||||
String currentFingerPrint =
|
|
||||||
await DeviceHelper.getDeviceFingerprint();
|
|
||||||
await storage.write(
|
|
||||||
key: BoxName.fingerPrint, value: currentFingerPrint);
|
|
||||||
|
|
||||||
if (tokenList != null && tokenList.isNotEmpty) {
|
|
||||||
// نأخذ أحدث توكن (أو الأول في القائمة)
|
|
||||||
var tokenData = tokenList[0];
|
|
||||||
String serverToken = tokenData['token'].toString();
|
|
||||||
String serverFingerPrint = tokenData['fingerPrint'].toString();
|
|
||||||
String localToken =
|
|
||||||
box.read(BoxName.tokenDriver)?.toString() ?? "";
|
|
||||||
|
|
||||||
// إذا اختلف التوكن أو البصمة => جهاز جديد
|
|
||||||
if (serverToken != localToken ||
|
|
||||||
serverFingerPrint != currentFingerPrint) {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
|
|
||||||
await Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'Device Change Detected'.tr,
|
|
||||||
middleText: 'Please verify your identity'.tr,
|
|
||||||
textConfirm: 'Verify'.tr,
|
|
||||||
confirmTextColor: Colors.white,
|
|
||||||
onConfirm: () {
|
|
||||||
Get.back();
|
|
||||||
Get.to(() => OtpVerificationPage(
|
|
||||||
phone: d['phone'].toString(),
|
|
||||||
deviceToken: currentFingerPrint,
|
|
||||||
// تمرير قائمة التوكنات لاستخدامها في التحقق
|
|
||||||
token: jsonEncode(
|
|
||||||
{'status': 'success', 'data': tokenList}),
|
|
||||||
ptoken: serverToken,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// الدخول للرئيسية
|
|
||||||
Get.offAll(() => HomeCaptain());
|
|
||||||
} else {
|
|
||||||
// لم يكمل الوثائق
|
|
||||||
Get.offAll(() => DriverVerificationScreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// الحساب موجود لكن غير مفعل
|
|
||||||
Get.offAll(() => PhoneNumberScreen());
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// استجابة غير متوقعة أو data فارغة
|
|
||||||
Get.snackbar("Error".tr, "Login Failed".tr);
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("Login Credentials Error: $e");
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loginByBoxData() async {
|
|
||||||
Get.to(() => HomeCaptain());
|
|
||||||
await CRUD().post(link: AppLink.addTokensDriver, payload: {
|
|
||||||
'token': box.read(BoxName.tokenDriver).toString(),
|
|
||||||
'captain_id': box.read(BoxName.driverID).toString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
|
|
||||||
import 'package:image_cropper/image_cropper.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/llama_ai.dart';
|
|
||||||
|
|
||||||
// class CarRegistrationRecognizerController extends GetxController {
|
|
||||||
// @override
|
|
||||||
// void onInit() {
|
|
||||||
// // scanText();
|
|
||||||
// super.onInit();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // The ImagePicker instance
|
|
||||||
// final ImagePicker _imagePicker = ImagePicker();
|
|
||||||
|
|
||||||
// // The GoogleMlKit TextRecognizer instance
|
|
||||||
// // final TextRecognizer _textRecognizer = TextRecognizer();
|
|
||||||
|
|
||||||
// // The scanned text
|
|
||||||
// String? scannedText;
|
|
||||||
// String? jsonOutput;
|
|
||||||
// final List<Map<String, dynamic>> lines = [];
|
|
||||||
// Map extracted = {};
|
|
||||||
// XFile? image;
|
|
||||||
// CroppedFile? croppedFile;
|
|
||||||
// // Picks an image from the camera or gallery and extracts the text
|
|
||||||
// final List<Map<String, dynamic>> extractedTextWithCoordinates = [];
|
|
||||||
|
|
||||||
// Future<void> scanText() async {
|
|
||||||
// // Pick an image from the camera or gallery
|
|
||||||
// image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
|
||||||
// update();
|
|
||||||
|
|
||||||
// // If no image was picked, return
|
|
||||||
// if (image == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Crop the image
|
|
||||||
// croppedFile = await ImageCropper().cropImage(
|
|
||||||
// sourcePath: image!.path,
|
|
||||||
// //
|
|
||||||
// uiSettings: [
|
|
||||||
// AndroidUiSettings(
|
|
||||||
// toolbarTitle: 'Cropper'.tr,
|
|
||||||
// toolbarColor: AppColor.blueColor,
|
|
||||||
// toolbarWidgetColor: AppColor.yellowColor,
|
|
||||||
// initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
// lockAspectRatio: false),
|
|
||||||
// IOSUiSettings(
|
|
||||||
// title: 'Cropper'.tr,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // If no cropped image was obtained, return
|
|
||||||
// if (croppedFile == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Convert the cropped file to an InputImage object
|
|
||||||
// final InputImage inputImage = InputImage.fromFile(File(croppedFile!.path));
|
|
||||||
|
|
||||||
// // Recognize the text in the image
|
|
||||||
// final RecognizedText recognizedText =
|
|
||||||
// await _textRecognizer.processImage(inputImage);
|
|
||||||
// scannedText = recognizedText.text;
|
|
||||||
|
|
||||||
// // Extract the scanned text line by line
|
|
||||||
// final List<Map<String, dynamic>> lines = [];
|
|
||||||
// for (var i = 0; i < recognizedText.blocks.length; i++) {
|
|
||||||
// lines.add({
|
|
||||||
// i.toString(): recognizedText.blocks[i].text,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// String result = lines.map((map) => map.values.first.toString()).join(' ');
|
|
||||||
// if (result.length > 2200) {
|
|
||||||
// result = result.substring(0, 2200);
|
|
||||||
// }
|
|
||||||
// Map result2 = await LlamaAi().getCarRegistrationData(result,
|
|
||||||
// 'vin,make,made,year,expiration_date,color,owner,registration_date'); //
|
|
||||||
|
|
||||||
// // Assign the result to the extracted variable
|
|
||||||
// extracted = result2;
|
|
||||||
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/print.dart';
|
|
||||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../firebase/firbase_messge.dart';
|
|
||||||
import '../../firebase/notification_service.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
|
|
||||||
class OtpVerificationController extends GetxController {
|
|
||||||
final String phone;
|
|
||||||
final String deviceToken;
|
|
||||||
final String token;
|
|
||||||
final otpCode = ''.obs;
|
|
||||||
final isLoading = false.obs;
|
|
||||||
final isVerifying = false.obs;
|
|
||||||
var canResend = false.obs;
|
|
||||||
var countdown = 120.obs;
|
|
||||||
Timer? _timer;
|
|
||||||
|
|
||||||
OtpVerificationController({
|
|
||||||
required this.phone,
|
|
||||||
required this.deviceToken,
|
|
||||||
required this.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
sendOtp(); // ترسل تلقائيًا عند فتح الصفحة
|
|
||||||
startCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void startCountdown() {
|
|
||||||
canResend.value = false;
|
|
||||||
countdown.value = 120;
|
|
||||||
_timer?.cancel();
|
|
||||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
if (countdown.value > 0) {
|
|
||||||
countdown.value--;
|
|
||||||
} else {
|
|
||||||
canResend.value = true;
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendOtp() async {
|
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
final response = await CRUD().post(
|
|
||||||
link:
|
|
||||||
'${AppLink.server}/auth/token_passenger/driver/send_otp_driver.php',
|
|
||||||
payload: {
|
|
||||||
'receiver': phone,
|
|
||||||
// 'device_token': deviceToken,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
// بإمكانك عرض رسالة نجاح هنا
|
|
||||||
} else {
|
|
||||||
// Get.snackbar('Error', 'Failed to send OTP');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar('Error', e.toString());
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> verifyOtp(String ptoken) async {
|
|
||||||
isVerifying.value = true;
|
|
||||||
var finger = await storage.read(key: BoxName.fingerPrint);
|
|
||||||
try {
|
|
||||||
final response = await CRUD().post(
|
|
||||||
link:
|
|
||||||
'${AppLink.server}/auth/token_passenger/driver/verify_otp_driver.php',
|
|
||||||
payload: {
|
|
||||||
'phone_number': phone,
|
|
||||||
'otp': otpCode.value,
|
|
||||||
'token': box.read(BoxName.tokenDriver).toString(),
|
|
||||||
'fingerPrint': finger.toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
Log.print('response: ${response}');
|
|
||||||
// Get.back(); // توجه إلى الصفحة التالية
|
|
||||||
|
|
||||||
await NotificationService.sendNotification(
|
|
||||||
target: ptoken.toString(),
|
|
||||||
title: 'token change'.tr,
|
|
||||||
body: 'token change'.tr,
|
|
||||||
isTopic: false, // Important: this is a token
|
|
||||||
tone: 'cancel',
|
|
||||||
driverList: [], category: 'token change',
|
|
||||||
);
|
|
||||||
|
|
||||||
Get.offAll(() => HomeCaptain());
|
|
||||||
} else {
|
|
||||||
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar('Error', e.toString());
|
|
||||||
} finally {
|
|
||||||
isVerifying.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/print.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
// تأكد من تعديل هذا المسار لصفحة التسجيل الحالية
|
|
||||||
|
|
||||||
class PhoneAuthHelper {
|
|
||||||
// --- New Endpoints for Egypt Market ---
|
|
||||||
// تم تحديث المسارات بناءً على طلبك لتعمل مع ملفات الكابتن الجديدة
|
|
||||||
static final String _baseUrl = '${AppLink.server}/auth/captin/';
|
|
||||||
static final String _sendOtpUrl = '${_baseUrl}sendOtpMessageDriver.php';
|
|
||||||
static final String _verifyOtpUrl = '${_baseUrl}verifyOtpDriver.php';
|
|
||||||
|
|
||||||
/// دالة تنسيق الرقم للسوق المصري والعالمي
|
|
||||||
static String formatPhoneNumber(String phone) {
|
|
||||||
// 1. إزالة المسافات والرموز الزائدة
|
|
||||||
String cleanPhone = phone.replaceAll(RegExp(r'[ \-\(\)]'), '').trim();
|
|
||||||
|
|
||||||
// 2. التحقق إذا كان الرقم مصرياً (يبدأ بـ 010, 011, 012, 015 أو +20)
|
|
||||||
// إذا بدأ بـ +20، نتركه كما هو (صيغة دولية صحيحة)
|
|
||||||
if (cleanPhone.startsWith('+20')) {
|
|
||||||
return cleanPhone;
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذا بدأ بـ 01 (مثل 010xxxx...)، نحذف الصفر ونضيف +20
|
|
||||||
if (cleanPhone.startsWith('01')) {
|
|
||||||
return '+20${cleanPhone.substring(1)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذا بدأ بـ 1 مباشرة (مثل 10xxxx...) وكان الطول 10 أرقام، نضيف +20
|
|
||||||
if (cleanPhone.startsWith('1') && cleanPhone.length == 10) {
|
|
||||||
return '+20$cleanPhone';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. للأرقام الدولية الأخرى (غير مصر):
|
|
||||||
// يجب التأكد من وجود علامة + في البداية ليعرف السيرفر أنه رقم دولي (للواتساب)
|
|
||||||
if (!cleanPhone.startsWith('+')) {
|
|
||||||
return '+$cleanPhone';
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanPhone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// إرسال الرمز (SMS للمصريين، واتساب للأجانب - يتم التعامل معه في السيرفر)
|
|
||||||
static Future<bool> sendOtp(String phoneNumber, {String? email}) async {
|
|
||||||
try {
|
|
||||||
final fixedPhone = formatPhoneNumber(phoneNumber);
|
|
||||||
Log.print('Sending OTP to: $fixedPhone');
|
|
||||||
|
|
||||||
// نجهز الإيميل والآيدي الحاليين
|
|
||||||
String currentDriverId = box.read(BoxName.driverID)?.toString() ?? '';
|
|
||||||
String currentEmail =
|
|
||||||
email ?? box.read(BoxName.emailDriver)?.toString() ?? '';
|
|
||||||
|
|
||||||
// إرسال الطلب للسيرفر
|
|
||||||
final response = await CRUD().post(
|
|
||||||
link: _sendOtpUrl,
|
|
||||||
payload: {
|
|
||||||
'phone_number': fixedPhone,
|
|
||||||
'driverId': currentDriverId,
|
|
||||||
'email': currentEmail,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Log.print('Send OTP Response: $response');
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
// فك الترميز في حال كان الرد نصاً
|
|
||||||
final data = response;
|
|
||||||
if (data['status'] == 'success') {
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
// هام جداً: حفظ البيانات التي ولدها السيرفر (ID & Email)
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
if (data['driverId'] != null &&
|
|
||||||
data['driverId'].toString().isNotEmpty) {
|
|
||||||
String newId = data['driverId'].toString();
|
|
||||||
Log.print("New Driver ID Generated by Server: $newId");
|
|
||||||
box.write(BoxName.driverID, newId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['email'] != null && data['email'].toString().isNotEmpty) {
|
|
||||||
String newEmail = data['email'].toString();
|
|
||||||
Log.print("New Email Generated by Server: $newEmail");
|
|
||||||
box.write(BoxName.emailDriver, newEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
mySnackbarSuccess('Verification code sent successfully'.tr);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
mySnackeBarError(data['message'] ?? 'Failed to send code'.tr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('Server Error, please try again'.tr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('Error sending OTP: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// التحقق من الرمز المدخل
|
|
||||||
/// ملاحظة هامة: دالة PHP الجديدة للتحقق لا ترجع بيانات السائق، فقط تقول "تم التحقق".
|
|
||||||
/// لذا نحتاج لمنطق إضافي هنا لتسجيل الدخول أو التسجيل.
|
|
||||||
static Future<void> verifyOtp(String phoneNumber, [String? otpCode]) async {
|
|
||||||
try {
|
|
||||||
if (otpCode == null || otpCode.isEmpty) {
|
|
||||||
mySnackeBarError('يرجى إدخال الكود'.tr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final fixedPhone = formatPhoneNumber(phoneNumber);
|
|
||||||
Log.print('Verifying OTP for: $fixedPhone with code: $otpCode');
|
|
||||||
|
|
||||||
final response = await CRUD().post(
|
|
||||||
link: _verifyOtpUrl,
|
|
||||||
payload: {
|
|
||||||
'phone_number': fixedPhone,
|
|
||||||
'token_code': otpCode, // ملف PHP الجديد يطلب token_code
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
final data = response;
|
|
||||||
|
|
||||||
if (data['status'] == 'success') {
|
|
||||||
// 1. التحقق تم بنجاح في قاعدة بيانات الهاتف
|
|
||||||
// بما أن ملف verifyOtpDriver.php الجديد لا يرجع بيانات السائق،
|
|
||||||
// يجب علينا الآن محاولة تسجيل الدخول بهذا الرقم لمعرفة هل هو مسجل أم لا.
|
|
||||||
box.write(BoxName.firstTimeLoadKey, 'true');
|
|
||||||
box.write(BoxName.phoneDriver, fixedPhone);
|
|
||||||
|
|
||||||
var driverId = box.read(BoxName.driverID);
|
|
||||||
Log.print('driverId: ${driverId}');
|
|
||||||
var email = box.read(BoxName.emailDriver);
|
|
||||||
Log.print('email: ${email}');
|
|
||||||
await Get.put(LoginDriverController()).loginWithGoogleCredential(
|
|
||||||
driverId,
|
|
||||||
email,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mySnackeBarError(data['message'] ?? 'كود التحقق غير صحيح'.tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('خطأ في الاتصال بالسيرفر'.tr);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
mySnackeBarError('حدث خطأ: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// دالة مساعدة لمحاولة تسجيل الدخول أو التوجيه للتسجيل
|
|
||||||
/// هذه الدالة ضرورية لأن ملف التحقق الجديد فصل عملية التحقق عن جلب البيانات
|
|
||||||
static Future<void> _checkUserAndLogin() async {}
|
|
||||||
}
|
|
||||||
@@ -1,419 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/ocr_controller.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/login_captin.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/verify_email_captain.dart';
|
|
||||||
|
|
||||||
import '../../../constant/colors.dart';
|
|
||||||
import '../../../views/auth/captin/ai_eg_controller.dart';
|
|
||||||
import '../../../views/auth/syria/registration_view.dart';
|
|
||||||
import '../../../views/home/Captin/home_captain/home_captin.dart';
|
|
||||||
import '../../functions/sms_egypt_controller.dart';
|
|
||||||
|
|
||||||
class RegisterCaptainController extends GetxController {
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final formKey3 = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
TextEditingController emailController = TextEditingController();
|
|
||||||
TextEditingController phoneController = TextEditingController();
|
|
||||||
TextEditingController passwordController = TextEditingController();
|
|
||||||
TextEditingController verifyCode = TextEditingController();
|
|
||||||
|
|
||||||
String birthDate = 'Birth Date'.tr;
|
|
||||||
String gender = 'Male'.tr;
|
|
||||||
bool isLoading = false;
|
|
||||||
bool isSent = false;
|
|
||||||
late String name;
|
|
||||||
late String licenseClass;
|
|
||||||
late String documentNo;
|
|
||||||
late String address;
|
|
||||||
late String height;
|
|
||||||
late String postalCode;
|
|
||||||
late String sex;
|
|
||||||
late String stateCode;
|
|
||||||
late String expireDate;
|
|
||||||
late String dob;
|
|
||||||
|
|
||||||
getBirthDate() {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Select Date'.tr,
|
|
||||||
content: SizedBox(
|
|
||||||
width: 300,
|
|
||||||
child: CalendarDatePicker(
|
|
||||||
initialDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
|
|
||||||
firstDate: DateTime.parse('1940-06-01'),
|
|
||||||
lastDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
|
|
||||||
onDateChanged: (date) {
|
|
||||||
// Get the selected date and convert it to a DateTime object
|
|
||||||
DateTime dateTime = date;
|
|
||||||
// Call the getOrders() function from the controller
|
|
||||||
birthDate = dateTime.toString().split(' ')[0];
|
|
||||||
update();
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
|
|
||||||
// onDateChanged: (DateTime value) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
// Get.put(SmsEgyptController());
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeGender(String value) {
|
|
||||||
gender = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValidEgyptianPhoneNumber(String phoneNumber) {
|
|
||||||
// Remove any non-digit characters (spaces, dashes, etc.)
|
|
||||||
phoneNumber = phoneNumber.replaceAll(RegExp(r'\D+'), '');
|
|
||||||
|
|
||||||
// Check if the phone number has exactly 11 digits
|
|
||||||
if (phoneNumber.length != 11) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the phone number starts with 010, 011, 012, or 015
|
|
||||||
RegExp validPrefixes = RegExp(r'^01[0125]\d{8}$');
|
|
||||||
|
|
||||||
return validPrefixes.hasMatch(phoneNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendOtpMessage() async {
|
|
||||||
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
if (formKey3.currentState!.validate()) {
|
|
||||||
if (box.read(BoxName.countryCode) == 'Egypt') {
|
|
||||||
if (isValidEgyptianPhoneNumber(phoneController.text)) {
|
|
||||||
var responseCheker = await CRUD()
|
|
||||||
.post(link: AppLink.checkPhoneNumberISVerfiedDriver, payload: {
|
|
||||||
'phone_number': ('+2${phoneController.text}'),
|
|
||||||
});
|
|
||||||
if (responseCheker != 'failure') {
|
|
||||||
var d = jsonDecode(responseCheker);
|
|
||||||
if (d['message'][0]['is_verified'].toString() == '1') {
|
|
||||||
Get.snackbar('Phone number is verified before'.tr, '',
|
|
||||||
backgroundColor: AppColor.greenColor);
|
|
||||||
box.write(BoxName.phoneVerified, '1');
|
|
||||||
box.write(BoxName.phone, ('+2${phoneController.text}'));
|
|
||||||
await Get.put(LoginDriverController()).loginWithGoogleCredential(
|
|
||||||
box.read(BoxName.driverID).toString(),
|
|
||||||
(box.read(BoxName.emailDriver).toString()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
|
|
||||||
'phone_number': ('+2${phoneController.text}'),
|
|
||||||
"driverId": box.read(BoxName.driverID),
|
|
||||||
"email": (box.read(BoxName.emailDriver)),
|
|
||||||
});
|
|
||||||
|
|
||||||
isSent = true;
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
|
|
||||||
'phone_number': ('+2${phoneController.text}'),
|
|
||||||
"driverId": box.read(BoxName.driverID),
|
|
||||||
"email": box.read(BoxName.emailDriver),
|
|
||||||
});
|
|
||||||
|
|
||||||
isSent = true;
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError(
|
|
||||||
'Phone Number wrong'.tr,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime? lastOtpSentTime; // Store the last OTP sent time
|
|
||||||
int otpResendInterval = 300; // 5 minutes in seconds
|
|
||||||
|
|
||||||
// Main function to handle OTP sending
|
|
||||||
// sendOtpMessage() async {
|
|
||||||
// if (_isOtpResendAllowed()) {
|
|
||||||
// isLoading = true;
|
|
||||||
// update();
|
|
||||||
|
|
||||||
// if (formKey3.currentState!.validate()) {
|
|
||||||
// String countryCode = box.read(BoxName.countryCode);
|
|
||||||
// String phoneNumber = phoneController.text;
|
|
||||||
|
|
||||||
// if (countryCode == 'Egypt' && isValidEgyptianPhoneNumber(phoneNumber)) {
|
|
||||||
// await _checkAndSendOtp(phoneNumber);
|
|
||||||
// } else {
|
|
||||||
// _showErrorMessage('Phone Number is not Egypt phone '.tr);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// isLoading = false;
|
|
||||||
// update();
|
|
||||||
// } else {
|
|
||||||
// _showCooldownMessage();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check if the resend OTP request is allowed (5 minutes cooldown)
|
|
||||||
// bool _isOtpResendAllowed() {
|
|
||||||
// if (lastOtpSentTime == null) return true;
|
|
||||||
|
|
||||||
// final int elapsedTime =
|
|
||||||
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
|
|
||||||
// return elapsedTime >= otpResendInterval;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Show message when user tries to resend OTP too soon
|
|
||||||
// void _showCooldownMessage() {
|
|
||||||
// int remainingTime = otpResendInterval -
|
|
||||||
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
|
|
||||||
// Get.snackbar(
|
|
||||||
// 'Please wait ${remainingTime ~/ 60}:${(remainingTime % 60).toString().padLeft(2, '0')} minutes before requesting again',
|
|
||||||
// '',
|
|
||||||
// backgroundColor: AppColor.redColor,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Check if the phone number has been verified, and send OTP if not verified
|
|
||||||
// _checkAndSendOtp(String phoneNumber) async {
|
|
||||||
// var responseChecker = await CRUD().post(
|
|
||||||
// link: AppLink.checkPhoneNumberISVerfiedDriver,
|
|
||||||
// payload: {
|
|
||||||
// 'phone_number': '+2$phoneNumber',
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (responseChecker != 'failure') {
|
|
||||||
// var responseData = jsonDecode(responseChecker);
|
|
||||||
// if (_isPhoneVerified(responseData)) {
|
|
||||||
// _handleAlreadyVerified();
|
|
||||||
// } else {
|
|
||||||
// await _sendOtpAndSms(phoneNumber);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// await _sendOtpAndSms(phoneNumber);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check if the phone number is already verified
|
|
||||||
bool _isPhoneVerified(dynamic responseData) {
|
|
||||||
return responseData['message'][0]['is_verified'].toString() == '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle case where phone number is already verified
|
|
||||||
_handleAlreadyVerified() {
|
|
||||||
mySnackbarSuccess('Phone number is already verified'.tr);
|
|
||||||
box.write(BoxName.phoneVerified, '1');
|
|
||||||
box.write(BoxName.phone, ('+2${phoneController.text}'));
|
|
||||||
Get.put(LoginDriverController()).loginWithGoogleCredential(
|
|
||||||
box.read(BoxName.driverID).toString(),
|
|
||||||
box.read(BoxName.emailDriver).toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send OTP and SMS
|
|
||||||
_sendOtpAndSms(String phoneNumber) async {
|
|
||||||
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
|
|
||||||
int randomNumber = Random().nextInt(100000) + 1;
|
|
||||||
|
|
||||||
await CRUD().post(
|
|
||||||
link: AppLink.sendVerifyOtpMessage,
|
|
||||||
payload: {
|
|
||||||
'phone_number': ('+2$phoneNumber'),
|
|
||||||
'token_code': (randomNumber.toString()),
|
|
||||||
'driverId': box.read(BoxName.driverID),
|
|
||||||
'email': box.read(BoxName.emailDriver),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await smsEgyptController.sendSmsEgypt(phoneNumber);
|
|
||||||
|
|
||||||
lastOtpSentTime = DateTime.now(); // Update the last OTP sent time
|
|
||||||
isSent = true;
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
verifySMSCode() async {
|
|
||||||
// var loginDriverController = Get.put(LoginDriverController());
|
|
||||||
if (formKey3.currentState!.validate()) {
|
|
||||||
var res = await CRUD().post(link: AppLink.verifyOtpDriver, payload: {
|
|
||||||
'phone_number': ('+2${phoneController.text}'),
|
|
||||||
'token_code': (verifyCode.text.toString()),
|
|
||||||
});
|
|
||||||
if (res != 'failure') {
|
|
||||||
// var dec = jsonDecode(res);
|
|
||||||
box.write(BoxName.phoneDriver, ('+2${phoneController.text}'));
|
|
||||||
box.write(BoxName.phoneVerified, '1');
|
|
||||||
|
|
||||||
// loginDriverController.isGoogleLogin == true
|
|
||||||
// ? await loginDriverController.loginUsingCredentialsWithoutGoogle(
|
|
||||||
// loginDriverController.passwordController.text.toString(),
|
|
||||||
// box.read(BoxName.emailDriver).toString(),
|
|
||||||
// )
|
|
||||||
// : await loginDriverController.loginUsingCredentials(
|
|
||||||
// box.read(BoxName.driverID).toString(),
|
|
||||||
// box.read(BoxName.emailDriver).toString(),
|
|
||||||
// );
|
|
||||||
// Get.offAll(() => SyrianCardAI());
|
|
||||||
Get.to(() => RegistrationView());
|
|
||||||
// } else {
|
|
||||||
// Get.snackbar('title', 'message');
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('you must insert token code '.tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendVerifications() async {
|
|
||||||
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
|
|
||||||
'email': emailController.text.isEmpty
|
|
||||||
? (Get.find<LoginDriverController>().emailController.text.toString())
|
|
||||||
: (emailController.text),
|
|
||||||
'token': (verifyCode.text),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
if (Get.find<LoginDriverController>().emailController.text.toString() !=
|
|
||||||
'') {
|
|
||||||
Get.offAll(() => HomeCaptain());
|
|
||||||
} else {
|
|
||||||
// Get.to(() => CarLicensePage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void nextToAIDetection() async {
|
|
||||||
//Todo dont forget this
|
|
||||||
if (formKey.currentState!.validate()) {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
Get.to(() => EgyptCardAI());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> payloadLisence = {};
|
|
||||||
|
|
||||||
void getFromController() {
|
|
||||||
name = Get.find<ScanDocumentsByApi>().name;
|
|
||||||
licenseClass = Get.find<ScanDocumentsByApi>().licenseClass.toString();
|
|
||||||
documentNo = Get.find<ScanDocumentsByApi>().documentNo.toString();
|
|
||||||
address = Get.find<ScanDocumentsByApi>().address.toString();
|
|
||||||
height = Get.find<ScanDocumentsByApi>().height.toString();
|
|
||||||
postalCode = Get.find<ScanDocumentsByApi>().address.toString();
|
|
||||||
sex = Get.find<ScanDocumentsByApi>().sex.toString();
|
|
||||||
stateCode = Get.find<ScanDocumentsByApi>().postalCode.toString();
|
|
||||||
expireDate = Get.find<ScanDocumentsByApi>().expireDate.toString();
|
|
||||||
dob = Get.find<ScanDocumentsByApi>().dob.toString();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addLisence() async {
|
|
||||||
getFromController();
|
|
||||||
var res = await CRUD().post(link: AppLink.addLicense, payload: {
|
|
||||||
'name': name,
|
|
||||||
'licenseClass': licenseClass,
|
|
||||||
'documentNo': documentNo,
|
|
||||||
'address': address,
|
|
||||||
'height': height,
|
|
||||||
'postalCode': postalCode,
|
|
||||||
'sex': sex,
|
|
||||||
'stateCode': stateCode,
|
|
||||||
'expireDate': expireDate,
|
|
||||||
'dateOfBirth': dob,
|
|
||||||
});
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
if (jsonDecode(res)['status'] == 'success') {
|
|
||||||
// Get.to(() => AiPage()); //todo rplace this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addRegisrationCarForDriver(String vin, make, model, year, color, owner,
|
|
||||||
expirationDate, registrationDate) async {
|
|
||||||
getFromController();
|
|
||||||
var res = await CRUD().post(link: AppLink.addRegisrationCar, payload: {
|
|
||||||
'vin': vin,
|
|
||||||
'make': make,
|
|
||||||
'model': model,
|
|
||||||
'year': year,
|
|
||||||
'expirationDate': expirationDate,
|
|
||||||
'color': color,
|
|
||||||
'owner': owner,
|
|
||||||
'registrationDate': registrationDate,
|
|
||||||
});
|
|
||||||
box.write(BoxName.vin, vin);
|
|
||||||
box.write(BoxName.make, make);
|
|
||||||
box.write(BoxName.model, model);
|
|
||||||
box.write(BoxName.year, year);
|
|
||||||
box.write(BoxName.expirationDate, expirationDate);
|
|
||||||
box.write(BoxName.color, color);
|
|
||||||
box.write(BoxName.owner, owner);
|
|
||||||
box.write(BoxName.registrationDate, registrationDate);
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
if (jsonDecode(res)['status'] == 'success') {
|
|
||||||
Get.offAll(() => LoginCaptin()); //todo replace this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future register() async {
|
|
||||||
getFromController();
|
|
||||||
if (formKey.currentState!.validate()) {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().post(link: AppLink.signUpCaptin, payload: {
|
|
||||||
'first_name': name.split(' ')[1],
|
|
||||||
'last_name': name.split(' ')[0],
|
|
||||||
'email': emailController.text,
|
|
||||||
'phone': phoneController.text,
|
|
||||||
'password': passwordController.text,
|
|
||||||
'gender': sex,
|
|
||||||
'site': address,
|
|
||||||
'birthdate': dob,
|
|
||||||
});
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
if (jsonDecode(res)['status'] == 'success') {
|
|
||||||
box.write(BoxName.driverID, jsonDecode(res)['message']);
|
|
||||||
box.write(BoxName.dobDriver, dob);
|
|
||||||
box.write(BoxName.sexDriver, sex);
|
|
||||||
box.write(BoxName.phoneDriver, phoneController.text);
|
|
||||||
box.write(BoxName.lastNameDriver, name.split(' ')[0]);
|
|
||||||
int randomNumber = Random().nextInt(100000) + 1;
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
|
|
||||||
'email': emailController.text,
|
|
||||||
'token': randomNumber.toString(),
|
|
||||||
});
|
|
||||||
Get.to(() => VerifyEmailCaptainPage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
// import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
|
|
||||||
|
|
||||||
// class FacebookSignIn {
|
|
||||||
// Future<UserCredential?> signInWithFacebook() async {
|
|
||||||
// final LoginResult result = await FacebookAuth.instance.login();
|
|
||||||
// if (result.status == LoginStatus.success) {
|
|
||||||
// // Create a credential from the access token
|
|
||||||
// final OAuthCredential credential =
|
|
||||||
// FacebookAuthProvider.credential(result.accessToken!.tokenString);
|
|
||||||
// // Once signed in, return the UserCredential
|
|
||||||
// return await FirebaseAuth.instance.signInWithCredential(credential);
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> signOut() async {
|
|
||||||
// try {
|
|
||||||
// await FacebookAuth.instance.logOut();
|
|
||||||
// print('Facebook Sign Out Successful');
|
|
||||||
// } catch (e) {
|
|
||||||
// print('Error during Facebook Sign Out: $e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<bool> isSignedIn() async {
|
|
||||||
// final accessToken = await FacebookAuth.instance.accessToken;
|
|
||||||
// return accessToken != null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
|
||||||
import 'package:sefer_driver/views/home/on_boarding_page.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
|
||||||
|
|
||||||
import '../../views/auth/captin/ai_eg_controller.dart';
|
|
||||||
import '../functions/add_error.dart';
|
|
||||||
import '../functions/encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class GoogleSignInHelper {
|
|
||||||
static final GoogleSignIn _googleSignIn = GoogleSignIn(
|
|
||||||
scopes: [
|
|
||||||
'email',
|
|
||||||
'profile',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Method to handle Google Sign-In
|
|
||||||
static Future<GoogleSignInAccount?> signIn() async {
|
|
||||||
try {
|
|
||||||
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
|
|
||||||
if (googleUser != null) {
|
|
||||||
await _handleSignUp(googleUser);
|
|
||||||
if (box.read(BoxName.countryCode) == 'Egypt') {
|
|
||||||
Get.to(() => SmsSignupEgypt());
|
|
||||||
} else if (box.read(BoxName.countryCode) == 'Jordan') {
|
|
||||||
Get.to(() => EgyptCardAI());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return googleUser;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<GoogleSignInAccount?> signInFromLogin() async {
|
|
||||||
try {
|
|
||||||
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
|
|
||||||
|
|
||||||
if (googleUser != null) {
|
|
||||||
// Handle sign-up and store user information
|
|
||||||
await _handleSignUp(googleUser);
|
|
||||||
|
|
||||||
// Retrieve driverID and emailDriver with added validation
|
|
||||||
final driverID =
|
|
||||||
(box.read(BoxName.driverID)!.toString()) ?? 'Unknown ID';
|
|
||||||
final emailDriver =
|
|
||||||
(box.read(BoxName.emailDriver)!.toString()) ?? 'Unknown Email';
|
|
||||||
|
|
||||||
// Debug print statements
|
|
||||||
print('Driver ID: $driverID');
|
|
||||||
print('Driver Email: $emailDriver');
|
|
||||||
|
|
||||||
// Check if driverID is a valid numeric string
|
|
||||||
// if (driverID != 'Unknown ID' && int.tryParse(driverID) != null) {
|
|
||||||
await Get.find<LoginDriverController>()
|
|
||||||
.loginWithGoogleCredential(driverID, emailDriver);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// print('Invalid driverID format: $driverID');
|
|
||||||
// Get.snackbar('Login Error', 'Invalid driver ID format.',
|
|
||||||
// backgroundColor: AppColor.redColor);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
return googleUser;
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
mySnackeBarError('$error');
|
|
||||||
CRUD.addError(error.toString(), stackTrace.toString(),
|
|
||||||
'GoogleSignInAccount?> signInFromLogin()');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
|
|
||||||
// Store driver information
|
|
||||||
box.write(BoxName.driverID,
|
|
||||||
(user.id) ?? 'Unknown ID'); // Ensure there's a fallback value
|
|
||||||
box.write(BoxName.emailDriver, (user.email) ?? 'Unknown Email');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to handle Google Sign-Out
|
|
||||||
static Future<void> signOut() async {
|
|
||||||
try {
|
|
||||||
await _googleSignIn.signOut();
|
|
||||||
await _handleSignOut();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _handleSignOut() async {
|
|
||||||
// Clear stored driver information
|
|
||||||
box.erase();
|
|
||||||
storage.deleteAll();
|
|
||||||
Get.offAll(OnBoardingPage());
|
|
||||||
// Perform any additional sign-out tasks or API calls here
|
|
||||||
// For example, you can notify your server about the user sign-out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to get the current signed-in user
|
|
||||||
static GoogleSignInAccount? getCurrentUser() {
|
|
||||||
return _googleSignIn.currentUser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// import 'dart:async';
|
|
||||||
// import 'dart:async';
|
|
||||||
// import 'dart:convert';
|
|
||||||
// import 'package:flutter/material.dart';
|
|
||||||
// import 'package:get/get.dart';
|
|
||||||
// import 'package:http/http.dart' as http;
|
|
||||||
// import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
// import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
// import 'package:sefer_driver/main.dart';
|
|
||||||
// import 'package:sefer_driver/views/auth/captin/ai_page.dart';
|
|
||||||
// import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
|
|
||||||
// import 'package:sefer_driver/views/home/on_boarding_page.dart';
|
|
||||||
// import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
// import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
// import '../functions/add_error.dart';
|
|
||||||
|
|
||||||
// /// Helper class to manage Google Sign-In via an external browser and polling.
|
|
||||||
// class GoogleSignInHelper {
|
|
||||||
// // URLs for your server endpoints
|
|
||||||
// static const String _startLoginUrl =
|
|
||||||
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/login.php';
|
|
||||||
// static const String _checkStatusUrl =
|
|
||||||
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/check_status.php';
|
|
||||||
|
|
||||||
// static Future<void> _initiateSignIn(
|
|
||||||
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
|
|
||||||
// try {
|
|
||||||
// // Show a loading dialog to the user
|
|
||||||
// Get.dialog(
|
|
||||||
// const Center(
|
|
||||||
// child: Material(
|
|
||||||
// color: Colors.transparent,
|
|
||||||
// child: Column(
|
|
||||||
// mainAxisSize: MainAxisSize.min,
|
|
||||||
// children: [
|
|
||||||
// CircularProgressIndicator(),
|
|
||||||
// SizedBox(height: 16),
|
|
||||||
// Text(
|
|
||||||
// "Waiting for browser sign-in...",
|
|
||||||
// style: TextStyle(color: Colors.white, fontSize: 16),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// barrierDismissible: false,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // 1. Get the auth URL and login token from the server
|
|
||||||
// final startResponse = await http.get(Uri.parse(_startLoginUrl));
|
|
||||||
// if (startResponse.statusCode != 200) {
|
|
||||||
// throw Exception('Failed to start login process.');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// final startData = jsonDecode(startResponse.body);
|
|
||||||
// final String authUrl = startData['authUrl'];
|
|
||||||
// final String loginToken = startData['loginToken'];
|
|
||||||
|
|
||||||
// // 2. Launch the URL in an external browser
|
|
||||||
// if (!await launchUrl(Uri.parse(authUrl),
|
|
||||||
// mode: LaunchMode.externalApplication)) {
|
|
||||||
// throw Exception('Could not launch browser.');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 3. Start polling the server for status
|
|
||||||
// await _startPolling(loginToken, onSignInSuccess);
|
|
||||||
// } catch (e) {
|
|
||||||
// addError(e.toString(), '_initiateSignIn');
|
|
||||||
// mySnackeBarError('Sign-in failed: ${e.toString()}');
|
|
||||||
// // Close the loading dialog on error
|
|
||||||
// if (Get.isDialogOpen ?? false) {
|
|
||||||
// Get.back();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static Future<void> _startPolling(String loginToken,
|
|
||||||
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
|
|
||||||
// Timer? poller;
|
|
||||||
// const int maxAttempts = 30; // Poll for 60 seconds (30 attempts * 2s)
|
|
||||||
// int attempts = 0;
|
|
||||||
|
|
||||||
// poller = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
|
||||||
// if (attempts >= maxAttempts) {
|
|
||||||
// timer.cancel();
|
|
||||||
// if (Get.isDialogOpen ?? false) Get.back();
|
|
||||||
// mySnackeBarError('Sign-in timed out. Please try again.');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// attempts++;
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// final statusResponse = await http.post(
|
|
||||||
// Uri.parse(_checkStatusUrl),
|
|
||||||
// headers: {'Content-Type': 'application/json'},
|
|
||||||
// body: jsonEncode({'loginToken': loginToken}),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (statusResponse.statusCode == 200) {
|
|
||||||
// final statusData = jsonDecode(statusResponse.body);
|
|
||||||
// if (statusData['status'] == 'success') {
|
|
||||||
// timer.cancel();
|
|
||||||
// if (Get.isDialogOpen ?? false) Get.back();
|
|
||||||
// // Success!
|
|
||||||
// onSignInSuccess(statusData['userData']);
|
|
||||||
// }
|
|
||||||
// // If status is 'pending', do nothing and wait for the next poll.
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// // Handle polling errors silently or log them
|
|
||||||
// debugPrint("Polling error: $e");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Triggers the sign-in process for a new user.
|
|
||||||
// static Future<void> signIn() async {
|
|
||||||
// await _initiateSignIn((userData) async {
|
|
||||||
// debugPrint('Sign-in success data: $userData');
|
|
||||||
// await _handleSignUp(userData);
|
|
||||||
// if (box.read(BoxName.countryCode) == 'Egypt') {
|
|
||||||
// Get.offAll(() => SmsSignupEgypt());
|
|
||||||
// } else if (box.read(BoxName.countryCode) == 'Jordan') {
|
|
||||||
// Get.offAll(() => AiPage());
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Triggers the sign-in process for an existing user.
|
|
||||||
// static Future<void> signInFromLogin() async {
|
|
||||||
// await _initiateSignIn((userData) async {
|
|
||||||
// debugPrint('Sign-in from login success data: $userData');
|
|
||||||
// await _handleSignUp(userData);
|
|
||||||
|
|
||||||
// final driverID = userData['id']?.toString() ?? 'Unknown ID';
|
|
||||||
// final emailDriver = userData['email']?.toString() ?? 'Unknown Email';
|
|
||||||
|
|
||||||
// debugPrint('Driver ID from server: $driverID');
|
|
||||||
// debugPrint('Driver Email from server: $emailDriver');
|
|
||||||
|
|
||||||
// await Get.find<LoginDriverController>()
|
|
||||||
// .loginWithGoogleCredential(driverID, emailDriver);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Stores user information received from the server.
|
|
||||||
// static Future<void> _handleSignUp(Map<String, dynamic> userData) async {
|
|
||||||
// box.write(BoxName.driverID, userData['id'] ?? 'Unknown ID');
|
|
||||||
// box.write(BoxName.emailDriver, userData['email'] ?? 'Unknown Email');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Clears local data.
|
|
||||||
// static Future<void> signOut() async {
|
|
||||||
// box.remove(BoxName.driverID);
|
|
||||||
// box.remove(BoxName.emailDriver);
|
|
||||||
// box.remove(BoxName.lang);
|
|
||||||
// box.remove(BoxName.nameDriver);
|
|
||||||
// box.remove(BoxName.passengerID);
|
|
||||||
// box.remove(BoxName.phoneDriver);
|
|
||||||
// box.remove(BoxName.tokenFCM);
|
|
||||||
// box.remove(BoxName.tokens);
|
|
||||||
// box.remove(BoxName.carPlate);
|
|
||||||
// box.remove(BoxName.lastNameDriver);
|
|
||||||
// box.remove(BoxName.agreeTerms);
|
|
||||||
// box.remove(BoxName.tokenDriver);
|
|
||||||
// box.remove(BoxName.countryCode);
|
|
||||||
// box.remove(BoxName.accountIdStripeConnect);
|
|
||||||
// box.remove(BoxName.phoneVerified);
|
|
||||||
// Get.offAll(() => OnBoardingPage());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/secure_storage.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/auth/verify_email_page.dart';
|
|
||||||
|
|
||||||
import '../functions/encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class LoginController extends GetxController {
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final formKeyAdmin = GlobalKey<FormState>();
|
|
||||||
TextEditingController emailController = TextEditingController();
|
|
||||||
TextEditingController phoneController = TextEditingController();
|
|
||||||
TextEditingController passwordController = TextEditingController();
|
|
||||||
TextEditingController adminPasswordController = TextEditingController();
|
|
||||||
TextEditingController adminNameController = TextEditingController();
|
|
||||||
bool isAgreeTerms = false;
|
|
||||||
bool isloading = false;
|
|
||||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
|
||||||
|
|
||||||
void changeAgreeTerm() {
|
|
||||||
isAgreeTerms = !isAgreeTerms;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveAgreementTerms() {
|
|
||||||
box.write(BoxName.agreeTerms, 'agreed');
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveCountryCode(String countryCode) {
|
|
||||||
box.write(BoxName.countryCode, countryCode);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void login() async {
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().get(link: AppLink.login, payload: {
|
|
||||||
'email': emailController.text,
|
|
||||||
'phone': phoneController.text,
|
|
||||||
'password': passwordController.text
|
|
||||||
});
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
if (res == 'failure') {
|
|
||||||
//Failure
|
|
||||||
mySnackeBarError('');
|
|
||||||
} else {
|
|
||||||
var jsonDecoeded = jsonDecode(res);
|
|
||||||
if (jsonDecoeded.isNotEmpty) {
|
|
||||||
if (jsonDecoeded['status'] == 'success') {
|
|
||||||
if (jsonDecoeded['data'][0]['verified'] == 1) {
|
|
||||||
box.write(BoxName.driverID, jsonDecoeded['data'][0]['id']);
|
|
||||||
box.write(BoxName.emailDriver, (jsonDecoeded['data'][0]['email']));
|
|
||||||
box.write(
|
|
||||||
BoxName.nameDriver,
|
|
||||||
jsonDecoeded['data'][0]['first_name'] +
|
|
||||||
' ' +
|
|
||||||
jsonDecoeded['data'][0]['last_name']);
|
|
||||||
box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']);
|
|
||||||
SecureStorage().saveData(BoxName.password, passwordController.text);
|
|
||||||
// Get.offAll(() => const MapPagePassenger());
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
await CRUD().post(link: AppLink.addTokens, payload: {
|
|
||||||
'token': box.read(BoxName.tokenFCM),
|
|
||||||
'passengerID': box.read(BoxName.passengerID).toString()
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'You must Verify email !.'.tr,
|
|
||||||
middleText: '',
|
|
||||||
backgroundColor: Colors.yellow[300],
|
|
||||||
onConfirm: () async {
|
|
||||||
int randomNumber = Random().nextInt(100000) + 1;
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
|
|
||||||
'email': emailController.text,
|
|
||||||
'token': randomNumber.toString(),
|
|
||||||
});
|
|
||||||
Get.to(() => const VerifyEmailPage());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (jsonDecoeded['status'] == 'Failure') {
|
|
||||||
mySnackeBarError(jsonDecoeded['data']);
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goToMapPage() {
|
|
||||||
if (box.read(BoxName.email) != null) {
|
|
||||||
// Get.offAll(() => const MapPagePassenger());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import '../../models/model/onboarding_model.dart';
|
|
||||||
import '../../views/auth/captin/login_captin.dart';
|
|
||||||
|
|
||||||
abstract class OnBoardingController extends GetxController {
|
|
||||||
void next();
|
|
||||||
void onPageChanged(int index);
|
|
||||||
}
|
|
||||||
|
|
||||||
class OnBoardingControllerImp extends OnBoardingController {
|
|
||||||
late PageController pageController;
|
|
||||||
int currentPage = 0;
|
|
||||||
bool isLastPage = false; // لتغيير نص الزر أو سلوكه
|
|
||||||
|
|
||||||
@override
|
|
||||||
void next() {
|
|
||||||
currentPage++;
|
|
||||||
if (currentPage > onBoardingList.length - 1) {
|
|
||||||
_completeOnBoarding();
|
|
||||||
} else {
|
|
||||||
pageController.animateToPage(
|
|
||||||
currentPage,
|
|
||||||
duration: const Duration(milliseconds: 600), // مدة معتدلة
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _completeOnBoarding() async {
|
|
||||||
await box.write(BoxName.onBoarding, 'yes');
|
|
||||||
// استخدام transition ناعم لمنع الرمش
|
|
||||||
Get.offAll(() => const LoginCaptin(),
|
|
||||||
transition: Transition.fadeIn,
|
|
||||||
duration: const Duration(milliseconds: 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onPageChanged(int index) {
|
|
||||||
currentPage = index;
|
|
||||||
isLastPage = index == onBoardingList.length - 1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
pageController = PageController();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
pageController.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
|
|
||||||
import '../../views/auth/captin/login_captin.dart';
|
|
||||||
import '../../views/auth/verify_email_page.dart';
|
|
||||||
|
|
||||||
class RegisterController extends GetxController {
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
TextEditingController firstNameController = TextEditingController();
|
|
||||||
TextEditingController lastNameController = TextEditingController();
|
|
||||||
TextEditingController emailController = TextEditingController();
|
|
||||||
TextEditingController phoneController = TextEditingController();
|
|
||||||
TextEditingController passwordController = TextEditingController();
|
|
||||||
TextEditingController siteController = TextEditingController();
|
|
||||||
TextEditingController verfyCode = TextEditingController();
|
|
||||||
|
|
||||||
String birthDate = 'Birth Date'.tr;
|
|
||||||
String gender = 'Male'.tr;
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
getBirthDate() {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Select Date'.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
content: SizedBox(
|
|
||||||
width: 300,
|
|
||||||
child: CalendarDatePicker(
|
|
||||||
initialDate:
|
|
||||||
DateTime.now().subtract(const Duration(days: 14 * 365)),
|
|
||||||
firstDate: DateTime.parse('1940-06-01'),
|
|
||||||
lastDate: DateTime.now().subtract(const Duration(days: 14 * 365)),
|
|
||||||
onDateChanged: (date) {
|
|
||||||
// Get the selected date and convert it to a DateTime object
|
|
||||||
DateTime dateTime = date;
|
|
||||||
// Call the getOrders() function from the controller
|
|
||||||
birthDate = dateTime.toString().split(' ')[0];
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
|
|
||||||
// onDateChanged: (DateTime value) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
confirm: MyElevatedButton(title: 'Ok'.tr, onPressed: () => Get.back()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeGender(String value) {
|
|
||||||
gender = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendVerifications() async {
|
|
||||||
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
|
|
||||||
'email': emailController.text,
|
|
||||||
'token': verfyCode.text,
|
|
||||||
});
|
|
||||||
var dec = jsonDecode(res);
|
|
||||||
if (dec['status'] == 'success') {
|
|
||||||
Get.offAll(() => LoginCaptin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void register() async {
|
|
||||||
if (formKey.currentState!.validate()) {
|
|
||||||
var res = await CRUD().post(link: AppLink.signUp, payload: {
|
|
||||||
'first_name': firstNameController.text.toString(),
|
|
||||||
'last_name': lastNameController.text.toString(),
|
|
||||||
'email': emailController.text.toString(),
|
|
||||||
'phone': phoneController.text.toString(),
|
|
||||||
'password': passwordController.text.toString(),
|
|
||||||
'gender': 'yet',
|
|
||||||
'site': siteController.text,
|
|
||||||
'birthdate': birthDate,
|
|
||||||
});
|
|
||||||
if (jsonDecode(res)['status'] == 'success') {
|
|
||||||
int randomNumber = Random().nextInt(100000) + 1;
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
|
|
||||||
'email': emailController.text,
|
|
||||||
'token': randomNumber.toString(),
|
|
||||||
});
|
|
||||||
Get.to(() => const VerifyEmailPage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,770 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:image_cropper/image_cropper.dart';
|
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/firebase/notification_service.dart';
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
// --- Final Submission ---
|
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
|
||||||
import 'package:mime/mime.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import '../../../constant/colors.dart';
|
|
||||||
import '../../../constant/info.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../../views/widgets/error_snakbar.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
import '../../functions/encrypt_decrypt.dart';
|
|
||||||
import '../../functions/package_info.dart';
|
|
||||||
import '../captin/login_captin_controller.dart';
|
|
||||||
|
|
||||||
// You can create a simple enum to manage image types
|
|
||||||
enum ImageType {
|
|
||||||
driverLicenseFront,
|
|
||||||
driverLicenseBack,
|
|
||||||
carLicenseFront,
|
|
||||||
carLicenseBack,
|
|
||||||
}
|
|
||||||
|
|
||||||
class RegistrationController extends GetxController {
|
|
||||||
// Page Controller for managing steps
|
|
||||||
late PageController pageController;
|
|
||||||
var currentPage =
|
|
||||||
0.obs; // Use .obs for reactive updates on the step indicator
|
|
||||||
|
|
||||||
// Loading state
|
|
||||||
var isLoading = false.obs;
|
|
||||||
var isloading = false;
|
|
||||||
CroppedFile? croppedFile;
|
|
||||||
final picker = ImagePicker();
|
|
||||||
var image;
|
|
||||||
File? myImage;
|
|
||||||
String? colorHex; // سيُملى من الدروب داون
|
|
||||||
// Form Keys for validation
|
|
||||||
final driverInfoFormKey = GlobalKey<FormState>();
|
|
||||||
final carInfoFormKey = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
// STEP 1: Driver Information Controllers
|
|
||||||
final firstNameController = TextEditingController();
|
|
||||||
final lastNameController = TextEditingController();
|
|
||||||
final nationalIdController = TextEditingController();
|
|
||||||
final bithdateController = TextEditingController();
|
|
||||||
final phoneController = TextEditingController(); // You can pre-fill this
|
|
||||||
final driverLicenseExpiryController = TextEditingController();
|
|
||||||
DateTime? driverLicenseExpiryDate;
|
|
||||||
|
|
||||||
// STEP 2: Car Information Controllers
|
|
||||||
final carPlateController = TextEditingController();
|
|
||||||
final carMakeController = TextEditingController();
|
|
||||||
final carModelController = TextEditingController();
|
|
||||||
final carYearController = TextEditingController();
|
|
||||||
final carColorController = TextEditingController();
|
|
||||||
final carVinController = TextEditingController(); // Chassis number
|
|
||||||
final carRegistrationExpiryController = TextEditingController();
|
|
||||||
DateTime? carRegistrationExpiryDate;
|
|
||||||
// داخل RegistrationController
|
|
||||||
|
|
||||||
// المتغيرات لتخزين القيم المختارة (لإرسالها للـ API لاحقاً)
|
|
||||||
int? selectedVehicleCategoryId; // سيخزن 1 أو 2 أو 3
|
|
||||||
int? selectedFuelTypeId; // سيخزن 1 أو 2 أو 3 أو 4
|
|
||||||
|
|
||||||
// قائمة أنواع المركبات (مطابقة لقاعدة البيانات)
|
|
||||||
final List<Map<String, dynamic>> vehicleCategoryOptions = [
|
|
||||||
{'id': 1, 'name': 'Car'.tr}, // ترجمة: سيارة
|
|
||||||
{'id': 2, 'name': 'Motorcycle'.tr}, // ترجمة: دراجة نارية
|
|
||||||
{'id': 3, 'name': 'Van / Bus'.tr}, // ترجمة: فان / باص
|
|
||||||
];
|
|
||||||
|
|
||||||
// قائمة أنواع الوقود
|
|
||||||
final List<Map<String, dynamic>> fuelTypeOptions = [
|
|
||||||
{'id': 1, 'name': 'Petrol'.tr}, // ترجمة: بنزين
|
|
||||||
{'id': 2, 'name': 'Diesel'.tr}, // ترجمة: ديزل
|
|
||||||
{'id': 3, 'name': 'Electric'.tr}, // ترجمة: كهربائي
|
|
||||||
{'id': 4, 'name': 'Hybrid'.tr}, // ترجمة: هايبرد
|
|
||||||
];
|
|
||||||
// STEP 3: Document Uploads
|
|
||||||
File? driverLicenseFrontImage;
|
|
||||||
File? driverLicenseBackImage;
|
|
||||||
File? carLicenseFrontImage;
|
|
||||||
File? carLicenseBackImage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
pageController = PageController();
|
|
||||||
// Pre-fill phone number if it exists in storage
|
|
||||||
// phoneController.text = box.read(BoxName.phoneDriver) ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
pageController.dispose();
|
|
||||||
// Dispose all other text controllers
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Page Navigation ---
|
|
||||||
void goToNextStep() {
|
|
||||||
bool isValid = false;
|
|
||||||
if (currentPage.value == 0) {
|
|
||||||
// Validate Step 1
|
|
||||||
isValid = driverInfoFormKey.currentState!.validate();
|
|
||||||
if (isValid) {
|
|
||||||
// Optional: Check if license is expired
|
|
||||||
// if (driverLicenseExpiryDate != null &&
|
|
||||||
// driverLicenseExpiryDate!.isBefore(DateTime.now())) {
|
|
||||||
// Get.snackbar('Expired License', 'Your driver’s license has expired.'.tr
|
|
||||||
// ,
|
|
||||||
// snackPosition: SnackPosition.BOTTOM,
|
|
||||||
// backgroundColor: Colors.red,
|
|
||||||
// colorText: Colors.white);
|
|
||||||
// return; // Stop progression
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
} else if (currentPage.value == 1) {
|
|
||||||
// Validate Step 2
|
|
||||||
isValid = carInfoFormKey.currentState!.validate();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
pageController.nextPage(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void goToPreviousStep() {
|
|
||||||
pageController.previousPage(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Image Picking ---
|
|
||||||
Future<void> pickImage(ImageType type) async {
|
|
||||||
try {
|
|
||||||
final picker = ImagePicker();
|
|
||||||
final picked = await picker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
imageQuality: 95, // جودة أولية من الكاميرا
|
|
||||||
maxWidth: 3000, // نسمح بصورة كبيرة ثم نصغّر نحن
|
|
||||||
);
|
|
||||||
if (picked == null) return;
|
|
||||||
|
|
||||||
// قصّ الصورة
|
|
||||||
final cropped = await ImageCropper().cropImage(
|
|
||||||
sourcePath: picked.path,
|
|
||||||
uiSettings: [
|
|
||||||
AndroidUiSettings(
|
|
||||||
toolbarTitle: 'Cropper'.tr,
|
|
||||||
toolbarColor: AppColor.accentColor,
|
|
||||||
toolbarWidgetColor: AppColor.redColor,
|
|
||||||
initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
lockAspectRatio: false,
|
|
||||||
),
|
|
||||||
IOSUiSettings(title: 'Cropper'.tr),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (cropped == null) return; // المستخدم ألغى
|
|
||||||
|
|
||||||
// قراءة bytes + التصحيح حسب EXIF ثم التصغير
|
|
||||||
final rawBytes = await File(cropped.path).readAsBytes();
|
|
||||||
final decoded = img.decodeImage(rawBytes);
|
|
||||||
if (decoded == null) throw Exception('Decode image failed');
|
|
||||||
|
|
||||||
// تصحيح اتجاه الصورة (EXIF)
|
|
||||||
final fixed = img.bakeOrientation(decoded);
|
|
||||||
|
|
||||||
// تصغير لعرض 800px (عدّل عند الحاجة)
|
|
||||||
final resized = img.copyResize(fixed, width: 800);
|
|
||||||
|
|
||||||
// حفظ مؤقت بصيغة JPG
|
|
||||||
final tmpDir = await getTemporaryDirectory();
|
|
||||||
final outPath =
|
|
||||||
'${tmpDir.path}/doc_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
|
||||||
final outFile = File(outPath);
|
|
||||||
await outFile.writeAsBytes(img.encodeJpg(resized, quality: 85));
|
|
||||||
|
|
||||||
// عيّن الملف في المتغير الصحيح حسب النوع
|
|
||||||
if (outFile != null) {
|
|
||||||
switch (type) {
|
|
||||||
case ImageType.driverLicenseFront:
|
|
||||||
driverLicenseFrontImage = File(outFile.path);
|
|
||||||
break;
|
|
||||||
case ImageType.driverLicenseBack:
|
|
||||||
driverLicenseBackImage = File(outFile.path);
|
|
||||||
break;
|
|
||||||
case ImageType.carLicenseFront:
|
|
||||||
carLicenseFrontImage = File(outFile.path);
|
|
||||||
break;
|
|
||||||
case ImageType.carLicenseBack:
|
|
||||||
carLicenseBackImage = File(outFile.path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
update(); // Use update() to refresh the GetBuilder UI
|
|
||||||
}
|
|
||||||
|
|
||||||
update(); // لتحديث الـ UI
|
|
||||||
|
|
||||||
// // الإرسال للذكاء الاصطناعي
|
|
||||||
// await sendToAI(type, imageFile: outFile);
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ثابت: 20 لون سيارة شائع
|
|
||||||
static const List<Map<String, String>> kCarColorOptions = [
|
|
||||||
{'key': 'color.white', 'hex': '#FFFFFF'},
|
|
||||||
{'key': 'color.black', 'hex': '#000000'},
|
|
||||||
{'key': 'color.silver', 'hex': '#C0C0C0'},
|
|
||||||
{'key': 'color.gray', 'hex': '#808080'},
|
|
||||||
{'key': 'color.gunmetal', 'hex': '#2A3439'},
|
|
||||||
{'key': 'color.red', 'hex': '#C62828'},
|
|
||||||
{'key': 'color.blue', 'hex': '#1565C0'},
|
|
||||||
{'key': 'color.navy', 'hex': '#0D47A1'},
|
|
||||||
{'key': 'color.green', 'hex': '#2E7D32'},
|
|
||||||
{'key': 'color.darkGreen', 'hex': '#1B5E20'},
|
|
||||||
{'key': 'color.beige', 'hex': '#D7CCC8'},
|
|
||||||
{'key': 'color.brown', 'hex': '#5D4037'},
|
|
||||||
{'key': 'color.maroon', 'hex': '#800000'},
|
|
||||||
{'key': 'color.burgundy', 'hex': '#800020'},
|
|
||||||
{'key': 'color.yellow', 'hex': '#F9A825'},
|
|
||||||
{'key': 'color.orange', 'hex': '#EF6C00'},
|
|
||||||
{'key': 'color.gold', 'hex': '#D4AF37'},
|
|
||||||
{'key': 'color.bronze', 'hex': '#CD7F32'},
|
|
||||||
{'key': 'color.champagne', 'hex': '#EFE1C6'},
|
|
||||||
{'key': 'color.purple', 'hex': '#6A1B9A'},
|
|
||||||
];
|
|
||||||
|
|
||||||
Color hexToColor(String hex) {
|
|
||||||
var v = hex.replaceAll('#', '');
|
|
||||||
if (v.length == 6) v = 'FF$v';
|
|
||||||
return Color(int.parse(v, radix: 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
//uploadSyrianDocs
|
|
||||||
// دالة مساعدة: تضيف الحقل إذا كان له قيمة
|
|
||||||
void _addField(Map<String, String> fields, String key, String? value) {
|
|
||||||
if (value != null && value.toString().isNotEmpty) {
|
|
||||||
fields[key] = value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// خريطة لتخزين روابط المستندات بعد الرفع
|
|
||||||
final Map<String, String> docUrls = {
|
|
||||||
'driver_license_front': '',
|
|
||||||
'driver_license_back': '',
|
|
||||||
'car_license_front': '',
|
|
||||||
'car_license_back': '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
|
|
||||||
Future<void> choosImage(String link, String imageType) async {
|
|
||||||
try {
|
|
||||||
final pickedImage = await picker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
preferredCameraDevice: CameraDevice.rear,
|
|
||||||
);
|
|
||||||
if (pickedImage == null) return;
|
|
||||||
|
|
||||||
image = File(pickedImage.path);
|
|
||||||
|
|
||||||
final croppedFile = await ImageCropper().cropImage(
|
|
||||||
sourcePath: image!.path,
|
|
||||||
uiSettings: [
|
|
||||||
AndroidUiSettings(
|
|
||||||
toolbarTitle: 'Cropper'.tr,
|
|
||||||
toolbarColor: AppColor.blueColor,
|
|
||||||
toolbarWidgetColor: AppColor.yellowColor,
|
|
||||||
initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
lockAspectRatio: false,
|
|
||||||
),
|
|
||||||
IOSUiSettings(title: 'Cropper'.tr),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (croppedFile == null) return;
|
|
||||||
|
|
||||||
// صورة للمعاينة داخل التطبيق
|
|
||||||
myImage = File(croppedFile.path);
|
|
||||||
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
|
|
||||||
final File compressedImage = await compressImage(File(croppedFile.path));
|
|
||||||
|
|
||||||
// تجهيز الحقول
|
|
||||||
final driverId = box.read(BoxName.driverID);
|
|
||||||
|
|
||||||
final payload = <String, String>{
|
|
||||||
'driverID': driverId,
|
|
||||||
'imageType': imageType, // مثال: driver_license_front
|
|
||||||
};
|
|
||||||
|
|
||||||
// الرفع وإرجاع الرابط
|
|
||||||
final String imageUrl = await uploadImage(compressedImage, payload, link);
|
|
||||||
|
|
||||||
// حفظ الرابط محلياً حسب النوع
|
|
||||||
docUrls[imageType] = imageUrl;
|
|
||||||
|
|
||||||
Log.print('✅ Uploaded $imageType => $imageUrl');
|
|
||||||
} catch (e, st) {
|
|
||||||
Log.print('❌ Error in choosImage: $e\n$st');
|
|
||||||
mySnackeBarError('Image Upload Failed'.tr);
|
|
||||||
} finally {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ترفع الملف وترجع رابط الصورة النهائي كـ String
|
|
||||||
Future<String> uploadImage(
|
|
||||||
File file, Map<String, String> data, String link) async {
|
|
||||||
final uri = Uri.parse(link);
|
|
||||||
final request = http.MultipartRequest('POST', uri);
|
|
||||||
|
|
||||||
// الهيدرز (كما عندك)
|
|
||||||
final headers = <String, String>{
|
|
||||||
'Authorization':
|
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
|
|
||||||
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
|
|
||||||
};
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
// اسم الملف: driverID.jpg (اختياري)
|
|
||||||
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
|
|
||||||
|
|
||||||
// إضافة الملف (من المسار مباشرة أسلم من الـ stream)
|
|
||||||
request.files.add(
|
|
||||||
await http.MultipartFile.fromPath(
|
|
||||||
'image', // تأكد أنه نفس اسم الحقل على السيرفر
|
|
||||||
file.path,
|
|
||||||
filename: forcedName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// الحقول الإضافية
|
|
||||||
data.forEach((k, v) => request.fields[k] = v);
|
|
||||||
|
|
||||||
// الإرسال
|
|
||||||
final streamed = await request.send();
|
|
||||||
final res = await http.Response.fromStream(streamed);
|
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
|
||||||
throw Exception(
|
|
||||||
'Failed to upload image: ${res.statusCode} - ${res.body}');
|
|
||||||
}
|
|
||||||
|
|
||||||
// نحاول استخراج رابط الصورة من أكثر من مفتاح محتمل
|
|
||||||
final body = jsonDecode(res.body);
|
|
||||||
final String? url = body['url'] ??
|
|
||||||
body['file_link'] ??
|
|
||||||
body['image_url'] ??
|
|
||||||
(body['data'] is Map ? body['data']['url'] : null);
|
|
||||||
|
|
||||||
if (url == null || url.isEmpty) {
|
|
||||||
// لو السيرفر يرجع هيكل مختلف، عدّل هنا المفتاح حسب استجابتك الفعلية
|
|
||||||
throw Exception(
|
|
||||||
'Upload succeeded but no image URL found in response: ${res.body}');
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> compressImage(File file) async {
|
|
||||||
final dir = await path_provider.getTemporaryDirectory();
|
|
||||||
final targetPath = "${dir.absolute.path}/temp.jpg";
|
|
||||||
|
|
||||||
var result = await FlutterImageCompress.compressAndGetFile(
|
|
||||||
file.absolute.path,
|
|
||||||
targetPath,
|
|
||||||
quality: 70,
|
|
||||||
minWidth: 1024,
|
|
||||||
minHeight: 1024,
|
|
||||||
);
|
|
||||||
|
|
||||||
return File(result!.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة رفع إلى السيرفر السوري: ترجع file_url (Signed URL)
|
|
||||||
Future<String> uploadToSyria({
|
|
||||||
required String docType,
|
|
||||||
required File file,
|
|
||||||
required Uri syrianUploadUri,
|
|
||||||
required String authHeader,
|
|
||||||
required String hmacHeader,
|
|
||||||
required String driverId,
|
|
||||||
Duration timeout = const Duration(seconds: 60),
|
|
||||||
http.Client? clientOverride,
|
|
||||||
}) async {
|
|
||||||
final client = clientOverride ?? http.Client();
|
|
||||||
try {
|
|
||||||
final mime = lookupMimeType(file.path) ?? 'image/jpeg';
|
|
||||||
final parts = mime.split('/');
|
|
||||||
|
|
||||||
final req = http.MultipartRequest('POST', syrianUploadUri);
|
|
||||||
req.headers.addAll({
|
|
||||||
'Authorization': authHeader,
|
|
||||||
'X-HMAC-Auth': hmacHeader,
|
|
||||||
});
|
|
||||||
|
|
||||||
req.fields['driver_id'] = driverId;
|
|
||||||
req.fields['doc_type'] = docType;
|
|
||||||
|
|
||||||
req.files.add(
|
|
||||||
await http.MultipartFile.fromPath(
|
|
||||||
'file',
|
|
||||||
file.path,
|
|
||||||
filename: p.basename(file.path),
|
|
||||||
contentType: MediaType(parts.first, parts.last),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ====== الطباعة قبل الإرسال ======
|
|
||||||
// Log.print('--- Syrian Upload Request ---');
|
|
||||||
// Log.print('URL: $syrianUploadUri');
|
|
||||||
// // Log.print('Method: POST');
|
|
||||||
// // Log.print('Headers: ${req.headers}');
|
|
||||||
// Log.print('Fields: ${req.fields}');
|
|
||||||
// // Log.print(
|
|
||||||
// // 'File: ${file.path} (${await file.length()} bytes, mime: $mime)');
|
|
||||||
// Log.print('-----------------------------');
|
|
||||||
|
|
||||||
// الإرسال
|
|
||||||
final streamed = await client.send(req).timeout(timeout);
|
|
||||||
final resp = await http.Response.fromStream(streamed);
|
|
||||||
|
|
||||||
// ====== الطباعة بعد الاستجابة ======
|
|
||||||
// Log.print('--- Syrian Upload Response ---');
|
|
||||||
Log.print('Status: ${resp.statusCode}');
|
|
||||||
// Log.print('Headers: ${resp.headers}');
|
|
||||||
// Log.print('Body: ${resp.body}');
|
|
||||||
// Log.print('-------------------------------');
|
|
||||||
|
|
||||||
Map<String, dynamic> j = {};
|
|
||||||
try {
|
|
||||||
j = jsonDecode(resp.body) as Map<String, dynamic>;
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('⚠️ Failed to parse JSON: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
// التحمّل لشكلين من الـ JSON:
|
|
||||||
final statusOk = j['status'] == 'success';
|
|
||||||
final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString();
|
|
||||||
final fileName =
|
|
||||||
(j['file_name'] ?? j['message']?['file_name'])?.toString();
|
|
||||||
|
|
||||||
if (resp.statusCode == 200 &&
|
|
||||||
statusOk &&
|
|
||||||
(fileUrl?.isNotEmpty ?? false)) {
|
|
||||||
// Log.print(
|
|
||||||
// '✅ Syrian upload success: $fileUrl (file: ${fileName ?? "-"})');
|
|
||||||
return fileUrl!;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception(
|
|
||||||
'❌ Syrian upload failed ($docType): ${j['message'] ?? resp.body}');
|
|
||||||
} finally {
|
|
||||||
if (clientOverride == null) client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> submitRegistration() async {
|
|
||||||
// 0) دوال/مساعدات محلية
|
|
||||||
void _addField(Map<String, String> fields, String key, String? value) {
|
|
||||||
if (value != null && value.isNotEmpty) {
|
|
||||||
fields[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1) تحقق من وجود الروابط
|
|
||||||
final driverFrontUrl = docUrls['driver_license_front'];
|
|
||||||
final driverBackUrl = docUrls['driver_license_back'];
|
|
||||||
final carFrontUrl = docUrls['car_license_front'];
|
|
||||||
final carBackUrl = docUrls['car_license_back'];
|
|
||||||
|
|
||||||
isLoading.value = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
final registerUri = Uri.parse(AppLink.register_driver_and_car);
|
|
||||||
final client = http.Client();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ترويسات مشتركة
|
|
||||||
final bearer =
|
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
|
|
||||||
final hmac = '${box.read(BoxName.hmac)}';
|
|
||||||
|
|
||||||
final req = http.MultipartRequest('POST', registerUri);
|
|
||||||
req.headers.addAll({
|
|
||||||
'Authorization': bearer,
|
|
||||||
'X-HMAC-Auth': hmac,
|
|
||||||
});
|
|
||||||
|
|
||||||
final fields = <String, String>{};
|
|
||||||
|
|
||||||
// --- Driver Data ---
|
|
||||||
_addField(fields, 'id', box.read(BoxName.driverID)?.toString());
|
|
||||||
_addField(fields, 'first_name', firstNameController.text);
|
|
||||||
_addField(fields, 'last_name', lastNameController.text);
|
|
||||||
_addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
|
|
||||||
_addField(fields, 'national_number', nationalIdController.text);
|
|
||||||
_addField(fields, 'birthdate', bithdateController.text);
|
|
||||||
_addField(fields, 'expiry_date', driverLicenseExpiryController.text);
|
|
||||||
_addField(fields, 'password', 'generated_password_or_token');
|
|
||||||
_addField(fields, 'status', 'yet');
|
|
||||||
_addField(fields, 'email', 'Not specified');
|
|
||||||
_addField(fields, 'gender', 'Male'); // يفضل ربطها بـ Dropdown أيضاً
|
|
||||||
|
|
||||||
// --- Car Data ---
|
|
||||||
_addField(fields, 'vin', 'yet');
|
|
||||||
_addField(fields, 'car_plate', carPlateController.text);
|
|
||||||
_addField(fields, 'make', carMakeController.text);
|
|
||||||
_addField(fields, 'model', carModelController.text);
|
|
||||||
_addField(fields, 'year', carYearController.text);
|
|
||||||
_addField(
|
|
||||||
fields,
|
|
||||||
'expiration_date',
|
|
||||||
driverLicenseExpiryController
|
|
||||||
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
|
|
||||||
_addField(fields, 'color', carColorController.text);
|
|
||||||
|
|
||||||
if (colorHex != null && colorHex!.isNotEmpty) {
|
|
||||||
_addField(fields, 'color_hex', colorHex!);
|
|
||||||
}
|
|
||||||
_addField(fields, 'owner',
|
|
||||||
'${firstNameController.text} ${lastNameController.text}');
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 1. إرسال رقم تصنيف المركبة (1=سيارة, 2=دراجة...)
|
|
||||||
if (selectedVehicleCategoryId != null) {
|
|
||||||
_addField(fields, 'vehicle_category_id',
|
|
||||||
selectedVehicleCategoryId.toString());
|
|
||||||
} else {
|
|
||||||
_addField(fields, 'vehicle_category_id', '1'); // قيمة افتراضية (سيارة)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. إرسال رقم ونوع الوقود
|
|
||||||
if (selectedFuelTypeId != null) {
|
|
||||||
// إرسال الرقم (للبحث السريع)
|
|
||||||
_addField(fields, 'fuel_type_id', selectedFuelTypeId.toString());
|
|
||||||
|
|
||||||
// إرسال الاسم نصاً (للتوافق مع العمود القديم 'fuel' إذا لزم الأمر)
|
|
||||||
// نبحث عن الاسم داخل القائمة بناءً على الرقم المختار
|
|
||||||
final fuelObj = fuelTypeOptions.firstWhere(
|
|
||||||
(e) => e['id'] == selectedFuelTypeId,
|
|
||||||
orElse: () => {'name': 'Petrol'});
|
|
||||||
_addField(fields, 'fuel', fuelObj['name'].toString());
|
|
||||||
} else {
|
|
||||||
_addField(fields, 'fuel_type_id', '1');
|
|
||||||
_addField(fields, 'fuel', 'Petrol');
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- روابط الصور ---
|
|
||||||
_addField(fields, 'driver_license_front', driverFrontUrl!);
|
|
||||||
_addField(fields, 'driver_license_back', driverBackUrl!);
|
|
||||||
_addField(fields, 'car_license_front', carFrontUrl!);
|
|
||||||
_addField(fields, 'car_license_back', carBackUrl!);
|
|
||||||
|
|
||||||
req.fields.addAll(fields);
|
|
||||||
|
|
||||||
// 3) الإرسال
|
|
||||||
final streamed =
|
|
||||||
await client.send(req).timeout(const Duration(seconds: 60));
|
|
||||||
final resp = await http.Response.fromStream(streamed);
|
|
||||||
|
|
||||||
// 4) معالجة الاستجابة
|
|
||||||
Map<String, dynamic>? json;
|
|
||||||
try {
|
|
||||||
json = jsonDecode(resp.body) as Map<String, dynamic>;
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
if (resp.statusCode == 200 && json?['status'] == 'success') {
|
|
||||||
Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
|
|
||||||
backgroundColor: Colors.green, colorText: Colors.white);
|
|
||||||
|
|
||||||
// منطق التوكن والإشعارات وتسجيل الدخول...
|
|
||||||
final email = box.read(BoxName.emailDriver);
|
|
||||||
final driverID = box.read(BoxName.driverID);
|
|
||||||
|
|
||||||
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
|
||||||
await CRUD().post(link: AppLink.addTokensDriver, payload: {
|
|
||||||
'captain_id': (box.read(BoxName.driverID)).toString(),
|
|
||||||
'token': (box.read(BoxName.tokenDriver)).toString(),
|
|
||||||
'fingerPrint': fingerPrint.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
NotificationService.sendNotification(
|
|
||||||
target: 'service',
|
|
||||||
title: 'New Driver Registration',
|
|
||||||
body: 'Driver $driverID has submitted registration.',
|
|
||||||
isTopic: true,
|
|
||||||
category: 'new_service_request',
|
|
||||||
);
|
|
||||||
|
|
||||||
final c = Get.isRegistered<LoginDriverController>()
|
|
||||||
? Get.find<LoginDriverController>()
|
|
||||||
: Get.put(LoginDriverController());
|
|
||||||
c.loginWithGoogleCredential(driverID, email);
|
|
||||||
} else {
|
|
||||||
final msg = (json?['message'] ?? 'Registration failed.').toString();
|
|
||||||
Get.snackbar('Error'.tr, msg,
|
|
||||||
backgroundColor: Colors.red, colorText: Colors.white);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Get.snackbar('Error'.tr, 'Error: $e',
|
|
||||||
backgroundColor: Colors.red, colorText: Colors.white);
|
|
||||||
} finally {
|
|
||||||
client.close();
|
|
||||||
isLoading.value = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// // 1) تحقق من الصور
|
|
||||||
// if (driverLicenseFrontImage == null ||
|
|
||||||
// driverLicenseBackImage == null ||
|
|
||||||
// carLicenseFrontImage == null ||
|
|
||||||
// carLicenseBackImage == null) {
|
|
||||||
// Get.snackbar(
|
|
||||||
// 'Missing Documents'.tr, 'Please upload all 4 required documents.'.tr,
|
|
||||||
// snackPosition: SnackPosition.BOTTOM,
|
|
||||||
// backgroundColor: Colors.orange,
|
|
||||||
// colorText: Colors.white);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// isLoading.value = true;
|
|
||||||
|
|
||||||
// final uri = Uri.parse(
|
|
||||||
// 'https://intaleq.xyz/intaleq/auth/syria/driver/register_driver_and_car.php',
|
|
||||||
// );
|
|
||||||
|
|
||||||
// final client = http.Client();
|
|
||||||
// try {
|
|
||||||
// final req = http.MultipartRequest('POST', uri);
|
|
||||||
|
|
||||||
// // مهم: لا تضع Content-Type يدويًا، الـ MultipartRequest يتكفّل فيه ببناء boundary.
|
|
||||||
// final headers = {
|
|
||||||
// 'Authorization':
|
|
||||||
// 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
|
|
||||||
// 'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
|
|
||||||
// };
|
|
||||||
// // 2) الحقول النصية
|
|
||||||
// final fields = <String, String>{};
|
|
||||||
|
|
||||||
// // --- Driver Data ---
|
|
||||||
// _addField(fields, 'id', box.read(BoxName.driverID)?.toString());
|
|
||||||
// _addField(fields, 'first_name', firstNameController.text);
|
|
||||||
// _addField(fields, 'last_name', lastNameController.text);
|
|
||||||
// _addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
|
|
||||||
// _addField(fields, 'national_number', nationalIdController.text);
|
|
||||||
// _addField(fields, 'expiry_date', driverLicenseExpiryController.text);
|
|
||||||
// _addField(
|
|
||||||
// fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
|
|
||||||
// _addField(fields, 'status', 'yet');
|
|
||||||
// _addField(fields, 'email',
|
|
||||||
// 'Not specified'); // سكربت السيرفر سيحوّلها null ويبني ايميل افتراضي
|
|
||||||
// _addField(fields, 'gender', 'Male');
|
|
||||||
|
|
||||||
// // --- Car Data (مطابقة لما يتوقّعه السكربت) ---
|
|
||||||
// _addField(fields, 'vin', 'carVinController.text);');
|
|
||||||
// _addField(fields, 'car_plate', carPlateController.text);
|
|
||||||
// _addField(fields, 'make', carMakeController.text);
|
|
||||||
// _addField(fields, 'model', carModelController.text);
|
|
||||||
// _addField(fields, 'year', carYearController.text);
|
|
||||||
// _addField(fields, 'expiration_date', 'carRegistrationExpiryController');
|
|
||||||
// _addField(fields, 'color', carColorController.text);
|
|
||||||
// _addField(fields, 'fuel', 'Gasoline'); // أو حسب اختيارك
|
|
||||||
// _addField(fields, 'color_hex', colorHex); // مهم
|
|
||||||
// // لو عندك حقول إضافية مطلوبة بالسكربت (مالك المركبة / الكود اللوني / الوقود) مرّرها:
|
|
||||||
// _addField(fields, 'owner',
|
|
||||||
// firstNameController.text + ' ' + lastNameController.text);
|
|
||||||
// // if (colorHex != null) _addField(fields, 'color_hex', colorHex);
|
|
||||||
// // if (fuelType != null) _addField(fields, 'fuel', fuelType);
|
|
||||||
// req.headers.addAll(headers);
|
|
||||||
// req.fields.addAll(fields);
|
|
||||||
|
|
||||||
// // 3) الملفات (4 صور) — مفاتيحها مطابقة للسكربت
|
|
||||||
// Future<void> addFile(String field, File file) async {
|
|
||||||
// final mime = lookupMimeType(file.path) ?? 'image/jpeg';
|
|
||||||
// final parts = mime.split('/');
|
|
||||||
// final mediaType = MediaType(parts.first, parts.last);
|
|
||||||
// req.files.add(
|
|
||||||
// await http.MultipartFile.fromPath(
|
|
||||||
// field,
|
|
||||||
// file.path,
|
|
||||||
// filename: p.basename(file.path),
|
|
||||||
// contentType: mediaType,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// await addFile('driver_license_front', driverLicenseFrontImage!);
|
|
||||||
// await addFile('driver_license_back', driverLicenseBackImage!);
|
|
||||||
// await addFile('car_license_front', carLicenseFrontImage!);
|
|
||||||
// await addFile('car_license_back', carLicenseBackImage!);
|
|
||||||
|
|
||||||
// // 4) الإرسال
|
|
||||||
// final streamed =
|
|
||||||
// await client.send(req).timeout(const Duration(seconds: 60));
|
|
||||||
// final resp = await http.Response.fromStream(streamed);
|
|
||||||
|
|
||||||
// // 5) فحص النتيجة
|
|
||||||
// Map<String, dynamic>? json;
|
|
||||||
// try {
|
|
||||||
// json = jsonDecode(resp.body) as Map<String, dynamic>;
|
|
||||||
// } catch (_) {}
|
|
||||||
|
|
||||||
// if (resp.statusCode == 200 &&
|
|
||||||
// json != null &&
|
|
||||||
// json['status'] == 'success') {
|
|
||||||
// // ممكن يرجّع driverID, carRegID, documents
|
|
||||||
// final driverID =
|
|
||||||
// (json['data']?['driverID'] ?? json['driverID'])?.toString();
|
|
||||||
// if (driverID != null && driverID.isNotEmpty) {
|
|
||||||
// box.write(BoxName.driverID, driverID);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
|
|
||||||
// snackPosition: SnackPosition.BOTTOM,
|
|
||||||
// backgroundColor: Colors.green,
|
|
||||||
// colorText: Colors.white);
|
|
||||||
|
|
||||||
// // TODO: انتقل للصفحة التالية أو حدّث الحالة…
|
|
||||||
// } else {
|
|
||||||
// final msg =
|
|
||||||
// (json?['message'] ?? 'Registration failed. Please try again.')
|
|
||||||
// .toString();
|
|
||||||
// Get.snackbar('Error'.tr, msg,
|
|
||||||
// snackPosition: SnackPosition.BOTTOM,
|
|
||||||
// backgroundColor: Colors.red,
|
|
||||||
// colorText: Colors.white);
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e',
|
|
||||||
// snackPosition: SnackPosition.BOTTOM,
|
|
||||||
// backgroundColor: Colors.red,
|
|
||||||
// colorText: Colors.white);
|
|
||||||
// } finally {
|
|
||||||
// client.close();
|
|
||||||
// isLoading.value = false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
|
|
||||||
class TokenController extends GetxController {
|
|
||||||
bool isloading = false;
|
|
||||||
|
|
||||||
Future addToken() async {
|
|
||||||
String? basicAuthCredentials =
|
|
||||||
await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
var res = await http.post(
|
|
||||||
Uri.parse(AppLink.addTokens),
|
|
||||||
headers: {
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
'token': box.read(BoxName.tokenFCM.toString()),
|
|
||||||
'passengerID': box.read(BoxName.passengerID).toString()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
var jsonToken = jsonDecode(res.body);
|
|
||||||
if (jsonToken['status'] == 'The token has been updated successfully.') {
|
|
||||||
mySnackbarSuccess('token updated'.tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
|
|
||||||
class VerifyEmailController extends GetxController {
|
|
||||||
TextEditingController verfyCode = TextEditingController();
|
|
||||||
@override
|
|
||||||
void onInit() async {
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendverfications() async {
|
|
||||||
await CRUD().post(link: AppLink.sendVerifyEmail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:googleapis_auth/auth_io.dart';
|
|
||||||
|
|
||||||
import '../../print.dart';
|
|
||||||
|
|
||||||
class AccessTokenManager {
|
|
||||||
static final AccessTokenManager _instance = AccessTokenManager._internal();
|
|
||||||
late final String serviceAccountJsonKey;
|
|
||||||
AccessToken? _accessToken;
|
|
||||||
DateTime? _expiryDate;
|
|
||||||
|
|
||||||
AccessTokenManager._internal();
|
|
||||||
|
|
||||||
factory AccessTokenManager(String jsonKey) {
|
|
||||||
if (_instance._isServiceAccountKeyInitialized()) {
|
|
||||||
// Prevent re-initialization
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
_instance.serviceAccountJsonKey = jsonKey;
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isServiceAccountKeyInitialized() {
|
|
||||||
try {
|
|
||||||
serviceAccountJsonKey; // Access to check if initialized
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getAccessToken() async {
|
|
||||||
if (_accessToken != null && DateTime.now().isBefore(_expiryDate!)) {
|
|
||||||
return _accessToken!.data;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final serviceAccountCredentials = ServiceAccountCredentials.fromJson(
|
|
||||||
json.decode(serviceAccountJsonKey));
|
|
||||||
final client = await clientViaServiceAccount(
|
|
||||||
serviceAccountCredentials,
|
|
||||||
['https://www.googleapis.com/auth/firebase.messaging'],
|
|
||||||
);
|
|
||||||
|
|
||||||
_accessToken = client.credentials.accessToken;
|
|
||||||
_expiryDate = client.credentials.accessToken.expiry;
|
|
||||||
client.close();
|
|
||||||
// Log.print('_accessToken!.data: ${_accessToken!.data}');
|
|
||||||
return _accessToken!.data;
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Failed to obtain access token');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class AppLifecycleManager {
|
|
||||||
static const platform = MethodChannel('com.sefer_driver/app_lifecycle');
|
|
||||||
|
|
||||||
static Future<void> bringAppToForeground() async {
|
|
||||||
try {
|
|
||||||
debugPrint('Attempting to bring app to foreground');
|
|
||||||
await platform.invokeMethod('bringAppToForeground');
|
|
||||||
debugPrint('Method invocation completed');
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
debugPrint("Failed to bring app to foreground: '${e.message}'.");
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Unexpected error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/colors.dart';
|
|
||||||
import '../../constant/style.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import '../../views/auth/captin/criminal_documents_page.dart';
|
|
||||||
import '../../views/home/Captin/home_captain/home_captin.dart';
|
|
||||||
import '../../views/home/Captin/orderCaptin/vip_order_page.dart';
|
|
||||||
import '../auth/google_sign.dart';
|
|
||||||
import '../functions/face_detect.dart';
|
|
||||||
import '../home/captin/map_driver_controller.dart';
|
|
||||||
import 'local_notification.dart';
|
|
||||||
|
|
||||||
class FirebaseMessagesController extends GetxController {
|
|
||||||
final fcmToken = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
List<String> tokens = [];
|
|
||||||
List dataTokens = [];
|
|
||||||
late String driverID;
|
|
||||||
late String driverToken;
|
|
||||||
NotificationSettings? notificationSettings;
|
|
||||||
NotificationController notificationController =
|
|
||||||
Get.put(NotificationController());
|
|
||||||
Future<void> getNotificationSettings() async {
|
|
||||||
// Get the current notification settings
|
|
||||||
NotificationSettings? notificationSettings =
|
|
||||||
await FirebaseMessaging.instance.getNotificationSettings();
|
|
||||||
'Notification authorization status: ${notificationSettings.authorizationStatus}';
|
|
||||||
|
|
||||||
// Call the update function if needed
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> requestFirebaseMessagingPermission() async {
|
|
||||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
// Check if the platform is Android
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
// Request permission for Android
|
|
||||||
await messaging.requestPermission();
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
// Request permission for iOS
|
|
||||||
NotificationSettings settings = await messaging.requestPermission(
|
|
||||||
alert: true,
|
|
||||||
announcement: true,
|
|
||||||
badge: true,
|
|
||||||
carPlay: true,
|
|
||||||
criticalAlert: true,
|
|
||||||
provisional: false,
|
|
||||||
sound: true,
|
|
||||||
);
|
|
||||||
messaging.setForegroundNotificationPresentationOptions(
|
|
||||||
alert: true, badge: true, sound: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future getToken() async {
|
|
||||||
fcmToken.getToken().then((token) {
|
|
||||||
Log.print('token fcm driver: ${token}');
|
|
||||||
box.write(BoxName.tokenDriver, (token!));
|
|
||||||
});
|
|
||||||
// 🔹 الاشتراك في topic
|
|
||||||
await fcmToken.subscribeToTopic("drivers"); // أو "users" حسب نوع المستخدم
|
|
||||||
print("Subscribed to 'drivers' topic ✅");
|
|
||||||
|
|
||||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
|
||||||
// If the app is in the background or terminated, show a system tray message
|
|
||||||
RemoteNotification? notification = message.notification;
|
|
||||||
AndroidNotification? android = notification?.android;
|
|
||||||
// if (notification != null && android != null) {
|
|
||||||
|
|
||||||
if (message.data.isNotEmpty) {
|
|
||||||
fireBaseTitles(message);
|
|
||||||
}
|
|
||||||
// if (message.data.isNotEmpty && message.notification != null) {
|
|
||||||
// fireBaseTitles(message);
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
|
|
||||||
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
|
||||||
if (message.data.isNotEmpty) {
|
|
||||||
fireBaseTitles(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fireBaseTitles(RemoteMessage message) async {
|
|
||||||
// [!! تعديل جوهري !!]
|
|
||||||
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
|
|
||||||
String category = message.data['category'] ?? '';
|
|
||||||
|
|
||||||
// اقرأ العنوان والنص (للعرض)
|
|
||||||
String title = message.notification?.title ?? '';
|
|
||||||
String body = message.notification?.body ?? '';
|
|
||||||
|
|
||||||
// استخدم switch لسهولة القراءة والصيانة
|
|
||||||
switch (category) {
|
|
||||||
case 'ORDER':
|
|
||||||
case 'Order': // Handle both cases for backward compatibility
|
|
||||||
// if (Platform.isAndroid) {
|
|
||||||
// notificationController.showNotification(title, body, 'order', '');
|
|
||||||
// }
|
|
||||||
var myListString = message.data['DriverList'];
|
|
||||||
if (myListString != null) {
|
|
||||||
var myList = jsonDecode(myListString) as List<dynamic>;
|
|
||||||
driverToken = myList[14].toString();
|
|
||||||
Get.put(HomeCaptainController()).changeRideId();
|
|
||||||
update();
|
|
||||||
Get.toNamed('/OrderRequestPage', arguments: {
|
|
||||||
'myListString': myListString,
|
|
||||||
'DriverList': myList,
|
|
||||||
'body': body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'OrderVIP':
|
|
||||||
var myListString = message.data['DriverList'];
|
|
||||||
if (myListString != null) {
|
|
||||||
var myList = jsonDecode(myListString) as List<dynamic>;
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'order', '');
|
|
||||||
}
|
|
||||||
Get.to(VipOrderPage(), arguments: {
|
|
||||||
'myListString': myListString,
|
|
||||||
'DriverList': myList,
|
|
||||||
'body': body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Cancel Trip':
|
|
||||||
case 'TRIP_CANCELLED':
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(
|
|
||||||
title, 'Passenger Cancel Trip'.tr, 'cancel', '');
|
|
||||||
}
|
|
||||||
Log.print("🔔 FCM: Ride Cancelled by Passenger received.");
|
|
||||||
|
|
||||||
// 1. استخراج السبب (أرسلناه من PHP باسم 'reason')
|
|
||||||
String reason = message.data['reason'] ?? 'No reason provided';
|
|
||||||
|
|
||||||
// 2. توجيه الأمر للكنترولر
|
|
||||||
if (Get.isRegistered<MapDriverController>()) {
|
|
||||||
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
|
|
||||||
Get.find<MapDriverController>()
|
|
||||||
.processRideCancelledByPassenger(reason, source: "FCM");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'VIP Order Accepted':
|
|
||||||
// This seems to be a notification for the passenger, but if the driver needs to see it:
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'order', '');
|
|
||||||
}
|
|
||||||
// Maybe show a simple snackbar confirmation
|
|
||||||
mySnackbarSuccess('You accepted the VIP order.'.tr);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'message From passenger':
|
|
||||||
case 'MSG_FROM_PASSENGER':
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'ding', '');
|
|
||||||
}
|
|
||||||
MyDialog().getDialog(title, body, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'token change':
|
|
||||||
case 'TOKEN_CHANGE':
|
|
||||||
GoogleSignInHelper.signOut();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'face detect':
|
|
||||||
case 'FACE_DETECT':
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'tone2', '');
|
|
||||||
}
|
|
||||||
String result0 = await faceDetector();
|
|
||||||
var result = jsonDecode(result0);
|
|
||||||
MyDialogContent().getDialog(
|
|
||||||
'Face Detection Result'.tr,
|
|
||||||
Text(
|
|
||||||
result['similar'].toString() == 'true'
|
|
||||||
? 'similar'.tr
|
|
||||||
: 'not similar'.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
() {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Hi ,I will go now':
|
|
||||||
case 'PASSENGER_COMING':
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'tone2', '');
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Criminal Document Required':
|
|
||||||
case 'DOC_REQUIRED':
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'tone2', '');
|
|
||||||
}
|
|
||||||
MyDialog().getDialog(title, 'You should have upload it .'.tr, () {
|
|
||||||
Get.to(() => const CriminalDocumemtPage());
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Order Applied':
|
|
||||||
case 'ORDER_TAKEN':
|
|
||||||
mySnackbarSuccess("The order has been accepted by another driver.".tr);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Log.print('Received unhandled notification category: $category');
|
|
||||||
// Optionally show a generic notification
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
notificationController.showNotification(title, body, 'default', '');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SnackbarController driverAppliedTripSnakBar() {
|
|
||||||
return Get.snackbar(
|
|
||||||
'Driver Applied the Ride for You'.tr,
|
|
||||||
'',
|
|
||||||
colorText: AppColor.greenColor,
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
snackPosition: SnackPosition.TOP,
|
|
||||||
titleText: Text(
|
|
||||||
'Applied'.tr,
|
|
||||||
style: const TextStyle(color: AppColor.redColor),
|
|
||||||
),
|
|
||||||
messageText: Text(
|
|
||||||
'Driver Applied the Ride for You'.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.approval),
|
|
||||||
shouldIconPulse: true,
|
|
||||||
margin: const EdgeInsets.all(16),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> cancelTripDialog() {
|
|
||||||
return Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'Passenger Cancel Trip'.tr,
|
|
||||||
middleText: '',
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Ok'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
box.write(BoxName.rideStatus, 'Cancel');
|
|
||||||
box.write(BoxName.statusDriverLocation, 'off');
|
|
||||||
Log.print(
|
|
||||||
'rideStatus from 347 : ${box.read(BoxName.rideStatus)}');
|
|
||||||
Get.offAll(HomeCaptain());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> cancelTripDialog1() {
|
|
||||||
return Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'Passenger Cancel Trip'.tr,
|
|
||||||
middleText:
|
|
||||||
'Trip Cancelled. The cost of the trip will be added to your wallet.'
|
|
||||||
.tr,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Ok'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
box.write(BoxName.rideStatus, 'Cancel');
|
|
||||||
Log.print(
|
|
||||||
'rideStatus from 364 : ${box.read(BoxName.rideStatus)}');
|
|
||||||
Get.offAll(HomeCaptain());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OverlayContent extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final String body;
|
|
||||||
|
|
||||||
OverlayContent(this.title, this.body);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Material(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
color: Colors.white,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8.0),
|
|
||||||
Text(
|
|
||||||
body,
|
|
||||||
style: const TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,536 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui'; // للألوان
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../../main.dart'; // للوصول لـ box
|
|
||||||
import '../../print.dart';
|
|
||||||
import '../../views/home/Captin/driver_map_page.dart';
|
|
||||||
import '../../views/home/Captin/orderCaptin/order_request_page.dart';
|
|
||||||
import '../functions/crud.dart';
|
|
||||||
import '../home/captin/home_captain_controller.dart';
|
|
||||||
|
|
||||||
class NotificationController extends GetxController {
|
|
||||||
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
|
||||||
FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 1. تهيئة الإشعارات (إعداد القنوات والأزرار للآيفون والأندرويد)
|
|
||||||
// ==============================================================================
|
|
||||||
Future<void> initNotifications() async {
|
|
||||||
// إعدادات الأندرويد
|
|
||||||
const AndroidInitializationSettings android =
|
|
||||||
AndroidInitializationSettings('@mipmap/launcher_icon');
|
|
||||||
|
|
||||||
// إعدادات أزرار الآيفون (Categories)
|
|
||||||
// هذا الجزء ضروري لظهور الأزرار في iOS
|
|
||||||
final List<DarwinNotificationCategory> darwinNotificationCategories = [
|
|
||||||
DarwinNotificationCategory(
|
|
||||||
'ORDER_CATEGORY', // المعرف المستخدم لربط الإشعار بالأزرار
|
|
||||||
actions: [
|
|
||||||
DarwinNotificationAction.plain('ACCEPT_ORDER', '✅ قبول'),
|
|
||||||
DarwinNotificationAction.plain('SHOW_DETAILS', '📄 تفاصيل'),
|
|
||||||
DarwinNotificationAction.plain(
|
|
||||||
'REJECT_ORDER',
|
|
||||||
'❌ رفض',
|
|
||||||
options: {
|
|
||||||
DarwinNotificationActionOption.destructive
|
|
||||||
}, // يظهر باللون الأحمر
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
// إعدادات الآيفون العامة
|
|
||||||
final DarwinInitializationSettings ios = DarwinInitializationSettings(
|
|
||||||
requestAlertPermission: true,
|
|
||||||
requestBadgePermission: true,
|
|
||||||
requestSoundPermission: true,
|
|
||||||
notificationCategories: darwinNotificationCategories, // تسجيل الأزرار
|
|
||||||
);
|
|
||||||
|
|
||||||
InitializationSettings initializationSettings =
|
|
||||||
InitializationSettings(android: android, iOS: ios);
|
|
||||||
|
|
||||||
tz.initializeTimeZones();
|
|
||||||
print('✅ Notifications initialized with Action Buttons Support');
|
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin.initialize(
|
|
||||||
initializationSettings,
|
|
||||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
|
||||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
|
||||||
);
|
|
||||||
|
|
||||||
// إنشاء قناة الأندرويد ذات الأهمية القصوى
|
|
||||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
|
||||||
'high_importance_channel',
|
|
||||||
'High Importance Notifications',
|
|
||||||
description: 'This channel is used for important notifications.',
|
|
||||||
importance: Importance.max, // أقصى أهمية
|
|
||||||
playSound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin
|
|
||||||
.resolvePlatformSpecificImplementation<
|
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
|
||||||
?.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 2. دالة عرض الإشعار المطور (شكل واضح + أزرار + صوت مخصص)
|
|
||||||
// ==============================================================================
|
|
||||||
void showOrderNotification(
|
|
||||||
String title, String body, String tone, String myListString) async {
|
|
||||||
// أ) تنسيق النص والبيانات بشكل جميل
|
|
||||||
String formattedBigText = body;
|
|
||||||
String summaryText = 'طلب جديد';
|
|
||||||
String price = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<dynamic> data = jsonDecode(myListString);
|
|
||||||
// استخراج البيانات (تأكد أن الاندكسات مطابقة للباك إند عندك)
|
|
||||||
price = _getVal(data, 26);
|
|
||||||
String distance = _getVal(data, 5);
|
|
||||||
String startLoc = _getVal(data, 29);
|
|
||||||
String endLoc = _getVal(data, 30);
|
|
||||||
String paxName = _getVal(data, 8);
|
|
||||||
// String rating = _getVal(data, 33);
|
|
||||||
|
|
||||||
// تنسيق النص ليكون 4 أسطر واضحة
|
|
||||||
formattedBigText = "👤 $paxName\n"
|
|
||||||
"💰 $price ${'SYP'.tr} | 🛣️ $distance كم\n"
|
|
||||||
"🟢 من: $startLoc\n"
|
|
||||||
"🏁 إلى: $endLoc";
|
|
||||||
|
|
||||||
summaryText = 'سعر الرحلة: $price';
|
|
||||||
} catch (e) {
|
|
||||||
print("Error formatting notification text: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ب) نمط النص الكبير (BigText) للأندرويد
|
|
||||||
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
|
||||||
formattedBigText,
|
|
||||||
contentTitle: '🚖 $title',
|
|
||||||
summaryText: summaryText,
|
|
||||||
htmlFormatContent: true,
|
|
||||||
htmlFormatContentTitle: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ج) معالجة اسم الصوت (أندرويد بدون امتداد، آيفون مع امتداد)
|
|
||||||
String soundNameAndroid = tone.contains('.') ? tone.split('.').first : tone;
|
|
||||||
String soundNameIOS = tone.contains('.') ? tone : "$tone.wav";
|
|
||||||
|
|
||||||
// د) إعدادات الأندرويد (الأزرار + Full Screen)
|
|
||||||
final androidDetails = AndroidNotificationDetails(
|
|
||||||
'high_importance_channel',
|
|
||||||
'High Importance Notifications',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.max,
|
|
||||||
fullScreenIntent: true, // يفتح الشاشة وتظهر التفاصيل
|
|
||||||
category: AndroidNotificationCategory.call, // يعامل كمكالمة (رنين مستمر)
|
|
||||||
visibility: NotificationVisibility.public,
|
|
||||||
ongoing: true, // يمنع الحذف بالسحب
|
|
||||||
sound: RawResourceAndroidNotificationSound(soundNameAndroid),
|
|
||||||
audioAttributesUsage: AudioAttributesUsage.alarm, // صوت عالٍ كالمنبه
|
|
||||||
styleInformation: bigTextStyleInformation,
|
|
||||||
color: const Color(0xFF1A252F),
|
|
||||||
|
|
||||||
// الأزرار الثلاثة
|
|
||||||
actions: <AndroidNotificationAction>[
|
|
||||||
const AndroidNotificationAction(
|
|
||||||
'ACCEPT_ORDER',
|
|
||||||
'✅ قبول فوري',
|
|
||||||
showsUserInterface: true,
|
|
||||||
titleColor: Color(0xFF4CAF50), // أخضر
|
|
||||||
),
|
|
||||||
const AndroidNotificationAction(
|
|
||||||
'SHOW_DETAILS',
|
|
||||||
'📄 التفاصيل',
|
|
||||||
showsUserInterface: true,
|
|
||||||
titleColor: Color(0xFF2196F3), // أزرق
|
|
||||||
),
|
|
||||||
const AndroidNotificationAction(
|
|
||||||
'REJECT_ORDER',
|
|
||||||
'❌ رفض',
|
|
||||||
showsUserInterface: false, // لا يفتح التطبيق
|
|
||||||
cancelNotification: true,
|
|
||||||
titleColor: Color(0xFFE53935), // أحمر
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// هـ) إعدادات الآيفون
|
|
||||||
final iosDetails = DarwinNotificationDetails(
|
|
||||||
sound: soundNameIOS,
|
|
||||||
presentAlert: true,
|
|
||||||
presentBadge: true,
|
|
||||||
presentSound: true,
|
|
||||||
categoryIdentifier: 'ORDER_CATEGORY', // ربط الأزرار
|
|
||||||
interruptionLevel: InterruptionLevel.critical, // محاولة لكسر الصامت
|
|
||||||
);
|
|
||||||
|
|
||||||
final details =
|
|
||||||
NotificationDetails(android: androidDetails, iOS: iosDetails);
|
|
||||||
|
|
||||||
// عرض الإشعار
|
|
||||||
await _flutterLocalNotificationsPlugin.show(
|
|
||||||
1001, // ID ثابت لاستبدال الإشعار القديم
|
|
||||||
title,
|
|
||||||
"$price - مسافة $formattedBigText", // نص مختصر يظهر في البار العلوي
|
|
||||||
details,
|
|
||||||
payload: jsonEncode({
|
|
||||||
'type': 'Order',
|
|
||||||
'data': myListString,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 3. معالجة الاستجابة (عند الضغط على الأزرار)
|
|
||||||
// ==============================================================================
|
|
||||||
Future<void> handleNotificationResponse(NotificationResponse response) async {
|
|
||||||
final payload = response.payload;
|
|
||||||
if (payload == null) return;
|
|
||||||
|
|
||||||
final payloadData = jsonDecode(payload) as Map<String, dynamic>;
|
|
||||||
final rawData = payloadData['data'];
|
|
||||||
|
|
||||||
List<dynamic> listData = [];
|
|
||||||
if (rawData is String) {
|
|
||||||
listData = jsonDecode(rawData);
|
|
||||||
} else if (rawData is List) {
|
|
||||||
listData = rawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("🔔 Notification Action: ${response.actionId}");
|
|
||||||
|
|
||||||
// أ) زر القبول
|
|
||||||
if (response.actionId == 'ACCEPT_ORDER') {
|
|
||||||
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
|
|
||||||
_processAcceptOrder(listData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ب) زر التفاصيل
|
|
||||||
else if (response.actionId == 'SHOW_DETAILS') {
|
|
||||||
// await _flutterLocalNotificationsPlugin.cancel(1001); // اختياري: حذف الإشعار
|
|
||||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ج) زر الرفض
|
|
||||||
else if (response.actionId == 'REJECT_ORDER') {
|
|
||||||
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
|
|
||||||
_processRejectOrder(listData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// د) الضغط على الإشعار نفسه (بدون أزرار)
|
|
||||||
else {
|
|
||||||
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 4. منطق القبول الآمن (Safe Accept Logic)
|
|
||||||
// ==============================================================================
|
|
||||||
Future<void> _processAcceptOrder(List<dynamic> data) async {
|
|
||||||
// إظهار Loading
|
|
||||||
Get.dialog(
|
|
||||||
WillPopScope(
|
|
||||||
onWillPop: () async => false,
|
|
||||||
child: const Center(
|
|
||||||
child: CircularProgressIndicator(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
barrierDismissible: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final driverId = box.read(BoxName.driverID);
|
|
||||||
String orderId = _getVal(data, 16);
|
|
||||||
String passengerToken = _getVal(data, 9);
|
|
||||||
|
|
||||||
print("🚀 Sending Accept Request for Order: $orderId");
|
|
||||||
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: "${AppLink.ride}/rides/acceptRide.php",
|
|
||||||
payload: {
|
|
||||||
'id': orderId,
|
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
|
||||||
'status': 'Apply',
|
|
||||||
'passengerToken': passengerToken,
|
|
||||||
'driver_id': driverId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
print("📥 Server Response: $res");
|
|
||||||
|
|
||||||
if (Get.isDialogOpen == true) Get.back(); // إغلاق اللودينج
|
|
||||||
|
|
||||||
// 🔴 فحص النتيجة بدقة (Map أو String)
|
|
||||||
bool isFailure = false;
|
|
||||||
if (res is Map && res['status'] == 'failure') {
|
|
||||||
isFailure = true;
|
|
||||||
} else if (res == 'failure') {
|
|
||||||
isFailure = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFailure) {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: "تنبيه",
|
|
||||||
middleText: "عذراً، الطلب أخذه سائق آخر.",
|
|
||||||
confirmTextColor: Colors.white,
|
|
||||||
onConfirm: () => Get.back(),
|
|
||||||
textConfirm: "حسناً",
|
|
||||||
);
|
|
||||||
return; // توقف هنا ولا تكمل
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ نجاح -> تجهيز الانتقال
|
|
||||||
|
|
||||||
// حماية من الكراش: التأكد من وجود HomeCaptainController
|
|
||||||
if (!Get.isRegistered<HomeCaptainController>()) {
|
|
||||||
print("♻️ Reviving HomeCaptainController...");
|
|
||||||
Get.put(HomeCaptainController());
|
|
||||||
} else {
|
|
||||||
Get.find<HomeCaptainController>().changeRideId();
|
|
||||||
}
|
|
||||||
|
|
||||||
box.write(BoxName.statusDriverLocation, 'on');
|
|
||||||
box.write(BoxName.rideStatus, 'Apply');
|
|
||||||
|
|
||||||
var rideArgs = _buildRideArgs(data);
|
|
||||||
box.write(BoxName.rideArguments, rideArgs);
|
|
||||||
|
|
||||||
// استخدام offAll لمنع الرجوع لصفحة الطلب
|
|
||||||
Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs);
|
|
||||||
} catch (e) {
|
|
||||||
if (Get.isDialogOpen == true) Get.back();
|
|
||||||
print("❌ Error in accept process: $e");
|
|
||||||
Get.snackbar("خطأ", "حدث خطأ غير متوقع");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 5. منطق الرفض (يعمل في الخلفية بدون فتح صفحات)
|
|
||||||
// ==============================================================================
|
|
||||||
Future<void> _processRejectOrder(List<dynamic> data) async {
|
|
||||||
try {
|
|
||||||
final driverId = box.read(BoxName.driverID);
|
|
||||||
String orderId = _getVal(data, 16);
|
|
||||||
|
|
||||||
if (driverId != null && orderId.isNotEmpty) {
|
|
||||||
print("📤 Rejecting Order: $orderId");
|
|
||||||
await CRUD().post(link: AppLink.addDriverOrder, payload: {
|
|
||||||
'driver_id': driverId,
|
|
||||||
'order_id': orderId,
|
|
||||||
'status': 'Refused'
|
|
||||||
});
|
|
||||||
print("✅ Order Rejected Successfully");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("❌ Error rejecting order: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 6. دوال مساعدة (Helpers)
|
|
||||||
// ==============================================================================
|
|
||||||
|
|
||||||
Map<String, dynamic> _buildRideArgs(List<dynamic> data) {
|
|
||||||
return {
|
|
||||||
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
|
|
||||||
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
|
|
||||||
'Duration': _getVal(data, 4), // انتبه: تأكد من الإندكس الصحيح للوقت
|
|
||||||
'totalCost': _getVal(data, 26),
|
|
||||||
'Distance': _getVal(data, 5),
|
|
||||||
'name': _getVal(data, 8),
|
|
||||||
'phone': _getVal(data, 10),
|
|
||||||
'email': _getVal(data, 28),
|
|
||||||
'WalletChecked': _getVal(data, 13),
|
|
||||||
'tokenPassenger': _getVal(data, 9),
|
|
||||||
'direction':
|
|
||||||
'https://www.google.com/maps/dir/${_getVal(data, 0)}/${_getVal(data, 1)}/',
|
|
||||||
'DurationToPassenger': _getVal(data, 15),
|
|
||||||
'rideId': _getVal(data, 16),
|
|
||||||
'passengerId': _getVal(data, 7),
|
|
||||||
'driverId': _getVal(data, 18),
|
|
||||||
'durationOfRideValue': _getVal(data, 19),
|
|
||||||
'paymentAmount': _getVal(data, 2),
|
|
||||||
'paymentMethod': _getVal(data, 13) == 'true' ? 'visa' : 'cash',
|
|
||||||
'isHaveSteps': _getVal(data, 20),
|
|
||||||
'step0': _getVal(data, 21),
|
|
||||||
'step1': _getVal(data, 22),
|
|
||||||
'step2': _getVal(data, 23),
|
|
||||||
'step3': _getVal(data, 24),
|
|
||||||
'step4': _getVal(data, 25),
|
|
||||||
'passengerWalletBurc': _getVal(data, 26),
|
|
||||||
'timeOfOrder': DateTime.now().toString(),
|
|
||||||
'totalPassenger': _getVal(data, 2),
|
|
||||||
'carType': _getVal(data, 31),
|
|
||||||
'kazan': _getVal(data, 32),
|
|
||||||
'startNameLocation': _getVal(data, 29),
|
|
||||||
'endNameLocation': _getVal(data, 30),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getVal(List<dynamic> data, int index) {
|
|
||||||
if (data.length > index && data[index] != null) {
|
|
||||||
return data[index].toString();
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
|
||||||
handleNotificationResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDidReceiveBackgroundNotificationResponse(
|
|
||||||
NotificationResponse response) {
|
|
||||||
handleNotificationResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// 7. الدوال القديمة (Old Scheduled Notifications) - لم يتم تغييرها
|
|
||||||
// ==============================================================================
|
|
||||||
|
|
||||||
void showNotification(
|
|
||||||
String title, String message, String tone, String payLoad) async {
|
|
||||||
// هذه الدالة القديمة للإشعارات البسيطة (ليس الطلبات)
|
|
||||||
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
|
|
||||||
message,
|
|
||||||
contentTitle: title.tr,
|
|
||||||
htmlFormatContent: true,
|
|
||||||
htmlFormatContentTitle: true,
|
|
||||||
);
|
|
||||||
AndroidNotificationDetails android = AndroidNotificationDetails(
|
|
||||||
'high_importance_channel',
|
|
||||||
'High Importance Notifications',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
|
||||||
);
|
|
||||||
|
|
||||||
DarwinNotificationDetails ios = const DarwinNotificationDetails(
|
|
||||||
sound: 'default',
|
|
||||||
presentAlert: true,
|
|
||||||
presentBadge: true,
|
|
||||||
presentSound: true,
|
|
||||||
);
|
|
||||||
NotificationDetails details =
|
|
||||||
NotificationDetails(android: android, iOS: ios);
|
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin.show(0, title, message, details,
|
|
||||||
payload: jsonEncode({'title': title, 'data': payLoad}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void scheduleNotificationsForSevenDays(
|
|
||||||
String title, String message, String tone) async {
|
|
||||||
final AndroidNotificationDetails android = AndroidNotificationDetails(
|
|
||||||
'high_importance_channel',
|
|
||||||
'High Importance Notifications',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
|
|
||||||
);
|
|
||||||
|
|
||||||
const DarwinNotificationDetails ios = DarwinNotificationDetails(
|
|
||||||
sound: 'default',
|
|
||||||
presentAlert: true,
|
|
||||||
presentBadge: true,
|
|
||||||
presentSound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
final NotificationDetails details =
|
|
||||||
NotificationDetails(android: android, iOS: ios);
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
if (await Permission.scheduleExactAlarm.isDenied) {
|
|
||||||
await Permission.scheduleExactAlarm.request();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int day = 0; day < 7; day++) {
|
|
||||||
final notificationTimes = [
|
|
||||||
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1},
|
|
||||||
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2},
|
|
||||||
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var time in notificationTimes) {
|
|
||||||
final notificationId = time['id'] as int;
|
|
||||||
bool isScheduled = box.read('notification_$notificationId') ?? false;
|
|
||||||
|
|
||||||
if (!isScheduled) {
|
|
||||||
await _scheduleNotificationForTime(
|
|
||||||
day,
|
|
||||||
time['hour'] as int,
|
|
||||||
time['minute'] as int,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
details,
|
|
||||||
notificationId,
|
|
||||||
);
|
|
||||||
box.write('notification_$notificationId', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================================================================
|
|
||||||
// دالة حذف إشعار الطلب (تستدعى عند أخذ الطلب من سائق آخر)
|
|
||||||
// ==============================================================================
|
|
||||||
Future<void> cancelOrderNotification() async {
|
|
||||||
// 1001 هو نفس الآيدي الذي استخدمناه عند عرض الإشعار
|
|
||||||
await _flutterLocalNotificationsPlugin.cancel(1001);
|
|
||||||
print("🗑️ Order Notification Cancelled (Taken by another driver)");
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _scheduleNotificationForTime(
|
|
||||||
int dayOffset,
|
|
||||||
int hour,
|
|
||||||
int minute,
|
|
||||||
String title,
|
|
||||||
String message,
|
|
||||||
NotificationDetails details,
|
|
||||||
int notificationId,
|
|
||||||
) async {
|
|
||||||
tz.initializeTimeZones();
|
|
||||||
var cairoLocation =
|
|
||||||
tz.getLocation('Africa/Cairo'); // تأكد من المنطقة الزمنية
|
|
||||||
|
|
||||||
final now = tz.TZDateTime.now(cairoLocation);
|
|
||||||
tz.TZDateTime scheduledDate = tz.TZDateTime(
|
|
||||||
cairoLocation,
|
|
||||||
now.year,
|
|
||||||
now.month,
|
|
||||||
now.day + dayOffset,
|
|
||||||
hour,
|
|
||||||
minute,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (scheduledDate.isBefore(now)) {
|
|
||||||
scheduledDate = scheduledDate.add(const Duration(days: 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
|
||||||
notificationId,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
scheduledDate,
|
|
||||||
details,
|
|
||||||
androidScheduleMode: AndroidScheduleMode.exact,
|
|
||||||
uiLocalNotificationDateInterpretation:
|
|
||||||
UILocalNotificationDateInterpretation.absoluteTime,
|
|
||||||
matchDateTimeComponents: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../../constant/links.dart'; // للترجمة .tr
|
|
||||||
|
|
||||||
class NotificationService {
|
|
||||||
static final String _serverUrl =
|
|
||||||
'${AppLink.server}/ride/firebase/send_fcm.php';
|
|
||||||
|
|
||||||
static Future<void> sendNotification({
|
|
||||||
required String target,
|
|
||||||
required String title,
|
|
||||||
required String body,
|
|
||||||
required String category, // إلزامي للتصنيف
|
|
||||||
String? tone,
|
|
||||||
List<String>? driverList,
|
|
||||||
bool isTopic = false,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// 1. تجهيز البيانات المخصصة (Data Payload)
|
|
||||||
Map<String, dynamic> customData = {};
|
|
||||||
|
|
||||||
customData['category'] = category;
|
|
||||||
|
|
||||||
// إذا كان هناك قائمة سائقين/ركاب، نضعها هنا
|
|
||||||
if (driverList != null && driverList.isNotEmpty) {
|
|
||||||
// نرسلها كـ JSON String لأن FCM v1 يدعم String Values فقط في الـ data
|
|
||||||
customData['driverList'] = jsonEncode(driverList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. تجهيز الطلب الرئيسي للسيرفر
|
|
||||||
final Map<String, dynamic> requestPayload = {
|
|
||||||
'target': target,
|
|
||||||
'title': title,
|
|
||||||
'body': body,
|
|
||||||
'isTopic': isTopic,
|
|
||||||
'data':
|
|
||||||
customData, // 🔥🔥 التغيير الجوهري: وضعنا البيانات داخل "data" 🔥🔥
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tone != null) {
|
|
||||||
requestPayload['tone'] = tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse(_serverUrl),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
|
||||||
},
|
|
||||||
body: jsonEncode(requestPayload),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
print('✅ Notification sent successfully.');
|
|
||||||
// print('Response: ${response.body}');
|
|
||||||
} else {
|
|
||||||
print('❌ Failed to send notification. Code: ${response.statusCode}');
|
|
||||||
print('Error Body: ${response.body}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('❌ Error sending notification: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class OverlayContent1 extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Material(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
color: Colors.white,
|
|
||||||
child: MyElevatedButton(
|
|
||||||
title: 'go to order',
|
|
||||||
onPressed: () async {
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: AppLink.addFeedBack,
|
|
||||||
payload: {
|
|
||||||
"passengerId": 'dddddd',
|
|
||||||
"feedBack": "eeeee",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
print(res);
|
|
||||||
if (res != 'failure') {
|
|
||||||
Navigator.push(
|
|
||||||
context, MaterialPageRoute(builder: (cont) => HomeCaptain()));
|
|
||||||
// Get.to(OrderRequestPage());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import 'crud.dart';
|
|
||||||
|
|
||||||
addError1(String error, String details, String where) async {
|
|
||||||
try {
|
|
||||||
// Get user information for the error log
|
|
||||||
final userId = box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
|
|
||||||
final userType =
|
|
||||||
box.read(BoxName.driverID) != null ? 'Driver' : 'passenger';
|
|
||||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
|
||||||
|
|
||||||
// Send the error data to the server
|
|
||||||
// Note: This is a fire-and-forget call. We don't await it or handle its response
|
|
||||||
// to prevent an infinite loop if the addError endpoint itself is failing.
|
|
||||||
CRUD().post(
|
|
||||||
link: AppLink.addError,
|
|
||||||
payload: {
|
|
||||||
'error': error.toString(),
|
|
||||||
'userId': userId.toString(),
|
|
||||||
'userType': userType,
|
|
||||||
'phone': phone.toString(),
|
|
||||||
'device': where, // The location of the error
|
|
||||||
'details': details, // The detailed stack trace or context
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// If logging the error itself fails, print to the console to avoid infinite loops.
|
|
||||||
print("Failed to log error to server: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:just_audio/just_audio.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class AudioController extends GetxController {
|
|
||||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
|
||||||
|
|
||||||
Future<void> playAudio() async {
|
|
||||||
// Check if the platform is Android
|
|
||||||
if (Theme.of(Get.context!).platform == TargetPlatform.android) {
|
|
||||||
try {
|
|
||||||
// Load the audio file from the raw resources
|
|
||||||
await _audioPlayer.setAsset(
|
|
||||||
'assets/order1.wav'); // Adjust the path based on your project structure
|
|
||||||
_audioPlayer.play();
|
|
||||||
} catch (e) {
|
|
||||||
// Handle errors, such as file not found
|
|
||||||
print('Error playing audio: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> playAudio1(String path) async {
|
|
||||||
// Check if the platform is Android
|
|
||||||
// if (Theme.of(Get.context!).platform == TargetPlatform.android) {
|
|
||||||
try {
|
|
||||||
// Load the audio file from the raw resources
|
|
||||||
await _audioPlayer
|
|
||||||
.setAsset(path); // Adjust the path based on your project structure
|
|
||||||
_audioPlayer.play();
|
|
||||||
} catch (e) {
|
|
||||||
// Handle errors, such as file not found
|
|
||||||
print('Error playing audio: $e');
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
// Release resources when done
|
|
||||||
_audioPlayer.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
|
||||||
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
|
||||||
import 'package:socket_io_client/socket_io_client.dart' as IO;
|
|
||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
|
|
||||||
import 'package:get_storage/get_storage.dart';
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
|
|
||||||
const String notificationChannelId = 'driver_service_channel';
|
|
||||||
const int notificationId = 888;
|
|
||||||
const String notificationIcon = '@mipmap/launcher_icon';
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
Future<bool> onStart(ServiceInstance service) async {
|
|
||||||
DartPluginRegistrant.ensureInitialized();
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
||||||
FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
await GetStorage.init();
|
|
||||||
final box = GetStorage();
|
|
||||||
|
|
||||||
IO.Socket? socket;
|
|
||||||
String driverId = box.read(BoxName.driverID) ?? '';
|
|
||||||
String token = box.read(BoxName.tokenDriver) ?? '';
|
|
||||||
|
|
||||||
if (driverId.isNotEmpty) {
|
|
||||||
socket = IO.io(
|
|
||||||
'https://location.intaleq.xyz',
|
|
||||||
IO.OptionBuilder()
|
|
||||||
.setTransports(['websocket'])
|
|
||||||
.disableAutoConnect()
|
|
||||||
.setQuery({'driver_id': driverId, 'token': token})
|
|
||||||
.setReconnectionAttempts(double.infinity)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
socket.connect();
|
|
||||||
|
|
||||||
socket.onConnect((_) {
|
|
||||||
print("✅ Background Service: Socket Connected!");
|
|
||||||
if (service is AndroidServiceInstance) {
|
|
||||||
flutterLocalNotificationsPlugin.show(
|
|
||||||
notificationId,
|
|
||||||
'أنت متصل الآن',
|
|
||||||
'بانتظار الطلبات...',
|
|
||||||
const NotificationDetails(
|
|
||||||
android: AndroidNotificationDetails(
|
|
||||||
notificationChannelId,
|
|
||||||
'خدمة السائق',
|
|
||||||
icon: notificationIcon,
|
|
||||||
ongoing: true,
|
|
||||||
importance: Importance.low,
|
|
||||||
priority: Priority.low,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('new_ride_request', (data) async {
|
|
||||||
print("🔔 Background Service: Received new_ride_request");
|
|
||||||
|
|
||||||
// 🔥 قراءة حالة التطبيق مباشرة قبل العرض
|
|
||||||
await GetStorage.init(); // تأكد من تحديث البيانات
|
|
||||||
final box = GetStorage();
|
|
||||||
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
|
|
||||||
|
|
||||||
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟
|
|
||||||
bool overlayActive = await Overlay.FlutterOverlayWindow.isActive();
|
|
||||||
|
|
||||||
if (isAppInForeground || overlayActive) {
|
|
||||||
print("🛑 App is FOREGROUND or Overlay already shown. Skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// عرض الـ Overlay
|
|
||||||
print("🚀 App is BACKGROUND. Showing Overlay...");
|
|
||||||
try {
|
|
||||||
await Overlay.FlutterOverlayWindow.showOverlay(
|
|
||||||
enableDrag: true,
|
|
||||||
overlayTitle: "طلب جديد",
|
|
||||||
overlayContent: "لديك طلب جديد وصل للتو!",
|
|
||||||
flag: OverlayFlag.focusPointer,
|
|
||||||
positionGravity: PositionGravity.auto,
|
|
||||||
height: WindowSize.matchParent,
|
|
||||||
width: WindowSize.matchParent,
|
|
||||||
startPosition: const OverlayPosition(0, -30),
|
|
||||||
);
|
|
||||||
await Overlay.FlutterOverlayWindow.shareData(data);
|
|
||||||
} catch (e) {
|
|
||||||
print("Overlay Error: $e");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
service.on('stopService').listen((event) {
|
|
||||||
socket?.disconnect();
|
|
||||||
service.stopSelf();
|
|
||||||
});
|
|
||||||
|
|
||||||
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
|
||||||
if (service is AndroidServiceInstance) {
|
|
||||||
if (await service.isForegroundService()) {
|
|
||||||
flutterLocalNotificationsPlugin.show(
|
|
||||||
notificationId,
|
|
||||||
'خدمة السائق نشطة',
|
|
||||||
'بانتظار الطلبات...',
|
|
||||||
const NotificationDetails(
|
|
||||||
android: AndroidNotificationDetails(
|
|
||||||
notificationChannelId,
|
|
||||||
'خدمة السائق',
|
|
||||||
icon: notificationIcon,
|
|
||||||
ongoing: true,
|
|
||||||
importance: Importance.low,
|
|
||||||
priority: Priority.low,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BackgroundServiceHelper {
|
|
||||||
static Future<void> initialize() async {
|
|
||||||
final service = FlutterBackgroundService();
|
|
||||||
|
|
||||||
await service.configure(
|
|
||||||
androidConfiguration: AndroidConfiguration(
|
|
||||||
onStart: onStart,
|
|
||||||
autoStart: false,
|
|
||||||
isForegroundMode: true,
|
|
||||||
notificationChannelId: notificationChannelId,
|
|
||||||
initialNotificationTitle: 'تطبيق السائق',
|
|
||||||
initialNotificationContent: 'تجهيز الخدمة...',
|
|
||||||
foregroundServiceNotificationId: notificationId,
|
|
||||||
),
|
|
||||||
iosConfiguration: IosConfiguration(
|
|
||||||
autoStart: false,
|
|
||||||
onForeground: onStart,
|
|
||||||
onBackground: onStart,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> startService() async {
|
|
||||||
final service = FlutterBackgroundService();
|
|
||||||
if (!await service.isRunning()) {
|
|
||||||
await service.startService();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> stopService() async {
|
|
||||||
final service = FlutterBackgroundService();
|
|
||||||
service.invoke("stopService");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import 'package:battery_plus/battery_plus.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class BatteryNotifier {
|
|
||||||
static final Battery _battery = Battery();
|
|
||||||
static int? _lastNotifiedLevel;
|
|
||||||
|
|
||||||
static Future<void> checkBatteryAndNotify() async {
|
|
||||||
try {
|
|
||||||
final int batteryLevel = await _battery.batteryLevel;
|
|
||||||
|
|
||||||
// ✅ لا تكرر الإشعار إذا الفرق قليل
|
|
||||||
if (_lastNotifiedLevel != null &&
|
|
||||||
(batteryLevel >= _lastNotifiedLevel! - 2)) return;
|
|
||||||
|
|
||||||
if (batteryLevel <= 30) {
|
|
||||||
Color backgroundColor = Colors.yellow;
|
|
||||||
if (batteryLevel <= 20) {
|
|
||||||
backgroundColor = Colors.red;
|
|
||||||
}
|
|
||||||
|
|
||||||
Get.snackbar(
|
|
||||||
"⚠️ تنبيه البطارية", // العنوان
|
|
||||||
"مستوى البطارية: $batteryLevel٪", // النص
|
|
||||||
snackPosition: SnackPosition.TOP,
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
colorText: Colors.white,
|
|
||||||
duration: const Duration(seconds: 10), // مدة الظهور
|
|
||||||
margin: const EdgeInsets.all(10),
|
|
||||||
);
|
|
||||||
|
|
||||||
_lastNotifiedLevel = batteryLevel;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Battery check error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
// import 'package:SEFER/constant/api_key.dart';
|
|
||||||
// import 'package:SEFER/controller/functions/crud.dart';
|
|
||||||
// // import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
|
||||||
// import 'package:get/get.dart';
|
|
||||||
// import 'package:permission_handler/permission_handler.dart';
|
|
||||||
|
|
||||||
// import '../../constant/box_name.dart';
|
|
||||||
// import '../firebase/firbase_messge.dart';
|
|
||||||
// import '../home/captin/map_driver_controller.dart';
|
|
||||||
// import '../../main.dart';
|
|
||||||
|
|
||||||
// class CallController extends GetxController {
|
|
||||||
// String channelName = ''; // Get.find<MapDriverController>().rideId;
|
|
||||||
// String token = '';
|
|
||||||
// // int uid = int.parse(box.read(BoxName.phoneDriver)); // uid of the local user
|
|
||||||
// int uid = 0;
|
|
||||||
// int? remoteUid; // uid of the remote user
|
|
||||||
// bool _isJoined = false; // Indicates if the local user has joined the channel
|
|
||||||
// String status = '';
|
|
||||||
// // late RtcEngine agoraEngine; // Agora engine instance
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// void onInit() {
|
|
||||||
// super.onInit();
|
|
||||||
|
|
||||||
// channelName = Get.find<MapDriverController>().rideId; // 'sefer300'; //
|
|
||||||
// remoteUid = int.parse(Get.find<MapDriverController>().passengerPhone);
|
|
||||||
// uid = int.parse(box.read(BoxName.phoneDriver));
|
|
||||||
|
|
||||||
// initAgoraFull();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// initAgoraFull() async {
|
|
||||||
// await fetchToken();
|
|
||||||
// // Set up an instance of Agora engine
|
|
||||||
// setupVoiceSDKEngine();
|
|
||||||
// // join();
|
|
||||||
// FirebaseMessagesController().sendNotificationToPassengerTokenCALL(
|
|
||||||
// 'Call Income',
|
|
||||||
// '${'You have call from driver'.tr} ${box.read(BoxName.nameDriver)}',
|
|
||||||
// Get.find<MapDriverController>().tokenPassenger,
|
|
||||||
// [
|
|
||||||
// token,
|
|
||||||
// channelName,
|
|
||||||
// uid.toString(),
|
|
||||||
// remoteUid.toString(),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// join();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// void onClose() {
|
|
||||||
// // agoraEngine.leaveChannel();
|
|
||||||
// super.onClose();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Future<void> setupVoiceSDKEngine() async {
|
|
||||||
// // // retrieve or request microphone permission
|
|
||||||
// // await [Permission.microphone].request();
|
|
||||||
|
|
||||||
// // //create an instance of the Agora engine
|
|
||||||
// // agoraEngine = createAgoraRtcEngine();
|
|
||||||
// // await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
|
|
||||||
// // // Register the event handler
|
|
||||||
// // agoraEngine.registerEventHandler(
|
|
||||||
// // RtcEngineEventHandler(
|
|
||||||
// // onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
|
||||||
// // // Get.snackbar(
|
|
||||||
// // // "Local user uid:${connection.localUid} joined the channel", '');
|
|
||||||
// // status = 'joined'.tr;
|
|
||||||
// // _isJoined = true;
|
|
||||||
// // update();
|
|
||||||
// // },
|
|
||||||
// // onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
|
|
||||||
// // // Get.snackbar("Remote user uid:$remoteUid joined the channel", '');
|
|
||||||
// // status = '${Get.find<MapDriverController>().passengerName} '
|
|
||||||
// // 'joined'
|
|
||||||
// // .tr;
|
|
||||||
// // remoteUid = remoteUid;
|
|
||||||
// // update();
|
|
||||||
// // },
|
|
||||||
// // onUserOffline: (RtcConnection connection, int? remoteUid,
|
|
||||||
// // UserOfflineReasonType reason) {
|
|
||||||
// // // Get.snackbar("Remote user uid:$remoteUid left the channel", '');
|
|
||||||
// // status = 'Call Left'.tr;
|
|
||||||
// // remoteUid = null;
|
|
||||||
// // update();
|
|
||||||
// // },
|
|
||||||
// // ),
|
|
||||||
// // );
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // void join() async {
|
|
||||||
// // // Set channel options including the client role and channel profile
|
|
||||||
// // ChannelMediaOptions options = const ChannelMediaOptions(
|
|
||||||
// // clientRoleType: ClientRoleType.clientRoleBroadcaster,
|
|
||||||
// // channelProfile: ChannelProfileType.channelProfileCommunication,
|
|
||||||
// // );
|
|
||||||
|
|
||||||
// // await agoraEngine.joinChannel(
|
|
||||||
// // token: token,
|
|
||||||
// // channelId: channelName,
|
|
||||||
// // options: options,
|
|
||||||
// // uid: uid,
|
|
||||||
// // );
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // void leave() {
|
|
||||||
// // _isJoined = false;
|
|
||||||
// // remoteUid = null;
|
|
||||||
// // update();
|
|
||||||
// // agoraEngine.leaveChannel();
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // // Clean up the resources when you leave
|
|
||||||
// // @override
|
|
||||||
// // void dispose() async {
|
|
||||||
// // await agoraEngine.leaveChannel();
|
|
||||||
// // super.dispose();
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// fetchToken() async {
|
|
||||||
// var res = await CRUD()
|
|
||||||
// .getAgoraToken(channelName: channelName, uid: uid.toString());
|
|
||||||
// token = res;
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import '../../main.dart';
|
|
||||||
|
|
||||||
class CameraClassController extends GetxController {
|
|
||||||
late CameraController cameraController;
|
|
||||||
late List<CameraDescription> cameras;
|
|
||||||
bool isCameraInitialized = false;
|
|
||||||
// final TextRecognizer _textRecognizer = TextRecognizer();
|
|
||||||
String? scannedText;
|
|
||||||
bool isloading = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
initializeCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initializeCamera() async {
|
|
||||||
try {
|
|
||||||
cameras = await availableCameras();
|
|
||||||
//update();
|
|
||||||
cameraController = CameraController(
|
|
||||||
cameras[0],
|
|
||||||
ResolutionPreset.medium,
|
|
||||||
enableAudio: false,
|
|
||||||
);
|
|
||||||
await cameraController.initialize();
|
|
||||||
isCameraInitialized = true;
|
|
||||||
update();
|
|
||||||
} catch (e) {
|
|
||||||
if (e is CameraException) {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'CameraAccessDenied':
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Camera Access Denied.'.tr,
|
|
||||||
middleText: '',
|
|
||||||
confirm:
|
|
||||||
MyElevatedButton(title: 'Open Settings'.tr, onPressed: () {}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Handle other errors here.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imgUrl = '';
|
|
||||||
Future extractCardId() async {
|
|
||||||
// Construct the path for the image file
|
|
||||||
final directory = await path_provider.getTemporaryDirectory();
|
|
||||||
final imagePath =
|
|
||||||
path.join(directory.path, '${box.read(BoxName.driverID)}.png');
|
|
||||||
|
|
||||||
// Capture the image and save it to the specified path
|
|
||||||
final XFile capturedImage = await cameraController.takePicture();
|
|
||||||
|
|
||||||
// Move the captured image to the desired path
|
|
||||||
await capturedImage.saveTo(imagePath);
|
|
||||||
await uploadImage(File(capturedImage.path));
|
|
||||||
|
|
||||||
extractByAPI('${AppLink.server}/card_image/' + box.read(BoxName.driverID));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future extractByAPI(String imgUrl) async {
|
|
||||||
var headers = {'apikey': 'K89368168788957'};
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST', Uri.parse('https://api.ocr.space/parse/image'));
|
|
||||||
request.fields.addAll({
|
|
||||||
'language': 'ara',
|
|
||||||
'isOverlayRequired': 'false',
|
|
||||||
'url': imgUrl,
|
|
||||||
'iscreatesearchablepdf': 'false',
|
|
||||||
'issearchablepdfhidetextlayer': 'false'
|
|
||||||
});
|
|
||||||
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
http.StreamedResponse response = await request.send();
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> uploadImage(File imageFile) async {
|
|
||||||
String? basicAuthCredentials =
|
|
||||||
await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(AppLink.uploadImage),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attach the image file to the request
|
|
||||||
request.files.add(
|
|
||||||
await http.MultipartFile.fromPath('image', imageFile.path),
|
|
||||||
); // Add the headers to the request
|
|
||||||
request.headers.addAll({
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the driverID to the request
|
|
||||||
request.fields['driverID'] = box.read(BoxName.driverID);
|
|
||||||
// Send the request
|
|
||||||
var response = await request.send();
|
|
||||||
|
|
||||||
// Read the response
|
|
||||||
var responseData = await response.stream.toBytes();
|
|
||||||
var responseString = String.fromCharCodes(responseData);
|
|
||||||
scannedText = responseString;
|
|
||||||
update();
|
|
||||||
// Return the link received from the server
|
|
||||||
return responseString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Future<void> takePictureAndMLGoogleScan() async {
|
|
||||||
// try {
|
|
||||||
// // Construct the path for the image file
|
|
||||||
// final directory = await path_provider.getTemporaryDirectory();
|
|
||||||
// final imagePath =
|
|
||||||
// path.join(directory.path, '${box.read(BoxName.driverID)}.png');
|
|
||||||
|
|
||||||
// // Capture the image and save it to the specified path
|
|
||||||
// final XFile capturedImage = await cameraController.takePicture();
|
|
||||||
|
|
||||||
// // Move the captured image to the desired path
|
|
||||||
// await capturedImage.saveTo(imagePath);
|
|
||||||
|
|
||||||
// // Recognize the text in the image
|
|
||||||
// final InputImage inputImage =
|
|
||||||
// InputImage.fromFile(File(capturedImage.path));
|
|
||||||
// final RecognizedText recognizedText =
|
|
||||||
// await _textRecognizer.processImage(inputImage);
|
|
||||||
// scannedText = recognizedText.text;
|
|
||||||
|
|
||||||
// // Extract the scanned text line by line
|
|
||||||
// final List<Map<String, dynamic>> lines = [];
|
|
||||||
// for (var i = 0; i < recognizedText.blocks.length; i++) {
|
|
||||||
// lines.add({
|
|
||||||
// 'line_number': i,
|
|
||||||
// 'text': recognizedText.blocks[i].text,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Convert the list of lines to a JSON string
|
|
||||||
// final String jsonOutput = jsonEncode(lines);
|
|
||||||
|
|
||||||
// update();
|
|
||||||
|
|
||||||
// // Print the JSON output
|
|
||||||
|
|
||||||
// // Get.back();
|
|
||||||
// } catch (e) {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
String getTextAsJSON(String text) {
|
|
||||||
final lines = text.split('\n');
|
|
||||||
final jsonList = lines.map((line) {
|
|
||||||
return {
|
|
||||||
'line_text': line,
|
|
||||||
'num_words': line.trim().split(' ').length,
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final json = {
|
|
||||||
'lines': jsonList,
|
|
||||||
'num_lines': lines.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
return jsonEncode(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> getTextBlocks(String text) {
|
|
||||||
return text.split('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Future<void> takePictureAndTesseractScan() async {
|
|
||||||
// try {
|
|
||||||
// // Construct the path for the image file
|
|
||||||
// final directory = await path_provider.getTemporaryDirectory();
|
|
||||||
// final imagePath =
|
|
||||||
// path.join(directory.path, '${box.read(BoxName.driverID)}.png');
|
|
||||||
|
|
||||||
// // Capture the image and save it to the specified path
|
|
||||||
// final XFile capturedImage = await cameraController.takePicture();
|
|
||||||
|
|
||||||
// // Move the captured image to the desired path
|
|
||||||
// await capturedImage.saveTo(imagePath);
|
|
||||||
|
|
||||||
// // Recognize the text in the image
|
|
||||||
// final languages = [
|
|
||||||
// 'eng',
|
|
||||||
// 'ara'
|
|
||||||
// ]; // Specify the languages you want to use for text extraction
|
|
||||||
|
|
||||||
// final text = await FlutterTesseractOcr.extractText(imagePath,
|
|
||||||
// language: languages.join('+'), // Combine multiple languages with '+'
|
|
||||||
// args: {
|
|
||||||
// "psm": "4",
|
|
||||||
// "preserve_interword_spaces": "1",
|
|
||||||
// // "rectangle": const Rect.fromLTWH(100, 100, 200, 200),
|
|
||||||
// } // Additional options if needed
|
|
||||||
// );
|
|
||||||
// isloading = false;
|
|
||||||
// final jsonText = getTextAsJSON(text);
|
|
||||||
// final textBlocks = getTextBlocks(text);
|
|
||||||
// update();
|
|
||||||
// scannedText =
|
|
||||||
// textBlocks.toString(); // Convert the extracted text to JSON.
|
|
||||||
|
|
||||||
// // Print the JSON to the console.
|
|
||||||
// update();
|
|
||||||
// } catch (e) {
|
|
||||||
// scannedText = '';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
cameraController.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,660 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:sefer_driver/env/env.dart';
|
|
||||||
|
|
||||||
import '../../constant/api_key.dart';
|
|
||||||
import '../../constant/char_map.dart';
|
|
||||||
import '../../constant/info.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import 'network/net_guard.dart';
|
|
||||||
import 'upload_image.dart';
|
|
||||||
|
|
||||||
class CRUD {
|
|
||||||
final NetGuard _netGuard = NetGuard();
|
|
||||||
final _client = http.Client();
|
|
||||||
|
|
||||||
/// Stores the signature of the last logged error to prevent duplicates.
|
|
||||||
static String _lastErrorSignature = '';
|
|
||||||
|
|
||||||
/// Stores the timestamp of the last logged error.
|
|
||||||
static DateTime _lastErrorTimestamp =
|
|
||||||
DateTime(2000); // Initialize with an old date
|
|
||||||
/// The minimum time that must pass before logging the same error again.
|
|
||||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
|
||||||
|
|
||||||
static Future<void> addError(
|
|
||||||
String error, String details, String where) async {
|
|
||||||
try {
|
|
||||||
final currentErrorSignature = '$where-$error';
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
if (currentErrorSignature == _lastErrorSignature &&
|
|
||||||
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastErrorSignature = currentErrorSignature;
|
|
||||||
_lastErrorTimestamp = now;
|
|
||||||
|
|
||||||
final userId =
|
|
||||||
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
|
|
||||||
final userType =
|
|
||||||
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
|
||||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
|
||||||
|
|
||||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
|
||||||
CRUD().post(
|
|
||||||
link: AppLink.addError,
|
|
||||||
payload: {
|
|
||||||
'error': error.toString(),
|
|
||||||
'userId': userId.toString(),
|
|
||||||
'userType': userType,
|
|
||||||
'phone': phone.toString(),
|
|
||||||
'device': where,
|
|
||||||
'details': details,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> get({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(X
|
|
||||||
.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(AppInformation.addd)[0]);
|
|
||||||
// Log.print('isTokenExpired: ${isTokenExpired}');
|
|
||||||
|
|
||||||
if (isTokenExpired) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
}
|
|
||||||
// await Get.put(LoginDriverController()).getJWT();
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization':
|
|
||||||
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
|
|
||||||
},
|
|
||||||
);
|
|
||||||
print(response.request);
|
|
||||||
Log.print('response.body: ${response.body}');
|
|
||||||
print(payload);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonData['status'];
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
// Specifically handle 401 Unauthorized
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
// Show snackbar prompting to re-login
|
|
||||||
await Get.put(LoginDriverController()).getJWT();
|
|
||||||
// mySnackbarSuccess('please order now'.tr);
|
|
||||||
|
|
||||||
return 'token_expired'; // Return a specific value for token expiration
|
|
||||||
} else {
|
|
||||||
// Other 401 errors
|
|
||||||
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
// 'crud().post - Other');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> getWallet({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
var s = await LoginDriverController().getJwtWallet();
|
|
||||||
Log.print('s: ${s}');
|
|
||||||
final hmac = box.read(BoxName.hmac);
|
|
||||||
Log.print('hmac: ${hmac}');
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization': 'Bearer $s',
|
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Log.print('response.request: ${response.request}');
|
|
||||||
Log.print('response.body: ${response.body}');
|
|
||||||
Log.print('payload: $payload');
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonData['status'];
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
// Specifically handle 401 Unauthorized
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
// Show snackbar prompting to re-login
|
|
||||||
// await Get.put(LoginDriverController()).getJwtWallet();
|
|
||||||
|
|
||||||
return 'token_expired'; // Return a specific value for token expiration
|
|
||||||
} else {
|
|
||||||
// Other 401 errors
|
|
||||||
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
// 'crud().post - Other');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> postWallet(
|
|
||||||
{required String link, Map<String, dynamic>? payload}) async {
|
|
||||||
var s = await LoginDriverController().getJwtWallet();
|
|
||||||
// Log.print('jwt: ${s}');
|
|
||||||
final hmac = box.read(BoxName.hmac);
|
|
||||||
// Log.print('hmac: ${hmac}');
|
|
||||||
var url = Uri.parse(link);
|
|
||||||
// Log.print('url: ${url}');
|
|
||||||
try {
|
|
||||||
// await LoginDriverController().getJWT();
|
|
||||||
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization': 'Bearer $s',
|
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Log.print('response.request:${response.request}');
|
|
||||||
Log.print('response.body: ${response.body}');
|
|
||||||
Log.print('payload:$payload');
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
try {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return jsonData;
|
|
||||||
} else {
|
|
||||||
return jsonData['status'];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// addError(e.toString(), 'crud().post - JSON decoding');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
// Specifically handle 401 Unauthorized
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
return 'token_expired'; // Return a specific value for token expiration
|
|
||||||
} else {
|
|
||||||
// Other 401 errors
|
|
||||||
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
// 'crud().post - Other');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// addError('HTTP request error: $e', 'crud().post - HTTP');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> post(
|
|
||||||
{required String link, Map<String, dynamic>? payload}) async {
|
|
||||||
var url = Uri.parse(link);
|
|
||||||
try {
|
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(X
|
|
||||||
.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(AppInformation.addd)[0]);
|
|
||||||
if (isTokenExpired) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
}
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization':
|
|
||||||
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
|
|
||||||
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
|
|
||||||
},
|
|
||||||
);
|
|
||||||
print(response.request);
|
|
||||||
Log.print('response.body: ${response.body}');
|
|
||||||
print(payload);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
try {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return jsonData;
|
|
||||||
} else {
|
|
||||||
return jsonData['status'];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// addError(e.toString(), url);
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
// Specifically handle 401 Unauthorized
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
if (jsonData['error'] == 'Token expired') {
|
|
||||||
// Show snackbar prompting to re-login
|
|
||||||
// await Get.put(LoginDriverController()).getJWT();
|
|
||||||
// MyDialog().getDialog(
|
|
||||||
// 'Session expired. Please log in again.'.tr,
|
|
||||||
// '',
|
|
||||||
// () {
|
|
||||||
// Get.put(LoginController()).loginUsingCredentials(
|
|
||||||
// box.read(BoxName.passengerID), box.read(BoxName.email));
|
|
||||||
// Get.back();
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
return 'token_expired'; // Return a specific value for token expiration
|
|
||||||
} else {
|
|
||||||
// Other 401 errors
|
|
||||||
// addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// addError('Non-200 response code: ${response.statusCode}',
|
|
||||||
// 'crud().post - Other');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// addError('HTTP request error: $e', 'crud().post - HTTP');
|
|
||||||
return 'failure';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> getAgoraToken({
|
|
||||||
required String channelName,
|
|
||||||
required String uid,
|
|
||||||
}) async {
|
|
||||||
var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
|
||||||
var res = await http.get(Uri.parse(
|
|
||||||
// 'https://repulsive-pig-rugby-shirt.cyclic.app/token?channelName=$channelName'),
|
|
||||||
'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'),
|
|
||||||
headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'});
|
|
||||||
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
var response = jsonDecode(res.body);
|
|
||||||
return response['token'];
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> getLlama({
|
|
||||||
required String link,
|
|
||||||
required String payload,
|
|
||||||
required String prompt,
|
|
||||||
}) async {
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization':
|
|
||||||
'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy'
|
|
||||||
// 'Authorization': 'Bearer ${Env.llamaKey}'
|
|
||||||
};
|
|
||||||
var data = json.encode({
|
|
||||||
"model": "Llama-3-70b-Inst-FW",
|
|
||||||
// "model": "llama-13b-chat",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content":
|
|
||||||
"Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"temperature": 0.9
|
|
||||||
});
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: data,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
return response.statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
String extractTextFromLines(Map<String, dynamic> jsonData) {
|
|
||||||
final readResult = jsonData['readResult'];
|
|
||||||
final blocks = readResult['blocks'];
|
|
||||||
|
|
||||||
final StringBuffer buffer = StringBuffer();
|
|
||||||
|
|
||||||
for (final block in blocks) {
|
|
||||||
final lines = block['lines'];
|
|
||||||
for (final line in lines) {
|
|
||||||
final text = line['text'];
|
|
||||||
buffer.write(text);
|
|
||||||
buffer.write('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> arabicTextExtractByVisionAndAI({
|
|
||||||
required String imagePath,
|
|
||||||
}) async {
|
|
||||||
var headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey
|
|
||||||
};
|
|
||||||
|
|
||||||
String imagePathFull =
|
|
||||||
'${AppLink.server}/card_image/$imagePath-${box.read(BoxName.driverID)}.jpg';
|
|
||||||
var request = http.Request('POST', Uri.parse(
|
|
||||||
// 'https://ocrhamza.cognitiveservices.azure.com/vision/v2.1/ocr?language=ar'));
|
|
||||||
// 'https://eastus.api.cognitive.microsoft.com/vision/v3.2/ocr'
|
|
||||||
'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01'));
|
|
||||||
request.body = json.encode({"url": imagePathFull});
|
|
||||||
// request.body = json.encode({"url": imagePathFull});
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
http.StreamedResponse response = await request.send();
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// 'response.stream.bytesToString(): ${await response.stream.bytesToString()}');
|
|
||||||
return await response.stream.bytesToString();
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> getChatGPT({
|
|
||||||
required String link,
|
|
||||||
required String payload,
|
|
||||||
}) async {
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}'
|
|
||||||
};
|
|
||||||
var data = json.encode({
|
|
||||||
"model": "gpt-3.5-turbo",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content":
|
|
||||||
"Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"temperature": 0.9
|
|
||||||
});
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: data,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
return response.statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> postStripe({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
// String? secretKey = await storage.read(key: BoxName.secretKey);
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization': 'Bearer ${AK.secretKeyStripe}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.body;
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> postPayMob({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
// String? basicAuthCredentials =
|
|
||||||
// await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(url,
|
|
||||||
body: payload, headers: {'Content-Type': 'application/json'});
|
|
||||||
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
return response.body;
|
|
||||||
} else {
|
|
||||||
return (jsonData['status']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return response.statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendEmail(String link, Map<String, String>? payload) async {
|
|
||||||
// التحقق من صلاحية التوكن
|
|
||||||
String rawJwt = box.read(BoxName.jwt);
|
|
||||||
String token = X
|
|
||||||
.r(X.r(X.r(rawJwt, cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(AppInformation.addd)[0];
|
|
||||||
|
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(token);
|
|
||||||
if (isTokenExpired) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
rawJwt = box.read(BoxName.jwt); // تحديث التوكن بعد التجديد
|
|
||||||
token = X
|
|
||||||
.r(X.r(X.r(rawJwt, cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(AppInformation.addd)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// إعداد الهيدر
|
|
||||||
final headers = {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"Authorization": "Bearer $token",
|
|
||||||
};
|
|
||||||
|
|
||||||
// إعداد الطلب
|
|
||||||
final request = http.Request('POST', Uri.parse(link));
|
|
||||||
request.bodyFields = payload ?? {};
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
// إرسال الطلب
|
|
||||||
final response = await request.send();
|
|
||||||
|
|
||||||
// التحقق من النتيجة
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
print("✅ Email sent successfully.");
|
|
||||||
} else {
|
|
||||||
print("❌ Failed to send email. Status: ${response.statusCode}");
|
|
||||||
final responseBody = await response.stream.bytesToString();
|
|
||||||
print("Response body: $responseBody");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> postFromDialogue({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
// String? basicAuthCredentials =
|
|
||||||
// await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.body.isNotEmpty) {
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
if (jsonData['status'] == 'success') {
|
|
||||||
Get.back();
|
|
||||||
// Get.snackbar(
|
|
||||||
// jsonData['status'],
|
|
||||||
// jsonData['message'],
|
|
||||||
// );
|
|
||||||
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (jsonData['status']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendVerificationRequest(String phoneNumber) async {
|
|
||||||
final accountSid = AK.accountSIDTwillo;
|
|
||||||
final authToken = AK.authTokenTwillo;
|
|
||||||
final verifySid = AK.twilloRecoveryCode;
|
|
||||||
|
|
||||||
final Uri verificationUri = Uri.parse(
|
|
||||||
'https://verify.twilio.com/v2/Services/$verifySid/Verifications');
|
|
||||||
|
|
||||||
// Send the verification request
|
|
||||||
final response = await http.post(
|
|
||||||
verificationUri,
|
|
||||||
headers: {
|
|
||||||
'Authorization':
|
|
||||||
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
'To': phoneNumber,
|
|
||||||
'Channel': 'sms',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
|
||||||
} else {}
|
|
||||||
|
|
||||||
// Prompt the user to enter the OTP
|
|
||||||
final otpCode = "123456"; // Replace with user input
|
|
||||||
|
|
||||||
// Check the verification code
|
|
||||||
final checkUri = Uri.parse(
|
|
||||||
'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck');
|
|
||||||
|
|
||||||
final checkResponse = await http.post(
|
|
||||||
checkUri,
|
|
||||||
headers: {
|
|
||||||
'Authorization':
|
|
||||||
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
'To': phoneNumber,
|
|
||||||
'Code': otpCode,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (checkResponse.statusCode == 201) {
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> getGoogleApi({
|
|
||||||
required String link,
|
|
||||||
Map<String, dynamic>? payload,
|
|
||||||
}) async {
|
|
||||||
var url = Uri.parse(
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
var response = await http.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
);
|
|
||||||
var jsonData = jsonDecode(response.body);
|
|
||||||
if (jsonData['status'] == 'OK') {
|
|
||||||
return jsonData;
|
|
||||||
}
|
|
||||||
return (jsonData['status']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> update({
|
|
||||||
required String endpoint,
|
|
||||||
required Map<String, dynamic> data,
|
|
||||||
required String id,
|
|
||||||
}) async {
|
|
||||||
// String? basicAuthCredentials =
|
|
||||||
// await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
var url = Uri.parse('$endpoint/$id');
|
|
||||||
var response = await http.put(
|
|
||||||
url,
|
|
||||||
body: json.encode(data),
|
|
||||||
headers: {
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return json.decode(response.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> delete({
|
|
||||||
required String endpoint,
|
|
||||||
required String id,
|
|
||||||
}) async {
|
|
||||||
// String? basicAuthCredentials =
|
|
||||||
// await storage.read(key: BoxName.basicAuthCredentials);
|
|
||||||
var url = Uri.parse('$endpoint/$id');
|
|
||||||
var response = await http.delete(
|
|
||||||
url,
|
|
||||||
headers: {
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return json.decode(response.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class LineChartPainter extends CustomPainter {
|
|
||||||
final List<double> data;
|
|
||||||
|
|
||||||
LineChartPainter(this.data);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
// Calculate the scale factor.
|
|
||||||
final scaleFactor = size.height / 240;
|
|
||||||
|
|
||||||
// Draw the line chart.
|
|
||||||
for (var i = 0; i < data.length - 1; i++) {
|
|
||||||
final x1 = i * size.width / data.length;
|
|
||||||
final y1 = data[i] * scaleFactor;
|
|
||||||
final x2 = (i + 1) * size.width / data.length;
|
|
||||||
final y2 = data[i + 1] * scaleFactor;
|
|
||||||
|
|
||||||
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), Paint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(LineChartPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'performance_test.dart'; // Make sure this path is correct
|
|
||||||
|
|
||||||
/// Analyzes various device hardware and software aspects to generate a compatibility score.
|
|
||||||
/// This class provides a standardized output for the UI to consume easily.
|
|
||||||
class DeviceAnalyzer {
|
|
||||||
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
|
|
||||||
|
|
||||||
/// Reads the total RAM from the system's meminfo file.
|
|
||||||
/// Returns the value in Megabytes (MB).
|
|
||||||
Future<double> _readTotalRamMB() async {
|
|
||||||
try {
|
|
||||||
final file = File('/proc/meminfo');
|
|
||||||
if (!await file.exists()) return 0.0;
|
|
||||||
final lines = await file.readAsLines();
|
|
||||||
for (var line in lines) {
|
|
||||||
if (line.startsWith('MemTotal')) {
|
|
||||||
// Extracts the numeric value from the line.
|
|
||||||
final kb = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '0') ?? 0;
|
|
||||||
return kb / 1024.0; // Convert from Kilobytes to Megabytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('❌ Error reading total RAM: $e');
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the current RAM usage percentage from the system's meminfo file.
|
|
||||||
Future<double> _readUsedRamPercent() async {
|
|
||||||
try {
|
|
||||||
final file = File('/proc/meminfo');
|
|
||||||
if (!await file.exists()) return 0.0;
|
|
||||||
final lines = await file.readAsLines();
|
|
||||||
int? total, available;
|
|
||||||
for (var line in lines) {
|
|
||||||
if (line.startsWith('MemTotal')) {
|
|
||||||
total = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
|
|
||||||
} else if (line.startsWith('MemAvailable')) {
|
|
||||||
available = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (total != null && available != null && total > 0) {
|
|
||||||
final used = total - available;
|
|
||||||
return (used / total) * 100.0;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('❌ Error reading used RAM: $e');
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main analysis function that runs all checks.
|
|
||||||
Future<Map<String, dynamic>> analyzeDevice() async {
|
|
||||||
List<Map<String, dynamic>> details = [];
|
|
||||||
|
|
||||||
if (!Platform.isAndroid) {
|
|
||||||
return {
|
|
||||||
'score': 0,
|
|
||||||
'details': [
|
|
||||||
{
|
|
||||||
'label': 'النظام غير مدعوم',
|
|
||||||
'status': false,
|
|
||||||
'achieved_score': 0,
|
|
||||||
'max_score': 100
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
final info = await _deviceInfo.androidInfo;
|
|
||||||
final data = info.data;
|
|
||||||
final features = List<String>.from(data['systemFeatures'] ?? []);
|
|
||||||
|
|
||||||
// 1. Android Version (Max: 10 points)
|
|
||||||
final version =
|
|
||||||
int.tryParse(info.version.release?.split('.').first ?? '0') ?? 0;
|
|
||||||
final int androidScore = version >= 9 ? 10 : 0;
|
|
||||||
details.add({
|
|
||||||
'label': 'إصدار أندرويد ${info.version.release}',
|
|
||||||
'status': androidScore > 0,
|
|
||||||
'achieved_score': androidScore,
|
|
||||||
'max_score': 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Total RAM (Max: 10 points)
|
|
||||||
final totalRam = await _readTotalRamMB();
|
|
||||||
int ramScore;
|
|
||||||
if (totalRam >= 8000) {
|
|
||||||
ramScore = 10;
|
|
||||||
} else if (totalRam >= 4000) {
|
|
||||||
ramScore = 5;
|
|
||||||
} else if (totalRam >= 3000) {
|
|
||||||
ramScore = 3;
|
|
||||||
} else {
|
|
||||||
ramScore = 0;
|
|
||||||
}
|
|
||||||
details.add({
|
|
||||||
'label': 'إجمالي الرام ${totalRam.toStringAsFixed(0)} ميجابايت',
|
|
||||||
'status': ramScore >= 5,
|
|
||||||
'achieved_score': ramScore,
|
|
||||||
'max_score': 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. CPU Cores (Max: 10 points)
|
|
||||||
final cores = Platform.numberOfProcessors;
|
|
||||||
int coreScore = cores >= 6 ? 10 : (cores >= 4 ? 5 : 0);
|
|
||||||
details.add({
|
|
||||||
'label': 'أنوية المعالج ($cores)',
|
|
||||||
'status': coreScore >= 5,
|
|
||||||
'achieved_score': coreScore,
|
|
||||||
'max_score': 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Free Storage (Max: 5 points)
|
|
||||||
final freeBytes = data['freeDiskSize'] ?? 0;
|
|
||||||
final freeGB = freeBytes / (1024 * 1024 * 1024);
|
|
||||||
int storeScore = freeGB >= 5 ? 5 : (freeGB >= 2 ? 3 : 0);
|
|
||||||
details.add({
|
|
||||||
'label': 'المساحة الحرة ${freeGB.toStringAsFixed(1)} جيجابايت',
|
|
||||||
'status': storeScore >= 3,
|
|
||||||
'achieved_score': storeScore,
|
|
||||||
'max_score': 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. GPS + Gyroscope Sensors (Max: 10 points)
|
|
||||||
bool okSensors = features.contains('android.hardware.location.gps') &&
|
|
||||||
features.contains('android.hardware.sensor.gyroscope');
|
|
||||||
final int sensorScore = okSensors ? 10 : 0;
|
|
||||||
details.add({
|
|
||||||
'label': 'حساسات GPS و Gyroscope',
|
|
||||||
'status': okSensors,
|
|
||||||
'achieved_score': sensorScore,
|
|
||||||
'max_score': 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. Storage Write Speed (Max: 20 points)
|
|
||||||
final writeSpeed = await PerformanceTester.testStorageWriteSpeed();
|
|
||||||
int writeScore;
|
|
||||||
if (writeSpeed >= 30) {
|
|
||||||
writeScore = 20;
|
|
||||||
} else if (writeSpeed >= 15) {
|
|
||||||
writeScore = 15;
|
|
||||||
} else if (writeSpeed >= 5) {
|
|
||||||
writeScore = 10;
|
|
||||||
} else {
|
|
||||||
writeScore = 5;
|
|
||||||
}
|
|
||||||
details.add({
|
|
||||||
'label': 'سرعة الكتابة (${writeSpeed.toStringAsFixed(1)} MB/s)',
|
|
||||||
'status': writeScore >= 10,
|
|
||||||
'achieved_score': writeScore,
|
|
||||||
'max_score': 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7. CPU Compute Speed (Max: 20 points)
|
|
||||||
final cpuTime = await PerformanceTester.testCPUSpeed();
|
|
||||||
int cpuScore;
|
|
||||||
if (cpuTime <= 1.0) {
|
|
||||||
cpuScore = 20;
|
|
||||||
} else if (cpuTime <= 2.5) {
|
|
||||||
cpuScore = 15;
|
|
||||||
} else if (cpuTime <= 4.0) {
|
|
||||||
cpuScore = 10;
|
|
||||||
} else {
|
|
||||||
cpuScore = 5;
|
|
||||||
}
|
|
||||||
details.add({
|
|
||||||
'label': 'سرعة المعالجة (${cpuTime.toStringAsFixed(2)} ثانية)',
|
|
||||||
'status': cpuScore >= 10,
|
|
||||||
'achieved_score': cpuScore,
|
|
||||||
'max_score': 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 8. Memory Pressure (Max: 15 points)
|
|
||||||
final usedPercent = await _readUsedRamPercent();
|
|
||||||
int memScore;
|
|
||||||
if (usedPercent <= 60) {
|
|
||||||
memScore = 15;
|
|
||||||
} else if (usedPercent <= 80) {
|
|
||||||
memScore = 10;
|
|
||||||
} else if (usedPercent <= 90) {
|
|
||||||
memScore = 5;
|
|
||||||
} else {
|
|
||||||
memScore = 0;
|
|
||||||
}
|
|
||||||
details.add({
|
|
||||||
'label': 'استخدام الرام الحالي (${usedPercent.toStringAsFixed(0)}%)',
|
|
||||||
'status': memScore >= 10,
|
|
||||||
'achieved_score': memScore,
|
|
||||||
'max_score': 15,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculate the final total score by summing up the achieved scores.
|
|
||||||
final totalScore = details.fold<int>(
|
|
||||||
0, (sum, item) => sum + (item['achieved_score'] as int));
|
|
||||||
|
|
||||||
return {
|
|
||||||
'score': totalScore.clamp(0, 100),
|
|
||||||
'details': details,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:get_storage/get_storage.dart';
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
|
|
||||||
class DeviceInfo {
|
|
||||||
final String? manufacturer;
|
|
||||||
final String? model;
|
|
||||||
final String? deviceId;
|
|
||||||
final String? osVersion;
|
|
||||||
final String? platform;
|
|
||||||
final String? deviceName;
|
|
||||||
final bool? isPhysicalDevice;
|
|
||||||
|
|
||||||
DeviceInfo({
|
|
||||||
this.manufacturer,
|
|
||||||
this.model,
|
|
||||||
this.deviceId,
|
|
||||||
this.osVersion,
|
|
||||||
this.platform,
|
|
||||||
this.deviceName,
|
|
||||||
this.isPhysicalDevice,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'manufacturer': manufacturer,
|
|
||||||
'model': model,
|
|
||||||
'deviceId': deviceId,
|
|
||||||
'osVersion': osVersion,
|
|
||||||
'platform': platform,
|
|
||||||
'deviceName': deviceName,
|
|
||||||
'isPhysicalDevice': isPhysicalDevice,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeviceController {
|
|
||||||
final box = GetStorage();
|
|
||||||
final _deviceInfo = DeviceInfoPlugin();
|
|
||||||
|
|
||||||
Future<DeviceInfo> getDeviceInfo() async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
return await _getAndroidDeviceInfo();
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
return await _getIosDeviceInfo();
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Unsupported platform');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DeviceInfo> _getAndroidDeviceInfo() async {
|
|
||||||
final androidInfo = await _deviceInfo.androidInfo;
|
|
||||||
final deviceInfo = DeviceInfo(
|
|
||||||
manufacturer: androidInfo.manufacturer,
|
|
||||||
model: androidInfo.model,
|
|
||||||
deviceId: androidInfo.serialNumber,
|
|
||||||
osVersion: androidInfo.version.release,
|
|
||||||
platform: 'Android',
|
|
||||||
deviceName: androidInfo.device,
|
|
||||||
isPhysicalDevice: androidInfo.isPhysicalDevice,
|
|
||||||
);
|
|
||||||
|
|
||||||
box.write(BoxName.deviceInfo, deviceInfo.toJson());
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DeviceInfo> _getIosDeviceInfo() async {
|
|
||||||
final iosInfo = await _deviceInfo.iosInfo;
|
|
||||||
final deviceInfo = DeviceInfo(
|
|
||||||
manufacturer: 'Apple',
|
|
||||||
model: iosInfo.model,
|
|
||||||
deviceId: iosInfo.identifierForVendor,
|
|
||||||
osVersion: iosInfo.systemVersion,
|
|
||||||
platform: 'iOS',
|
|
||||||
deviceName: iosInfo.name,
|
|
||||||
isPhysicalDevice: iosInfo.isPhysicalDevice,
|
|
||||||
);
|
|
||||||
|
|
||||||
box.write(BoxName.deviceInfo, deviceInfo.toJson());
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class DigitObscuringFormatter extends TextInputFormatter {
|
|
||||||
@override
|
|
||||||
TextEditingValue formatEditUpdate(
|
|
||||||
TextEditingValue oldValue, TextEditingValue newValue) {
|
|
||||||
final maskedText = maskDigits(newValue.text);
|
|
||||||
return newValue.copyWith(
|
|
||||||
text: maskedText,
|
|
||||||
selection: updateCursorPosition(maskedText, newValue.selection));
|
|
||||||
}
|
|
||||||
|
|
||||||
String maskDigits(String text) {
|
|
||||||
final totalDigits = text.length;
|
|
||||||
final visibleDigits = 4;
|
|
||||||
final hiddenDigits = totalDigits - visibleDigits * 2;
|
|
||||||
|
|
||||||
final firstVisibleDigits = text.substring(0, visibleDigits);
|
|
||||||
final lastVisibleDigits = text.substring(totalDigits - visibleDigits);
|
|
||||||
|
|
||||||
final maskedDigits = List.filled(hiddenDigits, '*').join();
|
|
||||||
|
|
||||||
return '$firstVisibleDigits$maskedDigits$lastVisibleDigits';
|
|
||||||
}
|
|
||||||
|
|
||||||
TextSelection updateCursorPosition(
|
|
||||||
String maskedText, TextSelection currentSelection) {
|
|
||||||
final cursorPosition = currentSelection.baseOffset;
|
|
||||||
final cursorOffset =
|
|
||||||
currentSelection.extentOffset - currentSelection.baseOffset;
|
|
||||||
final totalDigits = maskedText.length;
|
|
||||||
const visibleDigits = 4;
|
|
||||||
final hiddenDigits = totalDigits - visibleDigits * 2;
|
|
||||||
|
|
||||||
final updatedPosition = cursorPosition <= visibleDigits
|
|
||||||
? cursorPosition
|
|
||||||
: hiddenDigits + visibleDigits + (cursorPosition - visibleDigits);
|
|
||||||
|
|
||||||
return TextSelection.collapsed(
|
|
||||||
offset: updatedPosition, affinity: currentSelection.affinity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// import 'dart:io';
|
|
||||||
//
|
|
||||||
// import 'package:get/get.dart';
|
|
||||||
// import 'package:image_picker/image_picker.dart';
|
|
||||||
// import 'package:google_ml_kit/google_ml_kit.dart';
|
|
||||||
//
|
|
||||||
// class ImagePickerController extends GetxController {
|
|
||||||
// RxBool textScanning = false.obs;
|
|
||||||
// RxString scannedText = ''.obs;
|
|
||||||
//
|
|
||||||
// Future<void> getImage(ImageSource source) async {
|
|
||||||
// try {
|
|
||||||
// final pickedImage = await ImagePicker().pickImage(source: source);
|
|
||||||
// if (pickedImage != null) {
|
|
||||||
// textScanning.value = true;
|
|
||||||
// final imageFile = File(pickedImage.path);
|
|
||||||
// getRecognisedText(imageFile);
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// textScanning.value = false;
|
|
||||||
// scannedText.value = "Error occurred while scanning";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<void> getRecognisedText(File image) async {
|
|
||||||
// final inputImage = InputImage.fromFilePath(image.path);
|
|
||||||
// final textDetector = GoogleMlKit.vision.textRecognizer();
|
|
||||||
// final RecognizedText recognisedText =
|
|
||||||
// await textDetector.processImage(inputImage);
|
|
||||||
// await textDetector.close();
|
|
||||||
//
|
|
||||||
// scannedText.value = '';
|
|
||||||
// for (TextBlock block in recognisedText.blocks) {
|
|
||||||
// for (TextLine line in block.lines) {
|
|
||||||
// scannedText.value += line.text + '\n';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// textScanning.value = false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
|
|
||||||
import '../../constant/api_key.dart';
|
|
||||||
|
|
||||||
class KeyEncryption {
|
|
||||||
// استخدم مفتاح بطول 32 حرفًا
|
|
||||||
static final _key = encrypt.Key.fromUtf8(AK.keyOfApp);
|
|
||||||
static final _iv =
|
|
||||||
encrypt.IV.fromLength(16); // توليد تهيئة عشوائية بطول 16 بايت
|
|
||||||
|
|
||||||
static String encryptKey(String key) {
|
|
||||||
final encrypter =
|
|
||||||
encrypt.Encrypter(encrypt.AES(_key, mode: encrypt.AESMode.cbc));
|
|
||||||
final encrypted = encrypter.encrypt(key, iv: _iv);
|
|
||||||
final result = _iv.bytes + encrypted.bytes; // تضمين التهيئة مع النص المشفر
|
|
||||||
return base64Encode(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String decryptKey(String encryptedKey) {
|
|
||||||
print('encryptedKey: ${AK.keyOfApp}');
|
|
||||||
|
|
||||||
final decoded = base64Decode(encryptedKey);
|
|
||||||
print('encryptedKey: $encryptedKey');
|
|
||||||
final iv = encrypt.IV(decoded.sublist(0, 16)); // استخراج التهيئة
|
|
||||||
final encrypted =
|
|
||||||
encrypt.Encrypted(decoded.sublist(16)); // استخراج النص المشفر
|
|
||||||
final encrypter =
|
|
||||||
encrypt.Encrypter(encrypt.AES(_key, mode: encrypt.AESMode.cbc));
|
|
||||||
return encrypter.decrypt(encrypted, iv: iv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
|
|
||||||
import '../../constant/char_map.dart';
|
|
||||||
import '../../env/env.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
|
|
||||||
class EncryptionHelper {
|
|
||||||
static EncryptionHelper? _instance;
|
|
||||||
|
|
||||||
late final encrypt.Key key;
|
|
||||||
late final encrypt.IV iv;
|
|
||||||
|
|
||||||
EncryptionHelper._(this.key, this.iv);
|
|
||||||
static EncryptionHelper get instance {
|
|
||||||
if (_instance == null) {
|
|
||||||
throw Exception(
|
|
||||||
"EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main.");
|
|
||||||
}
|
|
||||||
return _instance!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes and stores the instance globally
|
|
||||||
static Future<void> initialize() async {
|
|
||||||
if (_instance != null) {
|
|
||||||
debugPrint("EncryptionHelper is already initialized.");
|
|
||||||
return; // Prevent re-initialization
|
|
||||||
}
|
|
||||||
debugPrint("Initializing EncryptionHelper...");
|
|
||||||
var keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0];
|
|
||||||
var initializationVector =
|
|
||||||
r(Env.initializationVector).toString().split(Env.addd)[0];
|
|
||||||
|
|
||||||
// Set the global instance
|
|
||||||
_instance = EncryptionHelper._(
|
|
||||||
encrypt.Key.fromUtf8(keyOfApp!),
|
|
||||||
encrypt.IV.fromUtf8(initializationVector!),
|
|
||||||
);
|
|
||||||
debugPrint("EncryptionHelper initialized successfully.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts a string
|
|
||||||
String encryptData(String plainText) {
|
|
||||||
try {
|
|
||||||
final encrypter =
|
|
||||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
|
||||||
final encrypted = encrypter.encrypt(plainText, iv: iv);
|
|
||||||
return encrypted.base64;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Encryption Error: $e');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a string
|
|
||||||
String decryptData(String encryptedText) {
|
|
||||||
try {
|
|
||||||
final encrypter =
|
|
||||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
|
||||||
final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
|
|
||||||
return encrypter.decrypt(encrypted, iv: iv);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Decryption Error: $e');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r(String string) {
|
|
||||||
return X.r(X.r(X.r(string, cn), cC), cs).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
c(String string) {
|
|
||||||
return X.c(X.c(X.c(string, cn), cC), cs).toString();
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:sefer_driver/constant/api_key.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:http/io_client.dart';
|
|
||||||
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import 'encrypt_decrypt.dart';
|
|
||||||
import 'upload_image.dart';
|
|
||||||
|
|
||||||
Future<String> faceDetector() async {
|
|
||||||
await ImageController().choosFace(AppLink.uploadEgypt, 'face_detect');
|
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
|
||||||
|
|
||||||
var headers = {
|
|
||||||
// 'Authorization': 'Basic ${AK.basicCompareFaces}',
|
|
||||||
'Authorization': 'Basic hamza:12345678',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
|
|
||||||
var request = http.Request(
|
|
||||||
// todo change server link
|
|
||||||
'POST',
|
|
||||||
Uri.parse('https://mohkh.online:5000/compare_faces'));
|
|
||||||
|
|
||||||
request.body = json.encode({
|
|
||||||
"url1": // TODO: jpg?
|
|
||||||
"${AppLink.server}/auth/uploads/documents_egypt/eg_id_front_${(box.read(BoxName.driverID))}.webp",
|
|
||||||
"url2":
|
|
||||||
"${AppLink.server}/card_image/face_detect-${(box.read(BoxName.driverID))}.jpg"
|
|
||||||
});
|
|
||||||
print('request.body: ${request.body}');
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
http.Client client = await createHttpClient();
|
|
||||||
http.StreamedResponse response = await client.send(request);
|
|
||||||
// http.StreamedResponse response = await request.send();
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
String result = await response.stream.bytesToString();
|
|
||||||
print('result: ${result}');
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
print('Error: ${response.reasonPhrase}');
|
|
||||||
return 'Error: ${response.reasonPhrase}';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception occurred: $e');
|
|
||||||
return 'Error: $e';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<http.Client> createHttpClient() async {
|
|
||||||
final SecurityContext securityContext = SecurityContext();
|
|
||||||
HttpClient httpClient = HttpClient(context: securityContext);
|
|
||||||
httpClient.badCertificateCallback =
|
|
||||||
(X509Certificate cert, String host, int port) => true; // Bypass SSL
|
|
||||||
return IOClient(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> faceDetector2(String url1, String url2) async {
|
|
||||||
var headers = {
|
|
||||||
'Authorization': 'Basic hamza:12345678',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
|
|
||||||
var request = http.Request(
|
|
||||||
'POST', Uri.parse('https://mohkh.online:5000/compare_faces'));
|
|
||||||
|
|
||||||
request.body = json.encode({"url1": url1, "url2": url2});
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
http.Client client = await createHttpClient(); // Use custom client
|
|
||||||
DateTime startTime = DateTime.now();
|
|
||||||
http.StreamedResponse response = await client.send(request);
|
|
||||||
DateTime endTime = DateTime.now();
|
|
||||||
Duration duration = endTime.difference(startTime);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
print(await response.stream.bytesToString());
|
|
||||||
print(duration.inSeconds);
|
|
||||||
|
|
||||||
return await response.stream.bytesToString();
|
|
||||||
} else {
|
|
||||||
print(await response.stream.bytesToString());
|
|
||||||
return 'Error: ${response.reasonPhrase}';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return 'Exception: $e';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
|
|
||||||
class GeoLocation {
|
|
||||||
Future<Position> getCurrentLocation() async {
|
|
||||||
bool serviceEnabled;
|
|
||||||
LocationPermission permission;
|
|
||||||
|
|
||||||
// Check if location services are enabled.
|
|
||||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
||||||
if (!serviceEnabled) {
|
|
||||||
// Location services are not enabled, so we request the user to enable it.
|
|
||||||
return Future.error('Location services are disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
permission = await Geolocator.checkPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
|
||||||
permission = await Geolocator.requestPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
|
||||||
// Permissions are denied, we cannot fetch the location.
|
|
||||||
return Future.error('Location permissions are denied');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
|
||||||
// Permissions are denied forever, we cannot request permissions.
|
|
||||||
return Future.error(
|
|
||||||
'Location permissions are permanently denied, we cannot request permissions.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we reach here, permissions are granted and we can fetch the location.
|
|
||||||
return await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
void showInBrowser(String url) async {
|
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
|
||||||
launchUrl(Uri.parse(url));
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> makePhoneCall(String phoneNumber) async {
|
|
||||||
// 1. Clean the number
|
|
||||||
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
|
|
||||||
|
|
||||||
// 2. Format logic (Syria/International)
|
|
||||||
if (formattedNumber.length > 6) {
|
|
||||||
if (formattedNumber.startsWith('09')) {
|
|
||||||
formattedNumber = '+963${formattedNumber.substring(1)}';
|
|
||||||
} else if (!formattedNumber.startsWith('+')) {
|
|
||||||
formattedNumber = '+$formattedNumber';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Create URI
|
|
||||||
final Uri launchUri = Uri(
|
|
||||||
scheme: 'tel',
|
|
||||||
path: formattedNumber,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Execute with externalApplication mode
|
|
||||||
try {
|
|
||||||
// Attempt to launch directly without checking canLaunchUrl first
|
|
||||||
// (Sometimes canLaunchUrl returns false on some devices even if it works)
|
|
||||||
if (!await launchUrl(launchUri, mode: LaunchMode.externalApplication)) {
|
|
||||||
throw 'Could not launch $launchUri';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback: Try checking canLaunchUrl if the direct launch fails
|
|
||||||
if (await canLaunchUrl(launchUri)) {
|
|
||||||
await launchUrl(launchUri);
|
|
||||||
} else {
|
|
||||||
print("Cannot launch url: $launchUri");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void launchCommunication(
|
|
||||||
String method, String contactInfo, String message) async {
|
|
||||||
String url;
|
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
switch (method) {
|
|
||||||
case 'phone':
|
|
||||||
url = 'tel:$contactInfo';
|
|
||||||
break;
|
|
||||||
case 'sms':
|
|
||||||
url = 'sms:$contactInfo?body=${Uri.encodeComponent(message)}';
|
|
||||||
break;
|
|
||||||
case 'whatsapp':
|
|
||||||
url =
|
|
||||||
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
|
|
||||||
break;
|
|
||||||
case 'email':
|
|
||||||
url =
|
|
||||||
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
switch (method) {
|
|
||||||
case 'phone':
|
|
||||||
url = 'tel:$contactInfo';
|
|
||||||
break;
|
|
||||||
case 'sms':
|
|
||||||
url = 'sms:$contactInfo?body=${Uri.encodeComponent(message)}';
|
|
||||||
break;
|
|
||||||
case 'whatsapp':
|
|
||||||
// Check if WhatsApp is installed
|
|
||||||
final bool whatsappInstalled =
|
|
||||||
await canLaunchUrl(Uri.parse('whatsapp://'));
|
|
||||||
if (whatsappInstalled) {
|
|
||||||
url =
|
|
||||||
'whatsapp://send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
|
|
||||||
} else {
|
|
||||||
// Provide an alternative action, such as opening the WhatsApp Web API
|
|
||||||
url =
|
|
||||||
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'email':
|
|
||||||
url =
|
|
||||||
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
|
||||||
await launchUrl(Uri.parse(url));
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
|
|
||||||
class LlamaAi {
|
|
||||||
Future<Map> getCarRegistrationData(String input, prompt) async {
|
|
||||||
Map exrtatDataFinal = {};
|
|
||||||
String oneLine = input.replaceAll('\n', ' ');
|
|
||||||
// var res = await CRUD().getLlama(link: AppLink.gemini, payload: oneLine);
|
|
||||||
var res = await CRUD()
|
|
||||||
.getLlama(link: AppLink.llama, payload: oneLine, prompt: prompt);
|
|
||||||
|
|
||||||
var decod = jsonDecode(res.toString());
|
|
||||||
// exrtatDataFinal = jsonDecode(extractDataFromJsonString(decod['choices']));
|
|
||||||
extractDataFromJsonString(decod['choices'][0]['message']['content']);
|
|
||||||
return exrtatDataFinal;
|
|
||||||
}
|
|
||||||
|
|
||||||
String extractDataFromJsonString(String jsonString) {
|
|
||||||
// Remove any leading or trailing whitespace from the string
|
|
||||||
jsonString = jsonString.trim();
|
|
||||||
|
|
||||||
// Extract the JSON substring from the given string
|
|
||||||
final startIndex = jsonString.indexOf('{');
|
|
||||||
final endIndex = jsonString.lastIndexOf('}');
|
|
||||||
final jsonSubstring = jsonString.substring(startIndex, endIndex + 1);
|
|
||||||
|
|
||||||
// Parse the JSON substring into a Map
|
|
||||||
final jsonData = jsonDecode(jsonSubstring);
|
|
||||||
|
|
||||||
// Return the extracted data
|
|
||||||
|
|
||||||
return jsonEncode(jsonData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
|
|
||||||
import 'background_service.dart';
|
|
||||||
|
|
||||||
Future<void> requestNotificationPermission() async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
|
||||||
if (androidInfo.version.sdkInt >= 33) {
|
|
||||||
// Android 13+
|
|
||||||
final status = await Permission.notification.request();
|
|
||||||
if (!status.isGranted) {
|
|
||||||
print('⚠️ إذن الإشعارات مرفوض');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// بعد الحصول على الإذن، ابدأ الخدمة
|
|
||||||
await BackgroundServiceHelper.startService();
|
|
||||||
}
|
|
||||||
|
|
||||||
class PermissionsHelper {
|
|
||||||
/// طلب إذن الإشعارات على Android 13+
|
|
||||||
static Future<bool> requestNotificationPermission() async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
|
||||||
|
|
||||||
// Android 13+ (API 33+) يحتاج إذن POST_NOTIFICATIONS
|
|
||||||
if (androidInfo.version.sdkInt >= 33) {
|
|
||||||
final status = await Permission.notification.request();
|
|
||||||
|
|
||||||
if (status.isDenied) {
|
|
||||||
print('⚠️ إذن الإشعارات مرفوض');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.isPermanentlyDenied) {
|
|
||||||
print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات');
|
|
||||||
await openAppSettings();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// طلب جميع الإذونات المطلوبة
|
|
||||||
static Future<bool> requestAllPermissions() async {
|
|
||||||
// إذن الإشعارات أولاً
|
|
||||||
bool notificationGranted = await requestNotificationPermission();
|
|
||||||
if (!notificationGranted) return false;
|
|
||||||
|
|
||||||
// إذن الموقع
|
|
||||||
final locationStatus = await Permission.location.request();
|
|
||||||
if (!locationStatus.isGranted) {
|
|
||||||
print('⚠️ إذن الموقع مرفوض');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// إذن الموقع في الخلفية
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
final bgLocationStatus = await Permission.locationAlways.request();
|
|
||||||
if (!bgLocationStatus.isGranted) {
|
|
||||||
print('⚠️ إذن الموقع في الخلفية مرفوض');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart' as geo;
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:location/location.dart';
|
|
||||||
import 'package:battery_plus/battery_plus.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart' as ph;
|
|
||||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart';
|
|
||||||
import 'package:socket_io_client/socket_io_client.dart' as IO;
|
|
||||||
import 'package:sefer_driver/constant/table_names.dart';
|
|
||||||
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import '../home/captin/home_captain_controller.dart';
|
|
||||||
import '../home/captin/map_driver_controller.dart';
|
|
||||||
import '../home/payment/captain_wallet_controller.dart';
|
|
||||||
import 'background_service.dart';
|
|
||||||
import 'crud.dart';
|
|
||||||
|
|
||||||
class LocationController extends GetxController with WidgetsBindingObserver {
|
|
||||||
// ===================================================================
|
|
||||||
// ====== Tunables ======
|
|
||||||
// ===================================================================
|
|
||||||
static const Duration recordIntervalNormal = Duration(seconds: 3);
|
|
||||||
static const Duration uploadBatchIntervalNormal = Duration(minutes: 2);
|
|
||||||
static const Duration recordIntervalPowerSave = Duration(seconds: 10);
|
|
||||||
static const Duration uploadBatchIntervalPowerSave = Duration(minutes: 5);
|
|
||||||
|
|
||||||
static const double lowWalletThreshold = -200;
|
|
||||||
static const int powerSaveTriggerLevel = 20;
|
|
||||||
static const int powerSaveExitLevel = 25;
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// ====== Services & Variables ======
|
|
||||||
// ===================================================================
|
|
||||||
late final Location location = Location();
|
|
||||||
final Battery _battery = Battery();
|
|
||||||
|
|
||||||
IO.Socket? socket;
|
|
||||||
bool isSocketConnected = false;
|
|
||||||
Timer? _socketHeartbeat;
|
|
||||||
|
|
||||||
StreamSubscription<LocationData>? _locSub;
|
|
||||||
StreamSubscription<BatteryState>? _batterySub;
|
|
||||||
|
|
||||||
Timer? _recordTimer;
|
|
||||||
Timer? _uploadBatchTimer;
|
|
||||||
|
|
||||||
late final HomeCaptainController _homeCtrl;
|
|
||||||
late final CaptainWalletController _walletCtrl;
|
|
||||||
|
|
||||||
LatLng myLocation = const LatLng(0, 0);
|
|
||||||
double heading = 0.0;
|
|
||||||
double speed = 0.0;
|
|
||||||
double totalDistance = 0.0;
|
|
||||||
bool _isReady = false;
|
|
||||||
bool _isPowerSavingMode = false;
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> _trackBuffer = [];
|
|
||||||
|
|
||||||
LatLng? _lastPosForDistance;
|
|
||||||
LatLng? _lastRecordedRealLoc;
|
|
||||||
DateTime? _lastRecordedTime;
|
|
||||||
|
|
||||||
LatLng? _lastSqlLoc;
|
|
||||||
double? _lastSpeed;
|
|
||||||
DateTime? _lastSpeedAt;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onInit() async {
|
|
||||||
super.onInit();
|
|
||||||
Log.print('🚀 LocationController Starting...');
|
|
||||||
|
|
||||||
// 1. Register Lifecycle Observer
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
box.write(BoxName.isAppInForeground, true);
|
|
||||||
|
|
||||||
// مراقب الحالة (Status Watcher)
|
|
||||||
box.listenKey(BoxName.statusDriverLocation, (value) {
|
|
||||||
if (value == 'blocked') {
|
|
||||||
Log.print("⛔ Driver is Blocked: Force Stopping Location Updates.");
|
|
||||||
stopLocationUpdates();
|
|
||||||
if (socket != null && socket!.connected) {
|
|
||||||
socket!.emit('update_location', {
|
|
||||||
'driver_id': box.read(BoxName.driverID),
|
|
||||||
'status': 'blocked',
|
|
||||||
'lat': myLocation.latitude,
|
|
||||||
'lng': myLocation.longitude,
|
|
||||||
'heading': heading,
|
|
||||||
'speed': speed * 3.6,
|
|
||||||
'distance': totalDistance
|
|
||||||
});
|
|
||||||
socket!.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bool deps = await _awaitDependencies();
|
|
||||||
if (!deps) return;
|
|
||||||
|
|
||||||
_isReady = true;
|
|
||||||
|
|
||||||
initSocket();
|
|
||||||
await _initLocationSettings();
|
|
||||||
_listenToBatteryChanges();
|
|
||||||
|
|
||||||
if (box.read(BoxName.statusDriverLocation) != 'blocked') {
|
|
||||||
await startLocationUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.print('✅ LocationController Initialized.');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
box.write(BoxName.isAppInForeground, false);
|
|
||||||
stopLocationUpdates();
|
|
||||||
_batterySub?.cancel();
|
|
||||||
_stopHeartbeat();
|
|
||||||
socket?.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 🔥 Lifecycle Manager (Fixes Freeze & Background issues)
|
|
||||||
// ===================================================================
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
if (state == AppLifecycleState.resumed) {
|
|
||||||
Log.print("📱 Lifecycle: App is in FOREGROUND");
|
|
||||||
box.write(BoxName.isAppInForeground, true);
|
|
||||||
|
|
||||||
// إيقاف خدمة الخلفية لأننا في الواجهة الآن
|
|
||||||
BackgroundServiceHelper.stopService();
|
|
||||||
|
|
||||||
// التأكد من أن السوكيت متصل، وإذا لا، نعيد الاتصال فوراً
|
|
||||||
if (socket == null || !socket!.connected) {
|
|
||||||
Log.print("🔄 Socket disconnected in background. Reconnecting now...");
|
|
||||||
initSocket();
|
|
||||||
} else {
|
|
||||||
// إذا كان متصلاً، ننعش المستمعين فقط للتأكد
|
|
||||||
_setupSocketListeners();
|
|
||||||
}
|
|
||||||
} else if (state == AppLifecycleState.paused ||
|
|
||||||
state == AppLifecycleState.detached) {
|
|
||||||
Log.print("📱 Lifecycle: App is in BACKGROUND");
|
|
||||||
box.write(BoxName.isAppInForeground, false);
|
|
||||||
|
|
||||||
// تشغيل خدمة الخلفية لضمان بقاء التطبيق حياً
|
|
||||||
BackgroundServiceHelper.startService();
|
|
||||||
|
|
||||||
// ملاحظة: لا نقطع السوكيت هنا، نتركه يعمل قدر الإمكان
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _awaitDependencies() async {
|
|
||||||
int attempts = 0;
|
|
||||||
while (attempts < 10) {
|
|
||||||
if (Get.isRegistered<HomeCaptainController>() &&
|
|
||||||
Get.isRegistered<CaptainWalletController>()) {
|
|
||||||
_homeCtrl = Get.find<HomeCaptainController>();
|
|
||||||
_walletCtrl = Get.find<CaptainWalletController>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// ====== Socket Logic (Improved) ======
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
void initSocket() {
|
|
||||||
String driverId = box.read(BoxName.driverID).toString();
|
|
||||||
String token = box.read(BoxName.tokenDriver).toString();
|
|
||||||
String platform = Platform.isIOS ? 'ios' : 'android';
|
|
||||||
|
|
||||||
// 1. إذا كان السوكيت موجوداً، فقط تأكد من اتصاله
|
|
||||||
if (socket != null) {
|
|
||||||
if (!socket!.connected) {
|
|
||||||
Log.print("🟡 Socket exists but disconnected. Reconnecting...");
|
|
||||||
socket!.connect();
|
|
||||||
}
|
|
||||||
_setupSocketListeners(); // تحديث المستمعين
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.print(
|
|
||||||
"🟡 [LocationController] Creating NEW Socket for Driver: $driverId");
|
|
||||||
|
|
||||||
// 2. إنشاء الاتصال
|
|
||||||
socket = IO.io(
|
|
||||||
'https://location.intaleq.xyz',
|
|
||||||
IO.OptionBuilder()
|
|
||||||
.setTransports(['websocket'])
|
|
||||||
.enableAutoConnect() // تفعيل إعادة الاتصال التلقائي
|
|
||||||
.setQuery(
|
|
||||||
{'driver_id': driverId, 'token': token, 'platform': platform})
|
|
||||||
.setReconnectionAttempts(double.infinity)
|
|
||||||
.setReconnectionDelay(2000)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
socket!.connect();
|
|
||||||
_setupSocketListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة منفصلة لضمان عدم تكرار المستمعين
|
|
||||||
void _setupSocketListeners() {
|
|
||||||
if (socket == null) return;
|
|
||||||
|
|
||||||
// تنظيف القديم أولاً
|
|
||||||
socket!.off('connect');
|
|
||||||
socket!.off('disconnect');
|
|
||||||
socket!.off('new_ride_request');
|
|
||||||
socket!.off('ride_cancelled');
|
|
||||||
|
|
||||||
socket!.onConnect((_) {
|
|
||||||
Log.print('✅ Socket Connected! ID: ${socket?.id}');
|
|
||||||
isSocketConnected = true;
|
|
||||||
_startHeartbeat();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket!.onDisconnect((_) {
|
|
||||||
Log.print('❌ Socket Disconnected');
|
|
||||||
isSocketConnected = false;
|
|
||||||
_stopHeartbeat();
|
|
||||||
});
|
|
||||||
socket!.onConnectError((err) {
|
|
||||||
Log.print('❌ Socket Connect Error: $err');
|
|
||||||
});
|
|
||||||
|
|
||||||
socket!.onError((err) {
|
|
||||||
Log.print('❌ Socket General Error: $err');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔥 الاستماع للطلبات الجديدة
|
|
||||||
socket!.on('new_ride_request', (data) {
|
|
||||||
Log.print("🔔 Socket: New Ride Request Arrived!");
|
|
||||||
|
|
||||||
// نستخدم Future.microtask لضمان عدم حظر الـ UI Thread
|
|
||||||
Future.microtask(() {
|
|
||||||
if (data != null) {
|
|
||||||
try {
|
|
||||||
List<dynamic> rawList = [];
|
|
||||||
if (data is String) {
|
|
||||||
var decoded = jsonDecode(data);
|
|
||||||
if (decoded is List) rawList = decoded;
|
|
||||||
} else if (data is List) {
|
|
||||||
if (data.isNotEmpty) {
|
|
||||||
rawList = (data[0] is List) ? data[0] : data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawList.isNotEmpty) {
|
|
||||||
Map<String, dynamic> convertedMap = {};
|
|
||||||
for (int i = 0; i < rawList.length; i++) {
|
|
||||||
convertedMap[i.toString()] = rawList[i];
|
|
||||||
}
|
|
||||||
handleIncomingOrder(convertedMap, "Socket");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("❌ Error processing socket data: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔥 الاستماع للإلغاء
|
|
||||||
socket!.on('cancel_ride', (data) {
|
|
||||||
Log.print("🚫 Socket: Ride Cancelled Event Received");
|
|
||||||
String reason = data['reason'] ?? 'No reason provided';
|
|
||||||
if (Get.isRegistered<MapDriverController>()) {
|
|
||||||
Get.find<MapDriverController>()
|
|
||||||
.processRideCancelledByPassenger(reason, source: "Socket");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// داخل LocationController
|
|
||||||
|
|
||||||
Future<void> handleIncomingOrder(
|
|
||||||
Map<String, dynamic> rideData, String source) async {
|
|
||||||
Log.print("📦 Socket Order Received from ($source)");
|
|
||||||
|
|
||||||
// 🔴 1. التحقق من حالة التطبيق قبل أي شيء 🔴
|
|
||||||
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
|
|
||||||
|
|
||||||
if (!isAppInForeground) {
|
|
||||||
Log.print(
|
|
||||||
"🛑 التطبيق في الخلفية. السوكيت سيتجاهل التوجيه ويترك المهمة للـ Overlay.");
|
|
||||||
return; // 👈 هذا السطر يمنع السوكيت من إكمال العمل وفتح الصفحة
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 2. التحقق من صحة البيانات
|
|
||||||
if (rideData.isEmpty || !rideData.containsKey('16')) {
|
|
||||||
Log.print("❌ Socket Error: Invalid Ride Data.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. تجهيز البيانات (DriverList)
|
|
||||||
List<dynamic> driverList = [];
|
|
||||||
if (rideData.length > 0) {
|
|
||||||
var sortedKeys = rideData.keys.map((e) => int.tryParse(e) ?? 0).toList()
|
|
||||||
..sort();
|
|
||||||
for (var key in sortedKeys) {
|
|
||||||
driverList.add(rideData[key.toString()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
|
|
||||||
try {
|
|
||||||
if (await TripOverlayPlugin.isOverlayActive()) {
|
|
||||||
Log.print("📲 Closing Overlay because App took control via Socket");
|
|
||||||
await TripOverlayPlugin.hideOverlay();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("Overlay check error: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Get.currentRoute != '/OrderRequestPage') {
|
|
||||||
Log.print("🚀 Socket: Navigating to OrderRequestPage...");
|
|
||||||
Get.toNamed('/OrderRequestPage', arguments: {
|
|
||||||
'myListString': jsonEncode(driverList),
|
|
||||||
'DriverList': driverList,
|
|
||||||
'body': 'New Trip Request via Socket ⚡'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Log.print(
|
|
||||||
"⚠️ User is already on OrderRequestPage. Skipping navigation.");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("❌ Socket Navigation Error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startHeartbeat() {
|
|
||||||
_socketHeartbeat?.cancel();
|
|
||||||
_socketHeartbeat = Timer.periodic(const Duration(seconds: 25), (timer) {
|
|
||||||
if (socket != null && isSocketConnected && myLocation.latitude != 0) {
|
|
||||||
emitLocationToSocket(myLocation, heading, speed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _stopHeartbeat() {
|
|
||||||
_socketHeartbeat?.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// In LocationController.dart
|
|
||||||
|
|
||||||
void emitLocationToSocket(LatLng pos, double head, double spd) {
|
|
||||||
String status = box.read(BoxName.statusDriverLocation) ?? 'on';
|
|
||||||
|
|
||||||
// Basic payload
|
|
||||||
var payload = {
|
|
||||||
'driver_id': box.read(BoxName.driverID),
|
|
||||||
'lat': pos.latitude,
|
|
||||||
'lng': pos.longitude,
|
|
||||||
'heading': head,
|
|
||||||
'speed': spd * 3.6,
|
|
||||||
'status': status,
|
|
||||||
'distance': totalDistance,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🔥 CRITICAL FIX: Inject Passenger ID if a ride is active 🔥
|
|
||||||
if (Get.isRegistered<MapDriverController>()) {
|
|
||||||
final mapCtrl = Get.find<MapDriverController>();
|
|
||||||
|
|
||||||
// Check if ride is started/active and we have a passenger ID
|
|
||||||
if (mapCtrl.isRideStarted && mapCtrl.passengerId != null) {
|
|
||||||
payload['passenger_id'] =
|
|
||||||
mapCtrl.passengerId; // This triggers the PHP forwarding
|
|
||||||
payload['ride_id'] = mapCtrl.rideId; // Good for debugging
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugLog.print to verify
|
|
||||||
//Log.print('🚀 Emitting Location: $payload');
|
|
||||||
|
|
||||||
if (socket != null && socket!.connected) {
|
|
||||||
socket!.emit('update_location', payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ===================================================================
|
|
||||||
// ====== Tracking Logic ======
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
Future<void> startLocationUpdates() async {
|
|
||||||
_isReady = true;
|
|
||||||
String currentStatus = box.read(BoxName.statusDriverLocation) ?? 'off';
|
|
||||||
if (currentStatus == 'blocked') {
|
|
||||||
stopLocationUpdates();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start background service
|
|
||||||
await BackgroundServiceHelper.startService();
|
|
||||||
|
|
||||||
if (socket == null || !socket!.connected) {
|
|
||||||
initSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_locSub != null) return;
|
|
||||||
|
|
||||||
if (await _ensureServiceAndPermission()) {
|
|
||||||
_subscribeLocationStream();
|
|
||||||
_startBatchTimers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _subscribeLocationStream() async {
|
|
||||||
_locSub?.cancel();
|
|
||||||
int interval = _isPowerSavingMode ? 10000 : 5000;
|
|
||||||
await location.enableBackgroundMode(enable: true);
|
|
||||||
location.changeSettings(
|
|
||||||
accuracy: LocationAccuracy.navigation,
|
|
||||||
interval: interval,
|
|
||||||
distanceFilter: _isPowerSavingMode ? 20 : 10,
|
|
||||||
);
|
|
||||||
|
|
||||||
_locSub = location.onLocationChanged.listen((LocationData loc) async {
|
|
||||||
if (loc.latitude == null || loc.longitude == null) return;
|
|
||||||
|
|
||||||
final now = DateTime.now();
|
|
||||||
final pos = LatLng(loc.latitude!, loc.longitude!);
|
|
||||||
|
|
||||||
myLocation = pos;
|
|
||||||
speed = loc.speed ?? 0.0;
|
|
||||||
heading = loc.heading ?? 0.0;
|
|
||||||
|
|
||||||
if (_lastPosForDistance != null) {
|
|
||||||
final d = _calculateDistance(_lastPosForDistance!, pos);
|
|
||||||
if (d > 5.0) totalDistance += d;
|
|
||||||
}
|
|
||||||
_lastPosForDistance = pos;
|
|
||||||
|
|
||||||
update();
|
|
||||||
emitLocationToSocket(pos, heading, speed);
|
|
||||||
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
|
|
||||||
}, onError: (e) => Log.print('❌ Location Stream Error: $e'));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> stopLocationUpdates() async {
|
|
||||||
Log.print("🛑 Stopping Location Updates...");
|
|
||||||
|
|
||||||
_locSub?.cancel();
|
|
||||||
_locSub = null;
|
|
||||||
_recordTimer?.cancel();
|
|
||||||
_uploadBatchTimer?.cancel();
|
|
||||||
_socketHeartbeat?.cancel();
|
|
||||||
|
|
||||||
if (socket != null && socket!.connected) {
|
|
||||||
String driverId = box.read(BoxName.driverID).toString();
|
|
||||||
socket!.emit('update_location', {
|
|
||||||
'driver_id': driverId,
|
|
||||||
'lat': myLocation.latitude,
|
|
||||||
'lng': myLocation.longitude,
|
|
||||||
'heading': heading,
|
|
||||||
'speed': speed * 3.6,
|
|
||||||
'status': 'close', // Changed to off
|
|
||||||
'distance': totalDistance
|
|
||||||
});
|
|
||||||
socket!.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
await BackgroundServiceHelper.stopService();
|
|
||||||
socket = null;
|
|
||||||
isSocketConnected = false;
|
|
||||||
_isReady = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// ====== Batch Logic & Helpers ======
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
void _startBatchTimers() {
|
|
||||||
_recordTimer?.cancel();
|
|
||||||
_uploadBatchTimer?.cancel();
|
|
||||||
|
|
||||||
final recDur =
|
|
||||||
_isPowerSavingMode ? recordIntervalPowerSave : recordIntervalNormal;
|
|
||||||
final upDur = _isPowerSavingMode
|
|
||||||
? uploadBatchIntervalPowerSave
|
|
||||||
: uploadBatchIntervalNormal;
|
|
||||||
|
|
||||||
_recordTimer =
|
|
||||||
Timer.periodic(recDur, (_) => _recordCurrentLocationToBuffer());
|
|
||||||
_uploadBatchTimer = Timer.periodic(upDur, (_) => _flushBufferToServer());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _recordCurrentLocationToBuffer() {
|
|
||||||
if (myLocation.latitude == 0) return;
|
|
||||||
final now = DateTime.now();
|
|
||||||
double distFromLast = 0.0;
|
|
||||||
if (_lastRecordedRealLoc != null) {
|
|
||||||
distFromLast = _calculateDistance(_lastRecordedRealLoc!, myLocation);
|
|
||||||
}
|
|
||||||
bool moved = distFromLast > 10.0;
|
|
||||||
bool timeForced = _lastRecordedTime == null ||
|
|
||||||
now.difference(_lastRecordedTime!).inSeconds >= 60;
|
|
||||||
|
|
||||||
if ((moved && speed > 0.5) || timeForced) {
|
|
||||||
_lastRecordedRealLoc = myLocation;
|
|
||||||
_lastRecordedTime = now;
|
|
||||||
final point = {
|
|
||||||
'lat': double.parse(myLocation.latitude.toStringAsFixed(6)),
|
|
||||||
'lng': double.parse(myLocation.longitude.toStringAsFixed(6)),
|
|
||||||
'spd': double.parse((speed * 3.6).toStringAsFixed(1)),
|
|
||||||
'head': int.parse(heading.toStringAsFixed(0)),
|
|
||||||
'st': box.read(BoxName.statusDriverLocation) ?? 'off',
|
|
||||||
'ts': now.toIso8601String(),
|
|
||||||
};
|
|
||||||
_trackBuffer.add(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _flushBufferToServer() async {
|
|
||||||
if (_trackBuffer.isEmpty) return;
|
|
||||||
List<Map<String, dynamic>> batch = List.from(_trackBuffer);
|
|
||||||
_trackBuffer.clear();
|
|
||||||
final String driverId = (box.read(BoxName.driverID) ?? '').toString();
|
|
||||||
try {
|
|
||||||
await CRUD().post(
|
|
||||||
link: '${AppLink.locationServer}/add_batch.php',
|
|
||||||
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('❌ Failed to upload batch: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenToBatteryChanges() async {
|
|
||||||
_battery.onBatteryStateChanged.listen((state) async {
|
|
||||||
int level = await _battery.batteryLevel;
|
|
||||||
bool previousMode = _isPowerSavingMode;
|
|
||||||
if (level <= powerSaveTriggerLevel) _isPowerSavingMode = true;
|
|
||||||
if (level >= powerSaveExitLevel) _isPowerSavingMode = false;
|
|
||||||
if (previousMode != _isPowerSavingMode) {
|
|
||||||
_startBatchTimers();
|
|
||||||
startLocationUpdates();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now,
|
|
||||||
{required double currentSpeed}) async {
|
|
||||||
final dist =
|
|
||||||
(_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos);
|
|
||||||
if (dist < 15.0) return;
|
|
||||||
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
|
|
||||||
try {
|
|
||||||
await sql.insertData({
|
|
||||||
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
|
|
||||||
'latitude': pos.latitude,
|
|
||||||
'longitude': pos.longitude,
|
|
||||||
'acceleration': accel,
|
|
||||||
'created_at': now.toIso8601String(),
|
|
||||||
'updated_at': now.toIso8601String(),
|
|
||||||
}, TableName.behavior);
|
|
||||||
_lastSqlLoc = pos;
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('SQLite Error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// استبدال دالة Haversine اليدوية بـ Geolocator في باقي الكود أيضاً
|
|
||||||
// لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed)
|
|
||||||
double _calculateDistance(LatLng a, LatLng b) {
|
|
||||||
return geo.Geolocator.distanceBetween(
|
|
||||||
a.latitude, a.longitude, b.latitude, b.longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
double? _calcAcceleration(double currentSpeed, DateTime now) {
|
|
||||||
if (_lastSpeed != null && _lastSpeedAt != null) {
|
|
||||||
final dt = now.difference(_lastSpeedAt!).inMilliseconds / 1000.0;
|
|
||||||
if (dt > 0.5) {
|
|
||||||
final a = (currentSpeed - _lastSpeed!) / dt;
|
|
||||||
_lastSpeed = currentSpeed;
|
|
||||||
_lastSpeedAt = now;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_lastSpeed = currentSpeed;
|
|
||||||
_lastSpeedAt = now;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initLocationSettings() async {
|
|
||||||
if (await _ensureServiceAndPermission()) {
|
|
||||||
try {
|
|
||||||
await location.enableBackgroundMode(enable: true);
|
|
||||||
location.changeSettings(
|
|
||||||
accuracy: LocationAccuracy.navigation,
|
|
||||||
interval: 1000,
|
|
||||||
distanceFilter: 10);
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("Warning: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔥🔥 هذه هي الدالة المعدلة التي تستخدم ph.Permission 🔥🔥
|
|
||||||
Future<bool> _ensureServiceAndPermission() async {
|
|
||||||
// 1. طلب إذن الإشعارات أولاً باستخدام permission_handler
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
var notificationStatus = await ph.Permission.notification.status;
|
|
||||||
if (!notificationStatus.isGranted) {
|
|
||||||
await ph.Permission.notification.request();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. طلب تفعيل خدمة الموقع (GPS) من بكج location
|
|
||||||
bool serviceEnabled = await location.serviceEnabled();
|
|
||||||
if (!serviceEnabled) {
|
|
||||||
serviceEnabled = await location.requestService();
|
|
||||||
if (!serviceEnabled) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. طلب إذن الموقع الأساسي من بكج location
|
|
||||||
PermissionStatus permissionGranted = await location.hasPermission();
|
|
||||||
if (permissionGranted == PermissionStatus.denied) {
|
|
||||||
permissionGranted = await location.requestPermission();
|
|
||||||
if (permissionGranted != PermissionStatus.granted) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ... (باقي الكود)
|
|
||||||
|
|
||||||
Future<LocationData?> getLocation() async {
|
|
||||||
try {
|
|
||||||
if (await _ensureServiceAndPermission()) {
|
|
||||||
return await location.getLocation();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('❌ FAILED to get single location: $e');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import '../../views/widgets/mydialoug.dart';
|
|
||||||
import '../auth/captin/login_captin_controller.dart';
|
|
||||||
|
|
||||||
class LocationPermissions {
|
|
||||||
// late Location location;
|
|
||||||
|
|
||||||
// Future locationPermissions() async {
|
|
||||||
// location = Location();
|
|
||||||
// var permissionStatus = await location.requestPermission();
|
|
||||||
// if (permissionStatus == PermissionStatus.denied) {
|
|
||||||
// // The user denied the location permission.
|
|
||||||
// Get.defaultDialog(title: 'GPS Required Allow !.'.tr, middleText: '');
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getPermissionLocation() async {
|
|
||||||
final PermissionStatus status = await Permission.locationAlways.status;
|
|
||||||
if (!await Permission.locationAlways.serviceStatus.isEnabled) {
|
|
||||||
Log.print('status.isGranted: ${status.isGranted}');
|
|
||||||
// box.write(BoxName.locationPermission, 'true');
|
|
||||||
await Permission.locationAlways.request();
|
|
||||||
Get.put(LoginDriverController()).update();
|
|
||||||
MyDialog().getDialog(
|
|
||||||
'Enable Location Permission'.tr, // {en:ar}
|
|
||||||
'Allowing location access will help us display orders near you. Please enable it now.'
|
|
||||||
.tr, // {en:ar}
|
|
||||||
() async {
|
|
||||||
Get.back();
|
|
||||||
box.write(BoxName.locationPermission, 'true');
|
|
||||||
await Permission.locationAlways.request();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getPermissionLocation1() async {
|
|
||||||
PermissionStatus status = await Permission.locationWhenInUse.request();
|
|
||||||
|
|
||||||
if (status.isGranted) {
|
|
||||||
// After granting when in use, request "always" location permission
|
|
||||||
status = await Permission.locationAlways.request();
|
|
||||||
|
|
||||||
if (status.isGranted) {
|
|
||||||
print("Background location permission granted");
|
|
||||||
} else {
|
|
||||||
print("Background location permission denied");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("Location permission denied");
|
|
||||||
await openAppSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import 'package:sefer_driver/views/home/on_boarding_page.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/my_textField.dart';
|
|
||||||
|
|
||||||
import '../../constant/style.dart';
|
|
||||||
|
|
||||||
class LogOutController extends GetxController {
|
|
||||||
TextEditingController checkTxtController = TextEditingController();
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final formKey1 = GlobalKey<FormState>();
|
|
||||||
final emailTextController = TextEditingController();
|
|
||||||
|
|
||||||
Future deleteMyAccountDriver(String id) async {
|
|
||||||
await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then(
|
|
||||||
(value) => Get.snackbar('Deleted'.tr, 'Your Account is Deleted',
|
|
||||||
backgroundColor: AppColor.redColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
checkBeforeDelete() async {
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: AppLink.deletecaptainAccounr,
|
|
||||||
payload: {'id': box.read(BoxName.driverID)});
|
|
||||||
return res['message'][0]['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
deletecaptainAccount() {
|
|
||||||
Get.defaultDialog(
|
|
||||||
backgroundColor: AppColor.yellowColor,
|
|
||||||
title: 'Are you sure to delete your account?'.tr,
|
|
||||||
middleText:
|
|
||||||
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ',
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: Get.width,
|
|
||||||
decoration: AppStyle.boxDecoration,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month'
|
|
||||||
.tr,
|
|
||||||
style: AppStyle.title.copyWith(color: AppColor.redColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
Form(
|
|
||||||
key: formKey,
|
|
||||||
child: SizedBox(
|
|
||||||
width: Get.width,
|
|
||||||
child: MyTextForm(
|
|
||||||
controller: checkTxtController,
|
|
||||||
label: 'Enter Your First Name'.tr,
|
|
||||||
hint: 'Enter Your First Name'.tr,
|
|
||||||
type: TextInputType.name,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Delete'.tr,
|
|
||||||
onPressed: () async {
|
|
||||||
if (checkTxtController.text == (box.read(BoxName.nameDriver))) {
|
|
||||||
// deletecaptainAccount();
|
|
||||||
|
|
||||||
var id = await checkBeforeDelete();
|
|
||||||
deleteMyAccountDriver(id);
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('Your Name is Wrong'.tr);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future logOutPassenger() async {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Are you Sure to LogOut?'.tr,
|
|
||||||
content: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Cancel'.tr,
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
// box.remove(BoxName.agreeTerms);
|
|
||||||
await box.erase();
|
|
||||||
await storage.deleteAll();
|
|
||||||
Get.offAll(OnBoardingPage());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Sign Out'.tr,
|
|
||||||
style:
|
|
||||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future logOutCaptain() async {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Are you Sure to LogOut?'.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
content: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Cancel'.tr,
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
// box.remove(BoxName.agreeTerms);
|
|
||||||
await box.erase();
|
|
||||||
await storage.deleteAll();
|
|
||||||
Get.offAll(OnBoardingPage());
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Sign Out'.tr,
|
|
||||||
style:
|
|
||||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
deletePassengerAccount() async {
|
|
||||||
if (formKey1.currentState!.validate()) {
|
|
||||||
if (box.read(BoxName.email).toString() == emailTextController.text) {
|
|
||||||
await CRUD().post(link: AppLink.passengerRemovedAccountEmail, payload: {
|
|
||||||
'email': box.read(BoxName.email),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mySnackeBarError(
|
|
||||||
'Email you inserted is Wrong.'.tr,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import 'net_guard.dart';
|
|
||||||
|
|
||||||
typedef BodyEncoder = Future<http.Response> Function();
|
|
||||||
|
|
||||||
class HttpRetry {
|
|
||||||
/// ريتراي لـ network/transient errors فقط.
|
|
||||||
static Future<http.Response> sendWithRetry(
|
|
||||||
BodyEncoder send, {
|
|
||||||
int maxRetries = 3,
|
|
||||||
Duration baseDelay = const Duration(milliseconds: 400),
|
|
||||||
Duration timeout = const Duration(seconds: 12),
|
|
||||||
}) async {
|
|
||||||
// ✅ Pre-flight check for internet connection
|
|
||||||
if (!await NetGuard().hasInternet()) {
|
|
||||||
// Immediately throw a specific exception if there's no internet.
|
|
||||||
// This avoids pointless retries.
|
|
||||||
throw const SocketException("No internet connection");
|
|
||||||
}
|
|
||||||
int attempt = 0;
|
|
||||||
while (true) {
|
|
||||||
attempt++;
|
|
||||||
try {
|
|
||||||
final res = await send().timeout(timeout);
|
|
||||||
return res;
|
|
||||||
} on TimeoutException catch (_) {
|
|
||||||
if (attempt >= maxRetries) rethrow;
|
|
||||||
} on SocketException catch (_) {
|
|
||||||
if (attempt >= maxRetries) rethrow;
|
|
||||||
} on HandshakeException catch (_) {
|
|
||||||
if (attempt >= maxRetries) rethrow;
|
|
||||||
} on http.ClientException catch (e) {
|
|
||||||
// مثال: Connection reset by peer
|
|
||||||
final msg = e.message.toLowerCase();
|
|
||||||
final transient = msg.contains('connection reset') ||
|
|
||||||
msg.contains('broken pipe') ||
|
|
||||||
msg.contains('timed out');
|
|
||||||
if (!transient || attempt >= maxRetries) rethrow;
|
|
||||||
}
|
|
||||||
// backoff: 0.4s, 0.8s, 1.6s
|
|
||||||
final delay = baseDelay * (1 << (attempt - 1));
|
|
||||||
await Future.delayed(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
||||||
import 'package:internet_connection_checker/internet_connection_checker.dart';
|
|
||||||
|
|
||||||
class NetGuard {
|
|
||||||
static final NetGuard _i = NetGuard._();
|
|
||||||
NetGuard._();
|
|
||||||
factory NetGuard() => _i;
|
|
||||||
|
|
||||||
bool _notified = false;
|
|
||||||
|
|
||||||
/// فحص: (أ) فيه شبكة؟ (ب) فيه انترنت؟ (ج) السيرفر نفسه reachable؟
|
|
||||||
Future<bool> hasInternet({Uri? mustReach}) async {
|
|
||||||
final connectivity = await Connectivity().checkConnectivity();
|
|
||||||
if (connectivity == ConnectivityResult.none) return false;
|
|
||||||
|
|
||||||
final hasNet =
|
|
||||||
await InternetConnectionChecker.createInstance().hasConnection;
|
|
||||||
if (!hasNet) return false;
|
|
||||||
|
|
||||||
if (mustReach != null) {
|
|
||||||
try {
|
|
||||||
final host = mustReach.host;
|
|
||||||
final result = await InternetAddress.lookup(host);
|
|
||||||
if (result.isEmpty || result.first.rawAddress.isEmpty) return false;
|
|
||||||
|
|
||||||
// اختباري خفيف عبر TCP (80/443) — 400ms timeout
|
|
||||||
final port = mustReach.scheme == 'http' ? 80 : 443;
|
|
||||||
final socket = await Socket.connect(host, port,
|
|
||||||
timeout: const Duration(milliseconds: 400));
|
|
||||||
socket.destroy();
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// إظهار إشعار مرة واحدة ثم إسكات التكرارات
|
|
||||||
void notifyOnce(void Function(String title, String msg) show) {
|
|
||||||
if (_notified) return;
|
|
||||||
_notified = true;
|
|
||||||
show('لا يوجد اتصال بالإنترنت', 'تحقق من الشبكة ثم حاول مجددًا.');
|
|
||||||
// إعادة السماح بعد 15 ثانية
|
|
||||||
Future.delayed(const Duration(seconds: 15), () => _notified = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,651 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:sefer_driver/constant/api_key.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/info.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
import 'package:sefer_driver/constant/table_names.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../auth/captin/register_captin_controller.dart';
|
|
||||||
import 'launch.dart';
|
|
||||||
|
|
||||||
//
|
|
||||||
// class TextExtractionController extends GetxController {
|
|
||||||
// String extractedText = '';
|
|
||||||
// bool isloading = false;
|
|
||||||
// File? _scannedImage;
|
|
||||||
// // Convert the extracted text to JSON
|
|
||||||
// // Convert the extracted text to JSON
|
|
||||||
// String getTextAsJSON(String text) {
|
|
||||||
// final lines = text.split('\n');
|
|
||||||
// final jsonList = lines.map((line) {
|
|
||||||
// return {
|
|
||||||
// 'line_text': line,
|
|
||||||
// 'num_words': line.trim().split(' ').length,
|
|
||||||
// };
|
|
||||||
// }).toList();
|
|
||||||
//
|
|
||||||
// final json = {
|
|
||||||
// 'lines': jsonList,
|
|
||||||
// 'num_lines': lines.length,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// return jsonEncode(json);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Convert the extracted text to blocks by line
|
|
||||||
// List<String> getTextBlocks(String text) {
|
|
||||||
// return text.split('\n');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Future<void> pickAndExtractText() async {
|
|
||||||
// // final pickedImage = await ImagePicker().pickImage(
|
|
||||||
// // source: ImageSource.camera,
|
|
||||||
// // preferredCameraDevice: CameraDevice.rear,
|
|
||||||
// // maxHeight: Get.height * .3,
|
|
||||||
// // maxWidth: Get.width * .8,
|
|
||||||
// // imageQuality: 99,
|
|
||||||
// // );
|
|
||||||
// // if (pickedImage != null) {
|
|
||||||
// // isloading = true;
|
|
||||||
// // update();
|
|
||||||
// // final imagePath = pickedImage.path;
|
|
||||||
// // final languages = [
|
|
||||||
// // 'eng',
|
|
||||||
// // 'ara'
|
|
||||||
// // ]; // Specify the languages you want to use for text extraction
|
|
||||||
//
|
|
||||||
// // try {
|
|
||||||
// // final text = await FlutterTesseractOcr.extractText(imagePath,
|
|
||||||
// // language:
|
|
||||||
// // languages.join('+'), // Combine multiple languages with '+'
|
|
||||||
// // args: {
|
|
||||||
// // "psm": "4",
|
|
||||||
// // "preserve_interword_spaces": "1",
|
|
||||||
// // // "rectangle": const Rect.fromLTWH(100, 100, 200, 200),
|
|
||||||
// // } // Additional options if needed
|
|
||||||
// // );
|
|
||||||
// // isloading = false;
|
|
||||||
// // final jsonText = getTextAsJSON(text);
|
|
||||||
// // final textBlocks = getTextBlocks(text);
|
|
||||||
// // update();
|
|
||||||
// // extractedText =
|
|
||||||
// // textBlocks.toString(); // Convert the extracted text to JSON.
|
|
||||||
//
|
|
||||||
// // // Print the JSON to the console.
|
|
||||||
// // update();
|
|
||||||
// // } catch (e) {
|
|
||||||
// // extractedText = '';
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class TextMLGoogleRecognizerController extends GetxController {
|
|
||||||
// @override
|
|
||||||
// void onInit() {
|
|
||||||
// scanText();
|
|
||||||
// super.onInit();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // The ImagePicker instance
|
|
||||||
// final ImagePicker _imagePicker = ImagePicker();
|
|
||||||
//
|
|
||||||
// // The GoogleMlKit TextRecognizer instance
|
|
||||||
// final TextRecognizer _textRecognizer = TextRecognizer();
|
|
||||||
//
|
|
||||||
// // The scanned text
|
|
||||||
// String? scannedText;
|
|
||||||
// String? jsonOutput;
|
|
||||||
// final List<Map<String, dynamic>> lines = [];
|
|
||||||
//
|
|
||||||
// Map decode = {};
|
|
||||||
//
|
|
||||||
// Future<void> scanText() async {
|
|
||||||
// // Pick an image from the camera or gallery
|
|
||||||
// final XFile? image =
|
|
||||||
// await _imagePicker.pickImage(source: ImageSource.gallery);
|
|
||||||
//
|
|
||||||
// // If no image was picked, return
|
|
||||||
// if (image == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Convert the XFile object to an InputImage object
|
|
||||||
// final InputImage inputImage = InputImage.fromFile(File(image.path));
|
|
||||||
//
|
|
||||||
// // Recognize the text in the image
|
|
||||||
// final RecognizedText recognizedText =
|
|
||||||
// await _textRecognizer.processImage(inputImage);
|
|
||||||
// scannedText = recognizedText.text;
|
|
||||||
// Map extractedData = {};
|
|
||||||
// // Extract the scanned text line by line
|
|
||||||
// for (var i = 0; i < recognizedText.blocks.length; i++) {
|
|
||||||
// final block = recognizedText.blocks[i];
|
|
||||||
// for (final line in block.lines) {
|
|
||||||
// final lineText = line.text;
|
|
||||||
//
|
|
||||||
// if (lineText.contains('DL')) {
|
|
||||||
// final dlNumber = lineText.split('DL')[1].trim();
|
|
||||||
// extractedData['dl_number'] = dlNumber;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains('USA')) {
|
|
||||||
// final usa = lineText.split('USA')[1].trim();
|
|
||||||
// extractedData['USA'] = usa;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains('DRIVER LICENSE')) {
|
|
||||||
// final driverl = lineText;
|
|
||||||
// extractedData['DRIVER_LICENSE'] = driverl;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (lineText.contains('EXP')) {
|
|
||||||
// final expiryDate = lineText.split('EXP')[1].trim();
|
|
||||||
// extractedData['expiry_date'] = expiryDate;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (lineText.contains('DOB')) {
|
|
||||||
// final dob = lineText.split('DOB')[1].trim();
|
|
||||||
// extractedData['dob'] = dob;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (lineText.contains("LN")) {
|
|
||||||
// if ((lineText.indexOf("LN") == 0)) {
|
|
||||||
// final lastName = lineText.split('LN')[1].trim();
|
|
||||||
// extractedData['lastName'] = lastName;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("FN")) {
|
|
||||||
// final firstName = lineText.split('FN')[1].trim();
|
|
||||||
// extractedData['firstName'] = firstName;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("RSTR")) {
|
|
||||||
// final rstr = lineText.split('RSTR')[1].trim();
|
|
||||||
// extractedData['rstr'] = rstr;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("CLASS")) {
|
|
||||||
// final class1 = lineText.split('CLASS')[1].trim();
|
|
||||||
// extractedData['class'] = class1;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("END")) {
|
|
||||||
// final end = lineText.split('END')[1].trim();
|
|
||||||
// extractedData['end'] = end;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("DD")) {
|
|
||||||
// final dd = lineText.split('DD')[1].trim();
|
|
||||||
// extractedData['dd'] = dd;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("EYES")) {
|
|
||||||
// final eyes = lineText.split('EYES')[1].trim();
|
|
||||||
// extractedData['eyes'] = eyes;
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("SEX")) {
|
|
||||||
// final parts = lineText.split("SEX ")[1];
|
|
||||||
// extractedData['sex'] = parts[0];
|
|
||||||
// }
|
|
||||||
// if (lineText.contains("HAIR")) {
|
|
||||||
// final hair = lineText.split('HAIR')[1].trim();
|
|
||||||
// extractedData['hair'] = hair;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (lineText.contains('STREET') || lineText.contains(',')) {
|
|
||||||
// final address = lineText;
|
|
||||||
// extractedData['address'] = address;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Repeat this process for other relevant data fields
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Convert the list of lines to a JSON string
|
|
||||||
// jsonOutput = jsonEncode(extractedData);
|
|
||||||
// decode = jsonDecode(jsonOutput!);
|
|
||||||
//
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
class ScanDocumentsByApi extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
Map<String, dynamic> responseMap = {};
|
|
||||||
final ImagePicker imagePicker = ImagePicker();
|
|
||||||
late Uint8List imagePortrait;
|
|
||||||
late Uint8List imageSignature;
|
|
||||||
late Uint8List imageDocumentFrontSide;
|
|
||||||
XFile? image;
|
|
||||||
XFile? imagePortraitFile;
|
|
||||||
XFile? imageFace;
|
|
||||||
late File tempFile;
|
|
||||||
late String imagePath;
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
late String name;
|
|
||||||
late String licenseClass;
|
|
||||||
late String documentNo;
|
|
||||||
late String address;
|
|
||||||
late String stateCode;
|
|
||||||
late String height;
|
|
||||||
late String sex;
|
|
||||||
late String postalCode;
|
|
||||||
late String dob;
|
|
||||||
late String expireDate;
|
|
||||||
|
|
||||||
// ///////////////////////
|
|
||||||
// late CameraController cameraController;
|
|
||||||
// late List<CameraDescription> cameras;
|
|
||||||
// bool isCameraInitialized = false;
|
|
||||||
// // final TextRecognizer _textRecognizer = TextRecognizer();
|
|
||||||
// String? scannedText;
|
|
||||||
|
|
||||||
// Future<void> initializeCamera(int cameraID) async {
|
|
||||||
// try {
|
|
||||||
// cameras = await availableCameras();
|
|
||||||
// //update();
|
|
||||||
// cameraController = CameraController(
|
|
||||||
// cameras[cameraID],
|
|
||||||
// ResolutionPreset.medium,
|
|
||||||
// enableAudio: false,
|
|
||||||
// );
|
|
||||||
// await cameraController.initialize();
|
|
||||||
// isCameraInitialized = true;
|
|
||||||
// update();
|
|
||||||
// } catch (e) {
|
|
||||||
// if (e is CameraException) {
|
|
||||||
// switch (e.code) {
|
|
||||||
// case 'CameraAccessDenied':
|
|
||||||
// Get.defaultDialog(
|
|
||||||
// title: 'Camera Access Denied.'.tr,
|
|
||||||
// middleText: '',
|
|
||||||
// confirm:
|
|
||||||
// MyElevatedButton(title: 'Open Settings', onPressed: () {}),
|
|
||||||
// );
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// // Handle other errors here.
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
///
|
|
||||||
|
|
||||||
Future<void> scanDocumentsByApi() async {
|
|
||||||
// String? visionApi = await storage.read(key: BoxName.visionApi);
|
|
||||||
// String? visionApi = AK.visionApi;
|
|
||||||
// Pick an image from the camera or gallery
|
|
||||||
image = await imagePicker.pickImage(source: ImageSource.camera); //
|
|
||||||
|
|
||||||
// If no image was picked, return
|
|
||||||
if (image == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var headers = {'X-BLOBR-KEY': AK.visionApi};
|
|
||||||
var request = http.MultipartRequest('POST',
|
|
||||||
Uri.parse('https://api.faceonlive.com/j2y3q25y1b6maif1/api/iddoc'));
|
|
||||||
request.files.add(await http.MultipartFile.fromPath('image', image!.path));
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
http.StreamedResponse response = await request.send();
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
String responseString = await response.stream.bytesToString();
|
|
||||||
responseMap = jsonDecode(responseString);
|
|
||||||
var ocrData = responseMap['data']['ocr'];
|
|
||||||
name = ocrData['name'].toString();
|
|
||||||
licenseClass = ocrData['dlClass'].toString();
|
|
||||||
documentNo = ocrData['documentNumber'].toString();
|
|
||||||
address = ocrData['address'].toString();
|
|
||||||
height = ocrData['height'].toString();
|
|
||||||
postalCode = ocrData['addressPostalCode'].toString();
|
|
||||||
sex = ocrData['sex'].toString();
|
|
||||||
stateCode = ocrData['addressJurisdictionCode'].toString();
|
|
||||||
expireDate = ocrData['dateOfExpiry'].toString();
|
|
||||||
dob = ocrData['dateOfBirth'].toString();
|
|
||||||
if (responseMap['data'] != null &&
|
|
||||||
responseMap['data']['image'] != null &&
|
|
||||||
responseMap['data']['image']['portrait'] != null) {
|
|
||||||
imagePortrait = base64Decode(responseMap['data']['image']['portrait']);
|
|
||||||
String tempPath = Directory.systemTemp.path;
|
|
||||||
tempFile = File('$tempPath/image.jpg');
|
|
||||||
await tempFile.writeAsBytes(imagePortrait);
|
|
||||||
|
|
||||||
imagePath = tempFile.path;
|
|
||||||
// imagePortraitFile=File(imagePath) ;
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
// Handle error or provide a default value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseMap['data']['image']['signature'] != null) {
|
|
||||||
imageSignature =
|
|
||||||
base64Decode(responseMap['data']['image']['signature']);
|
|
||||||
} else {
|
|
||||||
imageSignature = imagePortrait;
|
|
||||||
// Handle error or provide a default value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseMap['data'] != null &&
|
|
||||||
responseMap['data']['image'] != null &&
|
|
||||||
responseMap['data']['image']['documentFrontSide'] != null) {
|
|
||||||
imageDocumentFrontSide =
|
|
||||||
base64Decode(responseMap['data']['image']['documentFrontSide']);
|
|
||||||
} else {
|
|
||||||
// Handle error or provide a default value
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
late int times;
|
|
||||||
Future checkMatchFaceApi() async {
|
|
||||||
sql.getAllData(TableName.faceDetectTimes).then((value) {
|
|
||||||
if (value.isEmpty || value == null) {
|
|
||||||
sql.insertData({'faceDetectTimes': 1}, TableName.faceDetectTimes);
|
|
||||||
sql.getAllData(TableName.faceDetectTimes).then((value) {
|
|
||||||
times = value[0]['faceDetectTimes'];
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (times < 4) {
|
|
||||||
times++;
|
|
||||||
matchFaceApi();
|
|
||||||
sql.updateData(
|
|
||||||
{'faceDetectTimes': times}, TableName.faceDetectTimes, 1);
|
|
||||||
} else {
|
|
||||||
Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'You have finished all times '.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
middleText: 'if you want help you can email us here'.tr,
|
|
||||||
middleTextStyle: AppStyle.title,
|
|
||||||
cancel: MyElevatedButton(
|
|
||||||
title: 'Thanks'.tr,
|
|
||||||
kolor: AppColor.greenColor,
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Email Us'.tr,
|
|
||||||
kolor: AppColor.yellowColor, //
|
|
||||||
onPressed: () {
|
|
||||||
launchCommunication('email', 'support@mobile-app.store',
|
|
||||||
'${'Hi'.tr} ${AppInformation.appName}\n${'I cant register in your app in face detection '.tr}');
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Map res = {};
|
|
||||||
Future matchFaceApi() async {
|
|
||||||
// String? visionApi = await storage.read(key: BoxName.visionApi);
|
|
||||||
imageFace = await imagePicker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
preferredCameraDevice: CameraDevice.front,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If no image was picked, return
|
|
||||||
if (image == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final imageFile = File(imageFace!.path);
|
|
||||||
// Uint8List imageBytes = await imageFile.readAsBytes();
|
|
||||||
var headers = {'X-BLOBR-KEY': AK.visionApi};
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(
|
|
||||||
'https://api.faceonlive.com/sntzbspfsdupgid1/api/face_compare'));
|
|
||||||
request.files
|
|
||||||
.add(await http.MultipartFile.fromPath('image1', imageFile.path));
|
|
||||||
request.files.add(await http.MultipartFile.fromPath('image2', imagePath));
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
|
|
||||||
http.StreamedResponse response = await request.send();
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
res = jsonDecode(await response.stream.bytesToString());
|
|
||||||
|
|
||||||
update();
|
|
||||||
res['data']['result'].toString().contains('No face detected in image')
|
|
||||||
? Get.defaultDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'No face detected'.tr,
|
|
||||||
middleText: ''.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
kolor: AppColor.yellowColor,
|
|
||||||
title: 'Back'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
)) //
|
|
||||||
: Get.defaultDialog(
|
|
||||||
// barrierDismissible: false,
|
|
||||||
title: 'Image detecting result is '.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
res['data']['result'].toString(),
|
|
||||||
style: res['data']['result'].toString() == 'Different'
|
|
||||||
? AppStyle.title.copyWith(color: AppColor.redColor)
|
|
||||||
: AppStyle.title.copyWith(color: AppColor.greenColor),
|
|
||||||
),
|
|
||||||
res['data']['result'].toString() == 'Different'
|
|
||||||
? Text(
|
|
||||||
'${'Be sure for take accurate images please\nYou have'.tr} $times ${'from 3 times Take Attention'.tr}',
|
|
||||||
style: AppStyle.title,
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
'image verified'.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
confirm: res['data']['result'].toString() == 'Different'
|
|
||||||
? MyElevatedButton(
|
|
||||||
title: 'Back'.tr,
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
kolor: AppColor.redColor,
|
|
||||||
)
|
|
||||||
: MyElevatedButton(
|
|
||||||
title: 'Next'.tr,
|
|
||||||
onPressed: () async {
|
|
||||||
RegisterCaptainController registerCaptainController =
|
|
||||||
Get.put(RegisterCaptainController());
|
|
||||||
|
|
||||||
await registerCaptainController.register();
|
|
||||||
await registerCaptainController.addLisence();
|
|
||||||
await uploadImagePortrate();
|
|
||||||
// Get.to(() => CarLicensePage());
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// await uploadImage(
|
|
||||||
// tempFile, AppLink.uploadImagePortrate);
|
|
||||||
// Get.to(() => CarLicensePage());
|
|
||||||
// },
|
|
||||||
kolor: AppColor.greenColor,
|
|
||||||
));
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo upload images and fields
|
|
||||||
Future<String> uploadImagePortrate() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(AppLink.uploadImagePortrate),
|
|
||||||
);
|
|
||||||
|
|
||||||
request.files.add(
|
|
||||||
http.MultipartFile.fromBytes('image', imagePortrait),
|
|
||||||
);
|
|
||||||
|
|
||||||
request.headers.addAll({
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
|
||||||
});
|
|
||||||
request.fields['driverID'] = box.read(BoxName.driverID).toString();
|
|
||||||
|
|
||||||
var response = await request.send();
|
|
||||||
|
|
||||||
var responseData = await response.stream.toBytes();
|
|
||||||
var responseString = String.fromCharCodes(responseData);
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Print the response string
|
|
||||||
|
|
||||||
return responseString;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
// scanDocumentsByApi();
|
|
||||||
// initializeCamera(0);
|
|
||||||
sql.getAllData(TableName.faceDetectTimes).then((value) {
|
|
||||||
if (value.isEmpty) {
|
|
||||||
times = 0;
|
|
||||||
update();
|
|
||||||
// sql.insertData({'faceDetectTimes': 1}, TableName.faceDetectTimes);
|
|
||||||
} else {
|
|
||||||
times = value[0]['faceDetectTimes'];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// class PassportDataExtractor extends GetxController {
|
|
||||||
// @override
|
|
||||||
// void onInit() {
|
|
||||||
// extractPassportData();
|
|
||||||
// super.onInit();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final ImagePicker _imagePicker = ImagePicker();
|
|
||||||
// late final XFile? image;
|
|
||||||
// final TextRecognizer _textRecognizer = TextRecognizer();
|
|
||||||
//
|
|
||||||
// Future<Map<String, dynamic>> extractPassportData() async {
|
|
||||||
// image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
|
||||||
// update();
|
|
||||||
// if (image == null) {
|
|
||||||
// throw Exception('No image picked');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final InputImage inputImage = InputImage.fromFile(File(image!.path));
|
|
||||||
// final RecognizedText recognisedText =
|
|
||||||
// await _textRecognizer.processImage(inputImage);
|
|
||||||
//
|
|
||||||
// final Map<String, dynamic> extractedData = {};
|
|
||||||
// final List<Map<String, dynamic>> extractedTextWithCoordinates = [];
|
|
||||||
//
|
|
||||||
// for (TextBlock block in recognisedText.blocks) {
|
|
||||||
// for (TextLine line in block.lines) {
|
|
||||||
// final String lineText = line.text;
|
|
||||||
// final Rect lineBoundingBox = line.boundingBox!;
|
|
||||||
//
|
|
||||||
// extractedTextWithCoordinates.add({
|
|
||||||
// 'text': lineText,
|
|
||||||
// 'boundingBox': {
|
|
||||||
// 'left': lineBoundingBox.left,
|
|
||||||
// 'top': lineBoundingBox.top,
|
|
||||||
// 'width': lineBoundingBox.width,
|
|
||||||
// 'height': lineBoundingBox.height,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // if (lineText.contains('Passport Number')) {
|
|
||||||
// // final String passportNumber =
|
|
||||||
// // lineText.split('Passport Number')[1].trim();
|
|
||||||
// // extractedData['passportNumber'] = passportNumber;
|
|
||||||
// // }
|
|
||||||
// // if (lineText.contains('Given Names')) {
|
|
||||||
// // final String givenNames = lineText.split('Given Names')[1].trim();
|
|
||||||
// // extractedData['givenNames'] = givenNames;
|
|
||||||
// // }
|
|
||||||
// // if (lineText.contains('Surname')) {
|
|
||||||
// // final String surname = lineText.split('Surname')[1].trim();
|
|
||||||
// // extractedData['surname'] = surname;
|
|
||||||
// // }
|
|
||||||
// // if (lineText.contains('Nationality')) {
|
|
||||||
// // final String nationality = lineText.split('Nationality')[1].trim();
|
|
||||||
// // extractedData['nationality'] = nationality;
|
|
||||||
// // }
|
|
||||||
// // if (lineText.contains('Date of Birth')) {
|
|
||||||
// // final String dob = lineText.split('Date of Birth')[1].trim();
|
|
||||||
// // extractedData['dateOfBirth'] = dob;
|
|
||||||
// // }
|
|
||||||
// // Add more field extraction conditions as needed
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extractedData['extractedTextWithCoordinates'] =
|
|
||||||
// extractedTextWithCoordinates;
|
|
||||||
// return extractedData;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// class PassportDataController extends GetxController {
|
|
||||||
// PassportDataExtractor passportDataExtractor = PassportDataExtractor();
|
|
||||||
// List<Map<String, dynamic>> extractedTextWithCoordinates = [];
|
|
||||||
//
|
|
||||||
// Future<void> extractDataAndDrawBoundingBoxes() async {
|
|
||||||
// try {
|
|
||||||
// Map<String, dynamic> extractedData =
|
|
||||||
// await passportDataExtractor.extractPassportData();
|
|
||||||
// extractedTextWithCoordinates =
|
|
||||||
// extractedData['extractedTextWithCoordinates'];
|
|
||||||
// update(); // Notify GetX that the state has changed
|
|
||||||
// } catch (e) {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// class BoundingBoxPainter extends CustomPainter {
|
|
||||||
// final List<Map<String, dynamic>> boundingBoxes;
|
|
||||||
//
|
|
||||||
// BoundingBoxPainter(this.boundingBoxes);
|
|
||||||
//
|
|
||||||
// @override
|
|
||||||
// void paint(Canvas canvas, Size size) {
|
|
||||||
// final Paint paint = Paint()
|
|
||||||
// ..color = Colors.red
|
|
||||||
// ..style = PaintingStyle.stroke
|
|
||||||
// ..strokeWidth = 2.0;
|
|
||||||
//
|
|
||||||
// for (Map<String, dynamic> boundingBox in boundingBoxes) {
|
|
||||||
// double left = boundingBox['left'];
|
|
||||||
// double top = boundingBox['top'];
|
|
||||||
// double width = boundingBox['width'];
|
|
||||||
// double height = boundingBox['height'];
|
|
||||||
//
|
|
||||||
// Rect rect = Rect.fromLTWH(left, top, width, height);
|
|
||||||
// canvas.drawRect(rect, paint);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @override
|
|
||||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_confetti/flutter_confetti.dart';
|
|
||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:location/location.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../auth/captin/login_captin_controller.dart';
|
|
||||||
import '../home/payment/captain_wallet_controller.dart';
|
|
||||||
|
|
||||||
Future<void> getPermissionOverlay() async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
final bool status = await FlutterOverlayWindow.isPermissionGranted();
|
|
||||||
if (status == false) {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
'Allow overlay permission'.tr,
|
|
||||||
'To display orders instantly, please grant permission to draw over other apps.'
|
|
||||||
.tr,
|
|
||||||
() async {
|
|
||||||
Get.back();
|
|
||||||
await FlutterOverlayWindow.requestPermission();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showDriverGiftClaim(BuildContext context) async {
|
|
||||||
if (box.read(BoxName.is_claimed).toString() == '0' ||
|
|
||||||
box.read(BoxName.is_claimed) == null) {
|
|
||||||
MyDialog().getDialog(
|
|
||||||
'You have gift 300 EGP'.tr, 'This for new registration'.tr, () async {
|
|
||||||
Get.back();
|
|
||||||
var res = await CRUD().post(link: AppLink.updateDriverClaim, payload: {
|
|
||||||
'driverId': box.read(BoxName.driverID),
|
|
||||||
});
|
|
||||||
if (res != 'failure') {
|
|
||||||
Get.find<CaptainWalletController>()
|
|
||||||
.addDriverWallet('new driver', '300', '300');
|
|
||||||
Confetti.launch(
|
|
||||||
context,
|
|
||||||
options:
|
|
||||||
const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
|
|
||||||
);
|
|
||||||
box.write(BoxName.is_claimed, '1');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> closeOverlayIfFound() async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
bool isOverlayActive = await FlutterOverlayWindow.isActive();
|
|
||||||
if (isOverlayActive) {
|
|
||||||
await FlutterOverlayWindow.closeOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final location = Location();
|
|
||||||
Future<void> getLocationPermission() async {
|
|
||||||
bool serviceEnabled;
|
|
||||||
PermissionStatus permissionGranted;
|
|
||||||
|
|
||||||
// Check if location services are enabled
|
|
||||||
serviceEnabled = await location.serviceEnabled();
|
|
||||||
if (!serviceEnabled) {
|
|
||||||
serviceEnabled = await location.requestService();
|
|
||||||
if (!serviceEnabled) {
|
|
||||||
// Location services are still not enabled, handle the error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the app has permission to access location
|
|
||||||
permissionGranted = await location.hasPermission();
|
|
||||||
if (permissionGranted == PermissionStatus.denied) {
|
|
||||||
permissionGranted = await location.requestPermission();
|
|
||||||
if (permissionGranted != PermissionStatus.granted) {
|
|
||||||
// Location permission is still not granted, handle the error
|
|
||||||
permissionGranted = await location.requestPermission();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (permissionGranted.toString() == 'PermissionStatus.granted') {
|
|
||||||
box.write(BoxName.locationPermission, 'true');
|
|
||||||
Get.find<LoginDriverController>().update();
|
|
||||||
}
|
|
||||||
// update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getOverLay(String myListString) async {
|
|
||||||
bool isOverlayActive = await FlutterOverlayWindow.isActive();
|
|
||||||
if (isOverlayActive) {
|
|
||||||
await FlutterOverlayWindow.closeOverlay();
|
|
||||||
}
|
|
||||||
await FlutterOverlayWindow.showOverlay(
|
|
||||||
enableDrag: true,
|
|
||||||
flag: OverlayFlag.focusPointer,
|
|
||||||
visibility: NotificationVisibility.visibilityPublic,
|
|
||||||
positionGravity: PositionGravity.auto,
|
|
||||||
height: 700,
|
|
||||||
width: WindowSize.matchParent,
|
|
||||||
startPosition: const OverlayPosition(0, -150),
|
|
||||||
);
|
|
||||||
|
|
||||||
await FlutterOverlayWindow.shareData(myListString);
|
|
||||||
}
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import '../../constant/info.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import 'encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
Future<void> checkForUpdate(BuildContext context) async {
|
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
|
||||||
final currentVersion = packageInfo.buildNumber;
|
|
||||||
final version = packageInfo.version;
|
|
||||||
Log.print('version: ${version}');
|
|
||||||
print('currentVersion is : $currentVersion');
|
|
||||||
// Fetch the latest version from your server
|
|
||||||
String latestVersion = await getPackageInfo();
|
|
||||||
box.write(BoxName.packagInfo, version);
|
|
||||||
|
|
||||||
if (latestVersion.isNotEmpty && latestVersion != currentVersion) {
|
|
||||||
showUpdateDialog(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getPackageInfo() async {
|
|
||||||
final response = await CRUD().get(link: AppLink.packageInfo, payload: {
|
|
||||||
"platform": Platform.isAndroid ? 'android' : 'ios',
|
|
||||||
"appName": AppInformation.appVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
return jsonDecode(response)['message'][0]['version'];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
void showUpdateDialog(BuildContext context) {
|
|
||||||
final String storeUrl = Platform.isAndroid
|
|
||||||
? 'https://play.google.com/store/apps/details?id=com.intaleq_driver'
|
|
||||||
: 'https://apps.apple.com/jo/app/intaleq-driver/id6482995159';
|
|
||||||
|
|
||||||
showGeneralDialog(
|
|
||||||
context: context,
|
|
||||||
barrierDismissible: false,
|
|
||||||
barrierColor: Colors.black.withOpacity(0.5),
|
|
||||||
pageBuilder: (_, __, ___) {
|
|
||||||
return BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
|
||||||
child: Center(
|
|
||||||
child: AlertDialog(
|
|
||||||
// Using AlertDialog for a more Material Design look
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(16)), // More rounded corners
|
|
||||||
elevation: 4, // Add a bit more elevation
|
|
||||||
contentPadding: EdgeInsets.zero, // Remove default content padding
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 20.0),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/logo.png',
|
|
||||||
height: 72, // Slightly larger logo
|
|
||||||
width: 72,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: Text(
|
|
||||||
'Update Available'.tr,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
// Use theme's title style
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Text(
|
|
||||||
'A new version of the app is available. Please update to the latest version.'
|
|
||||||
.tr, // More encouraging message
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
// Use theme's body style
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 0),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
// Using TextButton for "Cancel"
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.grey,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text('Cancel'.tr),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: VerticalDivider(width: 0), // Using VerticalDivider
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ElevatedButton(
|
|
||||||
// Using ElevatedButton for "Update"
|
|
||||||
onPressed: () async {
|
|
||||||
if (await canLaunchUrl(Uri.parse(storeUrl))) {
|
|
||||||
await launchUrl(Uri.parse(storeUrl));
|
|
||||||
}
|
|
||||||
if (context.mounted) Navigator.pop(context);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: AppColor
|
|
||||||
.primaryColor, // Use theme's primary color
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimary, // Use theme's onPrimary color
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
bottomRight: Radius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text('Update'.tr),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
transitionBuilder: (_, animation, __, child) {
|
|
||||||
return ScaleTransition(
|
|
||||||
scale: CurvedAnimation(
|
|
||||||
parent: animation,
|
|
||||||
curve: Curves.easeOutCubic, // More natural curve
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeviceHelper {
|
|
||||||
static Future<String> getDeviceFingerprint() async {
|
|
||||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
|
||||||
var deviceData;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
// Fetch Android-specific device information
|
|
||||||
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
|
||||||
deviceData = androidInfo.toMap(); // Convert to a map for easier access
|
|
||||||
// Log.print('deviceData: ${jsonEncode(deviceData)}');
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
// Fetch iOS-specific device information
|
|
||||||
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
|
|
||||||
deviceData = iosInfo.toMap(); // Convert to a map for easier access
|
|
||||||
} else {
|
|
||||||
throw UnsupportedError('Unsupported platform');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract relevant device information
|
|
||||||
final String deviceId = Platform.isAndroid
|
|
||||||
? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
|
|
||||||
: deviceData['identifierForVendor'] ?? 'unknown';
|
|
||||||
|
|
||||||
final String deviceModel = deviceData['model'] ?? 'unknown';
|
|
||||||
final String osVersion = Platform.isAndroid
|
|
||||||
? deviceData['version']['release'] ?? 'unknown'
|
|
||||||
: deviceData['systemVersion'] ?? 'unknown';
|
|
||||||
|
|
||||||
// Log the extracted information
|
|
||||||
|
|
||||||
// Generate and return the encrypted fingerprint
|
|
||||||
final String fingerprint = '${deviceId}_${deviceModel}_$osVersion';
|
|
||||||
// Log.print('fingerprint: ${fingerprint}');
|
|
||||||
// print(EncryptionHelper.instance.encryptData(fingerprint));
|
|
||||||
return (fingerprint);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Failed to generate device fingerprint');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SecurityHelper {
|
|
||||||
/// Performs security checks and handles potential risks
|
|
||||||
static Future<void> performSecurityChecks() async {
|
|
||||||
bool isNotTrust = false;
|
|
||||||
bool isJailBroken = false;
|
|
||||||
bool isRealDevice = true;
|
|
||||||
bool isOnExternalStorage = false;
|
|
||||||
bool checkForIssues = false;
|
|
||||||
bool isDevMode = false;
|
|
||||||
bool isTampered = false;
|
|
||||||
String bundleId = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
|
|
||||||
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
|
|
||||||
isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
|
|
||||||
isOnExternalStorage =
|
|
||||||
await JailbreakRootDetection.instance.isOnExternalStorage;
|
|
||||||
|
|
||||||
List<JailbreakIssue> issues =
|
|
||||||
await JailbreakRootDetection.instance.checkForIssues;
|
|
||||||
checkForIssues = issues.isNotEmpty;
|
|
||||||
|
|
||||||
isDevMode = await JailbreakRootDetection.instance.isDevMode;
|
|
||||||
|
|
||||||
// Get Bundle ID
|
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
||||||
bundleId = packageInfo.packageName;
|
|
||||||
if (bundleId.isNotEmpty) {
|
|
||||||
// Pass the CORRECT bundle ID to isTampered
|
|
||||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Error during security checks: $e");
|
|
||||||
// Consider handling specific exceptions, not just general errors.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save values to storage (using GetStorage)
|
|
||||||
await box.write('isNotTrust', isNotTrust); // Use await for write operations
|
|
||||||
await box.write('isTampered', isTampered); // Use await
|
|
||||||
await box.write('isJailBroken', isJailBroken); // Use await
|
|
||||||
|
|
||||||
// debugPrint("Security Check Results:");
|
|
||||||
// debugPrint("isNotTrust: $isNotTrust");
|
|
||||||
// debugPrint("isJailBroken: $isJailBroken");
|
|
||||||
// debugPrint("isRealDevice: $isRealDevice");
|
|
||||||
// debugPrint("isOnExternalStorage: $isOnExternalStorage");
|
|
||||||
// debugPrint("checkForIssues: $checkForIssues");
|
|
||||||
// debugPrint("isDevMode: $isDevMode");
|
|
||||||
// debugPrint("isTampered: $isTampered");
|
|
||||||
// debugPrint("Bundle ID: $bundleId"); // Print the bundle ID
|
|
||||||
|
|
||||||
// Check for security risks and potentially show a warning
|
|
||||||
if (isJailBroken || isRealDevice == false || isTampered) {
|
|
||||||
// print("security_warning".tr); //using easy_localization
|
|
||||||
// Use a more robust approach to show a warning, like a dialog:
|
|
||||||
_showSecurityWarning();
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.security_check, 'passed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all app data
|
|
||||||
static Future<void> clearAllData() async {
|
|
||||||
//await storage.deleteAll(); // What's 'storage'? Be specific. Likely GetStorage as well.
|
|
||||||
await box.erase(); // Clear GetStorage data
|
|
||||||
exit(0); // This will terminate the app. Be VERY careful with this.
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _showSecurityWarning() {
|
|
||||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
|
||||||
RxInt secondsRemaining = 10.obs;
|
|
||||||
|
|
||||||
Get.dialog(
|
|
||||||
CupertinoAlertDialog(
|
|
||||||
title: Text("Security Warning".tr),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Obx(() => Text(
|
|
||||||
"Potential security risks detected. The application will close in @seconds seconds."
|
|
||||||
.trParams({
|
|
||||||
// Use trParams for placeholders
|
|
||||||
'seconds': secondsRemaining.value.toString(),
|
|
||||||
}),
|
|
||||||
// Wrap the Text widget in Obx
|
|
||||||
)),
|
|
||||||
SizedBox(height: 24), // More spacing before the progress bar
|
|
||||||
Obx(() => SizedBox(
|
|
||||||
width: double.infinity, // Make progress bar full width
|
|
||||||
child: CupertinoActivityIndicator(
|
|
||||||
// in case of loading
|
|
||||||
radius: 15,
|
|
||||||
animating: true,
|
|
||||||
))),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Obx(() => ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8), // Rounded corners
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: secondsRemaining.value / 10,
|
|
||||||
backgroundColor: Colors.grey.shade300, // Lighter background
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
CupertinoColors.systemRed), // iOS-style red
|
|
||||||
minHeight: 8, // Slightly thicker progress bar
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
barrierDismissible: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Timer.periodic(Duration(seconds: 1), (timer) {
|
|
||||||
secondsRemaining.value--;
|
|
||||||
if (secondsRemaining.value <= 0) {
|
|
||||||
timer.cancel();
|
|
||||||
// Get.back();
|
|
||||||
_clearDataAndExit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _clearDataAndExit() async {
|
|
||||||
await storage.deleteAll();
|
|
||||||
await box.erase();
|
|
||||||
Log.print('exit');
|
|
||||||
exit(0); // Exit the app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
class PerformanceTester {
|
|
||||||
/// ✅ فحص سرعة الكتابة إلى التخزين (Storage Write Speed) بوحدة MB/s
|
|
||||||
static Future<double> testStorageWriteSpeed() async {
|
|
||||||
try {
|
|
||||||
final tempDir = Directory.systemTemp;
|
|
||||||
final testFile = File('${tempDir.path}/speed_test.txt');
|
|
||||||
final data = List<int>.filled(1024 * 1024 * 5, 0); // 5MB
|
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
await testFile.writeAsBytes(data, flush: true);
|
|
||||||
stopwatch.stop();
|
|
||||||
|
|
||||||
await testFile.delete();
|
|
||||||
|
|
||||||
double seconds = stopwatch.elapsedMilliseconds / 1000;
|
|
||||||
if (seconds == 0) seconds = 0.001;
|
|
||||||
|
|
||||||
final speed = 5 / seconds;
|
|
||||||
return double.parse(speed.toStringAsFixed(2));
|
|
||||||
} catch (e) {
|
|
||||||
print("❌ Storage write error: $e");
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ✅ فحص سرعة المعالج (CPU Compute Speed) بوحدة الثواني
|
|
||||||
static Future<double> testCPUSpeed() async {
|
|
||||||
try {
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
double x = 0;
|
|
||||||
for (int i = 0; i < 100000000; i++) {
|
|
||||||
x += i * 0.000001;
|
|
||||||
}
|
|
||||||
stopwatch.stop();
|
|
||||||
return stopwatch.elapsedMilliseconds / 1000.0;
|
|
||||||
} catch (e) {
|
|
||||||
print("❌ CPU compute error: $e");
|
|
||||||
return 999.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// import 'package:ride/controller/functions/crud.dart';
|
|
||||||
|
|
||||||
// class RemoveAccount {
|
|
||||||
|
|
||||||
// void removeAccount()async{
|
|
||||||
// var res=await CRUD().post(link: link)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// import 'package:credit_card_scanner/credit_card_scanner.dart';
|
|
||||||
// import 'package:get/get.dart';
|
|
||||||
//
|
|
||||||
// class ScanIdCard extends GetxController {
|
|
||||||
// CardDetails? _cardDetails;
|
|
||||||
// CardScanOptions scanOptions = const CardScanOptions(
|
|
||||||
// scanCardHolderName: true,
|
|
||||||
// enableDebugLogs: true,
|
|
||||||
// validCardsToScanBeforeFinishingScan: 5,
|
|
||||||
// possibleCardHolderNamePositions: [
|
|
||||||
// CardHolderNameScanPosition.aboveCardNumber,
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// Future<void> scanCard() async {
|
|
||||||
// final CardDetails? cardDetails =
|
|
||||||
// await CardScanner.scanCard(scanOptions: scanOptions);
|
|
||||||
// if (cardDetails == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _cardDetails = cardDetails;
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
||||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/char_map.dart';
|
|
||||||
import '../../constant/info.dart';
|
|
||||||
import '../../constant/links.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import 'crud.dart';
|
|
||||||
|
|
||||||
class SecureStorage {
|
|
||||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
|
||||||
|
|
||||||
void saveData(String key, value) async {
|
|
||||||
await _storage.write(key: key, value: value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> readData(String boxName) async {
|
|
||||||
final String? value = await _storage.read(key: boxName);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const List<String> keysToFetch = [
|
|
||||||
'serverPHP',
|
|
||||||
'seferAlexandriaServer',
|
|
||||||
'seferPaymentServer',
|
|
||||||
'seferCairoServer',
|
|
||||||
'seferGizaServer',
|
|
||||||
];
|
|
||||||
|
|
||||||
class AppInitializer {
|
|
||||||
List<Map<String, dynamic>> links = [];
|
|
||||||
|
|
||||||
Future<void> initializeApp() async {
|
|
||||||
if (box.read(BoxName.jwt) == null) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
} else {
|
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(X
|
|
||||||
.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
|
|
||||||
.toString()
|
|
||||||
.split(AppInformation.addd)[0]);
|
|
||||||
if (isTokenExpired) {
|
|
||||||
await LoginDriverController().getJWT();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// await getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getAIKey(String key1) async {
|
|
||||||
var res =
|
|
||||||
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key1});
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
var d = jsonDecode(res)['message'];
|
|
||||||
final rawValue = d[key1].toString();
|
|
||||||
|
|
||||||
// ✅ اكتبها في storage
|
|
||||||
await storage.write(key: key1, value: rawValue);
|
|
||||||
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getKey() async {
|
|
||||||
try {
|
|
||||||
var res =
|
|
||||||
await CRUD().get(link: AppLink.getLocationAreaLinks, payload: {});
|
|
||||||
if (res != 'failure') {
|
|
||||||
links = List<Map<String, dynamic>>.from(jsonDecode(res)['message']);
|
|
||||||
|
|
||||||
await box.write(BoxName.locationName, links);
|
|
||||||
await box.write(BoxName.basicLink, (links[0]['server_link']));
|
|
||||||
await box.write(links[2]['name'], (links[2]['server_link']));
|
|
||||||
await box.write(links[1]['name'], (links[3]['server_link']));
|
|
||||||
await box.write(links[3]['name'], (links[1]['server_link']));
|
|
||||||
await box.write(BoxName.paymentLink, (links[4]['server_link']));
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
|
|
||||||
class SecurityChecks {
|
|
||||||
static const platform = MethodChannel(
|
|
||||||
'com.sefer_driver/security'); // Choose a unique channel name
|
|
||||||
|
|
||||||
static Future<bool> isDeviceCompromised() async {
|
|
||||||
try {
|
|
||||||
final bool result = await platform
|
|
||||||
.invokeMethod('isNativeRooted'); // Invoke the native method
|
|
||||||
return result;
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
print("Failed to check security status: ${e.message}");
|
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static isDeviceRootedFromNative(BuildContext context) async {
|
|
||||||
bool compromised = await isDeviceCompromised();
|
|
||||||
if (compromised) {
|
|
||||||
showDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text("Security Warning".tr),
|
|
||||||
content: Text(
|
|
||||||
"Your device appears to be compromised. The app will now close."
|
|
||||||
.tr),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
SystemNavigator.pop(); // Close the app
|
|
||||||
},
|
|
||||||
child: Text("OK"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
box.write(BoxName.security_check, 'passed');
|
|
||||||
// Continue with normal app flow
|
|
||||||
print("Device is secure.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/api_key.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/info.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/auth/captin/register_captin_controller.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import '../auth/captin/login_captin_controller.dart';
|
|
||||||
import 'encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class SmsEgyptController extends GetxController {
|
|
||||||
var headers = {'Content-Type': 'application/json'};
|
|
||||||
|
|
||||||
Future<String> getSender() async {
|
|
||||||
var res = await CRUD().get(link: AppLink.getSender, payload: {});
|
|
||||||
if (res != 'failure') {
|
|
||||||
var d = jsonDecode(res)['message'][0]['senderId'].toString();
|
|
||||||
return d;
|
|
||||||
} else {
|
|
||||||
return "Sefer Egy";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> sendSmsEgypt(String phone) async {
|
|
||||||
String sender = await getSender();
|
|
||||||
var body = jsonEncode({"receiver": "2$phone"});
|
|
||||||
|
|
||||||
var res = await http.post(
|
|
||||||
Uri.parse(AppLink.sendSms),
|
|
||||||
body: body,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (jsonDecode(res.body)['message'].toString() != "Success") {
|
|
||||||
await CRUD().post(link: AppLink.updatePhoneInvalidSMS, payload: {
|
|
||||||
"phone_number":
|
|
||||||
('+2${Get.find<RegisterCaptainController>().phoneController.text}')
|
|
||||||
});
|
|
||||||
box.write(BoxName.phoneDriver,
|
|
||||||
('+2${Get.find<RegisterCaptainController>().phoneController.text}'));
|
|
||||||
box.write(BoxName.phoneVerified, '1');
|
|
||||||
|
|
||||||
await Get.put(LoginDriverController()).loginWithGoogleCredential(
|
|
||||||
box.read(BoxName.driverID).toString(),
|
|
||||||
(box.read(BoxName.emailDriver).toString()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'You will receive code in sms message'.tr,
|
|
||||||
middleText: '',
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'OK'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future checkCredit(String phone, otp) async {
|
|
||||||
var res = await http.post(
|
|
||||||
Uri.parse(AppLink.checkCredit),
|
|
||||||
body: {
|
|
||||||
"username": AppInformation.appName,
|
|
||||||
"password": AK.smsPasswordEgypt,
|
|
||||||
},
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendSmsWithValidaty(String phone, otp) async {
|
|
||||||
var res = await http.post(
|
|
||||||
Uri.parse(AppLink.checkCredit),
|
|
||||||
body: {
|
|
||||||
"username": 'Sefer',
|
|
||||||
"password": AK.smsPasswordEgypt,
|
|
||||||
"message": "This is an example SMS message.",
|
|
||||||
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
|
|
||||||
"sender": "Sefer", //"Kazumi", // todo add sefer sender name
|
|
||||||
"receiver": "2$phone",
|
|
||||||
"validity": "10",
|
|
||||||
"StartTime": DateTime.now().toString() // "1/1/2024 10:00:00"
|
|
||||||
},
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendSmsStatus(String smsid) async {
|
|
||||||
var res = await http.post(
|
|
||||||
Uri.parse(AppLink.checkCredit),
|
|
||||||
body: {
|
|
||||||
"username": AppInformation.appName,
|
|
||||||
"password": AK.smsPasswordEgypt,
|
|
||||||
"smsid": smsid //"00b77dfc-5b8f-474d-9def-9f0158b70f98"
|
|
||||||
},
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
|
|
||||||
class Toast {
|
|
||||||
static void show(BuildContext context, String message, Color color) {
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
||||||
backgroundColor: color,
|
|
||||||
elevation: 3,
|
|
||||||
content: Text(
|
|
||||||
message,
|
|
||||||
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
|
||||||
),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
animation: const AlwaysStoppedAnimation(1.0),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10.0), // Custom border radius
|
|
||||||
),
|
|
||||||
width: Get.width * .8,
|
|
||||||
// shape: const StadiumBorder(
|
|
||||||
// side: BorderSide(
|
|
||||||
// color: AppColor.secondaryColor,
|
|
||||||
// width: 1.0,
|
|
||||||
// style: BorderStyle.solid,
|
|
||||||
// )),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
snackBar,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:flutter_tts/flutter_tts.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
|
|
||||||
class TextToSpeechController extends GetxController {
|
|
||||||
final FlutterTts flutterTts = FlutterTts();
|
|
||||||
bool isSpeaking = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
initTts();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
flutterTts.stop();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 1. تهيئة المحرك بإعدادات قوية للملاحة ---
|
|
||||||
Future<void> initTts() async {
|
|
||||||
try {
|
|
||||||
// جلب اللغة المحفوظة أو استخدام العربية كافتراضي
|
|
||||||
String lang = box.read(BoxName.lang) ?? 'ar-SA';
|
|
||||||
|
|
||||||
// تصحيح صيغة اللغة إذا لزم الأمر
|
|
||||||
if (lang == 'ar') lang = 'ar-SA';
|
|
||||||
if (lang == 'en') lang = 'en-US';
|
|
||||||
|
|
||||||
await flutterTts.setLanguage(lang);
|
|
||||||
await flutterTts.setSpeechRate(0.5); // سرعة متوسطة وواضحة
|
|
||||||
await flutterTts.setVolume(1.0);
|
|
||||||
await flutterTts.setPitch(1.0);
|
|
||||||
|
|
||||||
// إعدادات خاصة لضمان عمل الصوت مع الملاحة (خاصة للآيفون)
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
await flutterTts
|
|
||||||
.setIosAudioCategory(IosTextToSpeechAudioCategory.playback, [
|
|
||||||
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
|
|
||||||
IosTextToSpeechAudioCategoryOptions.duckOthers
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// الاستماع لحالة الانتهاء
|
|
||||||
flutterTts.setCompletionHandler(() {
|
|
||||||
isSpeaking = false;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
flutterTts.setStartHandler(() {
|
|
||||||
isSpeaking = true;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
print("TTS Init Error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 2. دالة التحدث (تقاطع الكلام القديم) ---
|
|
||||||
Future<void> speakText(String text) async {
|
|
||||||
if (text.isEmpty) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// إيقاف أي كلام حالي لضمان نطق التوجيه الجديد فوراً (أهم للملاحة)
|
|
||||||
await flutterTts.stop();
|
|
||||||
|
|
||||||
var result = await flutterTts.speak(text);
|
|
||||||
if (result == 1) {
|
|
||||||
isSpeaking = true;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// لا تعرض سناك بار هنا لتجنب إزعاج السائق أثناء القيادة
|
|
||||||
print('Failed to speak text: $error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 3. دالة الإيقاف (ضرورية لزر الكتم) ---
|
|
||||||
Future<void> stop() async {
|
|
||||||
try {
|
|
||||||
var result = await flutterTts.stop();
|
|
||||||
if (result == 1) {
|
|
||||||
isSpeaking = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("Error stopping TTS: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// import 'package:ride/constant/credential.dart';
|
|
||||||
// import 'package:twilio_flutter/twilio_flutter.dart';
|
|
||||||
//
|
|
||||||
// class TwilioSMS {
|
|
||||||
// TwilioFlutter twilioFlutter = TwilioFlutter(
|
|
||||||
// accountSid: AppCredintials.accountSIDTwillo,
|
|
||||||
// authToken: AppCredintials.authTokenTwillo,
|
|
||||||
// twilioNumber: '+962 7 9858 3052');
|
|
||||||
//
|
|
||||||
// Future<void> sendSMS({
|
|
||||||
// required String recipientPhoneNumber,
|
|
||||||
// required String message,
|
|
||||||
// }) async {
|
|
||||||
// try {
|
|
||||||
// await twilioFlutter.sendSMS(
|
|
||||||
// toNumber: recipientPhoneNumber,
|
|
||||||
// messageBody: message,
|
|
||||||
// );
|
|
||||||
// } catch (e) {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,553 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:sefer_driver/constant/api_key.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:image_cropper/image_cropper.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
|
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
|
||||||
|
|
||||||
import '../../constant/box_name.dart';
|
|
||||||
import '../../constant/colors.dart';
|
|
||||||
import '../../constant/info.dart';
|
|
||||||
import '../../main.dart';
|
|
||||||
import '../../print.dart';
|
|
||||||
import 'encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class ImageController extends GetxController {
|
|
||||||
File? myImage;
|
|
||||||
bool isloading = false;
|
|
||||||
CroppedFile? croppedFile;
|
|
||||||
final picker = ImagePicker();
|
|
||||||
var image;
|
|
||||||
|
|
||||||
Future<img.Image> detectAndCropDocument(File imageFile) async {
|
|
||||||
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
|
|
||||||
if (image == null) throw Exception('Unable to decode image');
|
|
||||||
|
|
||||||
int left = image.width, top = image.height, right = 0, bottom = 0;
|
|
||||||
|
|
||||||
// Threshold for considering a pixel as part of the document (adjust as needed)
|
|
||||||
const int threshold = 240;
|
|
||||||
|
|
||||||
for (int y = 0; y < image.height; y++) {
|
|
||||||
for (int x = 0; x < image.width; x++) {
|
|
||||||
final pixel = image.getPixel(x, y);
|
|
||||||
final luminance = img.getLuminance(pixel);
|
|
||||||
|
|
||||||
if (luminance < threshold) {
|
|
||||||
left = x < left ? x : left;
|
|
||||||
top = y < top ? y : top;
|
|
||||||
right = x > right ? x : right;
|
|
||||||
bottom = y > bottom ? y : bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a small padding
|
|
||||||
left = (left - 5).clamp(0, image.width);
|
|
||||||
top = (top - 5).clamp(0, image.height);
|
|
||||||
right = (right + 5).clamp(0, image.width);
|
|
||||||
bottom = (bottom + 5).clamp(0, image.height);
|
|
||||||
|
|
||||||
return img.copyCrop(image,
|
|
||||||
x: left, y: top, width: right - left, height: bottom - top);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> rotateImageIfNeeded(File imageFile) async {
|
|
||||||
img.Image croppedDoc = await detectAndCropDocument(imageFile);
|
|
||||||
|
|
||||||
// Check if the document is in portrait orientation
|
|
||||||
bool isPortrait = croppedDoc.height > croppedDoc.width;
|
|
||||||
|
|
||||||
img.Image processedImage;
|
|
||||||
if (isPortrait) {
|
|
||||||
// Rotate the image by 90 degrees clockwise
|
|
||||||
processedImage = img.copyRotate(croppedDoc, angle: 90);
|
|
||||||
} else {
|
|
||||||
processedImage = croppedDoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get temporary directory
|
|
||||||
final tempDir = await path_provider.getTemporaryDirectory();
|
|
||||||
final tempPath = tempDir.path;
|
|
||||||
|
|
||||||
// Create the processed image file
|
|
||||||
File processedFile = File('$tempPath/processed_image.jpg');
|
|
||||||
await processedFile.writeAsBytes(img.encodeJpg(processedImage));
|
|
||||||
|
|
||||||
return processedFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> rotateImage(File imageFile) async {
|
|
||||||
// Read the image file
|
|
||||||
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
|
|
||||||
|
|
||||||
if (image == null) return imageFile;
|
|
||||||
|
|
||||||
// Rotate the image by 90 degrees clockwise
|
|
||||||
img.Image rotatedImage = img.copyRotate(image, angle: 90);
|
|
||||||
|
|
||||||
// Get temporary directory
|
|
||||||
final tempDir = await path_provider.getTemporaryDirectory();
|
|
||||||
final tempPath = tempDir.path;
|
|
||||||
|
|
||||||
// Create the rotated image file
|
|
||||||
File rotatedFile = File('$tempPath/rotated_image.jpg');
|
|
||||||
await rotatedFile.writeAsBytes(img.encodeJpg(rotatedImage));
|
|
||||||
|
|
||||||
return rotatedFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
choosImage(String link, String imageType) async {
|
|
||||||
try {
|
|
||||||
final pickedImage = await picker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
preferredCameraDevice: CameraDevice.rear,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pickedImage == null) return;
|
|
||||||
|
|
||||||
image = File(pickedImage.path);
|
|
||||||
|
|
||||||
croppedFile = await ImageCropper().cropImage(
|
|
||||||
sourcePath: image!.path,
|
|
||||||
uiSettings: [
|
|
||||||
AndroidUiSettings(
|
|
||||||
toolbarTitle: 'Cropper'.tr,
|
|
||||||
toolbarColor: AppColor.blueColor,
|
|
||||||
toolbarWidgetColor: AppColor.yellowColor,
|
|
||||||
initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
lockAspectRatio: false,
|
|
||||||
),
|
|
||||||
IOSUiSettings(
|
|
||||||
title: 'Cropper'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (croppedFile == null) return;
|
|
||||||
|
|
||||||
myImage = File(croppedFile!.path);
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Rotate the compressed image
|
|
||||||
File processedImage = await rotateImageIfNeeded(File(croppedFile!.path));
|
|
||||||
File compressedImage = await compressImage(processedImage);
|
|
||||||
|
|
||||||
print('link =$link');
|
|
||||||
// Log.print('link: ${link}');
|
|
||||||
//n8u22456
|
|
||||||
await uploadImage(
|
|
||||||
compressedImage,
|
|
||||||
{
|
|
||||||
'driverID':
|
|
||||||
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
|
|
||||||
'imageType': imageType,
|
|
||||||
},
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print('Error in choosImage: $e');
|
|
||||||
mySnackeBarError('Image Upload Failed'.tr);
|
|
||||||
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
|
||||||
// backgroundColor: AppColor.primaryColor);
|
|
||||||
} finally {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
choosImageNewCAr(String link, String imageType) async {
|
|
||||||
try {
|
|
||||||
final pickedImage = await picker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
preferredCameraDevice: CameraDevice.rear,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pickedImage == null) return;
|
|
||||||
|
|
||||||
image = File(pickedImage.path);
|
|
||||||
|
|
||||||
croppedFile = await ImageCropper().cropImage(
|
|
||||||
sourcePath: image!.path,
|
|
||||||
uiSettings: [
|
|
||||||
AndroidUiSettings(
|
|
||||||
toolbarTitle: 'Cropper'.tr,
|
|
||||||
toolbarColor: AppColor.blueColor,
|
|
||||||
toolbarWidgetColor: AppColor.yellowColor,
|
|
||||||
initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
lockAspectRatio: false,
|
|
||||||
),
|
|
||||||
IOSUiSettings(
|
|
||||||
title: 'Cropper'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (croppedFile == null) return;
|
|
||||||
|
|
||||||
myImage = File(croppedFile!.path);
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Rotate the compressed image
|
|
||||||
File processedImage = await rotateImageIfNeeded(File(croppedFile!.path));
|
|
||||||
File compressedImage = await compressImage(processedImage);
|
|
||||||
|
|
||||||
print('link =$link');
|
|
||||||
// Log.print('link: ${link}');
|
|
||||||
//n8u22456
|
|
||||||
await uploadNewCar(
|
|
||||||
compressedImage,
|
|
||||||
{
|
|
||||||
'driverID': box.read(BoxName.driverID) +
|
|
||||||
'_' +
|
|
||||||
DateTime.now().toIso8601String(),
|
|
||||||
'imageType': imageType,
|
|
||||||
},
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
print('Error in choosImage: $e');
|
|
||||||
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
|
||||||
// backgroundColor: AppColor.primaryColor);
|
|
||||||
mySnackeBarError('Image Upload Failed'.tr);
|
|
||||||
} finally {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choosFaceFromDriverLicense(String link, String imageType) async {
|
|
||||||
// final pickedImage = await picker.pickImage(
|
|
||||||
// source: ImageSource.camera,
|
|
||||||
// preferredCameraDevice: CameraDevice.rear,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (pickedImage == null) return;
|
|
||||||
|
|
||||||
// image = File(pickedImage.path);
|
|
||||||
|
|
||||||
// File? processedImage;
|
|
||||||
|
|
||||||
// // For face images, use face detection and cropping
|
|
||||||
// processedImage = await detectAndCropFace(image!);
|
|
||||||
// if (processedImage == null) {
|
|
||||||
// Get.snackbar('Face Detection Failed', 'No face detected in the image.');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// isloading = true;
|
|
||||||
// update();
|
|
||||||
|
|
||||||
// File compressedImage = await compressImage(processedImage);
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// await uploadImage(
|
|
||||||
// compressedImage,
|
|
||||||
// {
|
|
||||||
// 'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
// 'imageType': imageType
|
|
||||||
// },
|
|
||||||
// link,
|
|
||||||
// );
|
|
||||||
// } catch (e) {
|
|
||||||
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
|
||||||
// backgroundColor: AppColor.redColor);
|
|
||||||
// } finally {
|
|
||||||
// isloading = false;
|
|
||||||
// update();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
choosFace(String link, String imageType) async {
|
|
||||||
final pickedImage = await picker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
|
||||||
preferredCameraDevice: CameraDevice.front,
|
|
||||||
);
|
|
||||||
if (pickedImage != null) {
|
|
||||||
image = File(pickedImage.path);
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
// Compress the image
|
|
||||||
File compressedImage = await compressImage(File(pickedImage.path));
|
|
||||||
|
|
||||||
// Save the picked image directly
|
|
||||||
// File savedImage = File(pickedImage.path);
|
|
||||||
print('link =$link');
|
|
||||||
try {
|
|
||||||
await uploadImage(
|
|
||||||
compressedImage,
|
|
||||||
{
|
|
||||||
'driverID':
|
|
||||||
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
|
|
||||||
'imageType': imageType
|
|
||||||
},
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
mySnackeBarError('Image Upload Failed'.tr);
|
|
||||||
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
|
||||||
// backgroundColor: AppColor.redColor);
|
|
||||||
} finally {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadImage(File file, Map data, String link) async {
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(link),
|
|
||||||
);
|
|
||||||
Log.print('request: ${request}');
|
|
||||||
var length = await file.length();
|
|
||||||
var stream = http.ByteStream(file.openRead());
|
|
||||||
final headers = {
|
|
||||||
'Authorization':
|
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
|
|
||||||
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
|
|
||||||
};
|
|
||||||
var multipartFile = http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: basename(file.path),
|
|
||||||
);
|
|
||||||
request.headers.addAll(headers);
|
|
||||||
// Set the file name to the driverID
|
|
||||||
|
|
||||||
request.files.add(
|
|
||||||
http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: '${box.read(BoxName.driverID)}.jpg',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
data.forEach((key, value) {
|
|
||||||
request.fields[key] = value;
|
|
||||||
});
|
|
||||||
var myrequest = await request.send();
|
|
||||||
var res = await http.Response.fromStream(myrequest);
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
Log.print('jsonDecode(res.body): ${jsonDecode(res.body)}');
|
|
||||||
return jsonDecode(res.body);
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
'Failed to upload image: ${res.statusCode} - ${res.body}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadNewCar(File file, Map data, String link) async {
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(link),
|
|
||||||
);
|
|
||||||
|
|
||||||
var length = await file.length();
|
|
||||||
var stream = http.ByteStream(file.openRead());
|
|
||||||
var multipartFile = http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: basename(file.path),
|
|
||||||
);
|
|
||||||
request.headers.addAll({
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
|
|
||||||
});
|
|
||||||
// Set the file name to the driverID
|
|
||||||
request.files.add(
|
|
||||||
http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: '${box.read(BoxName.driverID)}.jpg',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
data.forEach((key, value) {
|
|
||||||
request.fields[key] = value;
|
|
||||||
});
|
|
||||||
var myrequest = await request.send();
|
|
||||||
var res = await http.Response.fromStream(myrequest);
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
Log.print('jsonDecode(res.body): ${jsonDecode(res.body)}');
|
|
||||||
return jsonDecode(res.body);
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
'Failed to upload image: ${res.statusCode} - ${res.body}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
choosImagePicture(String link, String imageType) async {
|
|
||||||
final pickedImage = await picker.pickImage(
|
|
||||||
source: ImageSource.gallery,
|
|
||||||
// preferredCameraDevice: CameraDevice.rear,
|
|
||||||
// maxHeight: Get.height * .3,
|
|
||||||
// maxWidth: Get.width * .9,
|
|
||||||
// imageQuality: 100,
|
|
||||||
);
|
|
||||||
image = File(pickedImage!.path);
|
|
||||||
|
|
||||||
croppedFile = await ImageCropper().cropImage(
|
|
||||||
sourcePath: image!.path,
|
|
||||||
uiSettings: [
|
|
||||||
AndroidUiSettings(
|
|
||||||
toolbarTitle: 'Cropper'.tr,
|
|
||||||
toolbarColor: AppColor.blueColor,
|
|
||||||
toolbarWidgetColor: AppColor.yellowColor,
|
|
||||||
initAspectRatio: CropAspectRatioPreset.original,
|
|
||||||
lockAspectRatio: false),
|
|
||||||
IOSUiSettings(
|
|
||||||
title: 'Cropper'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
myImage = File(pickedImage.path);
|
|
||||||
isloading = true;
|
|
||||||
update();
|
|
||||||
// Save the cropped image
|
|
||||||
// File savedCroppedImage = File(croppedFile!.path);
|
|
||||||
File compressedImage = await compressImage(File(croppedFile!.path));
|
|
||||||
print('link =$link');
|
|
||||||
try {
|
|
||||||
await uploadImage(
|
|
||||||
compressedImage,
|
|
||||||
{'driverID': (box.read(BoxName.driverID)), 'imageType': imageType},
|
|
||||||
link,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.print('e: ${e}');
|
|
||||||
mySnackeBarError('Image Upload Failed'.tr);
|
|
||||||
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
|
||||||
// backgroundColor: AppColor.redColor);
|
|
||||||
} finally {
|
|
||||||
isloading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadImagePicture(File file, Map data, String link) async {
|
|
||||||
var request = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
|
|
||||||
);
|
|
||||||
|
|
||||||
var length = await file.length();
|
|
||||||
var stream = http.ByteStream(file.openRead());
|
|
||||||
var multipartFile = http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: basename(file.path),
|
|
||||||
);
|
|
||||||
request.headers.addAll({
|
|
||||||
'Authorization':
|
|
||||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
|
|
||||||
});
|
|
||||||
// Set the file name to the driverID
|
|
||||||
request.files.add(
|
|
||||||
http.MultipartFile(
|
|
||||||
'image',
|
|
||||||
stream,
|
|
||||||
length,
|
|
||||||
filename: '${(box.read(BoxName.driverID))}.jpg',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
data.forEach((key, value) {
|
|
||||||
request.fields[key] = value;
|
|
||||||
});
|
|
||||||
var myrequest = await request.send();
|
|
||||||
var res = await http.Response.fromStream(myrequest);
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
return jsonDecode(res.body);
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
'Failed to upload image: ${res.statusCode} - ${res.body}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> compressImage(File file) async {
|
|
||||||
final dir = await path_provider.getTemporaryDirectory();
|
|
||||||
final targetPath = "${dir.absolute.path}/temp.jpg";
|
|
||||||
|
|
||||||
var result = await FlutterImageCompress.compressAndGetFile(
|
|
||||||
file.absolute.path,
|
|
||||||
targetPath,
|
|
||||||
quality: 70,
|
|
||||||
minWidth: 1024,
|
|
||||||
minHeight: 1024,
|
|
||||||
);
|
|
||||||
|
|
||||||
return File(result!.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Future<File> detectAndCropFace(File imageFile) async {
|
|
||||||
// final inputImage = InputImage.fromFilePath(imageFile.path);
|
|
||||||
// final options = FaceDetectorOptions(
|
|
||||||
// enableClassification: false,
|
|
||||||
// enableLandmarks: false,
|
|
||||||
// enableTracking: false,
|
|
||||||
// minFaceSize: 0.15,
|
|
||||||
// performanceMode: FaceDetectorMode.accurate,
|
|
||||||
// );
|
|
||||||
// final faceDetector = FaceDetector(options: options);
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// final List<Face> faces = await faceDetector.processImage(inputImage);
|
|
||||||
// final image = img.decodeImage(await imageFile.readAsBytes());
|
|
||||||
|
|
||||||
// if (image == null) throw Exception('Unable to decode image');
|
|
||||||
|
|
||||||
// int left, top, width, height;
|
|
||||||
|
|
||||||
// if (faces.isNotEmpty) {
|
|
||||||
// // Face detected, crop around the face
|
|
||||||
// final face = faces[0];
|
|
||||||
// double padding = 0.2; // 20% padding
|
|
||||||
// int paddingX = (face.boundingBox.width * padding).round();
|
|
||||||
// int paddingY = (face.boundingBox.height * padding).round();
|
|
||||||
|
|
||||||
// left = (face.boundingBox.left - paddingX).round();
|
|
||||||
// top = (face.boundingBox.top - paddingY).round();
|
|
||||||
// width = (face.boundingBox.width + 2 * paddingX).round();
|
|
||||||
// height = (face.boundingBox.height + 2 * paddingY).round();
|
|
||||||
// } else {
|
|
||||||
// // No face detected, crop the center of the image
|
|
||||||
// int size = min(image.width, image.height);
|
|
||||||
// left = (image.width - size) ~/ 2;
|
|
||||||
// top = (image.height - size) ~/ 2;
|
|
||||||
// width = size;
|
|
||||||
// height = size;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Ensure dimensions are within image bounds
|
|
||||||
// left = left.clamp(0, image.width - 1);
|
|
||||||
// top = top.clamp(0, image.height - 1);
|
|
||||||
// width = width.clamp(1, image.width - left);
|
|
||||||
// height = height.clamp(1, image.height - top);
|
|
||||||
|
|
||||||
// final croppedImage =
|
|
||||||
// img.copyCrop(image, x: left, y: top, width: width, height: height);
|
|
||||||
|
|
||||||
// // Save the cropped image
|
|
||||||
// final tempDir = await path_provider.getTemporaryDirectory();
|
|
||||||
// final tempPath = tempDir.path;
|
|
||||||
// final croppedFile = File('$tempPath/cropped_image.jpg');
|
|
||||||
// await croppedFile.writeAsBytes(img.encodeJpg(croppedImage, quality: 100));
|
|
||||||
|
|
||||||
// return croppedFile;
|
|
||||||
// } finally {
|
|
||||||
// faceDetector.close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -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,146 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
|
|
||||||
import '../../../constant/table_names.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
|
|
||||||
class DriverBehaviorController extends GetxController {
|
|
||||||
Future<List<Map<String, dynamic>>> getAllData() async {
|
|
||||||
return await sql.getAllData(TableName.behavior);
|
|
||||||
}
|
|
||||||
|
|
||||||
var isLoading = false.obs;
|
|
||||||
var overallScore = 100.0.obs;
|
|
||||||
var lastTrips = [].obs;
|
|
||||||
|
|
||||||
Future<void> fetchDriverBehavior() async {
|
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
final response = await CRUD().get(
|
|
||||||
link: AppLink.get_driver_behavior,
|
|
||||||
payload: {"driver_id": box.read(BoxName.driverID).toString()},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
final json = jsonDecode(response);
|
|
||||||
|
|
||||||
overallScore.value =
|
|
||||||
double.parse(json['message']['overall_behavior_score'].toString());
|
|
||||||
lastTrips.value = json['message']['last_10_trips'];
|
|
||||||
} else {
|
|
||||||
// Get.snackbar("Error", json['message'] ?? "Unknown error");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Get.snackbar("Error", "Exception: $e");
|
|
||||||
Log.print('e: ${e}');
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> analyzeData() async {
|
|
||||||
final data = await getAllData();
|
|
||||||
if (data.isEmpty) return {};
|
|
||||||
|
|
||||||
double maxSpeed = 0;
|
|
||||||
double totalSpeed = 0;
|
|
||||||
int hardBrakes = 0;
|
|
||||||
double totalDistance = 0;
|
|
||||||
|
|
||||||
// متغيرات للمقارنة مع النقطة السابقة
|
|
||||||
double? prevLat, prevLng;
|
|
||||||
DateTime? prevTime;
|
|
||||||
|
|
||||||
// ترتيب البيانات حسب الوقت لضمان دقة الحساب (اختياري لكن مفضل)
|
|
||||||
// data.sort((a, b) => a['created_at'].compareTo(b['created_at']));
|
|
||||||
|
|
||||||
for (var item in data) {
|
|
||||||
// 1. قراءة البيانات بالأسماء الصحيحة من الجدول
|
|
||||||
double lat = item['latitude'] ?? item['lat'] ?? 0.0;
|
|
||||||
double lng = item['longitude'] ?? item['lng'] ?? 0.0;
|
|
||||||
double acc = item['acceleration'] ?? 0.0;
|
|
||||||
|
|
||||||
// قراءة الوقت لحساب السرعة
|
|
||||||
DateTime currentTime =
|
|
||||||
DateTime.tryParse(item['created_at'].toString()) ?? DateTime.now();
|
|
||||||
|
|
||||||
double currentSpeed = 0;
|
|
||||||
|
|
||||||
// 2. حساب السرعة والمسافة إذا وجدت نقطة سابقة
|
|
||||||
if (prevLat != null && prevLng != null && prevTime != null) {
|
|
||||||
double distKm = _calculateDistance(prevLat, prevLng, lat, lng);
|
|
||||||
int timeDiffSeconds = currentTime.difference(prevTime).inSeconds;
|
|
||||||
|
|
||||||
if (timeDiffSeconds > 0) {
|
|
||||||
// السرعة (كم/س) = (المسافة بالكيلومتر * 3600) / الزمن بالثواني
|
|
||||||
currentSpeed = (distKm * 3600) / timeDiffSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalDistance += distKm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تحديث القيم الإحصائية
|
|
||||||
if (currentSpeed > maxSpeed) maxSpeed = currentSpeed;
|
|
||||||
totalSpeed += currentSpeed;
|
|
||||||
|
|
||||||
// حساب الفرملة القوية (يعتمد على التسارع المحفوظ مسبقاً)
|
|
||||||
if (acc.abs() > 3.0) hardBrakes++;
|
|
||||||
|
|
||||||
// حفظ النقطة الحالية لتكون هي "السابقة" في الدورة التالية
|
|
||||||
prevLat = lat;
|
|
||||||
prevLng = lng;
|
|
||||||
prevTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تجنب القسمة على صفر
|
|
||||||
double avgSpeed = (data.length > 1) ? totalSpeed / (data.length - 1) : 0;
|
|
||||||
|
|
||||||
// حساب تقييم السلوك
|
|
||||||
double behaviorScore = 100 - (hardBrakes * 5) - ((maxSpeed > 100) ? 10 : 0);
|
|
||||||
behaviorScore = behaviorScore.clamp(0.0, 100.0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'max_speed': maxSpeed,
|
|
||||||
'avg_speed': avgSpeed,
|
|
||||||
'hard_brakes': hardBrakes,
|
|
||||||
'total_distance': totalDistance,
|
|
||||||
'behavior_score': behaviorScore,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendSummaryToServer(String driverId, String tripId) async {
|
|
||||||
final summary = await analyzeData();
|
|
||||||
if (summary.isEmpty) return;
|
|
||||||
|
|
||||||
final Map<String, dynamic> body = {
|
|
||||||
'driver_id': driverId,
|
|
||||||
'trip_id': tripId,
|
|
||||||
...summary, // فيه doubles
|
|
||||||
};
|
|
||||||
|
|
||||||
// اجبر كل القيم على String
|
|
||||||
final payload = body.map((k, v) => MapEntry(k, v?.toString() ?? ''));
|
|
||||||
|
|
||||||
await CRUD().post(link: AppLink.saveBehavior, payload: payload);
|
|
||||||
await clearData();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearData() async {
|
|
||||||
await sql.deleteAllData(TableName.behavior);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _calculateDistance(
|
|
||||||
double lat1, double lon1, double lat2, double lon2) {
|
|
||||||
const p = 0.017453292519943295;
|
|
||||||
final a = 0.5 -
|
|
||||||
cos((lat2 - lat1) * p) / 2 +
|
|
||||||
cos(lat1 * p) * cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)) / 2;
|
|
||||||
return 12742 * asin(sqrt(a)); // distance in km
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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 {
|
|
||||||
final String phone1 = '+963992952235';
|
|
||||||
final String phone2 = '+963992952235';
|
|
||||||
final TimeOfDay workStartTime = const TimeOfDay(hour: 12, minute: 0);
|
|
||||||
final TimeOfDay workEndTime = const TimeOfDay(hour: 19, 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
void showContactDialog(BuildContext context) {
|
|
||||||
TimeOfDay now = TimeOfDay.now();
|
|
||||||
|
|
||||||
showCupertinoModalPopup(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => CupertinoActionSheet(
|
|
||||||
title: Text('Contact Us'.tr),
|
|
||||||
message: Text('Choose a contact option'.tr),
|
|
||||||
actions: <Widget>[
|
|
||||||
if (_isWithinWorkTime(now))
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
child: Text(phone1),
|
|
||||||
onPressed: () => makePhoneCall(
|
|
||||||
phone1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_isWithinWorkTime(now))
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
child: Text(phone2),
|
|
||||||
onPressed: () => makePhoneCall(phone2),
|
|
||||||
),
|
|
||||||
if (!_isWithinWorkTime(now))
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
child: Text(
|
|
||||||
'Work time is from 10:00 - 17:00.\nYou can send a WhatsApp message or email.'
|
|
||||||
.tr),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
FontAwesome.whatsapp,
|
|
||||||
color: AppColor.greenColor,
|
|
||||||
),
|
|
||||||
Text('Send WhatsApp Message'.tr),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
launchCommunication('whatsapp', phone1, 'Hello'.tr),
|
|
||||||
),
|
|
||||||
CupertinoActionSheetAction(
|
|
||||||
child: Text('Send Email'.tr),
|
|
||||||
onPressed: () =>
|
|
||||||
launchCommunication('email', 'support@sefer.live', 'Hello'.tr),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
cancelButton: CupertinoActionSheetAction(
|
|
||||||
child: Text('Cancel'.tr),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/models/model/driver/rides_summary_model.dart';
|
|
||||||
|
|
||||||
class DurationController extends GetxController {
|
|
||||||
final data = DurationData;
|
|
||||||
// late AnimationController animationController;
|
|
||||||
late List<MonthlyDataModel> rideData;
|
|
||||||
late List<MonthlyRideModel> rideCountData;
|
|
||||||
late List<MonthlyPriceDriverModel> ridePriceDriverData;
|
|
||||||
Map<String, dynamic> jsonData1 = {};
|
|
||||||
Map<String, dynamic> jsonData2 = {};
|
|
||||||
bool isLoading = false;
|
|
||||||
String totalDurationToday = '';
|
|
||||||
var chartData;
|
|
||||||
var chartRideCount;
|
|
||||||
var chartRidePriceDriver;
|
|
||||||
List monthlyList = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() async {
|
|
||||||
super.onInit();
|
|
||||||
await fetchData();
|
|
||||||
await fetchRideDriver();
|
|
||||||
await getStaticDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
getStaticDriver() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.driverStatistic,
|
|
||||||
payload: {'driverID': box.read(BoxName.driverID)});
|
|
||||||
if (res == 'failure') {
|
|
||||||
monthlyList = [];
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
monthlyList = jsonDecode(res)['message'];
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fetchData() async {
|
|
||||||
isLoading = true;
|
|
||||||
update(); // Notify the observers about the loading state change
|
|
||||||
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.getTotalDriverDuration,
|
|
||||||
payload: {'driver_id': box.read(BoxName.driverID)},
|
|
||||||
);
|
|
||||||
jsonData1 = jsonDecode(res);
|
|
||||||
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
|
|
||||||
isLoading = false;
|
|
||||||
final List<dynamic> jsonData = jsonResponse['message'];
|
|
||||||
rideData = jsonData.map<MonthlyDataModel>((item) {
|
|
||||||
return MonthlyDataModel.fromJson(item);
|
|
||||||
}).toList();
|
|
||||||
final List<FlSpot> spots = rideData
|
|
||||||
.map((data) => FlSpot(
|
|
||||||
data.day.toDouble(),
|
|
||||||
data.totalDuration.toDouble(),
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
chartData = spots;
|
|
||||||
|
|
||||||
update(); // Notify the observers about the data and loading state change
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fetchRideDriver() async {
|
|
||||||
isLoading = true;
|
|
||||||
update(); // Notify the observers about the loading state change
|
|
||||||
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.getRidesDriverByDay,
|
|
||||||
payload: {'driver_id': box.read(BoxName.driverID)},
|
|
||||||
);
|
|
||||||
if (res != 'failure') {
|
|
||||||
jsonData2 = jsonDecode(res);
|
|
||||||
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
|
|
||||||
isLoading = false;
|
|
||||||
final List<dynamic> jsonData = jsonResponse['message'];
|
|
||||||
rideCountData = jsonData.map<MonthlyRideModel>((item) {
|
|
||||||
return MonthlyRideModel.fromJson(item);
|
|
||||||
}).toList();
|
|
||||||
ridePriceDriverData = jsonData.map<MonthlyPriceDriverModel>((item) {
|
|
||||||
return MonthlyPriceDriverModel.fromJson(item);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final List<FlSpot> spots = rideCountData
|
|
||||||
.map((data) => FlSpot(
|
|
||||||
data.day.toDouble(),
|
|
||||||
data.countRide.toDouble(),
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
chartRideCount = spots;
|
|
||||||
final List<FlSpot> spotsDriverPrices = ridePriceDriverData
|
|
||||||
.map((data) => FlSpot(
|
|
||||||
data.day.toDouble(),
|
|
||||||
data.pricePerDay.toDouble(),
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
chartRidePriceDriver = spotsDriverPrices;
|
|
||||||
|
|
||||||
update(); // Notify the observers about the data and loading state change
|
|
||||||
} else {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'No data yet!'.tr,
|
|
||||||
middleText: '',
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'OK'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DurationData> parseData(List<dynamic> json) {
|
|
||||||
return json.map((entry) {
|
|
||||||
final Map<String, dynamic> entryMap = entry;
|
|
||||||
final day = DateTime.parse(entryMap['day']);
|
|
||||||
final totalDuration = _parseDuration(entryMap['total_duration']);
|
|
||||||
return DurationData(day, totalDuration);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration _parseDuration(String durationString) {
|
|
||||||
final parts = durationString.split(':');
|
|
||||||
final hours = int.parse(parts[0]);
|
|
||||||
final minutes = int.parse(parts[1]);
|
|
||||||
final seconds = int.parse(parts[2]);
|
|
||||||
return Duration(hours: hours, minutes: minutes, seconds: seconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DurationData {
|
|
||||||
final DateTime day;
|
|
||||||
final Duration totalDuration;
|
|
||||||
|
|
||||||
DurationData(this.day, this.totalDuration);
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class AssuranceHealthController extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
Map tripCount = {};
|
|
||||||
|
|
||||||
Future getTripCountByCaptain() async {
|
|
||||||
var res = await CRUD().get(link: AppLink.getTripCountByCaptain, payload: {
|
|
||||||
"driver_id": box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
if (res != 'failure') {
|
|
||||||
tripCount = jsonDecode(res)['message'];
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addDriverHealthAssurance({
|
|
||||||
String? driverId,
|
|
||||||
String? assured,
|
|
||||||
required String healthInsuranceProvider,
|
|
||||||
}) async {
|
|
||||||
// Define the URL to your PHP backend
|
|
||||||
|
|
||||||
// Data to be sent to the backend
|
|
||||||
Map<String, String> data = {
|
|
||||||
"driver_id": box.read(BoxName.driverID).toString(),
|
|
||||||
"assured": '1',
|
|
||||||
"health_insurance_provider": healthInsuranceProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Send the POST request to your backend
|
|
||||||
var response = await CRUD()
|
|
||||||
.post(link: AppLink.addHealthInsuranceProvider, payload: data);
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
// Handle success (e.g., show a success message)
|
|
||||||
|
|
||||||
mySnackbarSuccess(
|
|
||||||
"You have successfully opted for health insurance.".tr);
|
|
||||||
} else {
|
|
||||||
// Handle failure (e.g., show an error message)
|
|
||||||
print("Failed to save health assurance data");
|
|
||||||
mySnackeBarError("Please enter a health insurance status.".tr);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Handle any errors
|
|
||||||
print("Error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../../../../constant/box_name.dart';
|
|
||||||
import '../../../../constant/links.dart';
|
|
||||||
import '../../../../constant/style.dart';
|
|
||||||
import '../../../../main.dart';
|
|
||||||
import '../../../../views/widgets/elevated_btn.dart';
|
|
||||||
import '../../../functions/crud.dart';
|
|
||||||
import '../../../functions/encrypt_decrypt.dart';
|
|
||||||
|
|
||||||
class HelpController extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final helpQuestionController = TextEditingController();
|
|
||||||
Map helpQuestionDate = {};
|
|
||||||
Map helpQuestionRepleyDate = {};
|
|
||||||
String status = '';
|
|
||||||
String qustion = '';
|
|
||||||
late int indexQuestion = 0;
|
|
||||||
getIndex(int i, String qustion1) async {
|
|
||||||
indexQuestion = i;
|
|
||||||
qustion = qustion1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addHelpQuestion() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().post(link: AppLink.addhelpCenter, payload: {
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
'helpQuestion': (helpQuestionController.text)
|
|
||||||
});
|
|
||||||
var d = jsonDecode(res);
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
if (d['status'].toString() == 'success') {
|
|
||||||
getHelpQuestion();
|
|
||||||
// Get.snackbar('Feedback data saved successfully'.tr, '',
|
|
||||||
// backgroundColor: AppColor.greenColor,
|
|
||||||
// snackPosition: SnackPosition.BOTTOM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void getHelpQuestion() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().get(link: AppLink.gethelpCenter, payload: {
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
if (res == "failure") {
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'There is no help Question here'.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
middleText: '',
|
|
||||||
confirm: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Add Question'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
}),
|
|
||||||
MyElevatedButton(
|
|
||||||
title: 'Back'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
helpQuestionDate = jsonDecode(res);
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future getHelpRepley(String id) async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().get(link: AppLink.getByIdhelpCenter, payload: {
|
|
||||||
'id': id,
|
|
||||||
});
|
|
||||||
if (res == "failure") {
|
|
||||||
status = 'not yet';
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
helpQuestionRepleyDate = jsonDecode(res);
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
getHelpQuestion();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class MaintainCenterController extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
Map tripCount = {};
|
|
||||||
|
|
||||||
Future getTripCountByCaptain() async {
|
|
||||||
var res = await CRUD().get(link: AppLink.getTripCountByCaptain, payload: {
|
|
||||||
"driver_id": box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
if (res != 'failure') {
|
|
||||||
tripCount = jsonDecode(res)['message'];
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/print.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:video_player/video_player.dart';
|
|
||||||
|
|
||||||
class VideoController extends GetxController {
|
|
||||||
var videos = [];
|
|
||||||
var isLoading = true.obs;
|
|
||||||
final String apiUrl = '${AppLink.server}/ride/videos_driver/get.php';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
fetchVideos();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
late VideoPlayerController videoPlayerController;
|
|
||||||
|
|
||||||
// Initialize the video player with the provided URL
|
|
||||||
Future<void> initializeVideo(String videoUrl) async {
|
|
||||||
videoPlayerController =
|
|
||||||
VideoPlayerController.networkUrl(Uri.parse(videoUrl));
|
|
||||||
await videoPlayerController.initialize();
|
|
||||||
videoPlayerController
|
|
||||||
.setLooping(true); // Set to true if you want the video to loop
|
|
||||||
update(); // Update the UI after the video has been initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play the video
|
|
||||||
void play() {
|
|
||||||
videoPlayerController.play();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause the video
|
|
||||||
void pause() {
|
|
||||||
videoPlayerController.pause();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
videoPlayerController
|
|
||||||
.dispose(); // Dispose of the video player controller when not in use
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchVideos() async {
|
|
||||||
try {
|
|
||||||
var res = await CRUD().get(link: apiUrl, payload: {});
|
|
||||||
if (res != 'failure') {
|
|
||||||
videos = jsonDecode(res)['message'];
|
|
||||||
// Log.print('videos: ${videos}');
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
mySnackeBarError(e.toString());
|
|
||||||
} finally {
|
|
||||||
isLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,740 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
// للألوان
|
|
||||||
// import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
// import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
// import 'package:latlong2/latlong.dart'
|
|
||||||
// as latlng; // هذا مهم جداً للتعامل مع إحداثيات OSM
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../constant/style.dart';
|
|
||||||
import '../../../constant/table_names.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../../views/home/my_wallet/walet_captain.dart';
|
|
||||||
import '../../../views/widgets/elevated_btn.dart';
|
|
||||||
import '../../firebase/firbase_messge.dart';
|
|
||||||
import '../../functions/background_service.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
import '../../functions/location_background_controller.dart';
|
|
||||||
import '../../functions/location_controller.dart';
|
|
||||||
import '../payment/captain_wallet_controller.dart';
|
|
||||||
|
|
||||||
class HomeCaptainController extends GetxController {
|
|
||||||
bool isActive = false;
|
|
||||||
DateTime? activeStartTime;
|
|
||||||
Duration activeDuration = Duration.zero;
|
|
||||||
Timer? activeTimer;
|
|
||||||
Map data = {};
|
|
||||||
bool isHomeMapActive = true;
|
|
||||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
bool isLoading = true;
|
|
||||||
late double kazan = 0;
|
|
||||||
double latePrice = 0;
|
|
||||||
double heavyPrice = 0;
|
|
||||||
double comfortPrice = 0,
|
|
||||||
speedPrice = 0,
|
|
||||||
deliveryPrice = 0,
|
|
||||||
mashwariPrice = 0,
|
|
||||||
familyPrice = 0,
|
|
||||||
fuelPrice = 0;
|
|
||||||
double naturePrice = 0;
|
|
||||||
bool isCallOn = false;
|
|
||||||
String totalMoneyToday = '0';
|
|
||||||
double? rating = 5;
|
|
||||||
String rideId = '0';
|
|
||||||
String countRideToday = '0';
|
|
||||||
String totalMoneyInSEFER = '0';
|
|
||||||
String totalDurationToday = '0';
|
|
||||||
Timer? timer;
|
|
||||||
late LatLng myLocation = const LatLng(33.5138, 36.2765);
|
|
||||||
String totalPoints = '0';
|
|
||||||
String countRefuse = '0';
|
|
||||||
bool mapType = false;
|
|
||||||
bool mapTrafficON = false;
|
|
||||||
double widthMapTypeAndTraffic = 50;
|
|
||||||
// === متغيرات الهيت ماب الجديدة ===
|
|
||||||
bool isHeatmapVisible = false;
|
|
||||||
Set<Polygon> heatmapPolygons =
|
|
||||||
{}; // سنستخدم Polygon لرسم المربعات على جوجل مابس
|
|
||||||
|
|
||||||
// Inject the LocationController class
|
|
||||||
// final locationController = Get.put(LocationController());
|
|
||||||
// الكود الصحيح
|
|
||||||
final locationController = Get.find<LocationController>();
|
|
||||||
// final locationBackController = Get.put(LocationBackgroundController());
|
|
||||||
String formatDuration(Duration duration) {
|
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
|
||||||
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
|
|
||||||
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
|
|
||||||
return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds";
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة لتغيير حالة الهيت ماب (عرض/إخفاء)
|
|
||||||
void toggleHeatmap() async {
|
|
||||||
isHeatmapVisible = !isHeatmapVisible;
|
|
||||||
if (isHeatmapVisible) {
|
|
||||||
await fetchAndDrawHeatmap();
|
|
||||||
} else {
|
|
||||||
heatmapPolygons.clear();
|
|
||||||
}
|
|
||||||
update(); // تحديث الواجهة
|
|
||||||
}
|
|
||||||
|
|
||||||
// داخل MapDriverController
|
|
||||||
|
|
||||||
// متغير لتخزين المربعات
|
|
||||||
// Set<Polygon> heatmapPolygons = {};
|
|
||||||
|
|
||||||
// دالة جلب البيانات ورسم الخريطة
|
|
||||||
Future<void> fetchAndDrawHeatmap() async {
|
|
||||||
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
|
|
||||||
final String jsonUrl = "${AppLink.server}/ride/rides/heatmap_live.json";
|
|
||||||
Log.print('jsonUrl: ${jsonUrl}');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// نستخدم timestamp لمنع الكاش من الموبايل نفسه
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse("$jsonUrl?t=${DateTime.now().millisecondsSinceEpoch}"));
|
|
||||||
Log.print('heatmap_live: ${response.body}');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final List<dynamic> data = json.decode(response.body);
|
|
||||||
_generatePolygons(data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("Heatmap Error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _generatePolygons(List<dynamic> data) {
|
|
||||||
Set<Polygon> tempPolygons = {};
|
|
||||||
|
|
||||||
// الأوفست لرسم المربع (نصف حجم الشبكة)
|
|
||||||
// الشبكة دقتها 0.01 درجة، لذا نصفها 0.005
|
|
||||||
double offset = 0.005;
|
|
||||||
|
|
||||||
for (var point in data) {
|
|
||||||
double lat = double.parse(point['lat'].toString());
|
|
||||||
double lng = double.parse(point['lng'].toString());
|
|
||||||
|
|
||||||
String intensity = point['intensity'] ?? 'low';
|
|
||||||
int count = int.parse(point['count'].toString()); // ✅ جلب العدد
|
|
||||||
|
|
||||||
Color color;
|
|
||||||
Color strokeColor;
|
|
||||||
|
|
||||||
// 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count)
|
|
||||||
if (intensity == 'high' || count >= 5) {
|
|
||||||
// منطقة مشتعلة (أحمر)
|
|
||||||
// إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات
|
|
||||||
color = Colors.red.withOpacity(0.35);
|
|
||||||
strokeColor = Colors.red.withOpacity(0.8);
|
|
||||||
} else if (intensity == 'medium' || count >= 3) {
|
|
||||||
// منطقة متوسطة (برتقالي)
|
|
||||||
color = Colors.orange.withOpacity(0.35);
|
|
||||||
strokeColor = Colors.orange.withOpacity(0.8);
|
|
||||||
} else {
|
|
||||||
// منطقة خفيفة (أصفر)
|
|
||||||
color = Colors.yellow.withOpacity(0.3);
|
|
||||||
strokeColor = Colors.yellow.withOpacity(0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
// رسم المربع
|
|
||||||
tempPolygons.add(Polygon(
|
|
||||||
polygonId: PolygonId("$lat-$lng"),
|
|
||||||
consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
|
|
||||||
points: [
|
|
||||||
LatLng(lat - offset, lng - offset),
|
|
||||||
LatLng(lat + offset, lng - offset),
|
|
||||||
LatLng(lat + offset, lng + offset),
|
|
||||||
LatLng(lat - offset, lng + offset),
|
|
||||||
],
|
|
||||||
fillColor: color,
|
|
||||||
strokeColor: strokeColor,
|
|
||||||
strokeWidth: 2,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
heatmapPolygons = tempPolygons;
|
|
||||||
update(); // تحديث الخريطة
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة لتشغيل الخريطة الحرارية كل فترة (مثلاً عند فتح الصفحة)
|
|
||||||
void startHeatmapCycle() {
|
|
||||||
fetchAndDrawHeatmap();
|
|
||||||
// يمكن تفعيل Timer هنا لو أردت تحديثها تلقائياً كل 5 دقائق
|
|
||||||
}
|
|
||||||
|
|
||||||
void goToWalletFromConnect() {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
Get.to(() => WalletCaptainRefactored());
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeRideId() {
|
|
||||||
rideId = 'rideId';
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addCustomCarIcon() {
|
|
||||||
ImageConfiguration config = ImageConfiguration(
|
|
||||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
|
|
||||||
BitmapDescriptor.asset(
|
|
||||||
config,
|
|
||||||
'assets/images/car.png',
|
|
||||||
// mipmaps: false,
|
|
||||||
).then((value) {
|
|
||||||
carIcon = value;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String stringActiveDuration = '';
|
|
||||||
void onButtonSelected() {
|
|
||||||
// تم الإصلاح: التأكد من أن المتحكم موجود قبل استخدامه لتجنب الكراش
|
|
||||||
if (!Get.isRegistered<CaptainWalletController>()) {
|
|
||||||
Get.put(CaptainWalletController());
|
|
||||||
}
|
|
||||||
totalPoints = Get.find<CaptainWalletController>().totalPoints;
|
|
||||||
isActive = !isActive;
|
|
||||||
if (isActive) {
|
|
||||||
if (double.parse(totalPoints) > -200) {
|
|
||||||
locationController.startLocationUpdates();
|
|
||||||
HapticFeedback.heavyImpact();
|
|
||||||
// locationBackController.startBackLocation();
|
|
||||||
activeStartTime = DateTime.now();
|
|
||||||
activeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
activeDuration = DateTime.now().difference(activeStartTime!);
|
|
||||||
stringActiveDuration = formatDuration(activeDuration);
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
locationController.stopLocationUpdates();
|
|
||||||
|
|
||||||
activeStartTime = null;
|
|
||||||
activeTimer?.cancel();
|
|
||||||
savePeriod(activeDuration);
|
|
||||||
activeDuration = Duration.zero;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
locationController.stopLocationUpdates();
|
|
||||||
|
|
||||||
activeStartTime = null;
|
|
||||||
activeTimer?.cancel();
|
|
||||||
savePeriod(activeDuration);
|
|
||||||
activeDuration = Duration.zero;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// متغيرات العداد للحظر
|
|
||||||
RxString remainingBlockTimeStr = "".obs;
|
|
||||||
Timer? _blockTimer;
|
|
||||||
|
|
||||||
/// دالة الفحص والدايلوج
|
|
||||||
void checkAndShowBlockDialog() {
|
|
||||||
String? blockStr = box.read(BoxName.blockUntilDate);
|
|
||||||
if (blockStr == null || blockStr.isEmpty) return;
|
|
||||||
|
|
||||||
DateTime blockExpiry = DateTime.parse(blockStr);
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
|
|
||||||
if (now.isBefore(blockExpiry)) {
|
|
||||||
// 1. إجبار السائق على وضع الأوفلاين
|
|
||||||
box.write(BoxName.statusDriverLocation, 'blocked');
|
|
||||||
update();
|
|
||||||
|
|
||||||
// 2. بدء العداد
|
|
||||||
_startBlockCountdown(blockExpiry);
|
|
||||||
|
|
||||||
// 3. إظهار الديالوج المانع
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: "حسابك مقيد مؤقتاً ⛔",
|
|
||||||
titleStyle:
|
|
||||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
|
||||||
barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً
|
|
||||||
onWillPop: () async => false, // 🚫 ممنوع زر الرجوع في الأندرويد
|
|
||||||
content: Obx(() => Column(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.timer_off_outlined,
|
|
||||||
size: 50, color: Colors.orange),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
const Text(
|
|
||||||
"لقد تجاوزت حد الإلغاء المسموح به (3 مرات).\nلا يمكنك العمل حتى انتهاء العقوبة.",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
remainingBlockTimeStr.value, // 🔥 الوقت يتحدث هنا
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
confirm: Obx(() {
|
|
||||||
// الزر يكون مفعلاً فقط عندما ينتهي الوقت
|
|
||||||
bool isFinished = remainingBlockTimeStr.value == "00:00:00" ||
|
|
||||||
remainingBlockTimeStr.value == "Done";
|
|
||||||
return ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: isFinished ? Colors.green : Colors.grey,
|
|
||||||
),
|
|
||||||
onPressed: isFinished
|
|
||||||
? () {
|
|
||||||
Get.back(); // إغلاق الديالوج
|
|
||||||
box.remove(BoxName.blockUntilDate); // إزالة الحظر
|
|
||||||
Get.snackbar("أهلاً بك", "يمكنك الآن استقبال الطلبات",
|
|
||||||
backgroundColor: Colors.green);
|
|
||||||
}
|
|
||||||
: null, // زر معطل
|
|
||||||
child: Text(isFinished ? "Go Online" : "انتظر انتهاء الوقت"),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// الوقت انتهى أصلاً -> تنظيف
|
|
||||||
box.remove(BoxName.blockUntilDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// دالة العداد التنازلي
|
|
||||||
void _startBlockCountdown(DateTime expiry) {
|
|
||||||
_blockTimer?.cancel();
|
|
||||||
_blockTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
if (now.isAfter(expiry)) {
|
|
||||||
// انتهى الوقت
|
|
||||||
remainingBlockTimeStr.value = "Done";
|
|
||||||
timer.cancel();
|
|
||||||
} else {
|
|
||||||
// حساب الفرق وتنسيقه
|
|
||||||
Duration diff = expiry.difference(now);
|
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
|
||||||
String hours = twoDigits(diff.inHours);
|
|
||||||
String minutes = twoDigits(diff.inMinutes.remainder(60));
|
|
||||||
String seconds = twoDigits(diff.inSeconds.remainder(60));
|
|
||||||
|
|
||||||
remainingBlockTimeStr.value = "$hours:$minutes:$seconds";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
_blockTimer?.cancel();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void getRefusedOrderByCaptain() async {
|
|
||||||
DateTime today = DateTime.now();
|
|
||||||
int todayDay = today.day;
|
|
||||||
|
|
||||||
String driverId = box.read(BoxName.driverID).toString();
|
|
||||||
|
|
||||||
String customQuery = '''
|
|
||||||
SELECT COUNT(*) AS count
|
|
||||||
FROM ${TableName.driverOrdersRefuse}
|
|
||||||
WHERE driver_id = '$driverId'
|
|
||||||
AND created_at LIKE '%$todayDay%'
|
|
||||||
''';
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<Map<String, dynamic>> results =
|
|
||||||
await sql.getCustomQuery(customQuery);
|
|
||||||
countRefuse = results[0]['count'].toString();
|
|
||||||
update();
|
|
||||||
if (double.parse(totalPoints) <= -200) {
|
|
||||||
// if (int.parse(countRefuse) > 3 || double.parse(totalPoints) <= -200) {
|
|
||||||
locationController.stopLocationUpdates();
|
|
||||||
activeStartTime = null;
|
|
||||||
activeTimer?.cancel();
|
|
||||||
savePeriod(activeDuration);
|
|
||||||
activeDuration = Duration.zero;
|
|
||||||
update();
|
|
||||||
|
|
||||||
Get.defaultDialog(
|
|
||||||
// backgroundColor: CupertinoColors.destructiveRed,
|
|
||||||
barrierDismissible: false,
|
|
||||||
title: 'You Are Stopped For this Day !'.tr,
|
|
||||||
content: Text(
|
|
||||||
'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
|
|
||||||
.tr,
|
|
||||||
style: AppStyle.title,
|
|
||||||
),
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Ok , See you Tomorrow'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeMapType() {
|
|
||||||
mapType = !mapType;
|
|
||||||
// heightButtomSheetShown = isButtomSheetShown == true ? 240 : 0;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeMapTraffic() {
|
|
||||||
mapTrafficON = !mapTrafficON;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// late GoogleMapController mapHomeCaptainController;
|
|
||||||
GoogleMapController? mapHomeCaptainController;
|
|
||||||
// final locationController = Get.find<LocationController>();
|
|
||||||
|
|
||||||
// --- FIX 2: Smart Map Creation ---
|
|
||||||
void onMapCreated(GoogleMapController controller) {
|
|
||||||
mapHomeCaptainController = controller;
|
|
||||||
|
|
||||||
// Check actual location before moving camera
|
|
||||||
var currentLoc = locationController.myLocation;
|
|
||||||
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
|
|
||||||
controller.animateCamera(
|
|
||||||
CameraUpdate.newLatLng(currentLoc),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Optional: Move to default city view instead of ocean
|
|
||||||
controller.animateCamera(
|
|
||||||
CameraUpdate.newLatLngZoom(myLocation, 10),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void savePeriod(Duration period) {
|
|
||||||
final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
|
|
||||||
periods.add(period.inSeconds);
|
|
||||||
box.write(BoxName.periods, periods);
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration calculateTotalDuration() {
|
|
||||||
final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
|
|
||||||
Duration totalDuration = Duration.zero;
|
|
||||||
for (dynamic periodInSeconds in periods) {
|
|
||||||
final periodDuration = Duration(seconds: periodInSeconds);
|
|
||||||
totalDuration += periodDuration;
|
|
||||||
}
|
|
||||||
return totalDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
void startPeriodicExecution() {
|
|
||||||
Timer.periodic(const Duration(minutes: 2), (timer) async {
|
|
||||||
await getCaptainDurationOnToday();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopTimer() {
|
|
||||||
timer?.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
getlocation() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
// This ensures we try to get a fix, but map doesn't crash if it fails
|
|
||||||
await locationController.getLocation();
|
|
||||||
|
|
||||||
var loc = locationController.myLocation;
|
|
||||||
if (loc.latitude != 0) {
|
|
||||||
myLocation = loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map walletDriverPointsDate = {};
|
|
||||||
|
|
||||||
Future getCaptainWalletFromBuyPoints() async {
|
|
||||||
// isLoading = true;
|
|
||||||
update();
|
|
||||||
|
|
||||||
var res = await CRUD().getWallet(
|
|
||||||
link: AppLink.getDriverPaymentPoints,
|
|
||||||
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
|
||||||
);
|
|
||||||
isLoading = false;
|
|
||||||
// update();
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
walletDriverPointsDate = jsonDecode(res);
|
|
||||||
double totalPointsDouble = double.parse(
|
|
||||||
walletDriverPointsDate['message'][0]['total_amount'].toString());
|
|
||||||
totalPoints = totalPointsDouble.toStringAsFixed(0);
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
totalPoints = '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. دالة نستدعيها عند قبول الطلب
|
|
||||||
void pauseHomeMapUpdates() {
|
|
||||||
isHomeMapActive = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. دالة نستدعيها عند العودة للصفحة الرئيسية
|
|
||||||
void resumeHomeMapUpdates() {
|
|
||||||
isHomeMapActive = true;
|
|
||||||
// إنعاش الخريطة عند العودة
|
|
||||||
if (mapHomeCaptainController != null) {
|
|
||||||
onMapCreated(mapHomeCaptainController!);
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() async {
|
|
||||||
// ✅ طلب الإذونات أولاً
|
|
||||||
bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
|
|
||||||
|
|
||||||
if (permissionsGranted) {
|
|
||||||
// ✅ بدء الخدمة بعد الحصول على الإذونات
|
|
||||||
await BackgroundServiceHelper.startService();
|
|
||||||
print('✅ Background service started successfully');
|
|
||||||
} else {
|
|
||||||
print('❌ لم يتم منح الإذونات - الخدمة لن تعمل');
|
|
||||||
// اعرض رسالة للمستخدم
|
|
||||||
}
|
|
||||||
// await locationBackController.requestLocationPermission();
|
|
||||||
Get.put(FirebaseMessagesController());
|
|
||||||
addToken();
|
|
||||||
await getlocation();
|
|
||||||
onButtonSelected();
|
|
||||||
addCustomCarIcon();
|
|
||||||
fetchHomeData();
|
|
||||||
// refreshFinancialData();
|
|
||||||
// getAllPayment();
|
|
||||||
startPeriodicExecution();
|
|
||||||
// getCaptainWalletFromBuyPoints();
|
|
||||||
// 🔥 الفحص عند تشغيل التطبيق
|
|
||||||
checkAndShowBlockDialog();
|
|
||||||
box.write(BoxName.statusDriverLocation, 'off');
|
|
||||||
// 2. عدل الليسنر ليصبح مشروطاً
|
|
||||||
locationController.addListener(() {
|
|
||||||
// الشرط الذهبي: إذا كانت الصفحة غير نشطة أو الخريطة غير موجودة، لا تفعل شيئاً
|
|
||||||
if (!isHomeMapActive || mapHomeCaptainController == null || isClosed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
// isActive الخاصة بالزر "متصل/غير متصل"
|
|
||||||
var loc = locationController.myLocation;
|
|
||||||
if (loc.latitude != 0 && loc.longitude != 0) {
|
|
||||||
try {
|
|
||||||
mapHomeCaptainController!.animateCamera(
|
|
||||||
CameraUpdate.newCameraPosition(
|
|
||||||
CameraPosition(
|
|
||||||
target: loc,
|
|
||||||
zoom: 17.5,
|
|
||||||
tilt: 50.0,
|
|
||||||
bearing: locationController.heading,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// التقاط الخطأ بصمت إذا حدث أثناء الانتقال
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// LocationController().getLocation();
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
addToken() async {
|
|
||||||
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
|
|
||||||
final payload = {
|
|
||||||
'token': (box.read(BoxName.tokenDriver)),
|
|
||||||
'captain_id': (box.read(BoxName.driverID)).toString(),
|
|
||||||
'fingerPrint': (fingerPrint).toString()
|
|
||||||
};
|
|
||||||
// Log.print('payload: ${payload}');
|
|
||||||
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
int morningRideCount = 0;
|
|
||||||
int afternoonRideCount = 0;
|
|
||||||
String weeklyIncome = '0';
|
|
||||||
|
|
||||||
// الدالة الموحدة لجلب كل البيانات المالية دفعة واحدة
|
|
||||||
Future<void> refreshFinancialData() async {
|
|
||||||
isLoading = true; // اختياري
|
|
||||||
update();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link: "${AppLink.server}/ride/driverWallet/get_financial_summary.php",
|
|
||||||
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
// معالجة الرد بذكاء سواء كان String أو Map
|
|
||||||
Map<String, dynamic> body = (res is String) ? jsonDecode(res) : res;
|
|
||||||
|
|
||||||
if (body['status'] == 'success') {
|
|
||||||
// البيانات قد تكون في data أو message
|
|
||||||
var data = body['data'] ?? body['message'];
|
|
||||||
|
|
||||||
// 1. البيانات المالية (من السيرفر المحلي)
|
|
||||||
totalMoneyInSEFER = data['cash_balance']?.toString() ?? '0';
|
|
||||||
totalPoints = double.parse(data['points_balance']?.toString() ?? '0')
|
|
||||||
.toStringAsFixed(0);
|
|
||||||
totalMoneyToday = data['today_income']?.toString() ?? '0';
|
|
||||||
|
|
||||||
// 2. بيانات الرحلات (من السيرفر البعيد - إن وجدت)
|
|
||||||
morningRideCount =
|
|
||||||
int.tryParse(data['morning_count']?.toString() ?? '0') ?? 0;
|
|
||||||
afternoonRideCount =
|
|
||||||
int.tryParse(data['afternoon_count']?.toString() ?? '0') ?? 0;
|
|
||||||
weeklyIncome = data['total_amount_last_week']?.toString() ?? '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("Error fetching unified summary: $e");
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة موحدة لجلب (الرحلات + التقييم + الإعدادات) في طلب واحد
|
|
||||||
Future<void> fetchHomeData() async {
|
|
||||||
// isLoading = true; // اختياري: إذا كنت تريد إظهار لودر
|
|
||||||
// update();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var res = await CRUD().post(
|
|
||||||
link:
|
|
||||||
"${AppLink.server}/ride/kazan/get_home_init.php", // تأكد من المسار الجديد
|
|
||||||
payload: {
|
|
||||||
'driver_id': box.read(BoxName.driverID).toString(),
|
|
||||||
'country': box.read(BoxName.countryCode).toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res != 'failure') {
|
|
||||||
var body = (res);
|
|
||||||
if (body['status'] == 'success') {
|
|
||||||
// التعامل مع البيانات القادمة سواء كانت في 'data' أو 'message'
|
|
||||||
var data = body['data'] ?? body['message'];
|
|
||||||
|
|
||||||
// 1. تحديث عدد الرحلات
|
|
||||||
if (data['ride_count'] != null) {
|
|
||||||
countRideToday = data['ride_count'].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. تحديث التقييم
|
|
||||||
if (data['rating'] != null) {
|
|
||||||
rating = double.tryParse(data['rating'].toString()) ?? 5.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. تحديث الأسعار (Kazan)
|
|
||||||
if (data['config'] != null) {
|
|
||||||
var config = data['config'];
|
|
||||||
kazan = double.tryParse(config['kazan']?.toString() ?? '0') ?? 0.0;
|
|
||||||
naturePrice =
|
|
||||||
double.tryParse(config['naturePrice']?.toString() ?? '0') ??
|
|
||||||
0.0;
|
|
||||||
heavyPrice =
|
|
||||||
double.tryParse(config['heavyPrice']?.toString() ?? '0') ?? 0.0;
|
|
||||||
latePrice =
|
|
||||||
double.tryParse(config['latePrice']?.toString() ?? '0') ?? 0.0;
|
|
||||||
comfortPrice =
|
|
||||||
double.tryParse(config['comfortPrice']?.toString() ?? '0') ??
|
|
||||||
0.0;
|
|
||||||
speedPrice =
|
|
||||||
double.tryParse(config['speedPrice']?.toString() ?? '0') ?? 0.0;
|
|
||||||
deliveryPrice =
|
|
||||||
double.tryParse(config['deliveryPrice']?.toString() ?? '0') ??
|
|
||||||
0.0;
|
|
||||||
mashwariPrice =
|
|
||||||
double.tryParse(config['freePrice']?.toString() ?? '0') ?? 0.0;
|
|
||||||
familyPrice =
|
|
||||||
double.tryParse(config['familyPrice']?.toString() ?? '0') ??
|
|
||||||
0.0;
|
|
||||||
fuelPrice =
|
|
||||||
double.tryParse(config['fuelPrice']?.toString() ?? '0') ?? 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("Error fetching home data: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
// تحديث الواجهة مرة واحدة فقط بعد جلب كل البيانات
|
|
||||||
// isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
double mpg = 0;
|
|
||||||
calculateConsumptionFuel() {
|
|
||||||
mpg = fuelPrice / 12; //todo in register car add mpg in box
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllPayment() async {
|
|
||||||
var res = await CRUD().getWallet(
|
|
||||||
link: AppLink.getAllPaymentFromRide,
|
|
||||||
payload: {'driverID': box.read(BoxName.driverID).toString()});
|
|
||||||
if (res == 'failure') {
|
|
||||||
totalMoneyInSEFER = '0';
|
|
||||||
} else {
|
|
||||||
data = jsonDecode(res);
|
|
||||||
|
|
||||||
totalMoneyInSEFER = data['message'][0]['total_amount'];
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeToAppliedRide(String status) {
|
|
||||||
box.write(BoxName.rideStatus, status);
|
|
||||||
Log.print('rideStatus from homcaptain : ${box.read(BoxName.rideStatus)}');
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getCaptainDurationOnToday() async {
|
|
||||||
try {
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.getTotalDriverDurationToday,
|
|
||||||
payload: {'driver_id': box.read(BoxName.driverID).toString()},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res == null || res == 'failure') {
|
|
||||||
totalDurationToday = '0';
|
|
||||||
update();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = jsonDecode(res);
|
|
||||||
totalDurationToday = data['message']?[0]?['total_duration'] ?? '0';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error in getCaptainDurationOnToday: $e');
|
|
||||||
totalDurationToday = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
activeTimer?.cancel();
|
|
||||||
stopTimer();
|
|
||||||
mapHomeCaptainController?.dispose(); // Dispose controller
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,86 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
|
|
||||||
class NavigationStep {
|
|
||||||
final String instruction;
|
|
||||||
final String maneuver;
|
|
||||||
final double distance;
|
|
||||||
final String duration;
|
|
||||||
final LatLng startLocation;
|
|
||||||
final LatLng endLocation;
|
|
||||||
final String htmlInstructions;
|
|
||||||
|
|
||||||
NavigationStep({
|
|
||||||
required this.instruction,
|
|
||||||
required this.maneuver,
|
|
||||||
required this.distance,
|
|
||||||
required this.duration,
|
|
||||||
required this.startLocation,
|
|
||||||
required this.endLocation,
|
|
||||||
required this.htmlInstructions,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory NavigationStep.fromJson(Map<String, dynamic> json) {
|
|
||||||
return NavigationStep(
|
|
||||||
instruction: json['html_instructions'] ?? '',
|
|
||||||
maneuver: json['maneuver'] ?? 'straight',
|
|
||||||
distance: (json['distance']['value'] ?? 0).toDouble(),
|
|
||||||
duration: json['duration']['text'] ?? '',
|
|
||||||
startLocation: LatLng(
|
|
||||||
json['start_location']['lat'].toDouble(),
|
|
||||||
json['start_location']['lng'].toDouble(),
|
|
||||||
),
|
|
||||||
endLocation: LatLng(
|
|
||||||
json['end_location']['lat'].toDouble(),
|
|
||||||
json['end_location']['lng'].toDouble(),
|
|
||||||
),
|
|
||||||
htmlInstructions: json['html_instructions'] ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get clean instruction text (remove HTML tags)
|
|
||||||
String get cleanInstruction {
|
|
||||||
return instruction
|
|
||||||
.replaceAll(RegExp(r'<[^>]*>'), '')
|
|
||||||
.replaceAll(' ', ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get instruction icon based on maneuver
|
|
||||||
IconData get instructionIcon {
|
|
||||||
switch (maneuver.toLowerCase()) {
|
|
||||||
case 'turn-left':
|
|
||||||
return Icons.turn_left;
|
|
||||||
case 'turn-right':
|
|
||||||
return Icons.turn_right;
|
|
||||||
case 'turn-slight-left':
|
|
||||||
return Icons.turn_slight_left;
|
|
||||||
case 'turn-slight-right':
|
|
||||||
return Icons.turn_slight_right;
|
|
||||||
case 'turn-sharp-left':
|
|
||||||
return Icons.turn_sharp_left;
|
|
||||||
case 'turn-sharp-right':
|
|
||||||
return Icons.turn_sharp_right;
|
|
||||||
case 'uturn-left':
|
|
||||||
case 'uturn-right':
|
|
||||||
return Icons.u_turn_left;
|
|
||||||
case 'straight':
|
|
||||||
return Icons.straight;
|
|
||||||
case 'ramp-left':
|
|
||||||
return Icons.ramp_left;
|
|
||||||
case 'ramp-right':
|
|
||||||
return Icons.ramp_right;
|
|
||||||
case 'merge':
|
|
||||||
return Icons.merge;
|
|
||||||
case 'fork-left':
|
|
||||||
case 'fork-right':
|
|
||||||
return Icons.call_split;
|
|
||||||
case 'ferry':
|
|
||||||
return Icons.directions_boat;
|
|
||||||
case 'roundabout-left':
|
|
||||||
case 'roundabout-right':
|
|
||||||
return Icons.roundabout_left;
|
|
||||||
default:
|
|
||||||
return Icons.navigation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
|
||||||
import 'package:sefer_driver/constant/api_key.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/tts.dart';
|
|
||||||
|
|
||||||
import '../../../main.dart';
|
|
||||||
|
|
||||||
/// Handles map-related logic: fetching routes, drawing polylines, and managing markers.
|
|
||||||
class NavigationService extends GetxService {
|
|
||||||
final CRUD _crud = CRUD();
|
|
||||||
final TextToSpeechController _tts = Get.put(TextToSpeechController());
|
|
||||||
|
|
||||||
final RxSet<Marker> markers = <Marker>{}.obs;
|
|
||||||
final RxSet<Polyline> polylines = <Polyline>{}.obs;
|
|
||||||
final RxString currentInstruction = "".obs;
|
|
||||||
|
|
||||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
_loadCustomIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadCustomIcons() async {
|
|
||||||
carIcon = await _createBitmapDescriptor('assets/images/car.png');
|
|
||||||
passengerIcon = await _createBitmapDescriptor('assets/images/picker.png');
|
|
||||||
startIcon = await _createBitmapDescriptor('assets/images/A.png');
|
|
||||||
endIcon = await _createBitmapDescriptor('assets/images/b.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<BitmapDescriptor> _createBitmapDescriptor(String assetName) {
|
|
||||||
return BitmapDescriptor.fromAssetImage(
|
|
||||||
ImageConfiguration(
|
|
||||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio),
|
|
||||||
assetName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> getRoute({
|
|
||||||
required LatLng origin,
|
|
||||||
required LatLng destination,
|
|
||||||
}) async {
|
|
||||||
final url =
|
|
||||||
'${AppLink.googleMapsLink}directions/json?language=${box.read(BoxName.lang)}&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}';
|
|
||||||
|
|
||||||
final response = await _crud.getGoogleApi(link: url, payload: {});
|
|
||||||
|
|
||||||
if (response != null && response['routes'].isNotEmpty) {
|
|
||||||
return response['routes'][0];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) {
|
|
||||||
final pointsString = routeData["overview_polyline"]["points"];
|
|
||||||
final points = decodePolyline(pointsString)
|
|
||||||
.map((p) => LatLng(p[0].toDouble(), p[1].toDouble()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final polyline = Polyline(
|
|
||||||
polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()),
|
|
||||||
points: points,
|
|
||||||
width: 8,
|
|
||||||
color: color,
|
|
||||||
);
|
|
||||||
|
|
||||||
polylines.add(polyline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateCarMarker(LatLng position, double heading) {
|
|
||||||
markers.removeWhere((m) => m.markerId.value == 'MyLocation');
|
|
||||||
markers.add(
|
|
||||||
Marker(
|
|
||||||
markerId: MarkerId('MyLocation'.tr),
|
|
||||||
position: position,
|
|
||||||
icon: carIcon,
|
|
||||||
rotation: heading,
|
|
||||||
anchor: const Offset(0.5, 0.5),
|
|
||||||
flat: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setInitialMarkers(
|
|
||||||
LatLng passengerLocation, LatLng passengerDestination) {
|
|
||||||
markers.clear();
|
|
||||||
markers.add(Marker(
|
|
||||||
markerId: const MarkerId('passengerLocation'),
|
|
||||||
position: passengerLocation,
|
|
||||||
icon: passengerIcon,
|
|
||||||
));
|
|
||||||
markers.add(Marker(
|
|
||||||
markerId: const MarkerId('passengerDestination'),
|
|
||||||
position: passengerDestination,
|
|
||||||
icon: endIcon,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearRoutes() {
|
|
||||||
polylines.clear();
|
|
||||||
currentInstruction.value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,617 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:just_audio/just_audio.dart';
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../../views/home/Captin/driver_map_page.dart';
|
|
||||||
import '../../../views/home/Captin/orderCaptin/marker_generator.dart';
|
|
||||||
import '../../../views/widgets/mydialoug.dart';
|
|
||||||
import '../../firebase/local_notification.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
import '../../functions/location_controller.dart';
|
|
||||||
import '../../home/captin/home_captain_controller.dart';
|
|
||||||
import '../../firebase/notification_service.dart';
|
|
||||||
import '../navigation/decode_polyline_isolate.dart';
|
|
||||||
|
|
||||||
class OrderRequestController extends GetxController
|
|
||||||
with WidgetsBindingObserver {
|
|
||||||
// --- متغيرات التايمر ---
|
|
||||||
double progress = 1.0;
|
|
||||||
int duration = 15;
|
|
||||||
int remainingTime = 15;
|
|
||||||
Timer? _timer;
|
|
||||||
|
|
||||||
bool applied = false;
|
|
||||||
final locationController = Get.put(LocationController());
|
|
||||||
|
|
||||||
// 🔥 متغير لمنع تكرار القبول
|
|
||||||
bool _isRideTakenHandled = false;
|
|
||||||
|
|
||||||
// --- الأيقونات والماركرز ---
|
|
||||||
BitmapDescriptor? driverIcon;
|
|
||||||
Map<MarkerId, Marker> markersMap = {};
|
|
||||||
Set<Marker> get markers => markersMap.values.toSet();
|
|
||||||
|
|
||||||
// --- البيانات والتحكم ---
|
|
||||||
// 🔥 تم إضافة myMapData لدعم السوكيت الجديد
|
|
||||||
List<dynamic>? myList;
|
|
||||||
Map<dynamic, dynamic>? myMapData;
|
|
||||||
|
|
||||||
GoogleMapController? mapController;
|
|
||||||
|
|
||||||
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
|
|
||||||
double latPassenger = 0.0;
|
|
||||||
double lngPassenger = 0.0;
|
|
||||||
double latDestination = 0.0;
|
|
||||||
double lngDestination = 0.0;
|
|
||||||
|
|
||||||
// --- متغيرات العرض ---
|
|
||||||
String passengerRating = "5.0";
|
|
||||||
String tripType = "Standard";
|
|
||||||
String totalTripDistance = "--";
|
|
||||||
String totalTripDuration = "--";
|
|
||||||
String tripPrice = "--";
|
|
||||||
|
|
||||||
String timeToPassenger = "جاري الحساب...";
|
|
||||||
String distanceToPassenger = "--";
|
|
||||||
|
|
||||||
// --- الخريطة ---
|
|
||||||
Set<Polyline> polylines = {};
|
|
||||||
|
|
||||||
// حالة التطبيق والصوت
|
|
||||||
bool isInBackground = false;
|
|
||||||
final AudioPlayer audioPlayer = AudioPlayer();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onInit() async {
|
|
||||||
// 🛑 حماية من الفتح المتكرر لنفس الطلب
|
|
||||||
if (Get.arguments == null) {
|
|
||||||
print("❌ OrderController Error: No arguments received.");
|
|
||||||
Get.back(); // إغلاق الصفحة فوراً
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.onInit();
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
_checkOverlay();
|
|
||||||
|
|
||||||
// 🔥 تهيئة البيانات هي الخطوة الأولى والأهم
|
|
||||||
_initializeData();
|
|
||||||
_parseExtraData();
|
|
||||||
|
|
||||||
// 1. تجهيز أيقونة السائق
|
|
||||||
await _prepareDriverIcon();
|
|
||||||
|
|
||||||
// 2. وضع الماركرز المبدئية
|
|
||||||
_updateMarkers(
|
|
||||||
paxTime: "...",
|
|
||||||
paxDist: "",
|
|
||||||
destTime: totalTripDuration,
|
|
||||||
destDist: totalTripDistance);
|
|
||||||
|
|
||||||
// 3. رسم مبدئي
|
|
||||||
_initialMapSetup();
|
|
||||||
|
|
||||||
// 4. الاستماع للسوكيت
|
|
||||||
_listenForRideTaken();
|
|
||||||
|
|
||||||
// 5. حساب المسارين
|
|
||||||
await _calculateFullJourney();
|
|
||||||
|
|
||||||
// 6. تشغيل التايمر
|
|
||||||
startTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// 🔥🔥🔥 Smart Data Handling (List & Map Support) 🔥🔥🔥
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
void _initializeData() {
|
|
||||||
var args = Get.arguments;
|
|
||||||
print("📦 Order Controller Received Type: ${args.runtimeType}");
|
|
||||||
print("📦 Order Controller Data: $args");
|
|
||||||
|
|
||||||
if (args != null) {
|
|
||||||
// الحالة 1: قائمة مباشرة (Legacy / Some Firebase formats)
|
|
||||||
if (args is List) {
|
|
||||||
myList = args;
|
|
||||||
}
|
|
||||||
// الحالة 2: خريطة (Map)
|
|
||||||
else if (args is Map) {
|
|
||||||
// أ) هل هي قادمة من Firebase وتحتوي على DriverList؟
|
|
||||||
if (args.containsKey('DriverList')) {
|
|
||||||
var listData = args['DriverList'];
|
|
||||||
if (listData is List) {
|
|
||||||
myList = listData;
|
|
||||||
} else if (listData is String) {
|
|
||||||
// أحياناً تصل كنص مشفر داخل الـ Map
|
|
||||||
try {
|
|
||||||
myList = jsonDecode(listData);
|
|
||||||
} catch (e) {
|
|
||||||
print("Error decoding DriverList: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ب) هل هي قادمة من Socket بالمفاتيح الرقمية ("0", "1", ...)؟
|
|
||||||
else {
|
|
||||||
myMapData = args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// تعبئة الإحداثيات باستخدام الدالة الذكية _getValueAt
|
|
||||||
latPassenger = _parseCoord(_getValueAt(0));
|
|
||||||
lngPassenger = _parseCoord(_getValueAt(1));
|
|
||||||
latDestination = _parseCoord(_getValueAt(3));
|
|
||||||
lngDestination = _parseCoord(_getValueAt(4));
|
|
||||||
|
|
||||||
print(
|
|
||||||
"📍 Parsed Coordinates: Pax($latPassenger, $lngPassenger) -> Dest($latDestination, $lngDestination)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🔥 دالة ذكية تجلب القيمة سواء كانت البيانات في List أو Map
|
|
||||||
dynamic _getValueAt(int index) {
|
|
||||||
// الأولوية للقائمة
|
|
||||||
if (myList != null && index < myList!.length) {
|
|
||||||
return myList![index];
|
|
||||||
}
|
|
||||||
// ثم الخريطة (السوكيت) - المفاتيح عبارة عن String
|
|
||||||
if (myMapData != null && myMapData!.containsKey(index.toString())) {
|
|
||||||
return myMapData![index.toString()];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// الدالة التي يستخدمها باقي الكود لجلب البيانات كنصوص
|
|
||||||
String _safeGet(int index) {
|
|
||||||
var val = _getValueAt(index);
|
|
||||||
if (val != null) {
|
|
||||||
return val.toString();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
double _parseCoord(dynamic val) {
|
|
||||||
if (val == null) return 0.0;
|
|
||||||
String s = val.toString().replaceAll(',', '').trim();
|
|
||||||
if (s.contains(' ')) s = s.split(' ')[0];
|
|
||||||
return double.tryParse(s) ?? 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseExtraData() {
|
|
||||||
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
|
|
||||||
tripType = _safeGet(31);
|
|
||||||
totalTripDistance = _safeGet(5);
|
|
||||||
totalTripDuration = _safeGet(19);
|
|
||||||
tripPrice = _safeGet(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// 🔥🔥🔥 Core Logic: Concurrent API Calls & Bounds 🔥🔥🔥
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Future<void> _calculateFullJourney() async {
|
|
||||||
try {
|
|
||||||
Position driverPos = await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
|
||||||
LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
|
|
||||||
|
|
||||||
updateDriverLocation(driverLatLng, driverPos.heading);
|
|
||||||
|
|
||||||
var pickupFuture = _fetchRouteData(
|
|
||||||
start: driverLatLng,
|
|
||||||
end: LatLng(latPassenger, lngPassenger),
|
|
||||||
color: Colors.amber,
|
|
||||||
id: 'pickup_route');
|
|
||||||
|
|
||||||
var tripFuture = _fetchRouteData(
|
|
||||||
start: LatLng(latPassenger, lngPassenger),
|
|
||||||
end: LatLng(latDestination, lngDestination),
|
|
||||||
color: Colors.green,
|
|
||||||
id: 'trip_route',
|
|
||||||
isDashed: true);
|
|
||||||
|
|
||||||
var results = await Future.wait([pickupFuture, tripFuture]);
|
|
||||||
|
|
||||||
var pickupResult = results[0];
|
|
||||||
var tripResult = results[1];
|
|
||||||
|
|
||||||
if (pickupResult != null) {
|
|
||||||
distanceToPassenger = pickupResult['distance_text'];
|
|
||||||
timeToPassenger = pickupResult['duration_text'];
|
|
||||||
polylines.add(pickupResult['polyline']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tripResult != null) {
|
|
||||||
totalTripDistance = tripResult['distance_text'];
|
|
||||||
totalTripDuration = tripResult['duration_text'];
|
|
||||||
polylines.add(tripResult['polyline']);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _updateMarkers(
|
|
||||||
paxTime: timeToPassenger,
|
|
||||||
paxDist: distanceToPassenger,
|
|
||||||
destTime: totalTripDuration,
|
|
||||||
destDist: totalTripDistance);
|
|
||||||
|
|
||||||
zoomToFitRide(driverLatLng);
|
|
||||||
|
|
||||||
update();
|
|
||||||
} catch (e) {
|
|
||||||
print("❌ Error in Journey Calculation: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> _fetchRouteData(
|
|
||||||
{required LatLng start,
|
|
||||||
required LatLng end,
|
|
||||||
required Color color,
|
|
||||||
required String id,
|
|
||||||
bool isDashed = false}) async {
|
|
||||||
try {
|
|
||||||
// حماية من الإحداثيات الصفرية
|
|
||||||
if (start.latitude == 0 || end.latitude == 0) return null;
|
|
||||||
|
|
||||||
String apiUrl = "https://routesy.intaleq.xyz/route/v1/driving";
|
|
||||||
String coords =
|
|
||||||
"${start.longitude},${start.latitude};${end.longitude},${end.latitude}";
|
|
||||||
String url = "$apiUrl/$coords?steps=false&overview=full";
|
|
||||||
|
|
||||||
var response = await http.get(Uri.parse(url));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var json = jsonDecode(response.body);
|
|
||||||
if (json['code'] == 'Ok' && json['routes'].isNotEmpty) {
|
|
||||||
var route = json['routes'][0];
|
|
||||||
|
|
||||||
double distM = double.parse(route['distance'].toString());
|
|
||||||
double durS = double.parse(route['duration'].toString());
|
|
||||||
|
|
||||||
String distText = "${(distM / 1000).toStringAsFixed(1)} كم";
|
|
||||||
String durText = "${(durS / 60).toStringAsFixed(0)} دقيقة";
|
|
||||||
|
|
||||||
String geometry = route['geometry'];
|
|
||||||
List<LatLng> points = await compute(decodePolylineIsolate, geometry);
|
|
||||||
|
|
||||||
Polyline polyline = Polyline(
|
|
||||||
polylineId: PolylineId(id),
|
|
||||||
color: color,
|
|
||||||
width: 5,
|
|
||||||
points: points,
|
|
||||||
patterns:
|
|
||||||
isDashed ? [PatternItem.dash(10), PatternItem.gap(5)] : [],
|
|
||||||
startCap: Cap.roundCap,
|
|
||||||
endCap: Cap.roundCap,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'distance_text': distText,
|
|
||||||
'duration_text': durText,
|
|
||||||
'polyline': polyline
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print("Route Fetch Error: $e");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void zoomToFitRide(LatLng driverPos) {
|
|
||||||
if (mapController == null) return;
|
|
||||||
|
|
||||||
// حماية من النقاط الصفرية
|
|
||||||
if (latPassenger == 0 || latDestination == 0) return;
|
|
||||||
|
|
||||||
List<LatLng> points = [
|
|
||||||
driverPos,
|
|
||||||
LatLng(latPassenger, lngPassenger),
|
|
||||||
LatLng(latDestination, lngDestination),
|
|
||||||
];
|
|
||||||
|
|
||||||
double minLat = points.first.latitude;
|
|
||||||
double maxLat = points.first.latitude;
|
|
||||||
double minLng = points.first.longitude;
|
|
||||||
double maxLng = points.first.longitude;
|
|
||||||
|
|
||||||
for (var p in points) {
|
|
||||||
if (p.latitude < minLat) minLat = p.latitude;
|
|
||||||
if (p.latitude > maxLat) maxLat = p.latitude;
|
|
||||||
if (p.longitude < minLng) minLng = p.longitude;
|
|
||||||
if (p.longitude > maxLng) maxLng = p.longitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapController!.animateCamera(CameraUpdate.newLatLngBounds(
|
|
||||||
LatLngBounds(
|
|
||||||
southwest: LatLng(minLat, minLng),
|
|
||||||
northeast: LatLng(maxLat, maxLng),
|
|
||||||
),
|
|
||||||
100.0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// Markers & Setup
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Future<void> _prepareDriverIcon() async {
|
|
||||||
driverIcon = await MarkerGenerator.createDriverMarker();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateMarkers(
|
|
||||||
{required String paxTime,
|
|
||||||
required String paxDist,
|
|
||||||
String? destTime,
|
|
||||||
String? destDist}) async {
|
|
||||||
// حماية إذا لم يتم جلب الإحداثيات
|
|
||||||
if (latPassenger == 0 || latDestination == 0) return;
|
|
||||||
|
|
||||||
final BitmapDescriptor pickupIcon =
|
|
||||||
await MarkerGenerator.createCustomMarkerBitmap(
|
|
||||||
title: paxTime,
|
|
||||||
subtitle: paxDist,
|
|
||||||
color: Colors.green.shade700,
|
|
||||||
iconData: Icons.person_pin_circle,
|
|
||||||
);
|
|
||||||
|
|
||||||
final BitmapDescriptor dropoffIcon =
|
|
||||||
await MarkerGenerator.createCustomMarkerBitmap(
|
|
||||||
title: destTime ?? totalTripDuration,
|
|
||||||
subtitle: destDist ?? totalTripDistance,
|
|
||||||
color: Colors.red.shade800,
|
|
||||||
iconData: Icons.flag,
|
|
||||||
);
|
|
||||||
|
|
||||||
markersMap[const MarkerId('pax')] = Marker(
|
|
||||||
markerId: const MarkerId('pax'),
|
|
||||||
position: LatLng(latPassenger, lngPassenger),
|
|
||||||
icon: pickupIcon,
|
|
||||||
anchor: const Offset(0.5, 0.85),
|
|
||||||
);
|
|
||||||
|
|
||||||
markersMap[const MarkerId('dest')] = Marker(
|
|
||||||
markerId: const MarkerId('dest'),
|
|
||||||
position: LatLng(latDestination, lngDestination),
|
|
||||||
icon: dropoffIcon,
|
|
||||||
anchor: const Offset(0.5, 0.85),
|
|
||||||
);
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initialMapSetup() async {
|
|
||||||
Position driverPos = await Geolocator.getCurrentPosition();
|
|
||||||
LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
|
|
||||||
|
|
||||||
if (driverIcon != null) {
|
|
||||||
markersMap[const MarkerId('driver')] = Marker(
|
|
||||||
markerId: const MarkerId('driver'),
|
|
||||||
position: driverLatLng,
|
|
||||||
icon: driverIcon!,
|
|
||||||
rotation: driverPos.heading,
|
|
||||||
anchor: const Offset(0.5, 0.5),
|
|
||||||
flat: true,
|
|
||||||
zIndex: 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (latPassenger != 0 && lngPassenger != 0) {
|
|
||||||
polylines.add(Polyline(
|
|
||||||
polylineId: const PolylineId('temp_line'),
|
|
||||||
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
|
|
||||||
color: Colors.grey,
|
|
||||||
width: 2,
|
|
||||||
patterns: [PatternItem.dash(10), PatternItem.gap(10)],
|
|
||||||
));
|
|
||||||
|
|
||||||
zoomToFitRide(driverLatLng);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateDriverLocation(LatLng newPos, double heading) {
|
|
||||||
if (driverIcon != null) {
|
|
||||||
markersMap[const MarkerId('driver')] = Marker(
|
|
||||||
markerId: const MarkerId('driver'),
|
|
||||||
position: newPos,
|
|
||||||
icon: driverIcon!,
|
|
||||||
rotation: heading,
|
|
||||||
anchor: const Offset(0.5, 0.5),
|
|
||||||
flat: true,
|
|
||||||
zIndex: 10,
|
|
||||||
);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMapCreated(GoogleMapController controller) {
|
|
||||||
mapController = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- قبول الطلب وإدارة التايمر ---
|
|
||||||
void startTimer() {
|
|
||||||
_timer?.cancel();
|
|
||||||
remainingTime = duration;
|
|
||||||
_playAudio();
|
|
||||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
if (remainingTime <= 0) {
|
|
||||||
timer.cancel();
|
|
||||||
_stopAudio();
|
|
||||||
if (!applied) Get.back();
|
|
||||||
} else {
|
|
||||||
remainingTime--;
|
|
||||||
progress = remainingTime / duration;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void endTimer() => _timer?.cancel();
|
|
||||||
void changeApplied() => applied = true;
|
|
||||||
|
|
||||||
void _playAudio() async {
|
|
||||||
try {
|
|
||||||
await audioPlayer.setAsset('assets/order.mp3', preload: true);
|
|
||||||
await audioPlayer.setLoopMode(LoopMode.one);
|
|
||||||
await audioPlayer.play();
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _stopAudio() => audioPlayer.stop();
|
|
||||||
|
|
||||||
void _listenForRideTaken() {
|
|
||||||
if (locationController.socket != null) {
|
|
||||||
locationController.socket!.off('ride_taken');
|
|
||||||
locationController.socket!.on('ride_taken', (data) {
|
|
||||||
if (_isRideTakenHandled) return;
|
|
||||||
String takenRideId = data['ride_id'].toString();
|
|
||||||
String myCurrentRideId = _safeGet(16);
|
|
||||||
String whoTookIt = data['taken_by_driver_id'].toString();
|
|
||||||
String myDriverId = box.read(BoxName.driverID).toString();
|
|
||||||
|
|
||||||
if (takenRideId == myCurrentRideId && whoTookIt != myDriverId) {
|
|
||||||
_isRideTakenHandled = true;
|
|
||||||
endTimer();
|
|
||||||
// 1. حذف الإشعار من شريط التنبيهات فوراً
|
|
||||||
NotificationController().cancelOrderNotification();
|
|
||||||
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
|
||||||
if (Get.isDialogOpen ?? false) Get.back();
|
|
||||||
Get.back();
|
|
||||||
Get.snackbar("تنبيه", "تم قبول الطلب من قبل سائق آخر",
|
|
||||||
backgroundColor: Colors.orange, colorText: Colors.white);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
super.didChangeAppLifecycleState(state);
|
|
||||||
if (state == AppLifecycleState.paused ||
|
|
||||||
state == AppLifecycleState.detached) {
|
|
||||||
isInBackground = true;
|
|
||||||
} else if (state == AppLifecycleState.resumed) {
|
|
||||||
isInBackground = false;
|
|
||||||
FlutterOverlayWindow.closeOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _checkOverlay() async {
|
|
||||||
if (Platform.isAndroid && await FlutterOverlayWindow.isActive()) {
|
|
||||||
await FlutterOverlayWindow.closeOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept Order Logic
|
|
||||||
Future<void> acceptOrder() async {
|
|
||||||
endTimer();
|
|
||||||
_stopAudio();
|
|
||||||
|
|
||||||
// 1. إرسال الطلب
|
|
||||||
var res = await CRUD()
|
|
||||||
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
|
|
||||||
'id': _safeGet(16),
|
|
||||||
'rideTimeStart': DateTime.now().toString(),
|
|
||||||
'status': 'Apply',
|
|
||||||
'passengerToken': _safeGet(9),
|
|
||||||
'driver_id': box.read(BoxName.driverID),
|
|
||||||
});
|
|
||||||
|
|
||||||
Log.print('res from orderrequestpage: ${res}');
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// تصحيح: فحص الرد بدقة (Map أو String)
|
|
||||||
// ============================================================
|
|
||||||
bool isFailure = false;
|
|
||||||
|
|
||||||
if (res is Map && res['status'] == 'failure') {
|
|
||||||
isFailure = true;
|
|
||||||
} else if (res == 'failure') {
|
|
||||||
isFailure = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFailure) {
|
|
||||||
// ⛔ حالة الفشل: الطلب مأخوذ
|
|
||||||
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
|
|
||||||
Get.back(); // إغلاق الديالوج
|
|
||||||
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// ✅ حالة النجاح
|
|
||||||
|
|
||||||
// حماية من الكراش: التأكد من وجود HomeCaptainController قبل استخدامه
|
|
||||||
if (!Get.isRegistered<HomeCaptainController>()) {
|
|
||||||
Get.put(HomeCaptainController());
|
|
||||||
} else {
|
|
||||||
Get.find<HomeCaptainController>().changeRideId();
|
|
||||||
}
|
|
||||||
|
|
||||||
box.write(BoxName.statusDriverLocation, 'on');
|
|
||||||
changeApplied();
|
|
||||||
|
|
||||||
var rideArgs = {
|
|
||||||
'passengerLocation': '${_safeGet(0)},${_safeGet(1)}',
|
|
||||||
'passengerDestination': '${_safeGet(3)},${_safeGet(4)}',
|
|
||||||
'Duration': totalTripDuration,
|
|
||||||
'totalCost': _safeGet(26),
|
|
||||||
'Distance': totalTripDistance,
|
|
||||||
'name': _safeGet(8),
|
|
||||||
'phone': _safeGet(10),
|
|
||||||
'email': _safeGet(28),
|
|
||||||
'WalletChecked': _safeGet(13),
|
|
||||||
'tokenPassenger': _safeGet(9),
|
|
||||||
'direction':
|
|
||||||
'https://www.google.com/maps/dir/${_safeGet(0)}/${_safeGet(1)}/',
|
|
||||||
'DurationToPassenger': timeToPassenger,
|
|
||||||
'rideId': _safeGet(16),
|
|
||||||
'passengerId': _safeGet(7),
|
|
||||||
'driverId': _safeGet(18),
|
|
||||||
'durationOfRideValue': totalTripDuration,
|
|
||||||
'paymentAmount': _safeGet(2),
|
|
||||||
'paymentMethod': _safeGet(13) == 'true' ? 'visa' : 'cash',
|
|
||||||
'isHaveSteps': _safeGet(20),
|
|
||||||
'step0': _safeGet(21),
|
|
||||||
'step1': _safeGet(22),
|
|
||||||
'step2': _safeGet(23),
|
|
||||||
'step3': _safeGet(24),
|
|
||||||
'step4': _safeGet(25),
|
|
||||||
'passengerWalletBurc': _safeGet(26),
|
|
||||||
'timeOfOrder': DateTime.now().toString(),
|
|
||||||
'totalPassenger': _safeGet(2),
|
|
||||||
'carType': _safeGet(31),
|
|
||||||
'kazan': _safeGet(32),
|
|
||||||
'startNameLocation': _safeGet(29),
|
|
||||||
'endNameLocation': _safeGet(30),
|
|
||||||
};
|
|
||||||
|
|
||||||
box.write(BoxName.rideArguments, rideArgs);
|
|
||||||
|
|
||||||
// الانتقال النهائي
|
|
||||||
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
locationController.socket?.off('ride_taken');
|
|
||||||
audioPlayer.dispose();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
_timer?.cancel();
|
|
||||||
mapController?.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,41 +0,0 @@
|
|||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
|
|
||||||
// تم تعديل الدالة لتقبل وسيط من نوع `dynamic` لحل مشكلة عدم تطابق الأنواع مع دالة `compute`.
|
|
||||||
// هذه الدالة لا تزال تعمل كدالة من المستوى الأعلى (Top-level function)
|
|
||||||
// وهو شرط أساسي لاستخدامها مع دالة compute.
|
|
||||||
List<LatLng> decodePolylineIsolate(dynamic encodedMessage) {
|
|
||||||
// التأكد من أن الرسالة المستقبلة هي من نوع String
|
|
||||||
if (encodedMessage is! String) {
|
|
||||||
// إرجاع قائمة فارغة أو إظهار خطأ إذا كان النوع غير صحيح
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final String encoded = encodedMessage;
|
|
||||||
|
|
||||||
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,705 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
|
|
||||||
import '../../../constant/box_name.dart';
|
|
||||||
import '../../../constant/country_polygons.dart';
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../env/env.dart';
|
|
||||||
import '../../../main.dart';
|
|
||||||
import '../../../print.dart';
|
|
||||||
import '../../functions/tts.dart';
|
|
||||||
import 'decode_polyline_isolate.dart';
|
|
||||||
|
|
||||||
class NavigationController extends GetxController {
|
|
||||||
// --- متغيرات الحالة العامة ---
|
|
||||||
bool isLoading = false;
|
|
||||||
GoogleMapController? mapController;
|
|
||||||
final TextEditingController placeDestinationController =
|
|
||||||
TextEditingController();
|
|
||||||
|
|
||||||
// --- متغيرات الخريطة والموقع ---
|
|
||||||
LatLng? myLocation;
|
|
||||||
double heading = 0.0;
|
|
||||||
final Set<Marker> markers = {};
|
|
||||||
final Set<Polyline> polylines = {};
|
|
||||||
|
|
||||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
BitmapDescriptor destinationIcon = BitmapDescriptor.defaultMarker;
|
|
||||||
|
|
||||||
// --- متغيرات النظام الذكي للتحديث ---
|
|
||||||
Timer? _locationUpdateTimer;
|
|
||||||
// تحديث كل ثانية لضمان سلاسة الحركة
|
|
||||||
Duration _currentUpdateInterval = const Duration(seconds: 1);
|
|
||||||
LatLng? _lastRecordedLocation;
|
|
||||||
|
|
||||||
// --- متغيرات البحث ---
|
|
||||||
List<dynamic> placesDestination = [];
|
|
||||||
Timer? _debounce;
|
|
||||||
|
|
||||||
// --- متغيرات الملاحة (Navigation) ---
|
|
||||||
LatLng? _finalDestination;
|
|
||||||
List<Map<String, dynamic>> routeSteps = [];
|
|
||||||
|
|
||||||
List<LatLng> _fullRouteCoordinates = [];
|
|
||||||
int _lastTraveledIndexInFullRoute = 0;
|
|
||||||
|
|
||||||
bool _nextInstructionSpoken = false;
|
|
||||||
String currentInstruction = "";
|
|
||||||
String nextInstruction = "";
|
|
||||||
int currentStepIndex = 0;
|
|
||||||
|
|
||||||
double currentSpeed = 0.0;
|
|
||||||
String distanceToNextStep = "";
|
|
||||||
|
|
||||||
// الرابط الجديد
|
|
||||||
static const String _routeApiBaseUrl =
|
|
||||||
"https://routesy.intaleq.xyz/route/v1/driving";
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
_initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
|
||||||
await _loadCustomIcons();
|
|
||||||
await _getCurrentLocationAndStartUpdates();
|
|
||||||
if (!Get.isRegistered<TextToSpeechController>()) {
|
|
||||||
Get.put(TextToSpeechController());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
_locationUpdateTimer?.cancel();
|
|
||||||
mapController?.dispose();
|
|
||||||
_debounce?.cancel();
|
|
||||||
placeDestinationController.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ✅ دوال الخريطة الأساسية
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
void onMapCreated(GoogleMapController controller) {
|
|
||||||
mapController = controller;
|
|
||||||
if (myLocation != null) {
|
|
||||||
animateCameraToPosition(myLocation!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> onMapLongPressed(LatLng tappedPoint) async {
|
|
||||||
Get.dialog(
|
|
||||||
AlertDialog(
|
|
||||||
title: const Text('بدء الملاحة؟'),
|
|
||||||
content: const Text('هل تريد الذهاب إلى هذا الموقع المحدد؟'),
|
|
||||||
actionsAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: const Text('إلغاء', style: TextStyle(color: Colors.grey)),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('اذهب الآن'),
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
startNavigationTo(tappedPoint, infoWindowTitle: 'الموقع المحدد');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ١. النظام الذكي للموقع (Optimized)
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
Future<void> _getCurrentLocationAndStartUpdates() async {
|
|
||||||
try {
|
|
||||||
Position position = await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
|
||||||
myLocation = LatLng(position.latitude, position.longitude);
|
|
||||||
getLocationArea(myLocation!.latitude, myLocation!.longitude);
|
|
||||||
update();
|
|
||||||
animateCameraToPosition(myLocation!);
|
|
||||||
_startLocationTimer();
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("Error getting initial location: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startLocationTimer() {
|
|
||||||
_locationUpdateTimer?.cancel();
|
|
||||||
_locationUpdateTimer = Timer.periodic(_currentUpdateInterval, (timer) {
|
|
||||||
_updateLocationAndProcess();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateLocationAndProcess() async {
|
|
||||||
try {
|
|
||||||
final position = await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
|
||||||
|
|
||||||
final newLoc = LatLng(position.latitude, position.longitude);
|
|
||||||
|
|
||||||
// فلتر الاهتزاز (Jitter Filter)
|
|
||||||
if (_lastRecordedLocation != null) {
|
|
||||||
double dist = Geolocator.distanceBetween(
|
|
||||||
newLoc.latitude,
|
|
||||||
newLoc.longitude,
|
|
||||||
_lastRecordedLocation!.latitude,
|
|
||||||
_lastRecordedLocation!.longitude);
|
|
||||||
if (dist < 2.0) return; // تجاهل الحركة الأقل من 2 متر
|
|
||||||
}
|
|
||||||
|
|
||||||
myLocation = newLoc;
|
|
||||||
_lastRecordedLocation = newLoc;
|
|
||||||
heading = position.heading;
|
|
||||||
currentSpeed = position.speed * 3.6;
|
|
||||||
|
|
||||||
_updateCarMarker();
|
|
||||||
|
|
||||||
if (polylines.isNotEmpty && _fullRouteCoordinates.isNotEmpty) {
|
|
||||||
animateCameraToPosition(
|
|
||||||
myLocation!,
|
|
||||||
bearing: heading,
|
|
||||||
zoom: 18.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
_updateTraveledPolylineSmart(myLocation!);
|
|
||||||
_checkNavigationStep(myLocation!);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
} catch (e) {
|
|
||||||
// Log.print("Loc update error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateCarMarker() {
|
|
||||||
if (myLocation == null) return;
|
|
||||||
markers.removeWhere((m) => m.markerId.value == 'myLocation');
|
|
||||||
markers.add(
|
|
||||||
Marker(
|
|
||||||
markerId: const MarkerId('myLocation'),
|
|
||||||
position: myLocation!,
|
|
||||||
icon: carIcon,
|
|
||||||
rotation: heading,
|
|
||||||
anchor: const Offset(0.5, 0.5),
|
|
||||||
flat: true,
|
|
||||||
zIndex: 2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void animateCameraToPosition(LatLng position,
|
|
||||||
{double zoom = 17.0, double bearing = 0.0}) {
|
|
||||||
mapController?.animateCamera(
|
|
||||||
CameraUpdate.newCameraPosition(
|
|
||||||
CameraPosition(
|
|
||||||
target: position, zoom: zoom, bearing: bearing, tilt: 45.0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ٢. خوارزمية رسم المسار الذكية (Sliding Window) - مخففة للجهاز
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
void _updateTraveledPolylineSmart(LatLng currentPos) {
|
|
||||||
if (_fullRouteCoordinates.isEmpty) return;
|
|
||||||
|
|
||||||
// نبحث فقط في الـ 60 نقطة القادمة لتخفيف الحمل على المعالج
|
|
||||||
int searchWindow = 60;
|
|
||||||
int startIndex = _lastTraveledIndexInFullRoute;
|
|
||||||
int endIndex = min(startIndex + searchWindow, _fullRouteCoordinates.length);
|
|
||||||
|
|
||||||
double minDistance = double.infinity;
|
|
||||||
int closestIndex = startIndex;
|
|
||||||
bool foundCloser = false;
|
|
||||||
|
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
|
||||||
final point = _fullRouteCoordinates[i];
|
|
||||||
final dist = Geolocator.distanceBetween(currentPos.latitude,
|
|
||||||
currentPos.longitude, point.latitude, point.longitude);
|
|
||||||
|
|
||||||
if (dist < minDistance) {
|
|
||||||
minDistance = dist;
|
|
||||||
closestIndex = i;
|
|
||||||
foundCloser = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// شرط التحديث: وجدنا نقطة أقرب، وهي أمامنا (وليست خلفنا)، والمسافة منطقية
|
|
||||||
if (foundCloser &&
|
|
||||||
minDistance < 50 &&
|
|
||||||
closestIndex > _lastTraveledIndexInFullRoute) {
|
|
||||||
_lastTraveledIndexInFullRoute = closestIndex;
|
|
||||||
|
|
||||||
// استخدام sublist وهو سريع جداً في Dart
|
|
||||||
final remaining =
|
|
||||||
_fullRouteCoordinates.sublist(_lastTraveledIndexInFullRoute);
|
|
||||||
final traveled =
|
|
||||||
_fullRouteCoordinates.sublist(0, _lastTraveledIndexInFullRoute + 1);
|
|
||||||
|
|
||||||
_updatePolylinesSets(traveled, remaining);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updatePolylinesSets(List<LatLng> traveled, List<LatLng> remaining) {
|
|
||||||
// إزالة الخطوط القديمة وإضافة الجديدة بدلاً من تحديث الكل
|
|
||||||
polylines.removeWhere((p) => p.polylineId.value == 'remaining_route');
|
|
||||||
polylines.removeWhere((p) => p.polylineId.value == 'traveled_route');
|
|
||||||
|
|
||||||
// المسار المتبقي (أزرق واضح)
|
|
||||||
polylines.add(Polyline(
|
|
||||||
polylineId: const PolylineId('remaining_route'),
|
|
||||||
points: remaining,
|
|
||||||
color: const Color(0xFF0D47A1),
|
|
||||||
width: 6,
|
|
||||||
startCap: Cap.roundCap,
|
|
||||||
endCap: Cap.roundCap,
|
|
||||||
jointType: JointType.round,
|
|
||||||
));
|
|
||||||
|
|
||||||
// المسار المقطوع (رمادي)
|
|
||||||
polylines.add(Polyline(
|
|
||||||
polylineId: const PolylineId('traveled_route'),
|
|
||||||
points: traveled,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
width: 6,
|
|
||||||
jointType: JointType.round,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ٣. التحكم في التوجيهات
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
void _checkNavigationStep(LatLng currentPosition) {
|
|
||||||
if (routeSteps.isEmpty || currentStepIndex >= routeSteps.length) return;
|
|
||||||
|
|
||||||
final step = routeSteps[currentStepIndex];
|
|
||||||
// في OSRM، يجب التأكد من وجود maneuver location
|
|
||||||
final maneuver = step['maneuver'];
|
|
||||||
final List<dynamic> location = maneuver['location']; // [lng, lat]
|
|
||||||
final endLatLng = LatLng(location[1], location[0]);
|
|
||||||
|
|
||||||
final distance = Geolocator.distanceBetween(
|
|
||||||
currentPosition.latitude,
|
|
||||||
currentPosition.longitude,
|
|
||||||
endLatLng.latitude,
|
|
||||||
endLatLng.longitude,
|
|
||||||
);
|
|
||||||
|
|
||||||
distanceToNextStep = distance > 1000
|
|
||||||
? "${(distance / 1000).toStringAsFixed(1)} كم"
|
|
||||||
: "${distance.toStringAsFixed(0)} متر";
|
|
||||||
|
|
||||||
// نطق التعليمات قبل 50 متر
|
|
||||||
if (distance < 50 &&
|
|
||||||
!_nextInstructionSpoken &&
|
|
||||||
nextInstruction.isNotEmpty) {
|
|
||||||
Get.find<TextToSpeechController>().speakText(nextInstruction);
|
|
||||||
_nextInstructionSpoken = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// الانتقال للخطوة التالية عند الاقتراب (20 متر)
|
|
||||||
if (distance < 20) {
|
|
||||||
_advanceStep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _advanceStep() {
|
|
||||||
currentStepIndex++;
|
|
||||||
if (currentStepIndex < routeSteps.length) {
|
|
||||||
currentInstruction = routeSteps[currentStepIndex]['instruction_text'];
|
|
||||||
|
|
||||||
// تجهيز التعليمات القادمة
|
|
||||||
if ((currentStepIndex + 1) < routeSteps.length) {
|
|
||||||
nextInstruction =
|
|
||||||
"ثم ${routeSteps[currentStepIndex + 1]['instruction_text']}";
|
|
||||||
} else {
|
|
||||||
nextInstruction = "ستصل إلى وجهتك";
|
|
||||||
}
|
|
||||||
|
|
||||||
_nextInstructionSpoken = false;
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
_finishNavigation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _finishNavigation() {
|
|
||||||
currentInstruction = "لقد وصلت إلى وجهتك";
|
|
||||||
nextInstruction = "";
|
|
||||||
distanceToNextStep = "";
|
|
||||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ٤. جلب المسار (🔥 تم التحديث للرابط الجديد 🔥)
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
Future<void> getRoute(LatLng origin, LatLng destination) async {
|
|
||||||
// 🔥 بناء الرابط حسب التنسيق: /driving/lng,lat;lng,lat
|
|
||||||
String coords =
|
|
||||||
"${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}";
|
|
||||||
|
|
||||||
// 🔥 تفعيل steps=true لجلب الخطوات
|
|
||||||
String url =
|
|
||||||
"$_routeApiBaseUrl/$coords?steps=true&overview=full&geometries=polyline";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// الرابط الجديد قد لا يحتاج API Key في الهيدر، ولكن نتركه إذا كان السيرفر يطلبه
|
|
||||||
// إذا كان الرابط عام، يمكن إزالة الهيدر
|
|
||||||
final response = await http.get(Uri.parse(url));
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
Get.snackbar('تنبيه', 'تعذر الاتصال بخدمة التوجيه.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final responseData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
// التحقق حسب هيكلية OSRM
|
|
||||||
if (responseData['code'] != 'Ok' ||
|
|
||||||
(responseData['routes'] as List).isEmpty) {
|
|
||||||
Get.snackbar('عذراً', 'لم يتم العثور على مسار.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var route = responseData['routes'][0];
|
|
||||||
|
|
||||||
// 1. فك تشفير Polyline
|
|
||||||
final pointsString = route['geometry'];
|
|
||||||
_fullRouteCoordinates = await compute(
|
|
||||||
decodePolylineIsolate as ComputeCallback<dynamic, List<LatLng>>,
|
|
||||||
pointsString);
|
|
||||||
|
|
||||||
// تهيئة الرسم
|
|
||||||
_lastTraveledIndexInFullRoute = 0;
|
|
||||||
_updatePolylinesSets([], _fullRouteCoordinates);
|
|
||||||
|
|
||||||
// 2. استخراج الخطوات (داخل Legs في OSRM)
|
|
||||||
var legs = route['legs'] as List;
|
|
||||||
if (legs.isNotEmpty) {
|
|
||||||
var steps = legs[0]['steps'] as List;
|
|
||||||
routeSteps = List<Map<String, dynamic>>.from(steps);
|
|
||||||
} else {
|
|
||||||
routeSteps = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. معالجة النصوص العربية للخطوات
|
|
||||||
for (var step in routeSteps) {
|
|
||||||
step['instruction_text'] = _createInstructionFromManeuver(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. بدء الملاحة
|
|
||||||
currentStepIndex = 0;
|
|
||||||
_nextInstructionSpoken = false;
|
|
||||||
if (routeSteps.isNotEmpty) {
|
|
||||||
currentInstruction = routeSteps[0]['instruction_text'];
|
|
||||||
if (routeSteps.length > 1) {
|
|
||||||
nextInstruction = "ثم ${routeSteps[1]['instruction_text']}";
|
|
||||||
} else {
|
|
||||||
nextInstruction = "الوجهة النهائية";
|
|
||||||
}
|
|
||||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. ضبط الكاميرا
|
|
||||||
if (_fullRouteCoordinates.isNotEmpty) {
|
|
||||||
final bounds = _boundsFromLatLngList(_fullRouteCoordinates);
|
|
||||||
mapController
|
|
||||||
?.animateCamera(CameraUpdate.newLatLngBounds(bounds, 80.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
} catch (e) {
|
|
||||||
Log.print("GetRoute Error: $e");
|
|
||||||
Get.snackbar('خطأ', 'حدث خطأ غير متوقع.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔥 تحويل تعليمات OSRM إلى العربية
|
|
||||||
String _createInstructionFromManeuver(Map<String, dynamic> step) {
|
|
||||||
if (step['maneuver'] == null) return "تابع المسير";
|
|
||||||
|
|
||||||
final maneuver = step['maneuver'];
|
|
||||||
final type = maneuver['type'] ?? 'continue';
|
|
||||||
final modifier = maneuver['modifier'] ?? 'straight';
|
|
||||||
final name = step['name'] ?? ''; // اسم الشارع
|
|
||||||
|
|
||||||
String instruction = "";
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'depart':
|
|
||||||
instruction = "انطلق";
|
|
||||||
break;
|
|
||||||
case 'arrive':
|
|
||||||
return "لقد وصلت إلى وجهتك، $name";
|
|
||||||
case 'turn':
|
|
||||||
case 'fork':
|
|
||||||
case 'roundabout':
|
|
||||||
case 'merge':
|
|
||||||
case 'on ramp':
|
|
||||||
case 'off ramp':
|
|
||||||
case 'end of road':
|
|
||||||
instruction = _getTurnInstruction(modifier);
|
|
||||||
break;
|
|
||||||
case 'new name':
|
|
||||||
instruction = "تابع المسير";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
instruction = "تابع المسير";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.isNotEmpty) {
|
|
||||||
if (type == 'new name' || type == 'continue') {
|
|
||||||
instruction += " على $name";
|
|
||||||
} else {
|
|
||||||
instruction += " نحو $name";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instruction;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getTurnInstruction(String modifier) {
|
|
||||||
switch (modifier) {
|
|
||||||
case 'uturn':
|
|
||||||
return "قم بالاستدارة والعودة";
|
|
||||||
case 'sharp right':
|
|
||||||
return "انعطف يميناً بحدة";
|
|
||||||
case 'right':
|
|
||||||
return "انعطف يميناً";
|
|
||||||
case 'slight right':
|
|
||||||
return "انعطف يميناً قليلاً";
|
|
||||||
case 'straight':
|
|
||||||
return "استمر للأمام";
|
|
||||||
case 'slight left':
|
|
||||||
return "انعطف يساراً قليلاً";
|
|
||||||
case 'left':
|
|
||||||
return "انعطف يساراً";
|
|
||||||
case 'sharp left':
|
|
||||||
return "انعطف يساراً بحدة";
|
|
||||||
default:
|
|
||||||
return "اتجه";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================================
|
|
||||||
// ٥. أدوات مساعدة
|
|
||||||
// =======================================================================
|
|
||||||
|
|
||||||
Future<void> startNavigationTo(LatLng destination,
|
|
||||||
{String infoWindowTitle = ''}) async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
try {
|
|
||||||
_finalDestination = destination;
|
|
||||||
clearRoute(isNewRoute: true);
|
|
||||||
|
|
||||||
markers.add(Marker(
|
|
||||||
markerId: const MarkerId('destination'),
|
|
||||||
position: destination,
|
|
||||||
icon: destinationIcon,
|
|
||||||
infoWindow: InfoWindow(title: infoWindowTitle),
|
|
||||||
));
|
|
||||||
|
|
||||||
await getRoute(myLocation!, destination);
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> recalculateRoute() async {
|
|
||||||
if (myLocation == null || _finalDestination == null || isLoading) return;
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
Get.snackbar('إعادة التوجيه', 'جاري حساب مسار جديد...',
|
|
||||||
backgroundColor: AppColor.goldenBronze);
|
|
||||||
await getRoute(myLocation!, _finalDestination!);
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearRoute({bool isNewRoute = false}) {
|
|
||||||
if (!isNewRoute) {
|
|
||||||
markers.removeWhere((m) => m.markerId.value == 'destination');
|
|
||||||
_finalDestination = null;
|
|
||||||
polylines.clear();
|
|
||||||
}
|
|
||||||
routeSteps.clear();
|
|
||||||
_fullRouteCoordinates.clear();
|
|
||||||
_lastTraveledIndexInFullRoute = 0;
|
|
||||||
currentInstruction = "";
|
|
||||||
nextInstruction = "";
|
|
||||||
distanceToNextStep = "";
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadCustomIcons() async {
|
|
||||||
carIcon = await BitmapDescriptor.fromAssetImage(
|
|
||||||
const ImageConfiguration(size: Size(40, 40)), 'assets/images/car.png');
|
|
||||||
destinationIcon = await BitmapDescriptor.fromAssetImage(
|
|
||||||
const ImageConfiguration(size: Size(30, 30)), 'assets/images/b.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getPlaces() async {
|
|
||||||
final q = placeDestinationController.text.trim();
|
|
||||||
if (q.length < 3) {
|
|
||||||
placesDestination = [];
|
|
||||||
update();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myLocation == null) return;
|
|
||||||
|
|
||||||
final lat = myLocation!.latitude;
|
|
||||||
final lng = myLocation!.longitude;
|
|
||||||
const radiusKm = 200.0;
|
|
||||||
final latDelta = _kmToLatDelta(radiusKm);
|
|
||||||
final lngDelta = _kmToLngDelta(radiusKm, lat);
|
|
||||||
|
|
||||||
final payload = {
|
|
||||||
'query': q,
|
|
||||||
'lat_min': (lat - latDelta).toString(),
|
|
||||||
'lat_max': (lat + latDelta).toString(),
|
|
||||||
'lng_min': (lng - lngDelta).toString(),
|
|
||||||
'lng_max': (lng + lngDelta).toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response =
|
|
||||||
await CRUD().post(link: AppLink.getPlacesSyria, payload: payload);
|
|
||||||
final responseData = (response);
|
|
||||||
|
|
||||||
List list;
|
|
||||||
if (responseData is Map && responseData['status'] == 'success') {
|
|
||||||
list = List.from(responseData['message'] as List);
|
|
||||||
} else if (responseData is List) {
|
|
||||||
list = List.from(responseData);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final p in list) {
|
|
||||||
final plat = double.tryParse(p['latitude']?.toString() ?? '0.0') ?? 0.0;
|
|
||||||
final plng =
|
|
||||||
double.tryParse(p['longitude']?.toString() ?? '0.0') ?? 0.0;
|
|
||||||
final distance = _haversineKm(lat, lng, plat, plng);
|
|
||||||
p['distanceKm'] = distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.sort((a, b) =>
|
|
||||||
(a['distanceKm'] as double).compareTo(b['distanceKm'] as double));
|
|
||||||
placesDestination = list;
|
|
||||||
update();
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception in getPlaces: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> selectDestination(dynamic place) async {
|
|
||||||
placeDestinationController.clear();
|
|
||||||
placesDestination = [];
|
|
||||||
final double lat = double.parse(place['latitude'].toString());
|
|
||||||
final double lng = double.parse(place['longitude'].toString());
|
|
||||||
await startNavigationTo(LatLng(lat, lng),
|
|
||||||
infoWindowTitle: place['name'] ?? 'وجهة');
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSearchChanged(String query) {
|
|
||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
|
||||||
_debounce = Timer(const Duration(milliseconds: 700), () => getPlaces());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- دوال مساعدة للحسابات ---
|
|
||||||
double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
|
||||||
const R = 6371.0;
|
|
||||||
final dLat = (lat2 - lat1) * (pi / 180.0);
|
|
||||||
final dLon = (lon2 - lon1) * (pi / 180.0);
|
|
||||||
final a = sin(dLat / 2) * sin(dLat / 2) +
|
|
||||||
cos(lat1 * pi / 180) *
|
|
||||||
cos(lat2 * pi / 180) *
|
|
||||||
sin(dLon / 2) *
|
|
||||||
sin(dLon / 2);
|
|
||||||
return R * 2 * atan2(sqrt(a), sqrt(1 - a));
|
|
||||||
}
|
|
||||||
|
|
||||||
double _kmToLatDelta(double km) => km / 111.32;
|
|
||||||
double _kmToLngDelta(double km, double lat) =>
|
|
||||||
km / (111.32 * cos(lat * pi / 180));
|
|
||||||
|
|
||||||
LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
|
|
||||||
double? x0, x1, y0, y1;
|
|
||||||
for (LatLng latLng in list) {
|
|
||||||
if (x0 == null) {
|
|
||||||
x0 = x1 = latLng.latitude;
|
|
||||||
y0 = y1 = latLng.longitude;
|
|
||||||
} else {
|
|
||||||
if (latLng.latitude > x1!) x1 = latLng.latitude;
|
|
||||||
if (latLng.latitude < x0) x0 = latLng.latitude;
|
|
||||||
if (latLng.longitude > y1!) y1 = latLng.longitude;
|
|
||||||
if (latLng.longitude < y0!) y0 = latLng.longitude;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return LatLngBounds(
|
|
||||||
northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- دوال التحقق من الدولة ---
|
|
||||||
String getLocationArea(double latitude, double longitude) {
|
|
||||||
LatLng p = LatLng(latitude, longitude);
|
|
||||||
if (isPointInPolygon(p, CountryPolygons.jordanBoundary)) {
|
|
||||||
box.write(BoxName.countryCode, 'Jordan');
|
|
||||||
return 'Jordan';
|
|
||||||
}
|
|
||||||
if (isPointInPolygon(p, CountryPolygons.syriaBoundary)) {
|
|
||||||
box.write(BoxName.countryCode, 'Syria');
|
|
||||||
return 'Syria';
|
|
||||||
}
|
|
||||||
if (isPointInPolygon(p, CountryPolygons.egyptBoundary)) {
|
|
||||||
box.write(BoxName.countryCode, 'Egypt');
|
|
||||||
return 'Egypt';
|
|
||||||
}
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPointInPolygon(LatLng point, List<LatLng> polygon) {
|
|
||||||
int intersections = 0;
|
|
||||||
for (int i = 0; i < polygon.length; i++) {
|
|
||||||
LatLng vertex1 = polygon[i];
|
|
||||||
LatLng vertex2 = polygon[(i + 1) % polygon.length];
|
|
||||||
if (_rayIntersectsSegment(point, vertex1, vertex2)) intersections++;
|
|
||||||
}
|
|
||||||
return intersections % 2 != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _rayIntersectsSegment(LatLng point, LatLng vertex1, LatLng vertex2) {
|
|
||||||
double px = point.longitude;
|
|
||||||
double py = point.latitude;
|
|
||||||
double v1x = vertex1.longitude;
|
|
||||||
double v1y = vertex1.latitude;
|
|
||||||
double v2x = vertex2.longitude;
|
|
||||||
double v2y = vertex2.latitude;
|
|
||||||
|
|
||||||
if ((py < v1y && py < v2y) || (py > v1y && py > v2y)) return false;
|
|
||||||
double intersectX = v1x + (py - v1y) * (v2x - v1x) / (v2y - v1y);
|
|
||||||
return intersectX > px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,425 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'dart:ui'; // For BackdropFilter
|
|
||||||
|
|
||||||
import 'navigation_controller.dart';
|
|
||||||
|
|
||||||
// ملاحظة: افترضتُ أن لديك لوناً أساسياً في هذا الملف
|
|
||||||
// import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
// سأستخدم اللون الأزرق كبديل مؤقت
|
|
||||||
const Color kPrimaryColor = Color(0xFF0D47A1);
|
|
||||||
|
|
||||||
class NavigationView extends StatelessWidget {
|
|
||||||
const NavigationView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// استخدام Get.find() بدلاً من Get.put() لضمان أن الكونترولر مُهيأ مسبقاً
|
|
||||||
// إذا كانت هذه هي نقطة الدخول، Get.put() صحيح.
|
|
||||||
final NavigationController controller = Get.put(NavigationController());
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: GetBuilder<NavigationController>(
|
|
||||||
builder: (_) => Stack(
|
|
||||||
children: [
|
|
||||||
// --- الخريطة ---
|
|
||||||
GoogleMap(
|
|
||||||
onMapCreated: controller.onMapCreated,
|
|
||||||
onLongPress: controller.onMapLongPressed,
|
|
||||||
initialCameraPosition: CameraPosition(
|
|
||||||
target: controller.myLocation ??
|
|
||||||
const LatLng(33.5138, 36.2765), // Default to Damascus
|
|
||||||
zoom: 16.0,
|
|
||||||
),
|
|
||||||
markers: controller.markers,
|
|
||||||
polylines: controller.polylines,
|
|
||||||
myLocationEnabled: false,
|
|
||||||
myLocationButtonEnabled: false,
|
|
||||||
compassEnabled: false,
|
|
||||||
zoomControlsEnabled: false, buildingsEnabled: false,
|
|
||||||
// تعديل الـ padding لإعطاء مساحة للعناصر العلوية والسفلية
|
|
||||||
// مساحة أكبر في الأعلى للبحث + النتائج، ومساحة أكبر بالأسفل للملاحة
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: controller.currentInstruction.isNotEmpty ? 170 : 0,
|
|
||||||
top: 150,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// --- واجهة البحث (تصميم زجاجي) ---
|
|
||||||
_buildGlassSearchUI(controller),
|
|
||||||
|
|
||||||
// --- إرشادات الملاحة (تصميم عائم) ---
|
|
||||||
if (controller.currentInstruction.isNotEmpty)
|
|
||||||
_buildFloatingNavigationUI(controller),
|
|
||||||
|
|
||||||
// --- أزرار التحكم (تصميم عائم) ---
|
|
||||||
_buildFloatingMapControls(controller),
|
|
||||||
|
|
||||||
// --- مؤشر التحميل ---
|
|
||||||
if (controller.isLoading)
|
|
||||||
Container(
|
|
||||||
color: Colors.black.withOpacity(0.5),
|
|
||||||
child: const Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
||||||
strokeWidth: 3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --- 1. واجهة البحث بالتصميم الزجاجي المطور ---
|
|
||||||
Widget _buildGlassSearchUI(NavigationController controller) {
|
|
||||||
return Positioned(
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// --- شريط البحث ---
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(28.0),
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
|
||||||
child: Container(
|
|
||||||
height: 56,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.85),
|
|
||||||
borderRadius: BorderRadius.circular(28.0),
|
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.4)),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 15,
|
|
||||||
offset: const Offset(0, 5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(left: 18.0, right: 10.0),
|
|
||||||
child: Icon(Icons.search,
|
|
||||||
color: kPrimaryColor, size: 24),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: controller.placeDestinationController,
|
|
||||||
onChanged: controller.onSearchChanged,
|
|
||||||
textInputAction: TextInputAction.search,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16, color: Colors.black87),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'إلى أين تريد الذهاب؟',
|
|
||||||
hintStyle: const TextStyle(
|
|
||||||
color: Colors.black45, fontSize: 16),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: const EdgeInsets.only(bottom: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// زر المسح أو إلغاء المسار
|
|
||||||
if (controller
|
|
||||||
.placeDestinationController.text.isNotEmpty)
|
|
||||||
_buildClearButton(controller)
|
|
||||||
else if (controller.polylines.isNotEmpty)
|
|
||||||
_buildCancelRouteButton(controller),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
|
|
||||||
// --- قائمة النتائج ---
|
|
||||||
if (controller.placesDestination.isNotEmpty)
|
|
||||||
_buildSearchResultsList(controller),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildClearButton(NavigationController controller) {
|
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(Icons.clear, color: Colors.grey, size: 22),
|
|
||||||
onPressed: () {
|
|
||||||
controller.placeDestinationController.clear();
|
|
||||||
controller.placesDestination = [];
|
|
||||||
controller.update();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCancelRouteButton(NavigationController controller) {
|
|
||||||
return IconButton(
|
|
||||||
tooltip: 'إلغاء المسار',
|
|
||||||
icon: const Icon(Icons.close, color: Colors.redAccent, size: 22),
|
|
||||||
onPressed: () => controller.clearRoute(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSearchResultsList(NavigationController controller) {
|
|
||||||
return ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(24.0),
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(maxHeight: 220),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.85),
|
|
||||||
borderRadius: BorderRadius.circular(24.0),
|
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.4)),
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
itemCount: controller.placesDestination.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final place = controller.placesDestination[index];
|
|
||||||
final distance = place['distanceKm'] as double?;
|
|
||||||
final address = (place['address'] ?? '').toString();
|
|
||||||
|
|
||||||
return Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => controller.selectDestination(place),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0, vertical: 12.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// أيقونة الموقع
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: kPrimaryColor.withOpacity(0.1),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(Icons.location_on_outlined,
|
|
||||||
color: kPrimaryColor, size: 20),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
// الاسم والعنوان
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
place['name'] ?? 'اسم غير معروف',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.black87),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
if (address.isNotEmpty)
|
|
||||||
Text(
|
|
||||||
address,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black54, fontSize: 13),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
// المسافة
|
|
||||||
if (distance != null)
|
|
||||||
Text(
|
|
||||||
'${distance.toStringAsFixed(1)} كم',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: kPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --- 2. أزرار التحكم بالتصميم العائم ---
|
|
||||||
Widget _buildFloatingMapControls(NavigationController controller) {
|
|
||||||
return Positioned(
|
|
||||||
// اجعلها تطفو فوق لوحة الملاحة
|
|
||||||
bottom: controller.currentInstruction.isNotEmpty ? 190 : 24,
|
|
||||||
right: 16,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (controller.polylines.isNotEmpty) ...[
|
|
||||||
FloatingActionButton(
|
|
||||||
heroTag: 'rerouteBtn',
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 6,
|
|
||||||
onPressed: () => controller.recalculateRoute(),
|
|
||||||
tooltip: 'إعادة حساب المسار',
|
|
||||||
child: const Icon(Icons.sync_alt, color: kPrimaryColor, size: 24),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
],
|
|
||||||
FloatingActionButton(
|
|
||||||
heroTag: 'gpsBtn',
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 6,
|
|
||||||
onPressed: () {
|
|
||||||
if (controller.myLocation != null) {
|
|
||||||
controller.animateCameraToPosition(
|
|
||||||
controller.myLocation!,
|
|
||||||
bearing: controller.heading,
|
|
||||||
zoom: 18.5,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.gps_fixed, color: Colors.black54, size: 24),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --- 3. واجهة الملاحة بالتصميم العائم المطور ---
|
|
||||||
Widget _buildFloatingNavigationUI(NavigationController controller) {
|
|
||||||
return Positioned(
|
|
||||||
bottom: 16,
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF1E88E5), Color(0xFF0D47A1)], // أزرق متدرج
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
blurRadius: 25,
|
|
||||||
offset: const Offset(0, 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(22, 20, 22, 22),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
// --- الصف العلوي: الإرشاد والمسافة ---
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// الأيقونة
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(Icons.navigation_rounded,
|
|
||||||
color: Colors.white, size: 28),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
// الإرشاد
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
controller.currentInstruction,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.3,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
// المسافة
|
|
||||||
Text(
|
|
||||||
controller.distanceToNextStep,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// --- فاصل ---
|
|
||||||
if (controller.nextInstruction.isNotEmpty ||
|
|
||||||
controller.currentSpeed > 0)
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 14.0),
|
|
||||||
child: Divider(color: Colors.white30, height: 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// --- الصف السفلي: الإرشاد التالي والسرعة ---
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// الإرشاد التالي
|
|
||||||
Expanded(
|
|
||||||
child: controller.nextInstruction.isNotEmpty
|
|
||||||
? Text(
|
|
||||||
'التالي: ${controller.nextInstruction}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white70,
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
)
|
|
||||||
: const SizedBox(), // يترك مساحة فارغة إذا لم يكن هناك إرشاد تالي
|
|
||||||
),
|
|
||||||
|
|
||||||
// السرعة
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
controller.currentSpeed.toStringAsFixed(0),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
const Text(
|
|
||||||
'كم/س',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white70,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
// lib/controllers/navigation/route_matcher_worker.dart
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:isolate';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
/// Worker entrypoint (spawnUri/spawn).
|
|
||||||
/// Messages:
|
|
||||||
/// - init: {'type':'init','coords': Float64List}
|
|
||||||
/// - match: {'type':'match','id': int, 'lat': double, 'lng': double, 'lastIndex': int, 'window': int}
|
|
||||||
/// - dispose: {'type':'dispose'}
|
|
||||||
///
|
|
||||||
/// Responses are sent back as Map via SendPort:
|
|
||||||
/// - {'type':'ready'}
|
|
||||||
/// - {'type':'matchResult','id': id, 'index': overallIndex, 'lat': lat, 'lng': lng, 'dist': meters}
|
|
||||||
void routeMatcherIsolateEntry(SendPort sendPort) {
|
|
||||||
final ReceivePort port = ReceivePort();
|
|
||||||
sendPort.send({'type': 'ready', 'port': port.sendPort});
|
|
||||||
|
|
||||||
Float64List? flat; // [lat,lng,lat,lng,...]
|
|
||||||
int nPoints = 0;
|
|
||||||
|
|
||||||
port.listen((dynamic message) {
|
|
||||||
try {
|
|
||||||
if (message is Map<String, dynamic>) {
|
|
||||||
final type = message['type'] as String? ?? '';
|
|
||||||
if (type == 'init') {
|
|
||||||
final data = message['coords'] as Float64List?;
|
|
||||||
if (data != null) {
|
|
||||||
flat = data;
|
|
||||||
nPoints = flat!.length ~/ 2;
|
|
||||||
sendPort.send({'type': 'inited', 'points': nPoints});
|
|
||||||
} else {
|
|
||||||
sendPort.send({'type': 'error', 'message': 'init missing coords'});
|
|
||||||
}
|
|
||||||
} else if (type == 'match') {
|
|
||||||
if (flat == null) {
|
|
||||||
sendPort.send({'type': 'error', 'message': 'not inited'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int id = message['id'] as int;
|
|
||||||
final double lat = (message['lat'] as num).toDouble();
|
|
||||||
final double lng = (message['lng'] as num).toDouble();
|
|
||||||
final int lastIndex = (message['lastIndex'] as int?) ?? 0;
|
|
||||||
final int window = (message['window'] as int?) ?? 120;
|
|
||||||
|
|
||||||
final result =
|
|
||||||
_findClosestWindowInternal(flat!, lat, lng, lastIndex, window);
|
|
||||||
sendPort.send({
|
|
||||||
'type': 'matchResult',
|
|
||||||
'id': id,
|
|
||||||
'index': result['index'],
|
|
||||||
'lat': result['lat'],
|
|
||||||
'lng': result['lng'],
|
|
||||||
'dist': result['dist']
|
|
||||||
});
|
|
||||||
} else if (type == 'dispose') {
|
|
||||||
port.close();
|
|
||||||
sendPort.send({'type': 'disposed'});
|
|
||||||
} else {
|
|
||||||
sendPort.send({'type': 'error', 'message': 'unknown message type'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, st) {
|
|
||||||
sendPort.send(
|
|
||||||
{'type': 'error', 'message': e.toString(), 'stack': st.toString()});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal helper: projection on segments, windowed search.
|
|
||||||
/// Returns Map {index, lat, lng, dist}
|
|
||||||
Map<String, dynamic> _findClosestWindowInternal(
|
|
||||||
Float64List flat, double lat, double lng, int lastIndex, int window) {
|
|
||||||
final int n = flat.length ~/ 2;
|
|
||||||
final int start = max(0, lastIndex - window);
|
|
||||||
final int end = min(n - 1, lastIndex + window);
|
|
||||||
|
|
||||||
double minDist = double.infinity;
|
|
||||||
int bestIdx = lastIndex;
|
|
||||||
double bestLat = flat[lastIndex * 2];
|
|
||||||
double bestLng = flat[lastIndex * 2 + 1];
|
|
||||||
|
|
||||||
for (int i = start; i < end; i++) {
|
|
||||||
final double aLat = flat[i * 2];
|
|
||||||
final double aLng = flat[i * 2 + 1];
|
|
||||||
final double bLat = flat[(i + 1) * 2];
|
|
||||||
final double bLng = flat[(i + 1) * 2 + 1];
|
|
||||||
|
|
||||||
final proj = _closestPointOnSegmentLatLng(lat, lng, aLat, aLng, bLat, bLng);
|
|
||||||
final double d = proj['dist'] as double;
|
|
||||||
if (d < minDist) {
|
|
||||||
minDist = d;
|
|
||||||
bestLat = proj['lat'] as double;
|
|
||||||
bestLng = proj['lng'] as double;
|
|
||||||
// choose overall index: i or i+1 depending on t
|
|
||||||
final double t = proj['t'] as double;
|
|
||||||
bestIdx = i + (t > 0.5 ? 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {'index': bestIdx, 'lat': bestLat, 'lng': bestLng, 'dist': minDist};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Projection math on geodetic points approximated in degrees (good for short distances).
|
|
||||||
Map<String, dynamic> _closestPointOnSegmentLatLng(
|
|
||||||
double px, double py, double ax, double ay, double bx, double by) {
|
|
||||||
// Here px=lat, py=lng; ax=lat, ay=lng, etc.
|
|
||||||
final double x0 = px;
|
|
||||||
final double y0 = py;
|
|
||||||
final double x1 = ax;
|
|
||||||
final double y1 = ay;
|
|
||||||
final double x2 = bx;
|
|
||||||
final double y2 = by;
|
|
||||||
|
|
||||||
final double dx = x2 - x1;
|
|
||||||
final double dy = y2 - y1;
|
|
||||||
double t = 0.0;
|
|
||||||
final double len2 = dx * dx + dy * dy;
|
|
||||||
if (len2 > 0) {
|
|
||||||
t = ((x0 - x1) * dx + (y0 - y1) * dy) / len2;
|
|
||||||
if (t < 0) t = 0;
|
|
||||||
if (t > 1) t = 1;
|
|
||||||
}
|
|
||||||
final double projX = x1 + t * dx;
|
|
||||||
final double projY = y1 + t * dy;
|
|
||||||
|
|
||||||
final double distMeters = _haversineDistanceMeters(x0, y0, projX, projY);
|
|
||||||
return {'lat': projX, 'lng': projY, 't': t, 'dist': distMeters};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Haversine distance (meters)
|
|
||||||
double _haversineDistanceMeters(
|
|
||||||
double lat1, double lng1, double lat2, double lng2) {
|
|
||||||
final double R = 6371000.0;
|
|
||||||
final double dLat = _deg2rad(lat2 - lat1);
|
|
||||||
final double dLon = _deg2rad(lng2 - lng1);
|
|
||||||
final double a = sin(dLat / 2) * sin(dLat / 2) +
|
|
||||||
cos(_deg2rad(lat1)) * cos(_deg2rad(lat2)) * sin(dLon / 2) * sin(dLon / 2);
|
|
||||||
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
|
||||||
return R * c;
|
|
||||||
}
|
|
||||||
|
|
||||||
double _deg2rad(double deg) => deg * pi / 180.0;
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:local_auth/local_auth.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
|
|
||||||
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
|
|
||||||
import '../../../views/widgets/mydialoug.dart';
|
|
||||||
import '../../firebase/notification_service.dart';
|
|
||||||
|
|
||||||
class CaptainWalletController extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
final formKeyTransfer = GlobalKey<FormState>();
|
|
||||||
final formKeyAccount = GlobalKey<FormState>();
|
|
||||||
Map walletDate = {};
|
|
||||||
Map walletDateVisa = {};
|
|
||||||
Map walletDriverPointsDate = {};
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
String totalAmount = '0';
|
|
||||||
double kazan = 0;
|
|
||||||
String totalAmountVisa = '0';
|
|
||||||
String totalPoints = '0';
|
|
||||||
final amountFromBudgetController = TextEditingController();
|
|
||||||
final newDriverPhoneController = TextEditingController();
|
|
||||||
final phoneWallet = TextEditingController();
|
|
||||||
final cardBank = TextEditingController();
|
|
||||||
final bankCode = TextEditingController();
|
|
||||||
|
|
||||||
payFromBudget() async {
|
|
||||||
if (formKey.currentState!.validate()) {
|
|
||||||
var pointFromBudget = int.parse((amountFromBudgetController.text));
|
|
||||||
|
|
||||||
// await getPaymentId('fromBudgetToPoints',
|
|
||||||
// int.parse((amountFromBudgetController.text)) * -1);
|
|
||||||
var paymentToken3 =
|
|
||||||
await generateToken((pointFromBudget * -1).toString());
|
|
||||||
var paymentID = await getPaymentId(
|
|
||||||
'fromBudgetToPoints', (pointFromBudget * -1).toString());
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'amount': (pointFromBudget * -1).toString(),
|
|
||||||
'rideId': paymentID.toString(),
|
|
||||||
'payment_method': 'myBudget',
|
|
||||||
'passengerID': 'myBudgetToPoint',
|
|
||||||
'token': paymentToken3,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
Future.delayed(const Duration(seconds: 1));
|
|
||||||
await addDriverWallet(
|
|
||||||
'fromBudget', pointFromBudget.toString(), pointFromBudget.toString());
|
|
||||||
update();
|
|
||||||
Get.back();
|
|
||||||
await refreshCaptainWallet();
|
|
||||||
NotificationController().showNotification(
|
|
||||||
'You have successfully charged your account'.tr,
|
|
||||||
'$pointFromBudget ${'has been added to your budget'.tr}',
|
|
||||||
'tone1',
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// متغيرات الحالة (تأكد من تعريفها في الـ Controller)
|
|
||||||
bool isError = false;
|
|
||||||
int morningCount = 0;
|
|
||||||
int afternoonCount = 0;
|
|
||||||
String lastWeekIncome = '0';
|
|
||||||
String bankAccount = '';
|
|
||||||
Future<void> refreshCaptainWallet() async {
|
|
||||||
// 1. تفعيل حالة التحميل مرة واحدة فقط
|
|
||||||
isLoading = true;
|
|
||||||
isError = false;
|
|
||||||
update(); // تحديث الواجهة لإظهار اللودر
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 2. طلب واحد فقط للسيرفر الموحد (The Aggregated Endpoint)
|
|
||||||
var response = await CRUD().getWallet(
|
|
||||||
link:
|
|
||||||
"${AppLink.paymentServer}/ride/driverWallet/get_wallet_summary.php",
|
|
||||||
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
|
||||||
);
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
|
|
||||||
if (response != 'failure') {
|
|
||||||
var body = jsonDecode(response);
|
|
||||||
if (body['status'] == "success") {
|
|
||||||
var data = body['message'];
|
|
||||||
walletDate = data;
|
|
||||||
// تفكيك البيانات من الرد الموحد
|
|
||||||
// التعامل مع القيم كـ String أو num لتجنب مشاكل الـ Parsing
|
|
||||||
totalAmount = (data['cash_balance'] ?? 0).toString();
|
|
||||||
|
|
||||||
// الفيزا + التحويلات مدموجة وجاهزة من السيرفر
|
|
||||||
totalAmountVisa = (data['wallet_balance'] ?? 0).toString();
|
|
||||||
|
|
||||||
// النقاط
|
|
||||||
double p = double.tryParse(data['points_balance'].toString()) ?? 0.0;
|
|
||||||
totalPoints = p.toStringAsFixed(0);
|
|
||||||
|
|
||||||
// بيانات الرحلات (تأتي من RID Server)
|
|
||||||
morningCount = int.tryParse(data['morning_count'].toString()) ?? 0;
|
|
||||||
afternoonCount =
|
|
||||||
int.tryParse(data['afternoon_count'].toString()) ?? 0;
|
|
||||||
lastWeekIncome = (data['weekly_income'] ?? 0).toString();
|
|
||||||
|
|
||||||
bankAccount = data['bank_account'] ?? "";
|
|
||||||
|
|
||||||
// حفظ بيانات البنك لوكالي لاستخدامها لاحقاً دون طلب
|
|
||||||
if (bankAccount.isNotEmpty) {
|
|
||||||
box.write(BoxName.accountIdStripeConnect, bankAccount);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isError = true;
|
|
||||||
mySnackeBarError("Failed to fetch wallet data");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
isLoading = false;
|
|
||||||
isError = true;
|
|
||||||
print("Wallet Refresh Error: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. تحديث نهائي واحد للواجهة
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
List amountToNewDriverMap = [];
|
|
||||||
bool isNewTransfer = false;
|
|
||||||
Future detectNewDriverFromMyBudget() async {
|
|
||||||
if (formKeyTransfer.currentState!.validate()) {
|
|
||||||
if (int.parse(amountFromBudgetController.text) <
|
|
||||||
double.parse(totalAmountVisa) &&
|
|
||||||
int.parse(amountFromBudgetController.text) > 10) {
|
|
||||||
//get new driver details
|
|
||||||
isNewTransfer = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().get(
|
|
||||||
link: AppLink.getDriverDetails,
|
|
||||||
payload: {'driver_phone': '+2${newDriverPhoneController.text}'});
|
|
||||||
isNewTransfer = false;
|
|
||||||
update();
|
|
||||||
if (res != 'failure') {
|
|
||||||
var d = jsonDecode(res);
|
|
||||||
amountToNewDriverMap = d['data'];
|
|
||||||
// update();
|
|
||||||
} else {
|
|
||||||
mySnackeBarError("This driver is not registered".tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mySnackeBarError('Your Budget less than needed'.tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String paymentToken = '';
|
|
||||||
Future<String> generateToken(String amount) async {
|
|
||||||
var res =
|
|
||||||
await CRUD().postWallet(link: AppLink.addPaymentTokenDriver, payload: {
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
'amount': amount.toString(),
|
|
||||||
});
|
|
||||||
var d = (res);
|
|
||||||
return d['message'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// late String paymentID;
|
|
||||||
Future<String> getPaymentId(String paymentMethod, amount) async {
|
|
||||||
// paymentToken = await generateToken(amount);
|
|
||||||
var res =
|
|
||||||
await CRUD().postWallet(link: AppLink.addDriverPaymentPoints, payload: {
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
'amount': amount.toString(),
|
|
||||||
'payment_method': paymentMethod.toString(),
|
|
||||||
});
|
|
||||||
var d = (res);
|
|
||||||
// paymentID = d['message'].toString();
|
|
||||||
return d['message'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addDriverWallet(String paymentMethod, point, count) async {
|
|
||||||
paymentToken = await generateToken(count);
|
|
||||||
var paymentID = await getPaymentId(paymentMethod, point.toString());
|
|
||||||
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
'paymentID': paymentID.toString(),
|
|
||||||
'amount': point,
|
|
||||||
'token': paymentToken,
|
|
||||||
'paymentMethod': paymentMethod.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addDriverPayment(String paymentMethod, point, wayPay) async {
|
|
||||||
paymentToken = await generateToken(point);
|
|
||||||
var paymentID = await getPaymentId(paymentMethod, point.toString());
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'amount': point.toString(),
|
|
||||||
'rideId': paymentID.toString(),
|
|
||||||
'payment_method': paymentMethod,
|
|
||||||
'passengerID': wayPay,
|
|
||||||
'token': paymentToken,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addDriverWalletFromPromo(String paymentMethod, point) async {
|
|
||||||
var resPromotion =
|
|
||||||
await CRUD().postWallet(link: AppLink.addpromotionDriver, payload: {
|
|
||||||
'driver_id': box.read(BoxName.driverID).toString(),
|
|
||||||
'payment_amount': point,
|
|
||||||
'timePromo': paymentMethod,
|
|
||||||
});
|
|
||||||
if (resPromotion != 'failure') {
|
|
||||||
paymentToken = await generateToken(point);
|
|
||||||
var paymentID = await getPaymentId(paymentMethod, point.toString());
|
|
||||||
var res =
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'amount': point,
|
|
||||||
'rideId': paymentID.toString(),
|
|
||||||
'payment_method': paymentMethod.toString(),
|
|
||||||
'passengerID': paymentMethod,
|
|
||||||
'token': paymentToken,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
if (res != 'failure') {
|
|
||||||
String title = 'wallet_updated'.tr; // Notification title
|
|
||||||
String message = '${'wallet_credited_message'.tr} $point';
|
|
||||||
String tone = 'default_tone'.tr; // Notification tone or sound
|
|
||||||
String payLoad =
|
|
||||||
'wallet_updated'; // Additional data payload for the notification
|
|
||||||
|
|
||||||
Get.find<NotificationController>()
|
|
||||||
.showNotification(title, message, tone, payLoad);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Get.back();
|
|
||||||
mySnackeBarError(
|
|
||||||
"A promotion record for this driver already exists for today.".tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addDriverWalletToInvitor(String paymentMethod, driverID, point) async {
|
|
||||||
paymentToken = await generateToken(point);
|
|
||||||
var paymentID = await getPaymentId(paymentMethod, point.toString());
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'driverID': driverID,
|
|
||||||
'amount': point,
|
|
||||||
'token': paymentToken,
|
|
||||||
'rideId': paymentID.toString(),
|
|
||||||
'payment_method': paymentMethod.toString(),
|
|
||||||
'passengerID': paymentMethod,
|
|
||||||
});
|
|
||||||
await addTripzWallet(paymentMethod,
|
|
||||||
(double.parse(point) * -2).toString()); // deduct 2 from Tripz wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addTripzWallet(String paymentMethod, String point) async {
|
|
||||||
var TripzToken = await generateToken(point.toString());
|
|
||||||
await CRUD().postWallet(link: AppLink.addTripzWallet, payload: {
|
|
||||||
'amount': point.toString(),
|
|
||||||
'paymentMethod': paymentMethod,
|
|
||||||
'passengerId': 'driver',
|
|
||||||
'token': TripzToken,
|
|
||||||
'driverId': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addTransferDriversWallet(String paymentMethod1, paymentMethod2) async {
|
|
||||||
var paymentID =
|
|
||||||
await getPaymentId(paymentMethod1, amountFromBudgetController.text);
|
|
||||||
paymentToken = await generateToken(
|
|
||||||
(int.parse(amountFromBudgetController.text) * -1).toString());
|
|
||||||
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'amount': (int.parse(amountFromBudgetController.text) * -1).toString(),
|
|
||||||
'rideId': paymentID.toString(),
|
|
||||||
'payment_method': paymentMethod1,
|
|
||||||
'passengerID': 'To ${amountToNewDriverMap[0]['id']}',
|
|
||||||
'token': paymentToken,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
paymentID = await getPaymentId(paymentMethod2,
|
|
||||||
(int.parse(amountFromBudgetController.text) - 5).toString());
|
|
||||||
paymentToken = await generateToken(amountFromBudgetController.text);
|
|
||||||
var res1 =
|
|
||||||
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
|
|
||||||
'driverID': amountToNewDriverMap[0]['id'].toString(),
|
|
||||||
'paymentID': paymentID.toString(),
|
|
||||||
'amount': ((int.parse(amountFromBudgetController.text) - 5))
|
|
||||||
// kazan) // double.parse(kazan) .08 for egypt
|
|
||||||
.toStringAsFixed(
|
|
||||||
0), // this will convert buddget to poitns by kazan .08
|
|
||||||
|
|
||||||
'token': paymentToken,
|
|
||||||
'paymentMethod': paymentMethod2.toString(),
|
|
||||||
});
|
|
||||||
if (res1 != 'failure') {
|
|
||||||
NotificationService.sendNotification(
|
|
||||||
target: amountToNewDriverMap[0]['token'].toString(),
|
|
||||||
title: 'Transfer'.tr,
|
|
||||||
body: '${'You have transfer to your wallet from'.tr}'
|
|
||||||
'${box.read(BoxName.nameDriver)}',
|
|
||||||
|
|
||||||
isTopic: false, // Important: this is a token
|
|
||||||
tone: 'ding',
|
|
||||||
driverList: [], category: 'Transfer',
|
|
||||||
);
|
|
||||||
await addTripzWallet('payout fee', '5');
|
|
||||||
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'transfer Successful'.tr,
|
|
||||||
middleText: '',
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
title: 'Ok'.tr,
|
|
||||||
onPressed: () async {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
|
|
||||||
await refreshCaptainWallet();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if account bank is created or not
|
|
||||||
Future checkAccountCaptainBank() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
if (box.read(BoxName.accountIdStripeConnect).toString().isEmpty) {
|
|
||||||
var res = await CRUD().get(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 = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() async {
|
|
||||||
await refreshCaptainWallet();
|
|
||||||
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,235 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:local_auth/local_auth.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/controller/payment/smsPaymnet/payment_services.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
import '../../../constant/links.dart';
|
|
||||||
import '../../../views/widgets/mydialoug.dart';
|
|
||||||
import '../../functions/crud.dart';
|
|
||||||
import 'captain_wallet_controller.dart';
|
|
||||||
|
|
||||||
class PaymobPayout extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
String dropdownValue = 'etisalat';
|
|
||||||
|
|
||||||
int payOutFee = 5;
|
|
||||||
payToDriverWallet(String amount, String issuer, String msisdn) async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
if (isAvailable) {
|
|
||||||
// Authenticate the user
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
var dec = await CRUD()
|
|
||||||
.postWallet(link: AppLink.paymobPayoutDriverWallet, payload: {
|
|
||||||
"issuer": issuer,
|
|
||||||
"method": "wallet",
|
|
||||||
"amount": amount, //9.0,
|
|
||||||
"full_name":
|
|
||||||
'${box.read(BoxName.nameDriver)} ${box.read(BoxName.lastNameDriver)}',
|
|
||||||
"msisdn": msisdn, //"01010101010",
|
|
||||||
"bank_transaction_type": "cash_transfer"
|
|
||||||
});
|
|
||||||
if (dec['disbursement_status'] == 'successful') {
|
|
||||||
var paymentToken = await Get.find<CaptainWalletController>()
|
|
||||||
.generateToken(
|
|
||||||
((-1) * (double.parse(dec['amount'].toString())) - payOutFee)
|
|
||||||
.toStringAsFixed(0));
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'rideId': DateTime.now().toIso8601String(),
|
|
||||||
'amount':
|
|
||||||
((-1) * (double.parse(dec['amount'].toString())) - payOutFee)
|
|
||||||
.toStringAsFixed(0),
|
|
||||||
'payment_method': 'payout',
|
|
||||||
'passengerID': 'myself',
|
|
||||||
'token': paymentToken,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
await Get.find<CaptainWalletController>()
|
|
||||||
.addTripzWallet('payout fee myself', payOutFee.toString());
|
|
||||||
await updatePaymentToPaid(box.read(BoxName.driverID).toString());
|
|
||||||
await sendEmail(
|
|
||||||
box.read(BoxName.driverID).toString(),
|
|
||||||
amount,
|
|
||||||
box.read(BoxName.phoneDriver).toString(),
|
|
||||||
box.read(BoxName.nameDriver).toString(),
|
|
||||||
'Wallet',
|
|
||||||
box.read(BoxName.emailDriver).toString());
|
|
||||||
|
|
||||||
mySnackbarSuccess('${'Transaction successful'.tr} ${dec['amount']}');
|
|
||||||
|
|
||||||
Get.find<CaptainWalletController>().refreshCaptainWallet();
|
|
||||||
} else if (dec['disbursement_status'] == 'failed') {
|
|
||||||
mySnackeBarError('Transaction failed'.tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future updatePaymentToPaid(String driverID) async {
|
|
||||||
await CRUD().postWallet(link: AppLink.updatePaymetToPaid, payload: {
|
|
||||||
'driverID': driverID.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendEmail(
|
|
||||||
String driverId, amount, phone, name, bankCardNumber, email) async {
|
|
||||||
await CRUD().sendEmail(AppLink.sendEmailToDrivertransaction, {
|
|
||||||
"driverID": driverId,
|
|
||||||
"total_amount": amount,
|
|
||||||
"phone": phone,
|
|
||||||
"name_arabic": name,
|
|
||||||
"accountBank": bankCardNumber,
|
|
||||||
"email": email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getAIKey(String key) async {
|
|
||||||
var res =
|
|
||||||
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key});
|
|
||||||
if (res != 'failure') {
|
|
||||||
var d = jsonDecode(res)['message'];
|
|
||||||
return d[key].toString();
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
payToDriverBankAccount(
|
|
||||||
String amount, String bankCardNumber, String bankCode) async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
if (isAvailable) {
|
|
||||||
// Authenticate the user
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
var body = {
|
|
||||||
"issuer": "bank_card",
|
|
||||||
"method": "bank_card",
|
|
||||||
"amount": amount, //9.0,
|
|
||||||
"full_name":
|
|
||||||
'${box.read(BoxName.nameDriver)} ${box.read(BoxName.lastNameDriver)}',
|
|
||||||
"bank_card_number": bankCardNumber, //"1111-2222-3333-4444",
|
|
||||||
"bank_code": bankCode, //"CIB",
|
|
||||||
"bank_transaction_type": "cash_transfer"
|
|
||||||
};
|
|
||||||
var dec = await CRUD().postWallet(
|
|
||||||
link:
|
|
||||||
'wl.tripz-egypt.com/v1/main/ride/payMob/paymob_driver/paymob_payout.php',
|
|
||||||
payload: body,
|
|
||||||
);
|
|
||||||
if (dec['disbursement_status'] == 'successful') {
|
|
||||||
var paymentToken = await Get.find<CaptainWalletController>()
|
|
||||||
.generateToken(
|
|
||||||
((-1) * (double.parse(dec['amount'].toString())) - payOutFee)
|
|
||||||
.toStringAsFixed(0));
|
|
||||||
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
|
|
||||||
'rideId': DateTime.now().toIso8601String(),
|
|
||||||
'amount':
|
|
||||||
((-1) * (double.parse(dec['amount'].toString())) - payOutFee)
|
|
||||||
.toStringAsFixed(0),
|
|
||||||
'payment_method': 'payout',
|
|
||||||
'passengerID': 'myself',
|
|
||||||
'token': paymentToken,
|
|
||||||
'driverID': box.read(BoxName.driverID).toString(),
|
|
||||||
});
|
|
||||||
await Get.find<CaptainWalletController>()
|
|
||||||
.addTripzWallet('payout fee myself', payOutFee.toString());
|
|
||||||
await updatePaymentToPaid(box.read(BoxName.driverID).toString());
|
|
||||||
await sendEmail(
|
|
||||||
box.read(BoxName.driverID).toString(),
|
|
||||||
amount,
|
|
||||||
box.read(BoxName.phoneDriver).toString(),
|
|
||||||
box.read(BoxName.nameDriver).toString(),
|
|
||||||
'Wallet',
|
|
||||||
box.read(BoxName.emailDriver).toString());
|
|
||||||
|
|
||||||
mySnackbarSuccess('${'Transaction successful'.tr} ${dec['amount']}');
|
|
||||||
Get.find<CaptainWalletController>().refreshCaptainWallet();
|
|
||||||
} else if (dec['disbursement_status'] == 'failed') {
|
|
||||||
mySnackeBarError('Transaction failed'.tr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future payToWalletDriverAll(
|
|
||||||
String amount, String issuer, String msisdn) async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
if (isAvailable) {
|
|
||||||
// Authenticate the user
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
await payToDriverWallet(amount, issuer, msisdn);
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future payToBankDriverAll(
|
|
||||||
String amount, String bankCardNumber, String bankCode) async {
|
|
||||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
|
||||||
if (isAvailable) {
|
|
||||||
// Authenticate the user
|
|
||||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
|
||||||
localizedReason: 'Use Touch ID or Face ID to confirm payment',
|
|
||||||
options: AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
sensitiveTransaction: true,
|
|
||||||
));
|
|
||||||
if (didAuthenticate) {
|
|
||||||
await payToDriverBankAccount(amount, bankCardNumber, bankCode);
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MyDialog().getDialog('Biometric Authentication'.tr,
|
|
||||||
'You should use Touch ID or Face ID to confirm payment'.tr, () {
|
|
||||||
Get.back();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.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<LocationController>().myLocation;
|
|
||||||
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,44 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/colors.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/constant/style.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/main.dart';
|
|
||||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
||||||
|
|
||||||
class FeedBackController extends GetxController {
|
|
||||||
bool isLoading = false;
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final feedbackController = TextEditingController();
|
|
||||||
|
|
||||||
void addFeedBack() async {
|
|
||||||
isLoading = true;
|
|
||||||
update();
|
|
||||||
var res = await CRUD().post(link: AppLink.addFeedBack, payload: {
|
|
||||||
'passengerId': box.read(BoxName.passengerID).toString(),
|
|
||||||
'feedBack': feedbackController.text
|
|
||||||
});
|
|
||||||
var d = jsonDecode(res);
|
|
||||||
if (d['status'].toString() == 'success') {
|
|
||||||
Get.defaultDialog(
|
|
||||||
title: 'Success'.tr,
|
|
||||||
titleStyle: AppStyle.title,
|
|
||||||
middleText: 'Feedback data saved successfully'.tr,
|
|
||||||
middleTextStyle: AppStyle.title,
|
|
||||||
confirm: MyElevatedButton(
|
|
||||||
kolor: AppColor.greenColor,
|
|
||||||
title: 'Ok'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
Get.back();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:sefer_driver/constant/box_name.dart';
|
|
||||||
import 'package:sefer_driver/constant/links.dart';
|
|
||||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
|
||||||
import 'package:sefer_driver/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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user