المجلدات وأسماء حزم 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>
233 lines
7.7 KiB
Dart
233 lines
7.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'performance_test.dart'; // Make sure this path is correct
|
|
|
|
/// Analyzes various device hardware and software aspects to generate a compatibility score.
|
|
/// This class provides a standardized output for the UI to consume easily.
|
|
class DeviceAnalyzer {
|
|
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
|
|
|
|
// نتيجة الفحص لا تتغير عمليا خلال جلسة واحدة، فنخزنها مؤقتا
|
|
// كي لا تُعاد الاختبارات الثقيلة (كتابة تخزين + حساب مكثف) كل مرة تُفتح الصفحة.
|
|
static List<Map<String, dynamic>>? _cachedResult;
|
|
static DateTime? _cachedAt;
|
|
static const Duration _cacheTtl = Duration(minutes: 30);
|
|
|
|
/// Reads the total RAM from the system's meminfo file.
|
|
/// Returns the value in Megabytes (MB).
|
|
Future<double> _readTotalRamMB() async {
|
|
try {
|
|
final file = File('/proc/meminfo');
|
|
if (!await file.exists()) return 0.0;
|
|
final lines = await file.readAsLines();
|
|
for (var line in lines) {
|
|
if (line.startsWith('MemTotal')) {
|
|
// Extracts the numeric value from the line.
|
|
final kb = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '0') ?? 0;
|
|
return kb / 1024.0; // Convert from Kilobytes to Megabytes
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('❌ Error reading total RAM: $e');
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
/// Reads the current RAM usage percentage from the system's meminfo file.
|
|
Future<double> _readUsedRamPercent() async {
|
|
try {
|
|
final file = File('/proc/meminfo');
|
|
if (!await file.exists()) return 0.0;
|
|
final lines = await file.readAsLines();
|
|
int? total, available;
|
|
for (var line in lines) {
|
|
if (line.startsWith('MemTotal')) {
|
|
total = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
|
|
} else if (line.startsWith('MemAvailable')) {
|
|
available = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
|
|
}
|
|
}
|
|
if (total != null && available != null && total > 0) {
|
|
final used = total - available;
|
|
return (used / total) * 100.0;
|
|
}
|
|
} catch (e) {
|
|
print('❌ Error reading used RAM: $e');
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
/// The main analysis function that runs all checks.
|
|
///
|
|
/// البنود من 1 إلى 7 تقيس قدرة الجهاز الثابتة (لا تتغير خلال الجلسة) وتُخزَّن
|
|
/// مؤقتا لتفادي إعادة تشغيل الاختبارات الثقيلة (كتابة تخزين + حساب مكثف) في كل
|
|
/// مرة تُفتح فيها الصفحة. أما بند ضغط الذاكرة الحالي (#8) فيُقاس دائما بشكل فوري
|
|
/// لأنه رقم متغير باستمرار ولا يصح تجميده مع بقية النتيجة.
|
|
Future<Map<String, dynamic>> analyzeDevice({bool forceRefresh = false}) async {
|
|
if (!Platform.isAndroid) {
|
|
return {
|
|
'score': 0,
|
|
'details': [
|
|
{
|
|
'label': 'النظام غير مدعوم',
|
|
'status': false,
|
|
'achieved_score': 0,
|
|
'max_score': 100
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
List<Map<String, dynamic>> staticDetails;
|
|
if (!forceRefresh &&
|
|
_cachedResult != null &&
|
|
_cachedAt != null &&
|
|
DateTime.now().difference(_cachedAt!) < _cacheTtl) {
|
|
staticDetails = _cachedResult!;
|
|
} else {
|
|
staticDetails = await _computeStaticDetails();
|
|
_cachedResult = staticDetails;
|
|
_cachedAt = DateTime.now();
|
|
}
|
|
|
|
// 8. Memory Pressure (Max: 15 points) — يُقاس دائما بشكل فوري، لا يُخزَّن مؤقتا.
|
|
final usedPercent = await _readUsedRamPercent();
|
|
int memScore;
|
|
if (usedPercent <= 60) {
|
|
memScore = 15;
|
|
} else if (usedPercent <= 80) {
|
|
memScore = 10;
|
|
} else if (usedPercent <= 90) {
|
|
memScore = 5;
|
|
} else {
|
|
memScore = 0;
|
|
}
|
|
final memDetail = {
|
|
'label': 'استخدام الرام الحالي (${usedPercent.toStringAsFixed(0)}%)',
|
|
'status': memScore >= 10,
|
|
'achieved_score': memScore,
|
|
'max_score': 15,
|
|
};
|
|
|
|
final details = [...staticDetails, memDetail];
|
|
final totalScore = details.fold<int>(
|
|
0, (sum, item) => sum + (item['achieved_score'] as int));
|
|
|
|
return {
|
|
'score': totalScore.clamp(0, 100),
|
|
'details': details,
|
|
};
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> _computeStaticDetails() async {
|
|
List<Map<String, dynamic>> details = [];
|
|
|
|
final info = await _deviceInfo.androidInfo;
|
|
final data = info.data;
|
|
final features = List<String>.from(data['systemFeatures'] ?? []);
|
|
|
|
// 1. Android Version (Max: 10 points)
|
|
final version =
|
|
int.tryParse(info.version.release?.split('.').first ?? '0') ?? 0;
|
|
final int androidScore = version >= 9 ? 10 : 0;
|
|
details.add({
|
|
'label': 'إصدار أندرويد ${info.version.release}',
|
|
'status': androidScore > 0,
|
|
'achieved_score': androidScore,
|
|
'max_score': 10,
|
|
});
|
|
|
|
// 2. Total RAM (Max: 10 points)
|
|
final totalRam = await _readTotalRamMB();
|
|
int ramScore;
|
|
if (totalRam >= 8000) {
|
|
ramScore = 10;
|
|
} else if (totalRam >= 4000) {
|
|
ramScore = 5;
|
|
} else if (totalRam >= 3000) {
|
|
ramScore = 3;
|
|
} else {
|
|
ramScore = 0;
|
|
}
|
|
details.add({
|
|
'label': 'إجمالي الرام ${totalRam.toStringAsFixed(0)} ميجابايت',
|
|
'status': ramScore >= 5,
|
|
'achieved_score': ramScore,
|
|
'max_score': 10,
|
|
});
|
|
|
|
// 3. CPU Cores (Max: 10 points)
|
|
final cores = Platform.numberOfProcessors;
|
|
int coreScore = cores >= 6 ? 10 : (cores >= 4 ? 5 : 0);
|
|
details.add({
|
|
'label': 'أنوية المعالج ($cores)',
|
|
'status': coreScore >= 5,
|
|
'achieved_score': coreScore,
|
|
'max_score': 10,
|
|
});
|
|
|
|
// 4. Free Storage (Max: 5 points)
|
|
final freeBytes = data['freeDiskSize'] ?? 0;
|
|
final freeGB = freeBytes / (1024 * 1024 * 1024);
|
|
int storeScore = freeGB >= 5 ? 5 : (freeGB >= 2 ? 3 : 0);
|
|
details.add({
|
|
'label': 'المساحة الحرة ${freeGB.toStringAsFixed(1)} جيجابايت',
|
|
'status': storeScore >= 3,
|
|
'achieved_score': storeScore,
|
|
'max_score': 5,
|
|
});
|
|
|
|
// 5. GPS + Gyroscope Sensors (Max: 10 points)
|
|
bool okSensors = features.contains('android.hardware.location.gps') &&
|
|
features.contains('android.hardware.sensor.gyroscope');
|
|
final int sensorScore = okSensors ? 10 : 0;
|
|
details.add({
|
|
'label': 'حساسات GPS و Gyroscope',
|
|
'status': okSensors,
|
|
'achieved_score': sensorScore,
|
|
'max_score': 10,
|
|
});
|
|
|
|
// 6. Storage Write Speed (Max: 20 points)
|
|
final writeSpeed = await PerformanceTester.testStorageWriteSpeed();
|
|
int writeScore;
|
|
if (writeSpeed >= 30) {
|
|
writeScore = 20;
|
|
} else if (writeSpeed >= 15) {
|
|
writeScore = 15;
|
|
} else if (writeSpeed >= 5) {
|
|
writeScore = 10;
|
|
} else {
|
|
writeScore = 5;
|
|
}
|
|
details.add({
|
|
'label': 'سرعة الكتابة (${writeSpeed.toStringAsFixed(1)} MB/s)',
|
|
'status': writeScore >= 10,
|
|
'achieved_score': writeScore,
|
|
'max_score': 20,
|
|
});
|
|
|
|
// 7. CPU Compute Speed (Max: 20 points)
|
|
final cpuTime = await PerformanceTester.testCPUSpeed();
|
|
int cpuScore;
|
|
if (cpuTime <= 1.0) {
|
|
cpuScore = 20;
|
|
} else if (cpuTime <= 2.5) {
|
|
cpuScore = 15;
|
|
} else if (cpuTime <= 4.0) {
|
|
cpuScore = 10;
|
|
} else {
|
|
cpuScore = 5;
|
|
}
|
|
details.add({
|
|
'label': 'سرعة المعالجة (${cpuTime.toStringAsFixed(2)} ثانية)',
|
|
'status': cpuScore >= 10,
|
|
'achieved_score': cpuScore,
|
|
'max_score': 20,
|
|
});
|
|
|
|
return details;
|
|
}
|
|
}
|