import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart' show kIsWeb; void main() { runApp( const MaterialApp(home: MapScreen(), debugShowCheckedModeBanner: false), ); } class MapScreen extends StatefulWidget { const MapScreen({super.key}); @override State createState() => _MapScreenState(); } class _MapScreenState extends State { MapLibreMapController? mapController; bool isLoading = false; String info = '📍 نظام انطلق المتكامل'; bool styleLoaded = false; bool isSyncing = false; bool isContinuousSyncEnabled = true; // Default to ON for premium experience double syncProgress = 0.0; // --- Feature: Custom Car Marker --- Future _addCarMarker() async { if (!styleLoaded || mapController == null) return; try { // Load the car icon image (from network for quick demo) final ByteData bytes = await NetworkAssetBundle( Uri.parse( 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Car_icon_blue.png/64px-Car_icon_blue.png', ), ).load(''); final Uint8List list = bytes.buffer.asUint8List(); await mapController!.addImage("car-icon", list); await mapController!.addSymbol( SymbolOptions( geometry: const LatLng(33.513, 36.276), // Damascus city center iconImage: "car-icon", iconSize: 0.5, textField: "سيارة انطلاقة #101", textOffset: const Offset(0, 2), textColor: "#3b82f6", textHaloColor: "#ffffff", textHaloWidth: 2, ), ); setState(() => info = '✅ تمت إضافة علامة السيارة'); } catch (e) { setState(() => info = 'خطأ في إضافة العلامة: $e'); } } // --- Feature: Polylines (Lines) --- Future _addSampleLine() async { if (!styleLoaded || mapController == null) return; await mapController!.addLine( LineOptions( geometry: [ const LatLng(33.515, 36.270), const LatLng(33.520, 36.280), const LatLng(33.510, 36.290), ], lineColor: "#f59e0b", lineWidth: 5.0, lineOpacity: 0.8, ), ); setState(() => info = '✅ تمت إضافة خط مسار'); } // --- Feature: Polygons (Geofence) --- Future _addSamplePolygon() async { if (!styleLoaded || mapController == null) return; await mapController!.addFill( FillOptions( geometry: [ [ const LatLng(33.518, 36.273), const LatLng(33.522, 36.273), const LatLng(33.522, 36.3), const LatLng(33.518, 36.4), const LatLng(33.518, 36.273), ], ], fillColor: "#10b981", fillOpacity: 0.3, fillOutlineColor: "#065f46", ), ); setState(() => info = '✅ تمت إضافة سياج جغرافي'); } // --- Feature: Circles --- Future _addSampleCircle() async { if (!styleLoaded || mapController == null) return; await mapController!.addCircle( CircleOptions( geometry: const LatLng(33.512, 36.280), circleColor: "#ef4444", circleRadius: 15.0, circleOpacity: 0.6, circleStrokeColor: "#ffffff", circleStrokeWidth: 2.0, ), ); setState(() => info = '✅ تمت إضافة نقطة محيطية'); } // --- Feature: Routing --- Future computeRoute() async { if (!styleLoaded || mapController == null) return; setState(() { isLoading = true; info = '⚡ جاري حساب المسار...'; }); try { final res = await http.get( Uri.parse( 'https://map-saas.intaleqapp.com/api/maps/route?fromLat=33.513&fromLng=36.276&toLat=33.530&toLng=36.310', ), headers: {'x-api-key': 'intaleq_secret_2026'}, ); final data = json.decode(res.body); final pts = _decodePoly(data['points']); await mapController?.clearLines(); await mapController?.addLine( LineOptions(geometry: pts, lineColor: "#3b82f6", lineWidth: 6.0), ); final latList = pts.map((p) => p.latitude).toList()..sort(); final lngList = pts.map((p) => p.longitude).toList()..sort(); mapController?.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: LatLng(latList.first, lngList.first), northeast: LatLng(latList.last, lngList.last), ), left: 50, right: 50, top: 100, bottom: 100, ), ); setState( () => info = '🏁 ${(data['distance'] / 1000).toStringAsFixed(1)} كم | ${(data['duration'] / 60).toStringAsFixed(0)} دقيقة', ); } catch (e) { setState(() => info = 'خطأ: $e'); } finally { setState(() => isLoading = false); } } // --- Feature: Fetch and Display Saved Places --- Future _fetchUserPlaces() async { if (!styleLoaded || mapController == null) return; try { final res = await http.get( Uri.parse('https://map-saas.intaleqapp.com/api/geocoding/places'), headers: {'x-api-key': 'intaleq_secret_2026'}, ); if (res.statusCode == 200) { final List data = json.decode(res.body); await mapController?.clearSymbols(); for (var place in data) { final lat = double.tryParse(place['latitude'].toString()); final lng = double.tryParse(place['longitude'].toString()); if (lat != null && lng != null) { await mapController?.addSymbol( SymbolOptions( geometry: LatLng(lat, lng), iconImage: "marker-15", // Default MapLibre icon if using standard style iconSize: 1.5, textField: place['name_ar'] ?? place['name'], textOffset: const Offset(0, 1.5), textColor: "#1e293b", textHaloColor: "#ffffff", textHaloWidth: 2, ), ); } } setState(() => info = '📍 تم تحميل ${data.length} موقع من النظام'); } } catch (e) { debugPrint('Error fetching places: $e'); } } // --- Feature: Offline Map Caching (Mobile Only) --- Future _downloadCurrentRegion() async { if (kIsWeb) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'التحميل اليدوي غير متاح على المتصفح (المتصفح يستخدم التخزين المؤقت للمتصفح تلقائياً)', ), ), ); return; } if (mapController == null) return; final region = await mapController!.getVisibleRegion(); final zoom = mapController!.cameraPosition?.zoom ?? 14.0; setState(() { isLoading = true; info = '⬇️ جاري بدء التحميل...'; }); try { // Create an offline region for current view // Zoom levels from current zoom -1 to +2 await downloadOfflineRegion( OfflineRegionDefinition( bounds: region, // تم التعديل هنا: محرك iOS يتطلب رابط إنترنت (URL) حقيقي لعملية الـ Offline mapStyleUrl: "https://map-saas.intaleqapp.com/styles/style.json", // يرجى التأكد من رفع ملف الستايل الخاص بك على هذا الرابط minZoom: zoom.floor() - 1.0, maxZoom: zoom.floor() + 2.0, ), metadata: { 'name': 'Manual Download ${DateTime.now().toIso8601String()}', }, ); setState(() => info = '✅ بدأ تحميل الخرائط لجهازك (Mobile Only)'); } catch (e) { setState(() => info = '❌ خطأ في التحميل: $e'); } finally { setState(() => isLoading = false); } } Future _clearMapCache() async { if (kIsWeb) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'الرجاء مسح بيانات المتصفح (Browser Cache) لإفراغ الذاكرة', ), ), ); return; } // On mobile, we can list and delete regions // For now, simple info update setState(() => info = '🗑️ تم إرسال طلب مسح الذاكرة المؤقتة'); } // --- Feature: Automatic Damascus Sync (Mobile Only) --- Future _initAutoOfflineSync() async { if (kIsWeb) return; try { // Damascus City Center Bounds roughly +/- 0.1 deg final damascusBounds = LatLngBounds( southwest: const LatLng(33.473, 36.226), northeast: const LatLng(33.583, 36.356), ); setState(() { isSyncing = true; info = '🔄 جاري تأمين خريطة دمشق (Offline)...'; }); await downloadOfflineRegion( OfflineRegionDefinition( bounds: damascusBounds, mapStyleUrl: "https://map-saas.intaleqapp.com/styles/style.json", minZoom: 0.0, maxZoom: 16.0, // High detail for Damascus ), metadata: {'name': 'Damascus_Auto_Sync'}, // onHttpError: (error) => debugPrint('Sync HTTP Error: $error'), // onTileError: (error) => debugPrint('Sync Tile Error: $error'), ); setState(() { isSyncing = false; info = '✅ تم تأمين دمشق بالكامل للعمل أوفلاين'; }); } catch (e) { debugPrint('Sync failed: $e'); setState(() => isSyncing = false); } } // --- Feature: Continuous Background Sync (on move) --- Future _handleCameraIdleSync() async { if (kIsWeb || !isContinuousSyncEnabled || mapController == null) return; final bounds = await mapController!.getVisibleRegion(); final zoom = mapController!.cameraPosition?.zoom ?? 14.0; debugPrint('Continuous Sync: Triggering for new view at zoom $zoom'); // Silent download for the current region // We don't update 'info' here to keep it subtle, or just use a small indicator try { await downloadOfflineRegion( OfflineRegionDefinition( bounds: bounds, mapStyleUrl: "https://map-saas.intaleqapp.com/styles/style.json", minZoom: zoom.floor() - 1.0, maxZoom: zoom.floor() + 2.0, ), metadata: {'name': 'Auto_Area_${DateTime.now().millisecondsSinceEpoch}'}, ); } catch (e) { debugPrint('Silent sync error (normal if redundant): $e'); } } List _decodePoly(dynamic p) { if (p is String) { var l = []; int index = 0, lat = 0, lng = 0; while (index < p.length) { int b, shift = 0, res = 0; do { b = p.codeUnitAt(index++) - 63; res |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); lat += (res & 1) != 0 ? ~(res >> 1) : (res >> 1); shift = 0; res = 0; do { b = p.codeUnitAt(index++) - 63; res |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); lng += (res & 1) != 0 ? ~(res >> 1) : (res >> 1); l.add(LatLng(lat / 1e5, lng / 1e5)); } return l; } else if (p is List) { return (p as List) .map((e) => LatLng(e[1].toDouble(), e[0].toDouble())) .toList(); } return []; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text( 'Intaleq Advanced Maps', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), ), centerTitle: true, backgroundColor: const Color(0xFF0f172a), ), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ const DrawerHeader( decoration: BoxDecoration(color: Color(0xFF0f172a)), child: Center( child: Text( 'أدوات انطلاقة المتقدمة', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), ListTile( leading: const Icon(Icons.location_on, color: Colors.blue), title: const Text('إضافة علامة سيارة'), onTap: () { Navigator.pop(context); _addCarMarker(); }, ), ListTile( leading: const Icon(Icons.timeline, color: Colors.orange), title: const Text('رسم خط مسار (Line)'), onTap: () { Navigator.pop(context); _addSampleLine(); }, ), ListTile( leading: const Icon(Icons.layers, color: Colors.green), title: const Text('رسم سياج جغرافي (Polygon)'), onTap: () { Navigator.pop(context); _addSamplePolygon(); }, ), ListTile( leading: const Icon( Icons.radio_button_checked, color: Colors.red, ), title: const Text('رسم دائرة (Circle)'), onTap: () { Navigator.pop(context); _addSampleCircle(); }, ), const Divider(), ListTile( leading: const Icon(Icons.refresh, color: Colors.blue), title: const Text('تحديث المواقع المسجلة'), onTap: () { Navigator.pop(context); _fetchUserPlaces(); }, ), ListTile( leading: const Icon(Icons.directions, color: Colors.blueAccent), title: const Text('اختبار المسار التلقائي'), onTap: () { Navigator.pop(context); computeRoute(); }, ), const Divider(), const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( 'إدارة الخرائط (Offline)', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.grey, ), ), ), ListTile( leading: Icon( Icons.download_for_offline, color: kIsWeb ? Colors.grey : Colors.green, ), title: const Text('تحميل النطاق الحالي للجهاز'), onTap: () { Navigator.pop(context); _downloadCurrentRegion(); }, ), SwitchListTile( secondary: Icon(Icons.sync, color: kIsWeb ? Colors.grey : Colors.blue), title: const Text('المزامنة المستمرة (Dynamic Sync)'), subtitle: const Text('حفظ أي منطقة يتم استكشافها تلقائياً', style: TextStyle(fontSize: 10)), value: isContinuousSyncEnabled, onChanged: kIsWeb ? null : (val) => setState(() => isContinuousSyncEnabled = val), ), ListTile( leading: Icon( Icons.delete_sweep, color: kIsWeb ? Colors.grey : Colors.redAccent, ), title: const Text('مسح الذاكرة المؤقتة'), onTap: () { Navigator.pop(context); _clearMapCache(); }, ), const Divider(), ListTile( leading: const Icon(Icons.delete_forever, color: Colors.grey), title: const Text('مسح كافة البيانات (UI)'), onTap: () { Navigator.pop(context); mapController?.clearLines(); mapController?.clearSymbols(); mapController?.clearFills(); mapController?.clearCircles(); }, ), ], ), ), body: Stack( children: [ MapLibreMap( styleString: "assets/style.json", initialCameraPosition: const CameraPosition( target: LatLng(33.513, 36.276), zoom: 14, ), onMapCreated: (MapLibreMapController c) => mapController = c, onStyleLoadedCallback: () async { setState(() => styleLoaded = true); // Load Premium Icons into the Map Controller final icons = ['hospital', 'police', 'pharmacy', 'restaurant', 'cafe', 'shop', 'tourist', 'train', 'arrow']; for (final icon in icons) { try { final ByteData bytes = await rootBundle.load('assets/icons/$icon.svg'); await mapController?.addImage(icon, bytes.buffer.asUint8List()); } catch (e) { debugPrint('Error loading asset icon $icon: $e'); } } _fetchUserPlaces(); _initAutoOfflineSync(); }, onCameraIdle: _handleCameraIdleSync, // Trigger continuous sync when movement stops ), if (!styleLoaded) Container( color: const Color(0xFFf8f9fa), child: const Center(child: CircularProgressIndicator()), ), Positioned( top: 10, left: 10, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(30), ), child: Text( info, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold, ), ), ), ), if (isLoading) const Center(child: CircularProgressIndicator()), ], ), ); } }