import 'dart:io'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import 'dart:math' as math; import '../main.dart'; import '../print.dart'; class OfflineMapService { static final OfflineMapService instance = OfflineMapService._(); OfflineMapService._(); final _offlineRegionName = "UserRegion"; bool _isDownloading = false; LatLng? _lastDownloadedCenter; /// Calculate bounding box for a given center and radius in km LatLngBounds _calculateBounds(LatLng center, double radiusKm) { const double earthRadius = 6371.0; // Latitude degrees per km double latDelta = (radiusKm / earthRadius) * (180 / math.pi); // Longitude degrees per km at given latitude double lngDelta = (radiusKm / earthRadius) * (180 / math.pi) / math.cos(center.latitude * math.pi / 180); return LatLngBounds( southwest: LatLng(center.latitude - latDelta, center.longitude - lngDelta), northeast: LatLng(center.latitude + latDelta, center.longitude + lngDelta), ); } /// Downloads a specified radius around a coordinate Future downloadRegion(LatLng center, {double radiusKm = 10.0, double minZoom = 6.0, double maxZoom = 15.0}) async { if (_isDownloading) return; // Avoid re-downloading if the user hasn't moved significantly (e.g. > 5km) if (_lastDownloadedCenter != null) { double distance = _calculateDistance(center, _lastDownloadedCenter!); if (distance < 5.0) return; // skip if close to previously downloaded } _isDownloading = true; try { final bounds = _calculateBounds(center, radiusKm); // Select style based on current theme final String styleStr = Get.isDarkMode ? "assets/style_dark.json" : "assets/style.json"; // iOS native crash guard: MLNTilePyramidOfflineRegion does not support relative asset URLs. // We skip native offline registration on iOS if using local assets to ensure stability. if (Platform.isIOS && !styleStr.startsWith('http')) { Log.print( "â„šī¸ Skipping native offline registration on iOS for asset-based style to prevent crash."); return; } final regionDefinition = OfflineRegionDefinition( bounds: bounds, mapStyleUrl: styleStr, minZoom: minZoom, maxZoom: maxZoom, ); // We'll update the last downloaded center immediately _lastDownloadedCenter = center; // MapLibre standard API for offline downloads await downloadOfflineRegion(regionDefinition, metadata: { 'name': '$_offlineRegionName-${center.latitude}-${center.longitude}', 'downloadDate': DateTime.now().toIso8601String(), }); // Reassurance log for the user Log.print("📍 Map Ready: Service is utilizing local tile cache."); Log.print( "✅ Offline Map Cached for Region: $center (radius: ${radiusKm}km, style: $styleStr)"); } catch (e) { Log.print("âš ī¸ Offline Map Download Failed: $e"); } finally { _isDownloading = false; } } /// Helper to calculate distance in km double _calculateDistance(LatLng p1, LatLng p2) { var p = 0.017453292519943295; var c = math.cos; var a = 0.5 - c((p2.latitude - p1.latitude) * p) / 2 + c(p1.latitude * p) * c(p2.latitude * p) * (1 - c((p2.longitude - p1.longitude) * p)) / 2; return 12742 * math.asin(math.sqrt(a)); } /// Clears all offline map regions and tiles from local storage Future clearCache() async { try { Log.print("â™ģī¸ Purging MapLibre Offline Cache..."); final List regions = await getListOfRegions(); for (var region in regions) { await deleteOfflineRegion(region.id); } Log.print("✅ Map cache cleared successfully. ${regions.length} regions removed."); } catch (e) { Log.print("âš ī¸ Failed to clear map cache: $e"); } } }