Initial commit for driver_tripz
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
arguments=
|
||||
auto.sync=false
|
||||
arguments=--init-script /var/folders/cn/w9j2wl8d11517_kjx3xk_vc00000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/cn/w9j2wl8d11517_kjx3xk_vc00000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
|
||||
auto.sync=true
|
||||
build.scans.enabled=false
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=
|
||||
java.home=/Users/hamzaaleghwairyeen/Library/Java/JavaVirtualMachines/openjdk-22.0.2/Contents/Home
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=false
|
||||
show.console.view=false
|
||||
show.executions.view=false
|
||||
override.workspace.settings=true
|
||||
show.console.view=true
|
||||
show.executions.view=true
|
||||
|
||||
@@ -25,24 +25,26 @@ def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '
|
||||
|
||||
android {
|
||||
namespace "com.sefer_driver"
|
||||
compileSdk 35
|
||||
compileSdk = 36
|
||||
ndkVersion "29.0.14033849"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
version "3.31.5"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.sefer_driver"
|
||||
minSdk 23
|
||||
targetSdk 35
|
||||
versionCode 149
|
||||
versionName '2.0.149'
|
||||
minSdk 29
|
||||
targetSdk 36
|
||||
versionCode 151
|
||||
versionName '2.0.151'
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +89,11 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
implementation "com.stripe:stripe-android:21.4.2"
|
||||
implementation 'com.stripe:paymentsheet:21.4.2'
|
||||
implementation 'com.scottyab:rootbeer-lib:0.1.0'
|
||||
implementation 'com.stripe:paymentsheet:20.52.2'
|
||||
implementation 'com.google.android.gms:play-services-safetynet:18.0.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||
implementation 'com.google.android.gms:play-services-safetynet:18.1.0'
|
||||
}
|
||||
@@ -33,9 +33,12 @@
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:label="Tripz Driver"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:theme="@style/LaunchTheme">
|
||||
|
||||
<activity
|
||||
|
||||
24
android/app/src/main/res/xml/network_security_config.xml
Normal file
24
android/app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- منع أي اتصال HTTP غير مشفّر -->
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<!-- الاعتماد على شهادات النظام فقط -->
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<!-- اسمح بالاتصال بدوميناتك فقط -->
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">tripz-egypt.com</domain>
|
||||
|
||||
</domain-config>
|
||||
|
||||
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
|
||||
<debug-overrides>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
</network-security-config>
|
||||
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -54,11 +55,13 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@@ -358,7 +358,10 @@ class LoginDriverController extends GetxController {
|
||||
' (jsonDecode(token): ${(jsonDecode(token)['data'][0]['token']).toString()}');
|
||||
if ((jsonDecode(token)['data'][0]['token']) !=
|
||||
(box.read(BoxName.tokenDriver))) {
|
||||
Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP(
|
||||
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||
? Get.find<FirebaseMessagesController>()
|
||||
: Get.put(FirebaseMessagesController());
|
||||
fcm.sendNotificationToDriverMAP(
|
||||
'token change',
|
||||
'change device'.tr,
|
||||
(jsonDecode(token)['data'][0]['token']).toString(),
|
||||
@@ -367,6 +370,7 @@ class LoginDriverController extends GetxController {
|
||||
await Get.defaultDialog(
|
||||
title: 'you will use this device?'.tr,
|
||||
middleText: '',
|
||||
barrierDismissible: false,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok'.tr,
|
||||
onPressed: () async {
|
||||
|
||||
@@ -14,9 +14,57 @@ import '../../constant/char_map.dart';
|
||||
import '../../constant/info.dart';
|
||||
import '../../print.dart';
|
||||
import 'gemeni.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,
|
||||
@@ -43,9 +91,9 @@ class CRUD {
|
||||
'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);
|
||||
print(response.request);
|
||||
Log.print('response.body: ${response.body}');
|
||||
print(payload);
|
||||
if (response.statusCode == 200) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'success') {
|
||||
@@ -94,9 +142,9 @@ class CRUD {
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
},
|
||||
);
|
||||
// Log.print('response.request: ${response.request}');
|
||||
// Log.print('response.body: ${response.body}');
|
||||
// print(payload);
|
||||
Log.print('response.request: ${response.request}');
|
||||
Log.print('response.body: ${response.body}');
|
||||
print(payload);
|
||||
if (response.statusCode == 200) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'success') {
|
||||
@@ -145,9 +193,9 @@ class CRUD {
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
},
|
||||
);
|
||||
// Log.print('response.request:${response.request}');
|
||||
// Log.print('response.body: ${response.body}');
|
||||
// Log.print('payload:$payload');
|
||||
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);
|
||||
@@ -203,9 +251,9 @@ class CRUD {
|
||||
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
|
||||
},
|
||||
);
|
||||
// print(response.request);
|
||||
// Log.print('response.body: ${response.body}');
|
||||
// print(payload);
|
||||
print(response.request);
|
||||
Log.print('response.body: ${response.body}');
|
||||
print(payload);
|
||||
if (response.statusCode == 200) {
|
||||
try {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
|
||||
48
lib/controller/functions/network/connection_check.dart
Normal file
48
lib/controller/functions/network/connection_check.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
lib/controller/functions/network/net_guard.dart
Normal file
48
lib/controller/functions/network/net_guard.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ class HomeCaptainController extends GetxController {
|
||||
// isLoading = true;
|
||||
update();
|
||||
|
||||
var res = await CRUD().get(
|
||||
var res = await CRUD().getWallet(
|
||||
link: AppLink.getDriverPaymentPoints,
|
||||
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
||||
);
|
||||
@@ -331,20 +331,6 @@ class HomeCaptainController extends GetxController {
|
||||
'fingerPrint': (fingerPrint).toString()
|
||||
});
|
||||
|
||||
// CRUD().post(
|
||||
// link: "${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php",
|
||||
// payload: {
|
||||
// 'token': box.read(BoxName.tokenDriver),
|
||||
// 'captain_id': box.read(BoxName.driverID).toString(),
|
||||
// 'fingerPrint': (fingerPrint).toString()
|
||||
// });
|
||||
// CRUD().post(
|
||||
// link: "${AppLink.seferGizaServer}/ride/firebase/addDriver.php",
|
||||
// payload: {
|
||||
// 'token': box.read(BoxName.tokenDriver),
|
||||
// 'captain_id': box.read(BoxName.driverID).toString(),
|
||||
// 'fingerPrint': (fingerPrint).toString()
|
||||
// });
|
||||
await CRUD().postWallet(
|
||||
link: "${AppLink.seferPaymentServer}/ride/firebase/addDriver.php",
|
||||
payload: {
|
||||
@@ -357,7 +343,7 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
getPaymentToday() async {
|
||||
var res = await CRUD().get(
|
||||
var res = await CRUD().getWallet(
|
||||
link: AppLink.getDriverPaymentToday,
|
||||
payload: {'driverID': box.read(BoxName.driverID).toString()});
|
||||
if (res != 'failure') {
|
||||
@@ -420,7 +406,7 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
getAllPayment() async {
|
||||
var res = await CRUD().get(
|
||||
var res = await CRUD().getWallet(
|
||||
link: AppLink.getAllPaymentFromRide,
|
||||
payload: {'driverID': box.read(BoxName.driverID).toString()});
|
||||
data = jsonDecode(res);
|
||||
|
||||
@@ -572,7 +572,64 @@ Raih Gai: For same-day return trips longer than 50km.
|
||||
"car_plate": "لوحة السيارة",
|
||||
"car_model": "طراز السيارة:",
|
||||
"education": "التعليم",
|
||||
"gender": "الجنس",
|
||||
"gender": "الجنس", "Driver Wallet": "محفظة السائق",
|
||||
"Fetching Wallet Data...": "جاري جلب بيانات المحفظة...",
|
||||
"Charge your Account": "اشحن حسابك",
|
||||
"Total Points": "إجمالي النقاط",
|
||||
"Info": "معلومات",
|
||||
"The 300 points equal 300 L.E for you \nSo go and gain your money":
|
||||
"٣٠٠ نقطة تساوي ٣٠٠ جنيه لك\nاذهب واحصل على أموالك",
|
||||
"OK": "حسناً",
|
||||
"Total Budget from trips is": "إجمالي الميزانية من الرحلات هو",
|
||||
"Total Amount:": "المبلغ الإجمالي:",
|
||||
"L.E": "جنيه",
|
||||
"This amount for all trip I get from Passengers":
|
||||
"هذا المبلغ هو مجموع كل الرحلات التي حصلت عليها من الركاب",
|
||||
"Total Budget from trips by\nCredit card is ":
|
||||
"إجمالي الميزانية من الرحلات عبر\nالبطاقة الائتمانية هو",
|
||||
"This amount for all trip I get from Passengers and Collected For me in":
|
||||
"هذا المبلغ هو مجموع الرحلات من الركاب وتم جمعه لي في",
|
||||
"Wallet": "المحفظة",
|
||||
"You can buy points from your budget":
|
||||
"يمكنك شراء النقاط من ميزانيتك",
|
||||
"Transfer budget": "تحويل الميزانية",
|
||||
"Daily Promos": "العروض اليومية",
|
||||
"Morning Promo": "عرض الصباح",
|
||||
"this is count of your all trips in the morning promo today from 7:00am-10:00am":
|
||||
"هذا عدد رحلاتك في عرض الصباح اليوم من 7 صباحاً إلى 10 صباحاً",
|
||||
"Afternoon Promo": "عرض بعد الظهر",
|
||||
"this is count of your all trips in the Afternoon promo today from 3:00pm-6:00 pm":
|
||||
"هذا عدد رحلاتك في عرض بعد الظهر اليوم من 3 مساءً إلى 6 مساءً",
|
||||
"You can purchase a budget to enable online access through the options listed below.":
|
||||
"يمكنك شراء ميزانية لتفعيل الوصول عبر الإنترنت من خلال الخيارات أدناه.",
|
||||
"Payment History": "سجل المدفوعات",
|
||||
"Weekly Budget": "الميزانية الأسبوعية",
|
||||
"Pay from my budget": "الدفع من ميزانيتي",
|
||||
"You have in account": "لديك في الحساب",
|
||||
"Confirm & Pay": "تأكيد ودفع",
|
||||
"Use Touch ID or Face ID to confirm payment":
|
||||
"استخدم البصمة أو بصمة الوجه لتأكيد الدفع",
|
||||
"Your Budget less than needed": "ميزانيتك أقل من المطلوب",
|
||||
'Weekly Payments': 'المدفوعات الأسبوعية',
|
||||
"Fetching Weekly Data...": "جاري جلب البيانات الأسبوعية...",
|
||||
'Transaction Details': 'تفاصيل المعاملة',
|
||||
'Total Weekly Earnings': 'إجمالي الأرباح الأسبوعية',
|
||||
'No Transactions Yet': 'لا توجد معاملات حتى الآن',
|
||||
'Your weekly payment history will appear here.':
|
||||
'سيظهر سجل مدفوعاتك الأسبوعي هنا.',
|
||||
"Loading History...": "جاري تحميل السجل...",
|
||||
'Credit': 'دفع',
|
||||
'Debit': 'خصم',
|
||||
"Use Touch ID or Face ID to confirm transfer":
|
||||
"استخدم البصمة أو بصمة الوجه لتأكيد التحويل",
|
||||
"You don't have enough money in your Tripz wallet":
|
||||
"لا تملك رصيداً كافياً في محفظة Tripz الخاصة بك",
|
||||
"Insert Account Bank": "إدخال حساب البنك",
|
||||
"Insert Card Bank Details to Receive Your Visa Money Weekly":
|
||||
"أدخل تفاصيل بطاقة البنك لاستلام أموالك عبر الفيزا أسبوعياً",
|
||||
"Insert card number": "أدخل رقم البطاقة",
|
||||
"Ok": "موافق",
|
||||
"bank account added succesfly": "تمت إضافة الحساب البنكي بنجاح",
|
||||
"birthdate": "تاريخ ميلاد",
|
||||
"Approve Driver Documents": "الموافقة على مستندات الشريك السائق",
|
||||
"Total Budget is": "الميزانية الإجمالية",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
@@ -15,6 +18,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'constant/api_key.dart';
|
||||
import 'constant/info.dart';
|
||||
import 'constant/notification.dart';
|
||||
import 'controller/firebase/firbase_messge.dart';
|
||||
import 'controller/firebase/local_notification.dart';
|
||||
import 'controller/functions/encrypt_decrypt.dart';
|
||||
@@ -95,6 +99,16 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
||||
}
|
||||
}
|
||||
|
||||
/// تهيئة Firebase بوعي لمنع تهيئة مكرّرة على أندرويد (isolates متعددة)
|
||||
Future<void> initFirebaseIfNeeded() async {
|
||||
if (Firebase.apps.isEmpty) {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform);
|
||||
} else {
|
||||
Firebase.app();
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
Log.print('Notification tapped in background!');
|
||||
@@ -122,21 +136,65 @@ Future<void> closeOverLay() async {
|
||||
}
|
||||
//--- Main Application ---
|
||||
|
||||
void main() async {
|
||||
void main() {
|
||||
runZonedGuarded(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
|
||||
await initFirebaseIfNeeded();
|
||||
await WakelockPlus.enable();
|
||||
await GetStorage.init();
|
||||
await initializeDateFormatting();
|
||||
|
||||
Stripe.publishableKey = AK.publishableKeyStripe;
|
||||
SystemChrome.setPreferredOrientations([
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
// سجل الهاندلر تبع رسائل الخلفية (لازم يكون Top-Level ومع @pragma)
|
||||
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
|
||||
|
||||
runApp(const MyApp());
|
||||
}, (error, stack) {
|
||||
// ==== START: ERROR FILTER ====
|
||||
final errorString = error.toString();
|
||||
|
||||
// اطبع كل شيء محلياً
|
||||
// (يمكنك استبدال print بـ Log.print إن رغبت)
|
||||
print("Caught Dart error: $error");
|
||||
print(stack);
|
||||
|
||||
// تجاهُل بعض الأخطاء المعروفة
|
||||
final isIgnoredError = errorString.contains('PERMISSION_DENIED') ||
|
||||
errorString.contains('FormatException') ||
|
||||
errorString.contains('Null check operator used on a null value');
|
||||
|
||||
if (!isIgnoredError) {
|
||||
// أرسل فقط ما ليس ضمن قائمة التجاهل
|
||||
CRUD.addError(error.toString(), stack.toString(), 'main');
|
||||
} else {
|
||||
print("Ignoring error and not sending to server: $errorString");
|
||||
}
|
||||
// ==== END: ERROR FILTER ====
|
||||
});
|
||||
}
|
||||
|
||||
// void main() async {
|
||||
// WidgetsFlutterBinding.ensureInitialized();
|
||||
// await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
|
||||
// await WakelockPlus.enable();
|
||||
// await GetStorage.init();
|
||||
// Stripe.publishableKey = AK.publishableKeyStripe;
|
||||
// SystemChrome.setPreferredOrientations([
|
||||
// DeviceOrientation.portraitUp,
|
||||
// DeviceOrientation.portraitDown,
|
||||
// ]);
|
||||
|
||||
// runApp(const MyApp());
|
||||
// }
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@@ -172,7 +230,16 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
|
||||
await FirebaseMessagesController().getToken();
|
||||
await NotificationController().initNotifications();
|
||||
final random = Random();
|
||||
final randomMessage =
|
||||
driverMessages[random.nextInt(driverMessages.length)];
|
||||
|
||||
// Schedule the notification with the random message
|
||||
NotificationController().scheduleNotificationsForSevenDays(
|
||||
randomMessage.split(':')[0],
|
||||
randomMessage.split(':')[1],
|
||||
"tone1",
|
||||
);
|
||||
// You can add your other initializations here
|
||||
// For example:
|
||||
|
||||
|
||||
@@ -29,249 +29,6 @@ import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'device_compatibility_page.dart';
|
||||
|
||||
// class CupertinoDrawerCaptain extends StatelessWidget {
|
||||
// final ImageController imageController = Get.put(ImageController());
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return CupertinoPageScaffold(
|
||||
// navigationBar: CupertinoNavigationBar(
|
||||
// middle: Text('Menu'.tr),
|
||||
// ),
|
||||
// child: SafeArea(
|
||||
// child: CustomScrollView(
|
||||
// slivers: [
|
||||
// const SliverToBoxAdapter(child: const UserAccountHeader()),
|
||||
// SliverList(
|
||||
// delegate: SliverChildListDelegate([
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.money_dollar,
|
||||
// text: 'Wallet'.tr,
|
||||
// onTap: () => Get.to(() => WalletCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.person,
|
||||
// text: 'Profile'.tr,
|
||||
// onTap: () => Get.to(() => ProfileCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.clock,
|
||||
// text: 'History of Trip'.tr,
|
||||
// onTap: () => Get.to(() => const HistoryCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.car_detailed,
|
||||
// text: 'Available for rides'.tr,
|
||||
// onTap: () => Get.to(() => const AvailableRidesPage()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.bell,
|
||||
// text: 'Notifications'.tr,
|
||||
// onTap: () => Get.to(() => const NotificationCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.question_circle,
|
||||
// text: 'Helping Center'.tr,
|
||||
// onTap: () => Get.to(() => HelpCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.share,
|
||||
// text: 'Share App'.tr,
|
||||
// onTap: () => Get.to(() => InviteScreen()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.wrench,
|
||||
// text: "Maintenance Center".tr,
|
||||
// onTap: () => Get.to(() => MaintainCenterPage()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon:
|
||||
// CupertinoIcons.heart, // Updated icon to represent health
|
||||
// text: "Health Insurance".tr, // Updated English text
|
||||
// onTap: () => Get.to(() => AssuranceHealthPage()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.mail,
|
||||
// text: "Contact Us".tr,
|
||||
// onTap: () => Get.to(() => ContactUsPage()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon:
|
||||
// Icons.play_circle_filled, // Icon representing video play
|
||||
// text: 'Videos Tutorials'.tr,
|
||||
// onTap: () => Get.to(() => VideoListPage()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: Icons.star, // Another option with a filled star icon
|
||||
// text: "Rate Our App".tr,
|
||||
// onTap: () => Get.to(() => RatingScreen()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.settings,
|
||||
// text: 'Settings'.tr,
|
||||
// onTap: () => Get.to(() => const SettingsCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// _buildDrawerItem(
|
||||
// icon: CupertinoIcons.square_arrow_right,
|
||||
// text: 'Sign Out'.tr,
|
||||
// onTap: () => Get.to(() => const LogoutCaptain()),
|
||||
// ),
|
||||
// _buildDivider(),
|
||||
// ]),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _buildDivider() {
|
||||
// return const Divider(
|
||||
// thickness: 1,
|
||||
// height: 1,
|
||||
// color: CupertinoColors.systemGrey4,
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _buildDrawerItem({
|
||||
// required IconData icon,
|
||||
// required String text,
|
||||
// required VoidCallback onTap,
|
||||
// }) {
|
||||
// return CupertinoButton(
|
||||
// onPressed: onTap,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// Icon(icon, color: CupertinoColors.activeBlue),
|
||||
// const SizedBox(width: 10),
|
||||
// Text(text, style: const TextStyle(color: CupertinoColors.label)),
|
||||
// const Spacer(),
|
||||
// const Icon(CupertinoIcons.right_chevron,
|
||||
// color: CupertinoColors.systemGrey),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class UserAccountHeader extends StatelessWidget {
|
||||
// const UserAccountHeader({super.key});
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Container(
|
||||
// padding: const EdgeInsets.all(16),
|
||||
// decoration: const BoxDecoration(
|
||||
// gradient: LinearGradient(
|
||||
// colors: [AppColor.blueColor, AppColor.twitterColor],
|
||||
// begin: Alignment.topLeft,
|
||||
// end: Alignment.bottomRight,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// GetBuilder<ImageController>(
|
||||
// builder: (imageController) {
|
||||
// return Stack(
|
||||
// children: [
|
||||
// imageController.isloading
|
||||
// ? const CupertinoActivityIndicator()
|
||||
// : Container(
|
||||
// width: 100,
|
||||
// height: 100,
|
||||
// decoration: BoxDecoration(
|
||||
// shape: BoxShape.circle,
|
||||
// image: DecorationImage(
|
||||
// fit: BoxFit.cover,
|
||||
// image: NetworkImage(
|
||||
// '${AppLink.seferCairoServer}/portrate_captain_image/${box.read(BoxName.driverID)}.jpg',
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Positioned(
|
||||
// right: 0,
|
||||
// top: 0,
|
||||
// child: CupertinoButton(
|
||||
// onPressed: () {
|
||||
// imageController.choosImagePicture(
|
||||
// AppLink.uploadImage1, 'portrait');
|
||||
// },
|
||||
// child: const Icon(CupertinoIcons.pencil_circle_fill,
|
||||
// color: CupertinoColors.white),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// const SizedBox(height: 10),
|
||||
// Text(
|
||||
// '${box.read(BoxName.nameDriver).toString().split(' ')[0]} ${box.read(BoxName.nameDriver).toString().split(' ')[1]}',
|
||||
// style: const TextStyle(
|
||||
// color: CupertinoColors.white,
|
||||
// fontSize: 18,
|
||||
// fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// const SizedBox(height: 5),
|
||||
// Text(
|
||||
// box.read(BoxName.emailDriver),
|
||||
// style: const TextStyle(color: CupertinoColors.white, fontSize: 14),
|
||||
// ),
|
||||
// const SizedBox(height: 10),
|
||||
// Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// Get.find<HomeCaptainController>().rating.toString(),
|
||||
// style: const TextStyle(
|
||||
// color: CupertinoColors.systemYellow, fontSize: 16),
|
||||
// ),
|
||||
// const SizedBox(width: 5),
|
||||
// // You might want to replace this with a Cupertino-style rating widget
|
||||
// // For now, we'll use text to represent stars
|
||||
// // const Text('★★★★★',
|
||||
// // style: TextStyle(color: CupertinoColors.systemYellow)),
|
||||
|
||||
// Container(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
// color: AppColor.greenColor,
|
||||
// child: RatingBar.builder(
|
||||
// initialRating: double.parse(
|
||||
// Get.find<HomeCaptainController>().rating.toString()),
|
||||
// minRating: 1,
|
||||
// direction: Axis.horizontal,
|
||||
// itemCount: 5,
|
||||
// itemSize: 20,
|
||||
// itemPadding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
// itemBuilder: (context, _) => const Icon(
|
||||
// Icons.star,
|
||||
// color: Colors.amber,
|
||||
// ),
|
||||
// onRatingUpdate: (rating) {},
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
class CupertinoDrawerCaptain extends StatelessWidget {
|
||||
final ImageController imageController = Get.put(ImageController());
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,278 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
|
||||
// --- DESIGN PALETTE IS NOW UPDATED TO USE AppColor ---
|
||||
const kLightBackgroundColor = Color(0xFFF5F8FA);
|
||||
const kCardBackgroundColor = AppColor.secondaryColor; // Using AppColor
|
||||
const kPrimaryBlue = AppColor.primaryColor; // Using AppColor
|
||||
const kPrimaryTextColor = Color(0xFF14171A);
|
||||
const kSecondaryTextColor = Color(0xFF657786);
|
||||
const kPositiveAmountColor = AppColor.greenColor; // Using AppColor
|
||||
const kNegativeAmountColor = AppColor.redColor; // Using AppColor
|
||||
|
||||
class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
const PaymentHistoryDriverPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Controller initialization remains the same.
|
||||
Get.put(DriverWalletHistoryController());
|
||||
return MyScafolld(
|
||||
title: 'Payment History'.tr,
|
||||
body: [
|
||||
GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) => controller.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: ListView.builder(
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: kLightBackgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Payment History'.tr,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
backgroundColor: kCardBackgroundColor,
|
||||
elevation: 0.5,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: kPrimaryBlue),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
body: GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) {
|
||||
// Smooth transition between loading and content states.
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: controller.isLoading
|
||||
? _buildLoadingState()
|
||||
: _buildContentState(controller),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the loading state UI.
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(kPrimaryBlue),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
"Loading History...".tr,
|
||||
style: TextStyle(color: kSecondaryTextColor, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the main content when data is available.
|
||||
Widget _buildContentState(DriverWalletHistoryController controller) {
|
||||
if (controller.archive.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemCount: controller.archive.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var list = controller.archive[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: double.parse(list['amount']) < 0
|
||||
? AppColor.redColor.withOpacity(.4)
|
||||
: AppColor.greenColor.withOpacity(.4)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
var transaction = controller.archive[index];
|
||||
// Animated entry for each card
|
||||
return _AnimatedListItem(
|
||||
index: index,
|
||||
child: _HistoryCard(transaction: transaction),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// A user-friendly message for when there are no transactions.
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
list['amount'],
|
||||
style: AppStyle.title,
|
||||
Icon(
|
||||
Icons.history_toggle_off,
|
||||
size: 80,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
list['created_at'],
|
||||
style: AppStyle.title,
|
||||
'No History Found'.tr,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Your entire transaction history will be displayed here.'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: kSecondaryTextColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom widget for a single history transaction card.
|
||||
class _HistoryCard extends StatelessWidget {
|
||||
final Map<String, dynamic> transaction;
|
||||
|
||||
const _HistoryCard({required this.transaction});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Determine if the amount is positive or negative
|
||||
final double amount =
|
||||
double.tryParse(transaction['amount'] ?? '0.0') ?? 0.0;
|
||||
final bool isPositive = amount >= 0;
|
||||
|
||||
// Safely parse and format the date
|
||||
String formattedDate = 'No Date';
|
||||
try {
|
||||
final date = DateTime.parse(transaction['created_at']);
|
||||
formattedDate = DateFormat('MMM d, yyyy hh:mm a').format(date);
|
||||
} catch (e) {
|
||||
// Keep the original string if parsing fails
|
||||
formattedDate = transaction['created_at'] ?? 'Invalid Date';
|
||||
}
|
||||
|
||||
final Color amountColor =
|
||||
isPositive ? kPositiveAmountColor : kNegativeAmountColor;
|
||||
final IconData amountIcon =
|
||||
isPositive ? Icons.arrow_upward : Icons.arrow_downward;
|
||||
final String title = isPositive ? 'Credit'.tr : 'Debit'.tr;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
elevation: 2,
|
||||
shadowColor: Colors.grey.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: amountColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(amountIcon, color: amountColor, size: 28),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Title and Date
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
formattedDate,
|
||||
style: const TextStyle(
|
||||
color: kSecondaryTextColor,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Amount
|
||||
Text(
|
||||
'${isPositive ? '+' : ''}${transaction['amount']} ${'L.E'.tr}',
|
||||
style: TextStyle(
|
||||
color: amountColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple animation widget to fade in list items.
|
||||
class _AnimatedListItem extends StatefulWidget {
|
||||
final int index;
|
||||
final Widget child;
|
||||
|
||||
const _AnimatedListItem({required this.index, required this.child});
|
||||
|
||||
@override
|
||||
State<_AnimatedListItem> createState() => _AnimatedListItemState();
|
||||
}
|
||||
|
||||
class _AnimatedListItemState extends State<_AnimatedListItem>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
);
|
||||
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOut,
|
||||
),
|
||||
);
|
||||
// Stagger the animation based on the item index
|
||||
Future.delayed(Duration(milliseconds: widget.index * 75), () {
|
||||
if (mounted) {
|
||||
_controller.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.2),
|
||||
end: Offset.zero,
|
||||
).animate(_controller),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,141 +1,297 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/controller/payment/driver_payment_controller.dart';
|
||||
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
// --- NEW LIGHT & DYNAMIC DESIGN PALETTE (TWITTER BLUE INSPIRED) ---
|
||||
const kLightBackgroundColor = Color(0xFFF5F8FA); // Off-white
|
||||
const kCardBackgroundColor = Color(0xFFFFFFFF); // Pure white for cards
|
||||
const kPrimaryBlue = Color(0xFF1DA1F2); // Twitter Blue
|
||||
const kSecondaryBlue = Color(0xFFE8F5FE); // Very light blue for accents
|
||||
const kPrimaryTextColor = Color(0xFF14171A); // Almost black
|
||||
const kSecondaryTextColor = Color(0xFF657786); // Gray for subtitles
|
||||
const kGreenAccent = Color(0xFF17BF63); // Green for income
|
||||
|
||||
class WeeklyPaymentPage extends StatelessWidget {
|
||||
const WeeklyPaymentPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Controller initialization remains the same.
|
||||
Get.put(DriverWalletHistoryController());
|
||||
return MyScafolld(
|
||||
title: 'Payment History'.tr,
|
||||
body: [
|
||||
GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) => controller.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: Get.width * .8,
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Row(
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Weekly Payments'.tr,
|
||||
style: const TextStyle(
|
||||
color: AppColor.blueColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
backgroundColor: kCardBackgroundColor,
|
||||
elevation: 0.5,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: kPrimaryBlue),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
body: GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) {
|
||||
// Smooth transition between loading and content states.
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: controller.isLoading
|
||||
? _buildLoadingState()
|
||||
: _buildContentState(controller),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the loading state UI.
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
controller.weeklyList.isEmpty
|
||||
? '0'
|
||||
: controller.weeklyList[0]
|
||||
['totalAmount']
|
||||
.toString(),
|
||||
style: AppStyle.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(kPrimaryBlue),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
' Total weekly is '.tr,
|
||||
style: AppStyle.title,
|
||||
"Fetching Weekly Data...".tr,
|
||||
style: TextStyle(color: kSecondaryTextColor, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the main content when data is available.
|
||||
Widget _buildContentState(DriverWalletHistoryController controller) {
|
||||
if (controller.weeklyList.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: [
|
||||
_buildTotalSummaryCard(controller),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionTitle('Transaction Details'.tr),
|
||||
const SizedBox(height: 8),
|
||||
_buildTransactionList(controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// A prominent card to display the total weekly amount.
|
||||
Widget _buildTotalSummaryCard(DriverWalletHistoryController controller) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [kPrimaryBlue, kPrimaryBlue.withOpacity(0.8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: kPrimaryBlue.withOpacity(0.3),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
child: SizedBox(
|
||||
height: Get.height * .75,
|
||||
child: controller.weeklyList.isNotEmpty
|
||||
? ListView.builder(
|
||||
itemCount: controller.weeklyList.length,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index) {
|
||||
var list = controller.weeklyList[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Card(
|
||||
elevation: 2,
|
||||
color: list['paymentMethod'] ==
|
||||
'visa'
|
||||
? AppColor.blueColor
|
||||
: AppColor.secondaryColor,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
list['paymentMethod'] ==
|
||||
'Remainder'
|
||||
? 'Remainder'.tr
|
||||
: list['paymentMethod'] ==
|
||||
'fromBudget'
|
||||
? 'fromBudget'.tr
|
||||
: list[
|
||||
'paymentMethod'],
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
8.0),
|
||||
child: Text(
|
||||
list['amount'],
|
||||
style: AppStyle.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat(
|
||||
'yyyy-MM-dd hh:mm a')
|
||||
.format(DateTime.parse(
|
||||
list[
|
||||
'dateUpdated'])),
|
||||
style: AppStyle.number,
|
||||
'Total Weekly Earnings'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${controller.weeklyList.isEmpty ? '0' : controller.weeklyList[0]['totalAmount'].toString()} ${'L.E'.tr}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the list of transaction cards.
|
||||
Widget _buildTransactionList(DriverWalletHistoryController controller) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: controller.weeklyList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var transaction = controller.weeklyList[index];
|
||||
return _TransactionCard(transaction: transaction);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// A user-friendly message for when there are no transactions.
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.receipt_long_outlined,
|
||||
size: 80,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'No Transactions Yet'.tr,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Your weekly payment history will appear here.'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: kSecondaryTextColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// A helper for creating styled section titles.
|
||||
Widget _buildSectionTitle(String title) {
|
||||
return Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom widget for a single transaction card for better code organization.
|
||||
class _TransactionCard extends StatelessWidget {
|
||||
final Map<String, dynamic> transaction;
|
||||
|
||||
const _TransactionCard({required this.transaction});
|
||||
|
||||
/// Helper to get descriptive details for each payment method.
|
||||
({IconData icon, String title, Color color}) _getPaymentDetails(
|
||||
String paymentMethod) {
|
||||
switch (paymentMethod) {
|
||||
case 'visa':
|
||||
return (
|
||||
icon: Icons.credit_card,
|
||||
title: 'Visa Payout'.tr,
|
||||
color: kPrimaryBlue
|
||||
);
|
||||
case 'fromBudget':
|
||||
return (
|
||||
icon: Icons.arrow_upward,
|
||||
title: 'Points Purchase'.tr,
|
||||
color: Colors.orange
|
||||
);
|
||||
case 'Remainder':
|
||||
return (
|
||||
icon: Icons.account_balance_wallet_outlined,
|
||||
title: 'Cash Balance'.tr,
|
||||
color: kGreenAccent
|
||||
);
|
||||
default:
|
||||
return (
|
||||
icon: Icons.receipt_long,
|
||||
title: paymentMethod.tr,
|
||||
color: kSecondaryTextColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final details = _getPaymentDetails(transaction['paymentMethod']);
|
||||
final date = DateTime.parse(transaction['dateUpdated']);
|
||||
final formattedDate = DateFormat('MMM d, yyyy').format(date);
|
||||
final formattedTime = DateFormat('hh:mm a').format(date);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
elevation: 2,
|
||||
shadowColor: Colors.grey.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: details.color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(details.icon, color: details.color, size: 28),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Title and Date
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
details.title,
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$formattedDate at $formattedTime',
|
||||
style: const TextStyle(
|
||||
color: kSecondaryTextColor,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Amount
|
||||
Text(
|
||||
'${transaction['amount']} ${'L.E'.tr}',
|
||||
style: const TextStyle(
|
||||
color: kPrimaryTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Foundation
|
||||
|
||||
import audio_session
|
||||
import battery_plus
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import file_selector_macos
|
||||
import firebase_auth
|
||||
@@ -37,6 +38,7 @@ import webview_flutter_wkwebview
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@@ -296,6 +296,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
connectivity_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_platform_interface
|
||||
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1302,6 +1318,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
internet_connection_checker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: internet_connection_checker
|
||||
sha256: "1c683e63e89c9ac66a40748b1b20889fd9804980da732bf2b58d6d5456c8e876"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1526,6 +1550,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nm
|
||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -96,6 +96,8 @@ dependencies:
|
||||
device_info_plus: ^11.3.0
|
||||
share_plus: ^11.0.0
|
||||
battery_plus: ^6.2.2
|
||||
connectivity_plus: ^7.0.0
|
||||
internet_connection_checker: ^1.0.0+1
|
||||
# flutter_isolate: ^2.1.0
|
||||
# lingo_hunter: ^1.0.3
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <battery_plus/battery_plus_windows_plugin.h>
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
@@ -23,6 +24,8 @@
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
BatteryPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin"));
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
battery_plus
|
||||
connectivity_plus
|
||||
file_selector_windows
|
||||
firebase_auth
|
||||
firebase_core
|
||||
|
||||
Reference in New Issue
Block a user