feat: Add Intaleq Native Android Kotlin & iOS Swift SDKs, Incline slope routing, Topographic contours and modular tactical app
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/offline_package_manager.dart';
|
||||
|
||||
class OfflinePackageDialog extends StatefulWidget {
|
||||
final VoidCallback onPackageSynced;
|
||||
|
||||
const OfflinePackageDialog({super.key, required this.onPackageSynced});
|
||||
|
||||
@override
|
||||
State<OfflinePackageDialog> createState() => _OfflinePackageDialogState();
|
||||
}
|
||||
|
||||
class _OfflinePackageDialogState extends State<OfflinePackageDialog> {
|
||||
bool _isDownloading = false;
|
||||
OfflinePackageInfo? _info;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPackageInfo();
|
||||
}
|
||||
|
||||
Future<void> _loadPackageInfo() async {
|
||||
final info = await OfflinePackageManager.getPackageInfo();
|
||||
if (mounted) setState(() => _info = info);
|
||||
}
|
||||
|
||||
Future<void> _startSync({bool force = false}) async {
|
||||
setState(() => _isDownloading = true);
|
||||
final success = await OfflinePackageManager.downloadFullPackage(force: force);
|
||||
if (mounted) {
|
||||
setState(() => _isDownloading = false);
|
||||
await _loadPackageInfo();
|
||||
if (success) {
|
||||
widget.onPackageSynced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canSync = _info?.canSyncNow ?? true;
|
||||
final remaining = _info?.daysUntilNextSync ?? 0;
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: const Color(0xFF0F172A),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: const BorderSide(color: Color(0xFF0071E3), width: 1.5)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x330071E3),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFF0071E3)),
|
||||
),
|
||||
child: const Icon(Icons.download_for_offline, color: Color(0xFF38BDF8), size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'حزمة بيانات الأردن التكتيكية',
|
||||
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'Offline Sovereign Data Package v2.4',
|
||||
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 10.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.white54, size: 18),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Package Stats Container
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF020617),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildStatRow('حجم حزمة التوجيه والطرق:', '3.5 MB (شبكة طوبولوجية كاملة)'),
|
||||
const Divider(color: Colors.white12, height: 14),
|
||||
_buildStatRow('حجم معالم PostGIS المفهرسة:', '~25 MB (SQLite FTS5 سريع)'),
|
||||
const Divider(color: Colors.white12, height: 14),
|
||||
_buildStatRow('محرك التوجيه غير المتصل:', '100% On-Device (خوارزمية A*)'),
|
||||
const Divider(color: Colors.white12, height: 14),
|
||||
_buildStatRow('قفل المزامنة الميداني:', 'كل 14 يوماً لحماية السيرفر'),
|
||||
if (_info?.lastSyncTime != null) ...[
|
||||
const Divider(color: Colors.white12, height: 14),
|
||||
_buildStatRow(
|
||||
'آخر مزامنة ناجحة:',
|
||||
'${_info!.lastSyncTime!.day}/${_info!.lastSyncTime!.month}/${_info!.lastSyncTime!.year}',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Live Sync Progress Stream
|
||||
ValueListenableBuilder<double>(
|
||||
valueListenable: OfflinePackageManager.syncProgress,
|
||||
builder: (context, progress, _) {
|
||||
return ValueListenableBuilder<String>(
|
||||
valueListenable: OfflinePackageManager.syncStatusMessage,
|
||||
builder: (context, msg, _) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isDownloading) ...[
|
||||
LinearProgressIndicator(
|
||||
value: progress > 0 ? progress : null,
|
||||
backgroundColor: const Color(0xFF1E293B),
|
||||
color: const Color(0xFF0071E3),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Text(
|
||||
msg,
|
||||
style: TextStyle(
|
||||
color: progress == 1.0 ? const Color(0xFF4ADE80) : const Color(0xFF94A3B8),
|
||||
fontSize: 11,
|
||||
fontWeight: progress == 1.0 ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Action Buttons
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isDownloading
|
||||
? null
|
||||
: (canSync
|
||||
? () => _startSync()
|
||||
: () => _startSync(force: true)),
|
||||
icon: _isDownloading
|
||||
? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: Icon(canSync ? Icons.sync : Icons.lock_clock, size: 16),
|
||||
label: Text(
|
||||
_isDownloading
|
||||
? 'جاري المزامنة...'
|
||||
: (canSync
|
||||
? 'مزامنة وتحديث الحزمة الآن'
|
||||
: 'الحزمة محدثة (تحديث إجباري / متبقي $remaining يوم)'),
|
||||
style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.bold),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: canSync ? const Color(0xFF0071E3) : const Color(0xFF1E293B),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(color: canSync ? const Color(0xFF0071E3) : Colors.white24),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatRow(String title, String value) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 11)),
|
||||
Text(value, style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user