Files
intaleq/intaleq_service/lib/controller/functions/session_manager.dart
T
Hamza-AyedandClaude Opus 5 0aab85c732 refactor: إعادة تسمية التطبيقات الأربعة siro_* → intaleq_*
المجلدات وأسماء حزم dart واستيراداتها:
  siro_rider → intaleq_rider    siro_driver  → intaleq_driver
  siro_admin → intaleq_admin    siro_service → intaleq_service

شمل ذلك `name:` في كل pubspec وكل `package:siro_*` في الاستيرادات
(115 ملفاً في rider وحده)، وإشارات أسماء المجلدات في docs/ وتعليق في
backend/Admin/notifications/broadcast.php. لم تبقَ إشارة واحدة (تحقّقت).

+ ضُمّت حزم get و get_storage داخل intaleq_driver/packages/ وحُوّلت
مساراتها الثلاثة من `../../Intaleq/packages/*` إلى `./packages/*`. كانت
تُحل عرضاً إلى مجلد App/Intaleq القديم المجاور — تبعية خارج المستودع
تنكسر بأول نقل. لم يبقَ في أي تطبيق تبعية مسار خارج الشجرة.

⚠️ لم تُمسّ بعد: هويات المتاجر (applicationId · PRODUCT_BUNDLE_IDENTIFIER ·
shorebird app_id) ولا الألوان ولا النصوص المرئية ولا النطاقات — كل منها
كوميت منفصل، والهويات تحتاج قرار المالك.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 16:10:58 +03:00

205 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_service/constant/box_name.dart';
import 'package:intaleq_service/controller/functions/crud.dart';
import 'package:intaleq_service/main.dart';
import 'package:intaleq_service/print.dart';
import 'package:intaleq_service/controller/functions/encrypt_decrypt.dart';
import 'package:intaleq_service/env/env.dart';
/// مدير الجلسة - مسؤول عن مراقبة صلاحية الجلسة
/// وتجديدها تلقائياً عند انتهائها
class SessionManager extends GetxController {
static SessionManager get instance => Get.find<SessionManager>();
/// حالة الجلسة الحالية
final Rx<SessionStatus> status = SessionStatus.valid.obs;
/// هل يتم تجديد الجلسة حالياً؟
final RxBool isRefreshing = false.obs;
/// عدد مرات إعادة المحاولة في حال فشل التجديد
static const int _maxRetries = 3;
@override
void onInit() {
super.onInit();
Log.print('🔐 SessionManager initialized');
}
/// التحقق من صلاحية الجلسة الحالية
SessionStatus checkSessionValidity() {
try {
final rawToken = box.read(BoxName.jwt)?.toString() ?? '';
if (rawToken.isEmpty) {
status.value = SessionStatus.expired;
return SessionStatus.expired;
}
final token = r(rawToken).toString().split(Env.addd)[0];
final isValid = CRUD.isJwtValid(token);
if (!isValid) {
status.value = SessionStatus.expired;
return SessionStatus.expired;
}
status.value = SessionStatus.valid;
return SessionStatus.valid;
} catch (e) {
Log.print('⚠️ Session check error: $e');
return SessionStatus.unknown;
}
}
/// محاولة تجديد الجلسة تلقائياً مع إظهار إشعار للمستخدم
Future<bool> refreshSession({bool silent = false}) async {
// إذا كان التجديد قيد التنفيذ بالفعل، ننتظر
if (isRefreshing.value) {
Log.print('🔄 Session refresh already in progress, waiting...');
// ننتظر حتى ينتهي التجديد الحالي
while (isRefreshing.value) {
await Future.delayed(const Duration(milliseconds: 500));
}
// نتحقق مرة أخرى بعد انتهاء التجديد
final rawToken = box.read(BoxName.jwt)?.toString() ?? '';
final token = rawToken.isNotEmpty ? r(rawToken).toString().split(Env.addd)[0] : '';
final isValid = CRUD.isJwtValid(token);
return isValid;
}
isRefreshing.value = true;
status.value = SessionStatus.refreshing;
try {
// إظهار إشعار للمستخدم بأن الجلسة تنتهي ويتم تجديدها
if (!silent) {
_showSessionExpiredNotification();
}
// محاولة تجديد الجلسة مع إعادة المحاولة
for (int attempt = 1; attempt <= _maxRetries; attempt++) {
Log.print('🔄 Session refresh attempt $attempt/$_maxRetries');
final success = await CRUD().getJWT();
if (success) {
status.value = SessionStatus.valid;
isRefreshing.value = false;
if (!silent) {
_showSessionRefreshedNotification();
}
Log.print('✅ Session refreshed successfully');
return true;
}
if (attempt < _maxRetries) {
await Future.delayed(Duration(seconds: attempt));
}
}
// فشلت كل محاولات التجديد
status.value = SessionStatus.expired;
isRefreshing.value = false;
if (!silent) {
_showSessionExpiredFinalNotification();
}
Log.print('❌ Session refresh failed after $_maxRetries attempts');
return false;
} catch (e) {
status.value = SessionStatus.expired;
isRefreshing.value = false;
Log.print('❌ Session refresh error: $e');
if (!silent) {
_showSessionExpiredFinalNotification();
}
return false;
}
}
/// إشعار للمستخدم: الجلسة على وشك الانتهاء ويتم تجديدها
void _showSessionExpiredNotification() {
if (!Get.isSnackbarOpen) {
Get.snackbar(
'⚠️ جاري تجديد الجلسة'.tr,
'انتهت صلاحية الجلسة، يتم تجديدها تلقائياً...'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.orange.withValues(alpha: 0.9),
colorText: Colors.white,
duration: const Duration(seconds: 3),
icon: const Icon(
Icons.refresh_rounded,
color: Colors.white,
size: 28,
),
);
}
}
/// إشعار للمستخدم: تم تجديد الجلسة بنجاح
void _showSessionRefreshedNotification() {
Get.snackbar(
'✅ تم تجديد الجلسة'.tr,
'تم تجديد الجلسة بنجاح، يمكنك متابعة العمل'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green.withValues(alpha: 0.9),
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
}
/// إشعار للمستخدم: فشل تجديد الجلسة - يجب إعادة تسجيل الدخول
void _showSessionExpiredFinalNotification() {
Get.defaultDialog(
title: '🔒 انتهت الجلسة'.tr,
titleStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
middleText:
'انتهت صلاحية الجلسة ولم نتمكن من تجديدها تلقائياً.\nيرجى إعادة تسجيل الدخول للمتابعة.'
.tr,
middleTextStyle: const TextStyle(fontSize: 16),
confirm: ElevatedButton(
onPressed: () {
Get.back();
_forceLogout();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
),
child: const Text('إعادة تسجيل الدخول'),
),
barrierDismissible: false,
);
}
/// تسجيل الخروج القسري عند فشل تجديد الجلسة
void _forceLogout() {
try {
box.erase();
} catch (_) {}
Get.offAllNamed('/login');
}
}
/// حالات الجلسة
enum SessionStatus {
/// الجلسة صالحة
valid,
/// الجلسة منتهية الصلاحية
expired,
/// جاري تجديد الجلسة
refreshing,
/// حالة غير معروفة
unknown,
}