Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.danger.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: AppColor.danger.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
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(),
|
onRefresh: () => controller.getComplaints(),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
|
||||||
itemCount: controller.complaintList.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final complaint = controller.complaintList[index];
|
final complaint = list[index];
|
||||||
return _buildComplaintCard(context, complaint);
|
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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
Reference in New Issue
Block a user