Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps

This commit is contained in:
Hamza-Ayed
2026-06-01 23:36:57 +03:00
parent e17866aa2f
commit ce984324ca
12 changed files with 160 additions and 82 deletions

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../main.dart';
import 'box_name.dart';
import 'colors.dart'; import 'colors.dart';
class AppStyle { class AppStyle {
@@ -83,4 +81,3 @@ class AppStyle {
static BoxDecoration boxDecoration = cardDecoration; static BoxDecoration boxDecoration = cardDecoration;
static BoxDecoration boxDecoration1 = elevatedCard; static BoxDecoration boxDecoration1 = elevatedCard;
} }

View File

@@ -6,8 +6,22 @@ import '../functions/crud.dart';
class ComplaintController extends GetxController { class ComplaintController extends GetxController {
var complaintList = [].obs; var complaintList = [].obs;
var isLoading = false.obs; var isLoading = false.obs;
var showOnlyDelayed = false.obs;
final CRUD _crud = CRUD(); final CRUD _crud = CRUD();
List<dynamic> get delayedComplaints {
final weekAgo = DateTime.now().subtract(const Duration(days: 7));
return complaintList.where((c) {
if (c['statusComplaint'] == 'Resolved') return false;
try {
final date = DateTime.parse(c['date_filed']);
return date.isBefore(weekAgo);
} catch (e) {
return false;
}
}).toList();
}
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();

View File

@@ -6,10 +6,8 @@ import 'package:sefer_admin1/constant/links.dart';
import 'package:sefer_admin1/controller/functions/crud.dart'; import 'package:sefer_admin1/controller/functions/crud.dart';
import 'package:sefer_admin1/controller/auth/otp_helper.dart'; import 'package:sefer_admin1/controller/auth/otp_helper.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart';
class DashboardController extends GetxController { class DashboardController extends GetxController {
bool isLoading = false; bool isLoading = false;
@@ -38,7 +36,6 @@ class DashboardController extends GetxController {
return; return;
} }
if (res != 'failure' && res != null) { if (res != 'failure' && res != null) {
try { try {
var d = res is String ? jsonDecode(res) : res; var d = res is String ? jsonDecode(res) : res;

View File

@@ -16,8 +16,8 @@ class DashboardV2Controller extends GetxController {
super.onInit(); super.onInit();
fetchRealtimeData(); fetchRealtimeData();
fetchSmartAlerts(); fetchSmartAlerts();
// Auto refresh every 30 seconds // Auto refresh every 2 minutes
_timer = Timer.periodic(const Duration(seconds: 30), (timer) { _timer = Timer.periodic(const Duration(minutes: 2), (timer) {
fetchRealtimeData(); fetchRealtimeData();
fetchSmartAlerts(); fetchSmartAlerts();
}); });

View File

@@ -31,7 +31,8 @@ class FinancialV2Controller extends GetxController {
Future<void> fetchStats() async { Future<void> fetchStats() async {
try { try {
var res = await CRUD().get(link: AppLink.financialStatsV2, payload: {}); var res =
await CRUD().getWallet(link: AppLink.financialStatsV2, payload: {});
if (res != 'failure' && res != null) { if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res; var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') { if (d['status'] == 'success') {
@@ -45,7 +46,8 @@ class FinancialV2Controller extends GetxController {
Future<void> fetchSettlements() async { Future<void> fetchSettlements() async {
try { try {
var res = await CRUD().get(link: AppLink.settlementsV2, payload: {}); var res =
await CRUD().getWallet(link: AppLink.settlementsV2, payload: {});
if (res != 'failure' && res != null) { if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res; var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') { if (d['status'] == 'success') {

View File

@@ -191,7 +191,7 @@ class OtpHelper extends GetxController {
textConfirm: 'تحقق', textConfirm: 'تحقق',
confirmTextColor: Colors.white, confirmTextColor: Colors.white,
onConfirm: () { onConfirm: () {
if (otpCode.length >= 5) { if (otpCode.length >= 3) {
Get.back(); Get.back();
verifyLoginOtp(phone, otpCode, password, fingerprint); verifyLoginOtp(phone, otpCode, password, fingerprint);
} else { } else {

View File

@@ -116,48 +116,6 @@ class CRUD {
} }
} }
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var s = await getJwtWallet();
final hmac = box.read(BoxName.hmac);
var url = Uri.parse(link);
// إضافة الـ HMAC للـ payload لزيادة التوافقية
if (payload != null && hmac != null) {
payload['hmac'] = hmac.toString();
}
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': box.read(BoxName.fingerPrint) ?? '',
},
);
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return jsonData;
}
return jsonData['status'] ?? 'failure';
} catch (e) {
return 'failure';
}
} else if (response.statusCode == 401) {
await getJwtWallet();
return 'token_expired';
} else {
return 'failure';
}
}
Future<dynamic> post( Future<dynamic> post(
{required String link, Map<String, dynamic>? payload}) async { {required String link, Map<String, dynamic>? payload}) async {
var url = Uri.parse(link); var url = Uri.parse(link);
@@ -248,7 +206,8 @@ class CRUD {
mainToken = mainTokenEnc.toString().split(AppInformation.addd)[0]; mainToken = mainTokenEnc.toString().split(AppInformation.addd)[0];
} }
Log.print('Wallet SSO mainToken length: ${mainToken.length}'); Log.print('Wallet SSO mainToken length: ${mainToken.length}');
Log.print('Wallet SSO token starts with: ${mainToken.substring(0, mainToken.length > 10 ? 10 : mainToken.length)}'); Log.print(
'Wallet SSO token starts with: ${mainToken.substring(0, mainToken.length > 10 ? 10 : mainToken.length)}');
// استخدام الـ SSO للسيرفر الرئيسي إذا كان الأدمن مسجل دخوله // استخدام الـ SSO للسيرفر الرئيسي إذا كان الأدمن مسجل دخوله
var response1 = await http.post( var response1 = await http.post(
@@ -307,6 +266,68 @@ class CRUD {
return null; return null;
} }
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
bool isRetry = false,
}) async {
var s = await getJwtWallet();
final hmac = box.read(BoxName.hmac);
var url = Uri.parse(link);
Log.print('--- getWallet Execution ---');
Log.print('URL: $url');
Log.print('JWT: $s');
Log.print('HMAC: $hmac');
Log.print('Is Retry: $isRetry');
Log.print('Payload: $payload');
if (payload != null && hmac != null) {
payload['hmac'] = hmac.toString();
}
try {
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': box.read(BoxName.fingerPrint) ?? '',
},
);
Log.print('Status Code: ${response.statusCode}');
Log.print('Response Body: ${response.body}');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return jsonData;
}
Log.print('Logic Error: Status is not success. Data: $jsonData');
return jsonData['status'] ?? 'failure';
} catch (e) {
Log.print('JSON Decode Error in getWallet: $e');
return 'failure';
}
} else if (response.statusCode == 401 && !isRetry) {
Log.print('Token expired (401). Clearing cache and retrying...');
await box.remove('wallet_jwt');
await box.remove('wallet_jwt_expiry');
return await getWallet(link: link, payload: payload, isRetry: true);
} else {
Log.print('HTTP Error in getWallet. Status: ${response.statusCode}');
return 'failure';
}
} catch (e) {
Log.print('HTTP Request Exception in getWallet: $e');
return 'failure';
}
}
Future<dynamic> postWallet( Future<dynamic> postWallet(
{required String link, Map<String, dynamic>? payload}) async { {required String link, Map<String, dynamic>? payload}) async {
var s = await getJwtWallet(); var s = await getJwtWallet();

View File

@@ -1,10 +1,15 @@
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
class Log { class Log {
Log._(); Log._();
static void print(String value, {StackTrace? stackTrace}) { static void print(dynamic value, {StackTrace? stackTrace}) {
developer.log(value, name: 'LOG', stackTrace: stackTrace); // استخدام debugPrint بدلاً من print العادي لتجنب تعليق الجهاز في الرسائل الطويلة
debugPrint("LOG: ${value.toString()}");
// يمكن أيضاً إرسالها للـ developer log لضمان ظهورها في الـ Debug Console الخاص بـ VS Code
developer.log(value.toString(), name: 'LOG', stackTrace: stackTrace);
} }
static Object? inspect(Object? object) { static Object? inspect(Object? object) {

View File

@@ -18,19 +18,59 @@ class ComplaintListPage extends StatelessWidget {
title: 'إدارة الشكاوى'.tr, title: 'إدارة الشكاوى'.tr,
isleading: true, isleading: true,
body: [ body: [
Obx(() => controller.isLoading.value && controller.complaintList.isEmpty Obx(() {
? const Center(child: CircularProgressIndicator()) if (controller.delayedComplaints.isNotEmpty) {
: RefreshIndicator( return Container(
onRefresh: () => controller.getComplaints(), margin: const EdgeInsets.all(16),
child: ListView.builder( padding: const EdgeInsets.all(16),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), decoration: BoxDecoration(
itemCount: controller.complaintList.length, color: AppColor.danger.withOpacity(0.1),
itemBuilder: (context, index) { borderRadius: BorderRadius.circular(16),
final complaint = controller.complaintList[index]; border: Border.all(color: AppColor.danger.withOpacity(0.3)),
return _buildComplaintCard(context, complaint); ),
}, child: Column(
), children: [
)), Row(
children: [
const Icon(Icons.warning_amber_rounded, color: AppColor.danger),
const SizedBox(width: 12),
Expanded(
child: Text(
'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!',
style: AppStyle.body.copyWith(color: AppColor.danger, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
Obx(() => MyElevatedButton(
title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط',
onPressed: () => controller.showOnlyDelayed.toggle(),
kolor: AppColor.danger,
)),
],
),
);
}
return const SizedBox.shrink();
}),
Obx(() {
final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList;
if (controller.isLoading.value && list.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return RefreshIndicator(
onRefresh: () => controller.getComplaints(),
child: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
itemCount: list.length,
itemBuilder: (context, index) {
final complaint = list[index];
return _buildComplaintCard(context, complaint);
},
),
);
}),
], ],
); );
} }
@@ -45,7 +85,9 @@ class ComplaintListPage extends StatelessWidget {
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: _buildStatusIndicator(c['statusComplaint'], statusColor), leading: _buildStatusIndicator(c['statusComplaint'], statusColor),
title: Text( title: Text(
c['complaint_type']?.toString() ?? 'شكوى عامة', c['description']?.toString() ?? 'بدون وصف',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppStyle.title, style: AppStyle.title,
), ),
subtitle: Column( subtitle: Column(
@@ -53,7 +95,7 @@ class ComplaintListPage extends StatelessWidget {
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'الرحلة: ${c['ride_id']}', 'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}',
style: AppStyle.caption, style: AppStyle.caption,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),

View File

@@ -79,7 +79,7 @@ class PromoManagementPage extends StatelessWidget {
children: [ children: [
Icon(Icons.money_rounded, size: 14, color: AppColor.success), Icon(Icons.money_rounded, size: 14, color: AppColor.success),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${promo['amount']} ${'دينار'.tr}', style: AppStyle.number.copyWith(color: AppColor.success)), Text('% ${promo['amount']}', style: AppStyle.number.copyWith(color: AppColor.success)),
const SizedBox(width: 12), const SizedBox(width: 12),
Icon(Icons.person_rounded, size: 14, color: AppColor.info), Icon(Icons.person_rounded, size: 14, color: AppColor.info),
const SizedBox(width: 4), const SizedBox(width: 4),
@@ -166,10 +166,10 @@ class PromoManagementPage extends StatelessWidget {
), ),
MyTextForm( MyTextForm(
controller: amountController, controller: amountController,
label: 'المبلغ', label: 'نسبة الخصم',
hint: 'أدخل قيمة الخصم', hint: 'أدخل نسبة الخصم (مثال: 25)',
type: TextInputType.number, type: TextInputType.number,
prefixIcon: Icons.attach_money_rounded, prefixIcon: Icons.percent_rounded,
), ),
MyTextForm( MyTextForm(
controller: descController, controller: descController,

View File

@@ -307,7 +307,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
Text('${d['completed_rides']} رحلة', Text('${d['completed_rides']} رحلة',
style: const TextStyle( style: const TextStyle(
color: AppColor.info, fontWeight: FontWeight.bold)), color: AppColor.info, fontWeight: FontWeight.bold)),
Text('${d['total_revenue']} ج.م', Text('${d['total_revenue']} ل.س',
style: const TextStyle( style: const TextStyle(
color: AppColor.success, color: AppColor.success,
fontSize: 12, fontSize: 12,

View File

@@ -241,7 +241,7 @@
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
3FC083AA4C1F1997BBCE63BC /* [CP] Embed Pods Frameworks */, 3FC083AA4C1F1997BBCE63BC /* [CP] Embed Pods Frameworks */,
6131D245E36334FE0924BDA0 /* [CP] Copy Pods Resources */, 89800430F081423EB842813E /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@@ -401,7 +401,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
6131D245E36334FE0924BDA0 /* [CP] Copy Pods Resources */ = { 89800430F081423EB842813E /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (