Update: 2026-06-26 17:29:23
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
@@ -10,21 +9,19 @@ import 'package:siro_admin/views/admin/quality/blacklist_page.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
import '../../controller/admin/dashboard_controller.dart';
|
||||
import '../../controller/admin/static_controller.dart';
|
||||
import '../../controller/functions/crud.dart';
|
||||
import '../../controller/notification_controller.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import '../widgets/snackbar.dart';
|
||||
import '../invoice/invoice_list_page.dart';
|
||||
import 'captain/captain.dart';
|
||||
import 'captain/syrian_driver_not_active.dart';
|
||||
import 'drivers/monitor_ride.dart';
|
||||
import 'employee/employee_page.dart';
|
||||
import 'enceypt/driver_fingerprint_migration.dart';
|
||||
import 'enceypt/encrypt.dart';
|
||||
import 'enceypt/fingerprint_migration.dart';
|
||||
import 'error/error/error_page.dart';
|
||||
import 'packages.dart';
|
||||
import 'passenger/passenger.dart';
|
||||
@@ -62,7 +59,6 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
static const Color _surface = AppColor.surface;
|
||||
static const Color _surfaceElevated = AppColor.surfaceElevated;
|
||||
static const Color _accent = AppColor.accent;
|
||||
static const Color _accentSoft = AppColor.accentSoft;
|
||||
static const Color _accentBorder = AppColor.accentBorder;
|
||||
static const Color _danger = AppColor.danger;
|
||||
static const Color _warning = AppColor.warning;
|
||||
@@ -135,8 +131,9 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final category = categories[index];
|
||||
if (category.items.isEmpty)
|
||||
if (category.items.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
@@ -792,8 +789,8 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
ActionCategory(
|
||||
title: 'المالية والإدارة',
|
||||
items: [
|
||||
ActionItem('الإدارة المالية V2', Icons.account_balance_rounded, _accent,
|
||||
() => Get.to(() => const FinancialV2Page())),
|
||||
ActionItem('الإدارة المالية V2', Icons.account_balance_rounded,
|
||||
_accent, () => Get.to(() => const FinancialV2Page())),
|
||||
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, _accent,
|
||||
() => Get.to(() => Wallet())),
|
||||
ActionItem('هدية 300', Icons.card_giftcard_rounded, _warning,
|
||||
@@ -839,10 +836,10 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
() => Get.to(() => ServerMonitorPage())),
|
||||
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, _danger,
|
||||
() => Get.to(() => ErrorListPage())),
|
||||
ActionItem('encrypt fp', Icons.error_outline_rounded, _danger,
|
||||
() => Get.to(() => FingerprintMigrationTool())),
|
||||
ActionItem('encrypt fp drivers', Icons.error_outline_rounded,
|
||||
_danger, () => Get.to(() => DriverFingerprintMigrationTool())),
|
||||
// ActionItem('encrypt fp', Icons.error_outline_rounded, _danger,
|
||||
// () => Get.to(() => FingerprintMigrationTool())),
|
||||
// ActionItem('encrypt fp drivers', Icons.error_outline_rounded,
|
||||
// _danger, () => Get.to(() => DriverFingerprintMigrationTool())),
|
||||
ActionItem(
|
||||
'أداة التشفير',
|
||||
Icons.lock_rounded,
|
||||
@@ -952,7 +949,8 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
color: const Color(0xFF4CAF50).withAlpha(30), // ~0.12 opacity
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: const Color(0xFF4CAF50).withAlpha(64)), // ~0.25 opacity
|
||||
color: const Color(0xFF4CAF50)
|
||||
.withAlpha(64)), // ~0.25 opacity
|
||||
),
|
||||
child: const Icon(Icons.message_rounded,
|
||||
color: Color(0xFF4CAF50), size: 28),
|
||||
@@ -1029,20 +1027,11 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
Get.back();
|
||||
var driverPhones = box
|
||||
.read(BoxName.tokensDrivers)['message'] as List?;
|
||||
if (driverPhones == null || driverPhones.isEmpty)
|
||||
if (driverPhones == null || driverPhones.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Get.snackbar(
|
||||
'بدأ الإرسال',
|
||||
'سيتم الإرسال في الخلفية',
|
||||
backgroundColor:
|
||||
const Color(0xFF4CAF50).withOpacity(0.15),
|
||||
colorText: _textPrimary,
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(16),
|
||||
icon: const Icon(Icons.check_circle_rounded,
|
||||
color: Color(0xFF4CAF50)),
|
||||
);
|
||||
mySnackbarInfo('سيتم الإرسال في الخلفية');
|
||||
|
||||
for (var driverData in driverPhones) {
|
||||
if (driverData['phone'] != null) {
|
||||
|
||||
@@ -15,12 +15,12 @@ class CaptainsPage extends StatelessWidget {
|
||||
Get.put(CaptainAdminController());
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
String myPhone = box.read(BoxName.adminPhone).toString();
|
||||
bool isSuperAdmin = false;
|
||||
final String myPhone = box.read(BoxName.adminPhone).toString();
|
||||
bool get isSuperAdmin =>
|
||||
myPhone == '963942542053' || myPhone == '963992952235';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
isSuperAdmin = myPhone == '963942542053' || myPhone == '963992952235';
|
||||
|
||||
return MyScafolld(
|
||||
title: 'Search for Captain'.tr,
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:get/get.dart';
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../controller/admin/captain_admin_controller.dart';
|
||||
import '../../../controller/firebase/firbase_messge.dart';
|
||||
import '../../../main.dart'; // Import main to access myPhone
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import '../../widgets/snackbar.dart';
|
||||
import '../quality/driver_scorecard_page.dart';
|
||||
import 'form_captain.dart';
|
||||
|
||||
@@ -387,8 +387,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
||||
// data['passengerToken'] ?? '', // Safety check
|
||||
// 'order.wav');
|
||||
Get.back();
|
||||
Get.snackbar("Success", "Notification Sent",
|
||||
backgroundColor: Colors.green.withOpacity(0.2));
|
||||
mySnackbarSuccess("Notification Sent");
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -413,8 +412,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
||||
// Call delete function here
|
||||
// controller.deleteCaptain(user['id']);
|
||||
Get.back();
|
||||
Get.snackbar("Deleted", "Captain has been removed",
|
||||
backgroundColor: Colors.red.withOpacity(0.2));
|
||||
mySnackbarWarning("Captain has been removed");
|
||||
},
|
||||
child: Text('Delete'.tr, style: const TextStyle(color: Colors.white)),
|
||||
),
|
||||
|
||||
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../constant/style.dart';
|
||||
import '../../../controller/admin/captain_admin_controller.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import '../../widgets/snackbar.dart';
|
||||
|
||||
class FormCaptain extends StatefulWidget {
|
||||
const FormCaptain({super.key});
|
||||
@@ -69,7 +69,7 @@ class _FormCaptainState extends State<FormCaptain> {
|
||||
print('Updating data: $updatedData');
|
||||
|
||||
Get.back(); // Go back after saving
|
||||
Get.snackbar('Success', 'Captain data updated successfully!');
|
||||
mySnackbarSuccess('Captain data updated successfully!');
|
||||
}
|
||||
// controller.updateCaptain(updatedData);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/mycircular.dart';
|
||||
|
||||
class RegisterCaptain extends StatelessWidget {
|
||||
RegisterCaptain({super.key});
|
||||
const RegisterCaptain({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import '../../../constant/info.dart';
|
||||
import '../../../constant/char_map.dart';
|
||||
|
||||
import '../../../controller/drivers/driver_not_active_controller.dart';
|
||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import 'driver_details_not_active_page.dart';
|
||||
|
||||
@@ -9,10 +9,8 @@ class DashboardV2Widget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Initialize controller
|
||||
final controller = Get.put(DashboardV2Controller());
|
||||
|
||||
return GetBuilder<DashboardV2Controller>(
|
||||
init: DashboardV2Controller(),
|
||||
builder: (ctrl) {
|
||||
if (ctrl.isLoading) {
|
||||
return const SliverToBoxAdapter(
|
||||
|
||||
@@ -12,23 +12,23 @@ class DashboardStatCard extends StatelessWidget {
|
||||
final Color? valueColor;
|
||||
|
||||
const DashboardStatCard({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
this.icon,
|
||||
this.iconColor,
|
||||
this.backgroundColor,
|
||||
this.valueColor,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Attempt to use AppStyle.boxDecoration1 properties if it's a BoxDecoration
|
||||
BoxDecoration? baseDecoration = AppStyle.boxDecoration1;
|
||||
Color? finalBackgroundColor =
|
||||
backgroundColor ?? baseDecoration?.color ?? Theme.of(context).cardColor;
|
||||
backgroundColor ?? baseDecoration.color ?? Theme.of(context).cardColor;
|
||||
BorderRadius? finalBorderRadius =
|
||||
baseDecoration?.borderRadius?.resolve(Directionality.of(context)) ??
|
||||
baseDecoration.borderRadius?.resolve(Directionality.of(context)) ??
|
||||
BorderRadius.circular(12.0);
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -5,6 +5,7 @@ import '../../../constant/style.dart';
|
||||
import '../../../controller/admin/driver_docs_controller.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/snackbar.dart';
|
||||
import '../../../constant/links.dart';
|
||||
|
||||
class DriverDocsReviewPage extends StatelessWidget {
|
||||
@@ -100,7 +101,7 @@ class DriverDocsReviewPage extends StatelessWidget {
|
||||
const SizedBox(height: 24),
|
||||
Text('الوثائق المرفوعة', style: AppStyle.title),
|
||||
const SizedBox(height: 12),
|
||||
...docs.map((doc) => _buildDocCard(doc)).toList(),
|
||||
...docs.map((doc) => _buildDocCard(doc)),
|
||||
const SizedBox(height: 32),
|
||||
MyElevatedButton(
|
||||
title: 'اعتماد وتفعيل الحساب',
|
||||
@@ -110,7 +111,7 @@ class DriverDocsReviewPage extends StatelessWidget {
|
||||
bool success = await controller.approveDriver(id);
|
||||
if (success) {
|
||||
Get.back();
|
||||
Get.snackbar('نجاح', 'تم تفعيل حساب السائق بنجاح');
|
||||
mySnackbarSuccess('تم تفعيل حساب السائق بنجاح');
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
import 'package:siro_admin/controller/functions/wallet.dart';
|
||||
import 'package:siro_admin/views/widgets/snackbar.dart';
|
||||
|
||||
import '../../../constant/links.dart'; // تأكد من المسار
|
||||
|
||||
@@ -17,11 +18,6 @@ class DriverGiftCheckerController extends GetxController {
|
||||
// قائمة السائقين (سنقوم بتحميلها للبحث عن الـ ID)
|
||||
List<dynamic> driversCache = [];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// fetchDriverCache(); // تحميل البيانات عند فتح الصفحة
|
||||
}
|
||||
|
||||
// 1. تحميل قائمة السائقين لاستخراج الـ ID منها
|
||||
Future<void> fetchDriverCache() async {
|
||||
@@ -45,8 +41,7 @@ class DriverGiftCheckerController extends GetxController {
|
||||
String phoneInput = phoneController.text.trim();
|
||||
|
||||
if (phoneInput.isEmpty) {
|
||||
Get.snackbar("تنبيه", "يرجى إدخال رقم الهاتف",
|
||||
backgroundColor: Colors.orange);
|
||||
mySnackbarWarning("يرجى إدخال رقم الهاتف");
|
||||
return;
|
||||
}
|
||||
await fetchDriverCache();
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart'; // Ensure get_storage is in pubspec.yaml
|
||||
import 'package:siro_admin/controller/functions/wallet.dart';
|
||||
import 'package:siro_admin/views/widgets/snackbar.dart';
|
||||
|
||||
// --- New Controller to handle the specific JSON URL ---
|
||||
class DriverCacheController extends GetxController {
|
||||
@@ -73,13 +74,7 @@ class DriverCacheController extends GetxController {
|
||||
paidDrivers.clear();
|
||||
box.remove('paid_drivers');
|
||||
update();
|
||||
Get.snackbar(
|
||||
"Storage Cleared",
|
||||
"Paid status history has been reset",
|
||||
backgroundColor: Colors.redAccent,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
mySnackbarInfo("Paid status history has been reset");
|
||||
}
|
||||
|
||||
// Check if driver is already paid
|
||||
@@ -93,9 +88,6 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Put the new controller
|
||||
final controller = Get.put(DriverCacheController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8FAFC), // slate-50 background
|
||||
body: SafeArea(
|
||||
@@ -580,8 +572,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
||||
String driverId = driver['driver_id']?.toString() ?? '';
|
||||
String phone = driver['phone']?.toString() ?? '';
|
||||
if (driverId.isEmpty || driverId == 'null') {
|
||||
Get.snackbar("Error", "Cannot pay driver with missing ID",
|
||||
backgroundColor: Colors.red, colorText: Colors.white);
|
||||
mySnackbarError("Cannot pay driver with missing ID");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,10 +633,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
||||
|
||||
Get.back(); // Close Dialog
|
||||
|
||||
Get.snackbar("Success", "Payment of $amount EGP sent to driver",
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
mySnackbarSuccess("Payment of $amount EGP sent to driver");
|
||||
},
|
||||
textCancel: 'Cancel',
|
||||
onCancel: () => Get.back(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:latlong2/latlong.dart';
|
||||
import 'package:siro_admin/constant/links.dart';
|
||||
// Keep your specific imports
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
import 'package:siro_admin/views/widgets/snackbar.dart';
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// 1. DATA MODELS
|
||||
@@ -49,20 +50,23 @@ String normalizePhone(String input) {
|
||||
final clean = input.replaceAll(RegExp(r'\D+'), '');
|
||||
|
||||
// Syria: 099XXXXXXX or 9639XXXXXXX
|
||||
if (clean.length == 10 && clean.startsWith('09'))
|
||||
if (clean.length == 10 && clean.startsWith('09')) {
|
||||
return '963${clean.substring(1)}';
|
||||
}
|
||||
if (clean.length == 12 && clean.startsWith('963')) return clean;
|
||||
if (clean.length == 9 && clean.startsWith('9')) return '963$clean';
|
||||
|
||||
// Jordan: 079XXXXXXX or 9627XXXXXXX
|
||||
if (clean.length == 10 && clean.startsWith('07'))
|
||||
if (clean.length == 10 && clean.startsWith('07')) {
|
||||
return '962${clean.substring(1)}';
|
||||
}
|
||||
if (clean.length == 12 && clean.startsWith('962')) return clean;
|
||||
if (clean.length == 9 && clean.startsWith('7')) return '962$clean';
|
||||
|
||||
// Egypt: 010XXXXXXXX or 2010XXXXXXXX
|
||||
if (clean.length == 11 && clean.startsWith('01'))
|
||||
if (clean.length == 11 && clean.startsWith('01')) {
|
||||
return '20${clean.substring(1)}';
|
||||
}
|
||||
if (clean.length == 13 && clean.startsWith('20')) return clean;
|
||||
|
||||
return clean;
|
||||
@@ -107,15 +111,7 @@ class RideMonitorController extends GetxController {
|
||||
|
||||
void startSearch() {
|
||||
if (phoneInputController.text.trim().isEmpty) {
|
||||
Get.snackbar(
|
||||
"تنبيه",
|
||||
"يرجى إدخال رقم الهاتف أولاً",
|
||||
backgroundColor: Colors.redAccent.withOpacity(0.9),
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.TOP,
|
||||
margin: const EdgeInsets.all(15),
|
||||
borderRadius: 15,
|
||||
);
|
||||
mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -297,9 +293,10 @@ class RideMonitorScreen extends StatelessWidget {
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: Obx(() {
|
||||
if (controller.isTracking.value)
|
||||
if (controller.isTracking.value) {
|
||||
return const SizedBox
|
||||
.shrink(); // إخفاء الـ AppBar في وضع التتبع للخريطة الكاملة
|
||||
}
|
||||
return AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
@@ -460,7 +457,7 @@ class RideMonitorScreen extends StatelessWidget {
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(
|
||||
points: controller.routePolyline.value,
|
||||
points: controller.routePolyline,
|
||||
strokeWidth: 6.0,
|
||||
color: primaryColor.withOpacity(0.9),
|
||||
borderStrokeWidth: 2.0,
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// driver_fingerprint_migration.dart
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// المنطق ببساطة:
|
||||
// 1. خذ البصمة كما هي من DB
|
||||
// 2. split('_') → احذف آخر جزء (OS version)
|
||||
// 3. join('_') → encrypt → رفع
|
||||
//
|
||||
// مثال:
|
||||
// "abc123_SamsungA51_13" → "abc123_SamsungA51" → encrypt
|
||||
// "TECNO_LH7n-GL_14" → "TECNO_LH7n-GL" → encrypt
|
||||
// "unknown_2412DPC0AG_15" → "unknown_2412DPC0AG" → encrypt
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../print.dart';
|
||||
|
||||
class DriverFingerprintMigrationTool extends StatefulWidget {
|
||||
const DriverFingerprintMigrationTool({super.key});
|
||||
|
||||
@override
|
||||
State<DriverFingerprintMigrationTool> createState() =>
|
||||
_DriverFingerprintMigrationToolState();
|
||||
}
|
||||
|
||||
class _DriverFingerprintMigrationToolState
|
||||
extends State<DriverFingerprintMigrationTool> {
|
||||
bool _isRunning = false;
|
||||
bool _isDone = false;
|
||||
int _total = 0;
|
||||
int _processed = 0;
|
||||
int _updated = 0;
|
||||
int _failed = 0;
|
||||
String _currentLog = '';
|
||||
|
||||
static const int _batchSize = 50;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// المنطق الأساسي — حذف آخر جزء بعد "_"
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
String _removeLastSegment(String raw) {
|
||||
final parts = raw.split('_');
|
||||
if (parts.length <= 1) return raw; // جزء واحد — ما في شيء نحذفه
|
||||
parts.removeLast();
|
||||
return parts.join('_');
|
||||
}
|
||||
|
||||
Future<void> _startMigration() async {
|
||||
setState(() {
|
||||
_isRunning = true;
|
||||
_isDone = false;
|
||||
_processed = 0;
|
||||
_updated = 0;
|
||||
_failed = 0;
|
||||
_currentLog = 'جارٍ جلب بصمات السائقين...';
|
||||
});
|
||||
|
||||
try {
|
||||
final records = await _fetchAll();
|
||||
if (records == null) {
|
||||
_log('❌ فشل في جلب البيانات');
|
||||
setState(() => _isRunning = false);
|
||||
return;
|
||||
}
|
||||
|
||||
_total = records.length;
|
||||
_log('✅ تم جلب $_total بصمة — بدء المعالجة...');
|
||||
|
||||
for (int i = 0; i < records.length; i += _batchSize) {
|
||||
final batch = records.skip(i).take(_batchSize).toList();
|
||||
_log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total');
|
||||
await Future.wait(batch.map(_processSingle));
|
||||
if (i + _batchSize < records.length) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
}
|
||||
|
||||
_log('🎉 اكتمل!\nمحدَّث: $_updated | فاشل: $_failed');
|
||||
setState(() {
|
||||
_isDone = true;
|
||||
_isRunning = false;
|
||||
});
|
||||
} catch (e) {
|
||||
_log('❌ خطأ: $e');
|
||||
setState(() => _isRunning = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>?> _fetchAll() async {
|
||||
try {
|
||||
final response = await CRUD().post(
|
||||
link: AppLink.getAllDriverFingerprints,
|
||||
payload: {'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd'},
|
||||
);
|
||||
if (response == 'failure' || response == null) return null;
|
||||
|
||||
final data = response['data'];
|
||||
if (data is! List) return null;
|
||||
|
||||
return List<Map<String, dynamic>>.from(data);
|
||||
} catch (e) {
|
||||
Log.print('fetchAll error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _processSingle(Map<String, dynamic> record) async {
|
||||
final captainId = record['captain_id']?.toString() ?? '';
|
||||
final rawFp = record['fingerPrint']?.toString() ?? '';
|
||||
|
||||
if (captainId.isEmpty || rawFp.isEmpty) {
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// ── حذف آخر جزء (OS version) ─────────────────────────────
|
||||
final String newRaw = _removeLastSegment(rawFp);
|
||||
final String encrypted = EncryptionHelper.instance.encryptData(newRaw);
|
||||
|
||||
Log.print('🔄 [$captainId] "$rawFp" → "$newRaw" → encrypted');
|
||||
|
||||
// ── رفع للسيرفر ──────────────────────────────────────────
|
||||
final res = await CRUD().post(
|
||||
link: AppLink.updateDriverFingerprintAdmin,
|
||||
payload: {
|
||||
'captain_id': captainId,
|
||||
'fingerprint': encrypted,
|
||||
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd',
|
||||
},
|
||||
);
|
||||
|
||||
if (res != 'failure' && res?['status'] == 'success') {
|
||||
setState(() {
|
||||
_updated++;
|
||||
_processed++;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('❌ [$captainId]: $e');
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String msg) {
|
||||
Log.print(msg);
|
||||
setState(() => _currentLog = msg);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Driver FP Migration')),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.orange.shade200),
|
||||
),
|
||||
child: const Text(
|
||||
'⚠️ تُستخدم مرة واحدة فقط\n\n'
|
||||
'"abc123_Samsung_13" → "abc123_Samsung" → encrypt\n'
|
||||
'"TECNO_LH7n_14" → "TECNO_LH7n" → encrypt',
|
||||
style:
|
||||
TextStyle(fontSize: 13, height: 1.7, fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_total > 0) ...[
|
||||
Text('التقدم: $_processed / $_total',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: _total > 0 ? _processed / _total : 0,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
color: _isDone ? Colors.green : Colors.blue,
|
||||
minHeight: 8,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (_processed > 0)
|
||||
Row(children: [
|
||||
_chip('محدَّث', _updated, Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
_chip('فاشل', _failed, Colors.red),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
if (_currentLog.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(_currentLog,
|
||||
style:
|
||||
const TextStyle(fontFamily: 'monospace', fontSize: 12)),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 52,
|
||||
child: ElevatedButton(
|
||||
onPressed: (_isRunning || _isDone) ? null : _startMigration,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _isDone ? Colors.green : Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: _isRunning
|
||||
? const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white, strokeWidth: 2)),
|
||||
SizedBox(width: 12),
|
||||
Text('جارٍ الترحيل...',
|
||||
style:
|
||||
TextStyle(color: Colors.white, fontSize: 16)),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
_isDone ? '✅ اكتمل الترحيل' : 'بدء ترحيل بصمات السائقين',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(String label, int value, Color color) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Text('$label: $value',
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
|
||||
);
|
||||
}
|
||||
@@ -9,14 +9,11 @@ import '../../../constant/box_name.dart';
|
||||
// ─── Custom Colors ────────────────────────────────────────────────────────────
|
||||
class _AppColors {
|
||||
static const bg = Color(0xFF0A0D14);
|
||||
static const surface = Color(0xFF111622);
|
||||
static const card = Color(0xFF161D2E);
|
||||
static const border = Color(0xFF1F2D4A);
|
||||
static const accent = Color(0xFF00E5FF);
|
||||
static const accentDim = Color(0xFF0097A7);
|
||||
static const accentGlow = Color(0x2200E5FF);
|
||||
static const accentDecrypt = Color(0xFF7C4DFF);
|
||||
static const accentDecryptDim = Color(0xFF512DA8);
|
||||
static const accentDecryptGlow = Color(0x227C4DFF);
|
||||
static const textPrimary = Color(0xFFE8F0FE);
|
||||
static const textSec = Color(0xFF7A8BAA);
|
||||
@@ -27,7 +24,7 @@ class _AppColors {
|
||||
class EncryptToolPage extends StatefulWidget {
|
||||
final String adminToken;
|
||||
|
||||
const EncryptToolPage({Key? key, required this.adminToken}) : super(key: key);
|
||||
const EncryptToolPage({super.key, required this.adminToken});
|
||||
|
||||
@override
|
||||
State<EncryptToolPage> createState() => _EncryptToolPageState();
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// fingerprint_migration.dart
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// أداة ترحيل البصمات القديمة للنظام الجديد
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// المشكلة:
|
||||
// البصمة القديمة = encrypt(androidId_model_osVersion)
|
||||
// البصمة الجديدة = encrypt(androidId_model)
|
||||
//
|
||||
// الحل:
|
||||
// 1. نجيب كل البصمات من السيرفر (batch 50 في المرة)
|
||||
// 2. نفك تشفير كل بصمة بـ EncryptionHelper
|
||||
// 3. نحذف آخر جزء (osVersion) مع الـ _ قبله
|
||||
// 4. نعيد التشفير
|
||||
// 5. نرفع البصمة المحدّثة للسيرفر
|
||||
//
|
||||
// يُستخدم مرة واحدة فقط ثم يُحذف من التطبيق
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:siro_admin/controller/functions/encrypt_decrypt.dart' as X;
|
||||
|
||||
import '../../../constant/char_map.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../print.dart';
|
||||
|
||||
class FingerprintMigrationTool extends StatefulWidget {
|
||||
const FingerprintMigrationTool({super.key});
|
||||
|
||||
@override
|
||||
State<FingerprintMigrationTool> createState() =>
|
||||
_FingerprintMigrationToolState();
|
||||
}
|
||||
|
||||
class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
|
||||
// ── حالة الترحيل ──────────────────────────────────────────
|
||||
bool _isRunning = false;
|
||||
bool _isDone = false;
|
||||
int _total = 0;
|
||||
int _processed = 0;
|
||||
int _updated = 0; // بصمات تم تحديثها
|
||||
int _skipped = 0; // بصمات كانت بالفعل بالنظام الجديد
|
||||
int _failed = 0; // فشل في المعالجة
|
||||
String _currentLog = '';
|
||||
|
||||
static const int _batchSize = 50;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// الدالة الرئيسية للترحيل
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<void> _startMigration() async {
|
||||
setState(() {
|
||||
_isRunning = true;
|
||||
_isDone = false;
|
||||
_processed = 0;
|
||||
_updated = 0;
|
||||
_skipped = 0;
|
||||
_failed = 0;
|
||||
_currentLog = 'جارٍ جلب البصمات من السيرفر...';
|
||||
});
|
||||
|
||||
try {
|
||||
// ── 1. جلب كل البصمات من السيرفر ──────────────────────
|
||||
final allFingerprints = await _fetchAllFingerprints();
|
||||
|
||||
if (allFingerprints == null) {
|
||||
_log('❌ فشل في جلب البيانات من السيرفر');
|
||||
setState(() => _isRunning = false);
|
||||
return;
|
||||
}
|
||||
|
||||
_total = allFingerprints.length;
|
||||
_log('✅ تم جلب $_total بصمة — بدء المعالجة...');
|
||||
|
||||
// ── 2. معالجة على batches ──────────────────────────────
|
||||
for (int i = 0; i < allFingerprints.length; i += _batchSize) {
|
||||
final batch = allFingerprints.skip(i).take(_batchSize).toList();
|
||||
|
||||
_log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total');
|
||||
|
||||
// معالجة الـ batch بالتوازي
|
||||
await Future.wait(
|
||||
batch.map((record) => _processSingleRecord(record)),
|
||||
);
|
||||
|
||||
// استراحة قصيرة بين الـ batches لحماية السيرفر
|
||||
if (i + _batchSize < allFingerprints.length) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
}
|
||||
|
||||
_log('🎉 اكتمل الترحيل!\n'
|
||||
'محدَّث: $_updated | متجاوز: $_skipped | فاشل: $_failed');
|
||||
|
||||
setState(() {
|
||||
_isDone = true;
|
||||
_isRunning = false;
|
||||
});
|
||||
} catch (e) {
|
||||
_log('❌ خطأ عام: $e');
|
||||
setState(() => _isRunning = false);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// جلب كل البصمات من السيرفر
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<List<Map<String, dynamic>>?> _fetchAllFingerprints() async {
|
||||
try {
|
||||
final response = await CRUD().post(
|
||||
link: AppLink.getAllFingerprints, // أضفه في AppLink
|
||||
payload: {
|
||||
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd'
|
||||
}, // مفتاح أمان للـ endpoint
|
||||
);
|
||||
|
||||
if (response == 'failure' || response == null) return null;
|
||||
|
||||
final data = response['data'];
|
||||
if (data is! List) return null;
|
||||
|
||||
return List<Map<String, dynamic>>.from(data);
|
||||
} catch (e) {
|
||||
Log.print('fetchAllFingerprints error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// معالجة بصمة واحدة
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<void> _processSingleRecord(Map<String, dynamic> record) async {
|
||||
final String passengerID = record['passengerID']?.toString() ?? '';
|
||||
final String encryptedFp = record['fingerPrint']?.toString() ?? '';
|
||||
final String userType = record['userType']?.toString() ?? 'passenger';
|
||||
|
||||
if (passengerID.isEmpty || encryptedFp.isEmpty) {
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// ── فك التشفير ────────────────────────────────────────
|
||||
final String rawFp = EncryptionHelper.instance.decryptData(encryptedFp);
|
||||
|
||||
// ── تحليل البصمة ──────────────────────────────────────
|
||||
// الشكل القديم: "androidId_model_osVersion" (3 أجزاء أو أكثر)
|
||||
// الشكل الجديد: "androidId_model" (جزءان فقط)
|
||||
final List<String> parts = rawFp.split('_');
|
||||
|
||||
if (parts.length <= 2) {
|
||||
// البصمة بالفعل بالنظام الجديد — تجاوزها
|
||||
setState(() {
|
||||
_skipped++;
|
||||
_processed++;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// ── حذف آخر جزء (osVersion) ──────────────────────────
|
||||
// مثال: "abc123_SamsungA51_13" → "abc123_SamsungA51"
|
||||
// نأخذ أول جزأين فقط بغض النظر عن عدد الأجزاء
|
||||
final String newRawFp = '${parts[0]}_${parts[1]}';
|
||||
|
||||
// ── إعادة التشفير ─────────────────────────────────────
|
||||
final String newEncryptedFp =
|
||||
EncryptionHelper.instance.encryptData(newRawFp);
|
||||
|
||||
// ── رفع البصمة الجديدة للسيرفر ───────────────────────
|
||||
final response = await CRUD().post(
|
||||
link: AppLink.updateFingerprintAdmin, // أضفه في AppLink
|
||||
payload: {
|
||||
'passengerID': passengerID,
|
||||
'fingerprint': newEncryptedFp,
|
||||
'userType': userType,
|
||||
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd',
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure' && response?['status'] == 'success') {
|
||||
setState(() {
|
||||
_updated++;
|
||||
_processed++;
|
||||
});
|
||||
Log.print('✅ Updated: $passengerID | $rawFp → $newRawFp');
|
||||
} else {
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
Log.print('❌ Failed update: $passengerID');
|
||||
}
|
||||
} catch (e) {
|
||||
// فشل فك التشفير أو إعادة التشفير
|
||||
setState(() {
|
||||
_failed++;
|
||||
_processed++;
|
||||
});
|
||||
Log.print('❌ Process error for $passengerID: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
Log.print(message);
|
||||
setState(() => _currentLog = message);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// UI
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Fingerprint Migration Tool')),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ── شرح الأداة ──────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.orange.shade200),
|
||||
),
|
||||
child: const Text(
|
||||
'⚠️ هذه الأداة تُستخدم مرة واحدة فقط\n'
|
||||
'تقوم بتحديث بصمات الأجهزة القديمة\n'
|
||||
'لتكون متوافقة مع النظام الجديد (بدون OS version)',
|
||||
style: TextStyle(fontSize: 14, height: 1.6),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
print(EncryptionHelper.instance.decryptData('hbgbitbXrXrBr'));
|
||||
},
|
||||
child: Text(
|
||||
"Decrypt Test",
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
print(EncryptionHelper.instance.encryptData(
|
||||
'1B501143-C579-461C-B556-4E8B390EEFE1_iPhone'));
|
||||
},
|
||||
child: Text(
|
||||
"Encrypt Test",
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
print(r('hbgbitbXrXrBr'));
|
||||
},
|
||||
child: Text(
|
||||
"decrypt X.r",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── شريط التقدم ─────────────────────────────────
|
||||
if (_total > 0) ...[
|
||||
Text('التقدم: $_processed / $_total'),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: _total > 0 ? _processed / _total : 0,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
color: _isDone ? Colors.green : Colors.blue,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// ── إحصائيات ────────────────────────────────────
|
||||
if (_processed > 0)
|
||||
Row(children: [
|
||||
_statChip('محدَّث', _updated, Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
_statChip('متجاوز', _skipped, Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
_statChip('فاشل', _failed, Colors.red),
|
||||
]),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── السجل الحالي ─────────────────────────────────
|
||||
if (_currentLog.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(_currentLog,
|
||||
style: const TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// ── زر التشغيل ──────────────────────────────────
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: (_isRunning || _isDone) ? null : _startMigration,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _isDone ? Colors.green : Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: _isRunning
|
||||
? const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white, strokeWidth: 2),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Text('جارٍ الترحيل...',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
_isDone ? '✅ اكتمل الترحيل' : 'بدء الترحيل',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _statChip(String label, int value, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Text('$label: $value',
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:siro_admin/constant/colors.dart';
|
||||
import 'package:siro_admin/constant/links.dart';
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
|
||||
@@ -40,7 +39,7 @@ class ErrorLog {
|
||||
}
|
||||
|
||||
class ErrorListPage extends StatefulWidget {
|
||||
const ErrorListPage({Key? key}) : super(key: key);
|
||||
const ErrorListPage({super.key});
|
||||
|
||||
@override
|
||||
State<ErrorListPage> createState() => _ErrorListPageState();
|
||||
|
||||
@@ -204,8 +204,9 @@ class FinancialV2Page extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildSettlementsList(List<dynamic> settlements) {
|
||||
if (settlements.isEmpty)
|
||||
if (settlements.isEmpty) {
|
||||
return const Center(child: Text('لا توجد تسويات معلقة'));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
|
||||
@@ -214,7 +214,7 @@ class MarketingPage extends StatelessWidget {
|
||||
const SizedBox(width: 24),
|
||||
_buildStatItem(
|
||||
'الفارق',
|
||||
'${((double.tryParse(anomaly['competitor_price']?.toString() ?? '0') ?? 0) - (double.tryParse(anomaly['our_price']?.toString() ?? '0') ?? 0)).toStringAsFixed(2)}',
|
||||
((double.tryParse(anomaly['competitor_price']?.toString() ?? '0') ?? 0) - (double.tryParse(anomaly['our_price']?.toString() ?? '0') ?? 0)).toStringAsFixed(2),
|
||||
_info,
|
||||
),
|
||||
],
|
||||
@@ -264,7 +264,7 @@ class MarketingPage extends StatelessWidget {
|
||||
: 0.0;
|
||||
|
||||
List<FlSpot> competitorSpots = [];
|
||||
if (hourlyData is List && hourlyData.isNotEmpty) {
|
||||
if (hourlyData.isNotEmpty) {
|
||||
for (int i = 0; i < hourlyData.length && i < 24; i++) {
|
||||
final val = hourlyData[i];
|
||||
final double avg = double.tryParse((val['avg_price_per_km'] ?? 0).toString()) ?? 0.0;
|
||||
@@ -280,7 +280,7 @@ class MarketingPage extends StatelessWidget {
|
||||
|
||||
List<FlSpot> siroSpots = [];
|
||||
if (siroSpeedPrice > 0) {
|
||||
for (int i = 0; i < (hourlyData is List ? hourlyData.length : 1); i++) {
|
||||
for (int i = 0; i < hourlyData.length; i++) {
|
||||
siroSpots.add(FlSpot(i.toDouble(), siroSpeedPrice));
|
||||
}
|
||||
}
|
||||
@@ -303,7 +303,7 @@ class MarketingPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
hourlyData is List && hourlyData.isNotEmpty
|
||||
hourlyData.isNotEmpty
|
||||
? 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر ${hourlyData.length} ساعة'
|
||||
: 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر 24 ساعة',
|
||||
style: const TextStyle(fontSize: 10, color: _textSecondary),
|
||||
@@ -346,7 +346,7 @@ class MarketingPage extends StatelessWidget {
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
minX: 0,
|
||||
maxX: ((hourlyData is List ? hourlyData.length : 1) - 1).toDouble(),
|
||||
maxX: (hourlyData.length - 1).toDouble(),
|
||||
minY: 0,
|
||||
maxY: chartMaxY,
|
||||
lineBarsData: [
|
||||
@@ -391,10 +391,8 @@ class MarketingPage extends StatelessWidget {
|
||||
return GetBuilder<MarketingController>(
|
||||
builder: (c) {
|
||||
final heatmapList = c.heatmapData;
|
||||
final siroSpeedPrice = c.currentSiroPriceHeatmap;
|
||||
|
||||
List<Map<String, dynamic>> regions = [];
|
||||
if (heatmapList is List && heatmapList.isNotEmpty) {
|
||||
if (heatmapList.isNotEmpty) {
|
||||
for (final item in heatmapList) {
|
||||
final double pci = double.tryParse(item['pci'].toString()) ?? 1.0;
|
||||
final pct = ((1 - pci) * 100).abs().toStringAsFixed(1);
|
||||
@@ -433,7 +431,6 @@ class MarketingPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...regions.map((region) {
|
||||
final pciVal = (region['pci'] as num).toDouble();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
@@ -462,7 +459,7 @@ class MarketingPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -725,7 +722,7 @@ class MarketingPage extends StatelessWidget {
|
||||
style: const TextStyle(fontSize: 10, color: _textSecondary),
|
||||
),
|
||||
value: c.isAutopilotEnabled,
|
||||
activeColor: _accent,
|
||||
activeThumbColor: _accent,
|
||||
onChanged: c.toggleAutopilot,
|
||||
),
|
||||
),
|
||||
@@ -978,65 +975,4 @@ class MarketingPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWinbackCampaignsSection(BuildContext context, MarketingController c) {
|
||||
return Card(
|
||||
color: _surface,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: const BorderSide(color: _divider)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.radar, color: _accent, size: 24),
|
||||
SizedBox(width: 8),
|
||||
Text('حملات استعادة العملاء (Win-Back)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: _textPrimary)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'يتم البحث عن الركاب المنقطعين عن التطبيق والذين يتواجدون حالياً بالقرب من مناطق تشهد أسعار ذروة لدى المنافسين.',
|
||||
style: TextStyle(color: _textSecondary, fontSize: 11),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => c.fetchWinbackTargets(),
|
||||
icon: const Icon(Icons.person_search, size: 18),
|
||||
label: const Text('بحث عن أهداف حالية'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _accent.withOpacity(0.1),
|
||||
foregroundColor: _accent,
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (c.winbackTotalCount > 0) ...[
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Implement trigger specific winback campaign
|
||||
Get.snackbar("Campaign Triggered", "SMS sent to ${c.winbackTotalCount} targets");
|
||||
},
|
||||
icon: const Icon(Icons.send),
|
||||
label: Text('إرسال SMS لـ ${c.winbackTotalCount}'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _success,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:get/get.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:siro_admin/constant/links.dart';
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
import 'package:siro_admin/views/widgets/my_textField.dart';
|
||||
import 'package:siro_admin/views/widgets/snackbar.dart';
|
||||
|
||||
import '../../print.dart';
|
||||
|
||||
@@ -14,7 +14,6 @@ const Color _bg = Color(0xFF0D1117);
|
||||
const Color _surface = Color(0xFF161B22);
|
||||
const Color _surfaceElevated = Color(0xFF1C2333);
|
||||
const Color _accent = Color(0xFF00D4AA);
|
||||
const Color _danger = Color(0xFFFF5370);
|
||||
const Color _warning = Color(0xFFFFCB6B);
|
||||
const Color _info = Color(0xFF82AAFF);
|
||||
const Color _textPrimary = Color(0xFFE6EDF3);
|
||||
@@ -556,15 +555,7 @@ class PackageController extends GetxController {
|
||||
|
||||
updatePackages(String id, String version) async {
|
||||
if (version.trim().isEmpty) {
|
||||
Get.snackbar(
|
||||
'تنبيه',
|
||||
'يرجى إدخال رقم الإصدار',
|
||||
backgroundColor: _warning.withOpacity(0.15),
|
||||
colorText: _textPrimary,
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(16),
|
||||
icon: const Icon(Icons.warning_rounded, color: _warning),
|
||||
);
|
||||
mySnackbarWarning('يرجى إدخال رقم الإصدار');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -578,26 +569,10 @@ class PackageController extends GetxController {
|
||||
|
||||
if (response != 'failure') {
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
'تم التحديث',
|
||||
'تم تحديث الإصدار بنجاح',
|
||||
backgroundColor: _accent.withOpacity(0.15),
|
||||
colorText: _textPrimary,
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(16),
|
||||
icon: const Icon(Icons.check_circle_rounded, color: _accent),
|
||||
);
|
||||
mySnackbarSuccess('تم تحديث الإصدار بنجاح');
|
||||
fetchPackages();
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'خطأ',
|
||||
'فشل التحديث، يرجى المحاولة مجدداً',
|
||||
backgroundColor: _danger.withOpacity(0.15),
|
||||
colorText: _textPrimary,
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(16),
|
||||
icon: const Icon(Icons.error_rounded, color: _danger),
|
||||
);
|
||||
mySnackbarError('فشل التحديث، يرجى المحاولة مجدداً');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import '../../widgets/mycircular.dart';
|
||||
import '../../widgets/snackbar.dart';
|
||||
import 'passenger_details_page.dart';
|
||||
|
||||
class Passengrs extends StatelessWidget {
|
||||
@@ -376,15 +377,7 @@ class Passengrs extends StatelessWidget {
|
||||
Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Not Allowed'.tr,
|
||||
'Prizes can only be added on Saturdays.'.tr,
|
||||
backgroundColor: Colors.red.withOpacity(0.1),
|
||||
colorText: Colors.red,
|
||||
icon: const Icon(Icons.error_outline, color: Colors.red),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
margin: const EdgeInsets.all(10),
|
||||
);
|
||||
mySnackbarWarning('Prizes can only be added on Saturdays.'.tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@ import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../views/widgets/snackbar.dart';
|
||||
import '../../../controller/admin/passenger_admin_controller.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../controller/firebase/firbase_messge.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../main.dart'; // To access 'box' for admin phone check
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import 'form_passenger.dart';
|
||||
|
||||
class PassengerDetailsPage extends StatelessWidget {
|
||||
const PassengerDetailsPage({super.key});
|
||||
@@ -399,8 +398,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
||||
// data['passengerToken'],
|
||||
// 'order.wav');
|
||||
Get.back();
|
||||
Get.snackbar('Success', 'Notification sent successfully!',
|
||||
backgroundColor: Colors.green.withOpacity(0.2));
|
||||
mySnackbarSuccess('Notification sent successfully!');
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -438,13 +436,11 @@ class PassengerDetailsPage extends StatelessWidget {
|
||||
// 3. Handle Result
|
||||
if (res['status'] == 'success') {
|
||||
Get.back(); // Go back to list page
|
||||
Get.snackbar('Deleted', 'Passenger removed successfully',
|
||||
backgroundColor: Colors.red.withOpacity(0.2));
|
||||
mySnackbarWarning('Passenger removed successfully');
|
||||
// Ideally, trigger a refresh on the controller here
|
||||
// Get.find<PassengerAdminController>().getAll();
|
||||
} else {
|
||||
Get.snackbar('Error', res['message'] ?? 'Failed to delete',
|
||||
backgroundColor: Colors.red.withOpacity(0.2));
|
||||
mySnackbarError(res['message'] ?? 'Failed to delete');
|
||||
}
|
||||
},
|
||||
child: Text('Delete'.tr, style: const TextStyle(color: Colors.white)),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../constant/style.dart';
|
||||
import '../../../controller/admin/kazan_controller.dart';
|
||||
import '../../../views/widgets/snackbar.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
|
||||
@@ -17,39 +18,77 @@ class KazanEditorPage extends StatelessWidget {
|
||||
title: 'تعديل أسعار كازان'.tr,
|
||||
isleading: true,
|
||||
body: [
|
||||
Obx(() => controller.isLoading.value && controller.kazanData.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
Column(
|
||||
children: [
|
||||
_buildCountryDropdown(),
|
||||
Expanded(
|
||||
child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionHeader('⚙️ الإعدادات العامة'),
|
||||
_buildGeneralSettings(),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'),
|
||||
_buildKmPricesGrid(),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'),
|
||||
_buildMinutePrices(),
|
||||
const SizedBox(height: 32),
|
||||
MyElevatedButton(
|
||||
title: '💾 حفظ جميع التعديلات',
|
||||
icon: Icons.save_rounded,
|
||||
onPressed: () => _handleSave(),
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCountryDropdown() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: Obx(() => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColor.divider),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: controller.selectedCountry.value,
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
items: controller.countries.map((c) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: c['name'],
|
||||
child: Row(
|
||||
children: [
|
||||
// ⚙️ الإعدادات العامة
|
||||
_buildSectionHeader('⚙️ الإعدادات العامة'),
|
||||
_buildGeneralSettings(),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 🚗 أسعار الكيلومتر لكل نوع سيارة
|
||||
_buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'),
|
||||
_buildKmPricesGrid(),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ⏱️ أسعار الدقيقة
|
||||
_buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'),
|
||||
_buildMinutePrices(),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 💾 زر الحفظ
|
||||
MyElevatedButton(
|
||||
title: '💾 حفظ جميع التعديلات',
|
||||
icon: Icons.save_rounded,
|
||||
onPressed: () => _handleSave(),
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Text(c['flag']!, style: const TextStyle(fontSize: 22)),
|
||||
const SizedBox(width: 12),
|
||||
Text(c['name']!),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (val) {
|
||||
if (val != null) controller.setCountry(val);
|
||||
},
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -373,26 +412,18 @@ class KazanEditorPage extends StatelessWidget {
|
||||
final data = Map<String, dynamic>.from(controller.kazanData);
|
||||
data['adminId'] = 'admin1';
|
||||
|
||||
// التأكد من وجود country (إذا لم يكن موجوداً، استخدم 'syria')
|
||||
// التأكد من وجود country
|
||||
if (!data.containsKey('country') ||
|
||||
data['country'] == null ||
|
||||
data['country'].toString().isEmpty) {
|
||||
data['country'] = 'Syria';
|
||||
data['country'] = controller.selectedCountry.value;
|
||||
}
|
||||
|
||||
bool success = await controller.updateKazan(data);
|
||||
if (success) {
|
||||
Get.snackbar("نجاح", "تم تحديث الأسعار بنجاح",
|
||||
backgroundColor: AppColor.successSoft,
|
||||
colorText: AppColor.textPrimary,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
margin: const EdgeInsets.all(16));
|
||||
mySnackbarSuccess('تم تحديث أسعار ${controller.selectedCountry.value} بنجاح');
|
||||
} else {
|
||||
Get.snackbar("خطأ", "فشل تحديث الأسعار",
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: AppColor.textPrimary,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
margin: const EdgeInsets.all(16));
|
||||
mySnackbarError('فشل تحديث الأسعار');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
||||
const Color(0xFFEF4444))),
|
||||
Obx(() => _buildStatCard(
|
||||
'الإيرادات',
|
||||
'${controller.totalRevenue.value.toStringAsFixed(0)}',
|
||||
controller.totalRevenue.value.toStringAsFixed(0),
|
||||
Icons.payments_rounded,
|
||||
const Color(0xFFF59E0B))),
|
||||
],
|
||||
@@ -817,8 +817,9 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
String formattedPhone = phone;
|
||||
if (!formattedPhone.startsWith('+'))
|
||||
if (!formattedPhone.startsWith('+')) {
|
||||
formattedPhone = '+$formattedPhone';
|
||||
}
|
||||
final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone);
|
||||
if (await canLaunchUrl(launchUri)) await launchUrl(launchUri);
|
||||
},
|
||||
@@ -860,11 +861,13 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
||||
|
||||
// Helper Methods for Status
|
||||
Color _getStatusColor(String status) {
|
||||
if (status == 'Begin' || status == 'Arrived')
|
||||
if (status == 'Begin' || status == 'Arrived') {
|
||||
return const Color(0xFF10B981);
|
||||
}
|
||||
if (status == 'Finished') return const Color(0xFF14B8A6);
|
||||
if (status.contains('Cancel') || status == 'TimeOut')
|
||||
if (status.contains('Cancel') || status == 'TimeOut') {
|
||||
return const Color(0xFFEF4444);
|
||||
}
|
||||
if (status == 'New') return const Color(0xFF3B82F6);
|
||||
return Colors.grey;
|
||||
}
|
||||
@@ -872,8 +875,9 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
||||
String _getStatusText(String status) {
|
||||
if (status == 'Begin' || status == 'Arrived') return 'جارية';
|
||||
if (status == 'Finished') return 'مكتملة';
|
||||
if (status == 'CancelFromDriver' || status == 'CancelFromDriverAfterApply')
|
||||
if (status == 'CancelFromDriver' || status == 'CancelFromDriverAfterApply') {
|
||||
return 'ألغى السائق';
|
||||
}
|
||||
if (status == 'CancelFromPassenger') return 'ألغى الراكب';
|
||||
if (status == 'TimeOut') return 'انتهى الوقت';
|
||||
if (status == 'New') return 'جديدة';
|
||||
@@ -1147,12 +1151,6 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
||||
required String phone,
|
||||
required Color color,
|
||||
}) {
|
||||
String displayPhone = phone;
|
||||
if (!widget.isAdmin && phone.length > 4) {
|
||||
displayPhone =
|
||||
phone.substring(phone.length - 4).padLeft(phone.length, '*');
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
@@ -1192,8 +1190,9 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
String formattedPhone = phone;
|
||||
if (!formattedPhone.startsWith('+'))
|
||||
if (!formattedPhone.startsWith('+')) {
|
||||
formattedPhone = '+$formattedPhone';
|
||||
}
|
||||
final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone);
|
||||
if (await canLaunchUrl(launchUri)) await launchUrl(launchUri);
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../../widgets/mycircular.dart';
|
||||
|
||||
class Rides extends StatelessWidget {
|
||||
Rides({super.key});
|
||||
RideAdminController rideAdminController = Get.put(RideAdminController());
|
||||
final RideAdminController rideAdminController = Get.put(RideAdminController());
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScafolld(title: 'Rides'.tr, isleading: true, body: [
|
||||
|
||||
@@ -2,15 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/constant/colors.dart';
|
||||
import 'package:siro_admin/controller/admin/security_v2_controller.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class AuditLogsPage extends StatelessWidget {
|
||||
const AuditLogsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(SecurityV2Controller());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.bg,
|
||||
appBar: AppBar(
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:get/get.dart';
|
||||
import '../../../controller/server/server_monitor_controller.dart';
|
||||
|
||||
class ServerMonitorPage extends StatelessWidget {
|
||||
const ServerMonitorPage({Key? key}) : super(key: key);
|
||||
const ServerMonitorPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -187,7 +187,7 @@ class AddStaffPage extends StatelessWidget {
|
||||
border: Border.all(color: Colors.white.withOpacity(0.05)),
|
||||
),
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: value,
|
||||
initialValue: value,
|
||||
dropdownColor: fillColor,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -33,7 +33,7 @@ class _PendingAdminsPageState extends State<PendingAdminsPage> {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('فشل في جلب البيانات: $e');
|
||||
mySnackbarError('فشل في جلب البيانات: $e');
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class _PendingAdminsPageState extends State<PendingAdminsPage> {
|
||||
_fetchPendingAdmins(); // تحديث القائمة
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('حدث خطأ: $e');
|
||||
mySnackbarError('حدث خطأ: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/constant/colors.dart';
|
||||
import 'package:siro_admin/controller/admin/analytics_v2_controller.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class AdvancedAnalyticsPage extends StatelessWidget {
|
||||
const AdvancedAnalyticsPage({super.key});
|
||||
@@ -191,8 +190,9 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
||||
final passengers = data['passenger_daily'] as List<dynamic>? ?? [];
|
||||
final drivers = data['driver_daily'] as List<dynamic>? ?? [];
|
||||
|
||||
if (passengers.isEmpty && drivers.isEmpty)
|
||||
if (passengers.isEmpty && drivers.isEmpty) {
|
||||
return const Center(child: Text('لا توجد بيانات'));
|
||||
}
|
||||
|
||||
List<BarChartGroupData> barGroups = [];
|
||||
int maxLength =
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart' hide TextDirection;
|
||||
import 'package:siro_admin/controller/functions/launch.dart';
|
||||
|
||||
import '../../../controller/admin/static_controller.dart';
|
||||
|
||||
@@ -26,8 +26,6 @@ class StaticDash extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(StaticController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _bg,
|
||||
body: GetBuilder<StaticController>(
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../../widgets/my_scafold.dart';
|
||||
|
||||
class Wallet extends StatelessWidget {
|
||||
Wallet({super.key});
|
||||
WalletAdminController walletAdminController =
|
||||
final WalletAdminController walletAdminController =
|
||||
Get.put(WalletAdminController());
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Reference in New Issue
Block a user