From 4ad33d17e0da356e480a0b1d35c6c6f578b4d622 Mon Sep 17 00:00:00 2001 From: stopflock Date: Sun, 28 Sep 2025 19:00:07 -0500 Subject: [PATCH] Remove world map area --- lib/dev_config.dart | 3 +- .../offline_areas_section.dart | 37 +---- lib/services/offline_area_service.dart | 28 +++- .../offline_area_downloader.dart | 26 +-- .../offline_areas/world_area_manager.dart | 153 ------------------ lib/widgets/download_area_dialog.dart | 20 +-- 6 files changed, 44 insertions(+), 223 deletions(-) delete mode 100644 lib/services/offline_areas/world_area_manager.dart diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 93b1a2b..9b1c74a 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; /// Developer/build-time configuration for global/non-user-tunable constants. -const int kWorldMinZoom = 1; -const int kWorldMaxZoom = 5; // Example: Default tile storage estimate (KB per tile), for size estimates const double kTileEstimateKb = 25.0; @@ -66,6 +64,7 @@ const int kMaxUserDownloadZoomSpan = 7; // Download area limits and constants const int kMaxReasonableTileCount = 20000; +const int kAbsoluteMaxTileCount = 50000; const int kAbsoluteMaxZoom = 19; // Camera icon configuration diff --git a/lib/screens/settings_screen_sections/offline_areas_section.dart b/lib/screens/settings_screen_sections/offline_areas_section.dart index f02feb4..2c895cf 100644 --- a/lib/screens/settings_screen_sections/offline_areas_section.dart +++ b/lib/screens/settings_screen_sections/offline_areas_section.dart @@ -49,7 +49,7 @@ class _OfflineAreasSectionState extends State { : '--'; String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' + - locService.t('offlineAreas.zoomLevels', params: [area.minZoom.toString(), area.maxZoom.toString()]) + '\n' + + 'Max zoom: Z${area.maxZoom}' + '\n' + '${locService.t('offlineAreas.latitude')}: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' + '${locService.t('offlineAreas.latitude')}: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}'; @@ -59,9 +59,7 @@ class _OfflineAreasSectionState extends State { subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesTotal}'; } subtitle += '\n${locService.t('offlineAreas.size')}: $diskStr'; - if (!area.isPermanent) { - subtitle += '\n${locService.t('offlineAreas.cameras')}: ${area.nodes.length}'; - } + subtitle += '\n${locService.t('offlineAreas.cameras')}: ${area.nodes.length}'; return Card( child: ListTile( leading: Icon(area.status == OfflineAreaStatus.complete @@ -76,10 +74,9 @@ class _OfflineAreasSectionState extends State { ? area.name : locService.t('offlineAreas.areaIdFallback', params: [area.id.substring(0, 6)])), ), - if (!area.isPermanent) - IconButton( - icon: const Icon(Icons.edit, size: 20), - tooltip: locService.t('offlineAreas.renameArea'), + IconButton( + icon: const Icon(Icons.edit, size: 20), + tooltip: locService.t('offlineAreas.renameArea'), onPressed: () async { String? newName = await showDialog( context: context, @@ -116,29 +113,7 @@ class _OfflineAreasSectionState extends State { } }, ), - if (area.isPermanent && area.status != OfflineAreaStatus.downloading) - IconButton( - icon: const Icon(Icons.refresh, color: Colors.blue), - tooltip: locService.t('offlineAreas.refreshWorldTiles'), - onPressed: () async { - await service.downloadArea( - id: area.id, - bounds: area.bounds, - minZoom: area.minZoom, - maxZoom: area.maxZoom, - directory: area.directory, - name: area.name, - onProgress: (progress) {}, - onComplete: (status) {}, - tileProviderId: area.tileProviderId, - tileProviderName: area.tileProviderName, - tileTypeId: area.tileTypeId, - tileTypeName: area.tileTypeName, - ); - setState(() {}); - }, - ) - else if (!area.isPermanent && area.status != OfflineAreaStatus.downloading) + if (area.status != OfflineAreaStatus.downloading) IconButton( icon: const Icon(Icons.delete, color: Colors.red), tooltip: locService.t('offlineAreas.deleteOfflineArea'), diff --git a/lib/services/offline_area_service.dart b/lib/services/offline_area_service.dart index 8322a7f..952684f 100644 --- a/lib/services/offline_area_service.dart +++ b/lib/services/offline_area_service.dart @@ -7,7 +7,7 @@ import 'package:path_provider/path_provider.dart'; import 'offline_areas/offline_area_models.dart'; import 'offline_areas/offline_tile_utils.dart'; import 'offline_areas/offline_area_downloader.dart'; -import 'offline_areas/world_area_manager.dart'; + import '../models/osm_camera_node.dart'; import '../app_state.dart'; import 'map_data_provider.dart'; @@ -59,8 +59,7 @@ class OfflineAreaService { if (_initialized) return; await _loadAreasFromDisk(); - await WorldAreaManager.ensureWorldArea(_areas, getOfflineAreaDir, downloadArea); - await saveAreasToDisk(); // Save any world area updates + await _cleanupLegacyWorldAreas(); _initialized = true; } @@ -262,9 +261,6 @@ class OfflineAreaService { } _areas.remove(area); await saveAreasToDisk(); - if (area.isPermanent) { - await WorldAreaManager.ensureWorldArea(_areas, getOfflineAreaDir, downloadArea); - } } void deleteArea(String id) async { @@ -277,5 +273,25 @@ class OfflineAreaService { await saveAreasToDisk(); } + /// Remove any legacy world areas from previous versions + Future _cleanupLegacyWorldAreas() async { + final worldAreas = _areas.where((area) => area.isPermanent || area.id == 'world').toList(); + + if (worldAreas.isNotEmpty) { + debugPrint('OfflineAreaService: Cleaning up ${worldAreas.length} legacy world area(s)'); + + for (final area in worldAreas) { + final dir = Directory(area.directory); + if (await dir.exists()) { + await dir.delete(recursive: true); + debugPrint('OfflineAreaService: Deleted world area directory: ${area.directory}'); + } + _areas.remove(area); + } + + await saveAreasToDisk(); + debugPrint('OfflineAreaService: Legacy world area cleanup complete'); + } + } } diff --git a/lib/services/offline_areas/offline_area_downloader.dart b/lib/services/offline_areas/offline_area_downloader.dart index 72a564f..fba5328 100644 --- a/lib/services/offline_areas/offline_area_downloader.dart +++ b/lib/services/offline_areas/offline_area_downloader.dart @@ -10,7 +10,6 @@ import '../../models/osm_camera_node.dart'; import '../map_data_provider.dart'; import 'offline_area_models.dart'; import 'offline_tile_utils.dart'; -import 'package:deflockapp/dev_config.dart'; /// Handles the actual downloading process for offline areas class OfflineAreaDownloader { @@ -27,12 +26,7 @@ class OfflineAreaDownloader { required Future Function() saveAreasToDisk, required Future Function(OfflineArea) getAreaSizeBytes, }) async { - Set> allTiles; - if (area.isPermanent) { - allTiles = computeTileList(globalWorldBounds(), kWorldMinZoom, kWorldMaxZoom); - } else { - allTiles = computeTileList(bounds, minZoom, maxZoom); - } + Set> allTiles = computeTileList(bounds, minZoom, maxZoom); area.tilesTotal = allTiles.length; // Download tiles with retry logic @@ -45,17 +39,13 @@ class OfflineAreaDownloader { getAreaSizeBytes: getAreaSizeBytes, ); - // Download nodes for non-permanent areas - if (!area.isPermanent) { - await _downloadNodes( - area: area, - bounds: bounds, - minZoom: minZoom, - directory: directory, - ); - } else { - area.nodes = []; - } + // Download nodes for all areas + await _downloadNodes( + area: area, + bounds: bounds, + minZoom: minZoom, + directory: directory, + ); return success; } diff --git a/lib/services/offline_areas/world_area_manager.dart b/lib/services/offline_areas/world_area_manager.dart deleted file mode 100644 index 349c351..0000000 --- a/lib/services/offline_areas/world_area_manager.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:io'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_map/flutter_map.dart' show LatLngBounds; -import 'package:path_provider/path_provider.dart'; - -import 'offline_area_models.dart'; -import 'offline_tile_utils.dart'; -import 'package:deflockapp/dev_config.dart'; - -/// Manages the world area (permanent offline area for base map) -class WorldAreaManager { - static const String _worldAreaId = 'world'; - static const String _worldAreaName = 'World Base Map'; - - /// Ensure world area exists and check if download is needed - static Future ensureWorldArea( - List areas, - Future Function() getOfflineAreaDir, - Future Function({ - required String id, - required LatLngBounds bounds, - required int minZoom, - required int maxZoom, - required String directory, - String? name, - String? tileProviderId, - String? tileProviderName, - String? tileTypeId, - String? tileTypeName, - }) downloadArea, - ) async { - // Find existing world area - OfflineArea? world; - for (final area in areas) { - if (area.isPermanent) { - world = area; - break; - } - } - - // Create world area if it doesn't exist, or update existing area without provider info - if (world == null) { - final appDocDir = await getOfflineAreaDir(); - final dir = "${appDocDir.path}/$_worldAreaId"; - world = OfflineArea( - id: _worldAreaId, - name: _worldAreaName, - bounds: globalWorldBounds(), - minZoom: kWorldMinZoom, - maxZoom: kWorldMaxZoom, - directory: dir, - status: OfflineAreaStatus.downloading, - isPermanent: true, - // World area always uses OpenStreetMap - tileProviderId: 'openstreetmap', - tileProviderName: 'OpenStreetMap', - tileTypeId: 'osm_street', - tileTypeName: 'Street Map', - ); - areas.insert(0, world); - } else if (world.tileProviderId == null || world.tileTypeId == null) { - // Update existing world area that lacks provider metadata - final updatedWorld = OfflineArea( - id: world.id, - name: world.name, - bounds: world.bounds, - minZoom: world.minZoom, - maxZoom: world.maxZoom, - directory: world.directory, - status: world.status, - progress: world.progress, - tilesDownloaded: world.tilesDownloaded, - tilesTotal: world.tilesTotal, - nodes: world.nodes, - sizeBytes: world.sizeBytes, - isPermanent: world.isPermanent, - // Add missing provider metadata - tileProviderId: 'openstreetmap', - tileProviderName: 'OpenStreetMap', - tileTypeId: 'osm_street', - tileTypeName: 'Street Map', - ); - final index = areas.indexOf(world); - areas[index] = updatedWorld; - world = updatedWorld; - } - - // Check world area status and start download if needed - await _checkAndStartWorldDownload(world, downloadArea); - return world; - } - - /// Check world area download status and start if needed - static Future _checkAndStartWorldDownload( - OfflineArea world, - Future Function({ - required String id, - required LatLngBounds bounds, - required int minZoom, - required int maxZoom, - required String directory, - String? name, - String? tileProviderId, - String? tileProviderName, - String? tileTypeId, - String? tileTypeName, - }) downloadArea, - ) async { - if (world.status == OfflineAreaStatus.complete) return; - - // Count existing tiles - final expectedTiles = computeTileList( - globalWorldBounds(), - kWorldMinZoom, - kWorldMaxZoom, - ); - - int filesFound = 0; - for (final tile in expectedTiles) { - final file = File('${world.directory}/tiles/${tile[0]}/${tile[1]}/${tile[2]}.png'); - if (file.existsSync()) { - filesFound++; - } - } - - // Update world area stats - world.tilesTotal = expectedTiles.length; - world.tilesDownloaded = filesFound; - world.progress = (world.tilesTotal == 0) ? 0.0 : (filesFound / world.tilesTotal); - - if (filesFound == world.tilesTotal) { - world.status = OfflineAreaStatus.complete; - debugPrint('WorldAreaManager: World area download already complete.'); - } else { - world.status = OfflineAreaStatus.downloading; - debugPrint('WorldAreaManager: Starting world area download. ${world.tilesDownloaded}/${world.tilesTotal} tiles found.'); - - // Start download (fire and forget) - use OSM for world areas - downloadArea( - id: world.id, - bounds: world.bounds, - minZoom: world.minZoom, - maxZoom: world.maxZoom, - directory: world.directory, - name: world.name, - tileProviderId: 'openstreetmap', - tileProviderName: 'OpenStreetMap', - tileTypeId: 'osm_street', - tileTypeName: 'Street Map', - ); - } - } -} \ No newline at end of file diff --git a/lib/widgets/download_area_dialog.dart b/lib/widgets/download_area_dialog.dart index b4391ba..59e6b6c 100644 --- a/lib/widgets/download_area_dialog.dart +++ b/lib/widgets/download_area_dialog.dart @@ -73,12 +73,12 @@ class _DownloadAreaDialogState extends State { }); } - /// Calculate the maximum zoom level that keeps tile count under the limit + /// Calculate the maximum zoom level that keeps tile count under the absolute limit int _calculateMaxZoomForTileLimit(LatLngBounds bounds, int minZoom) { for (int zoom = minZoom; zoom <= kAbsoluteMaxZoom; zoom++) { final tileCount = computeTileList(bounds, minZoom, zoom).length; - if (tileCount > kMaxReasonableTileCount) { - // Return the previous zoom level that was still under the limit + if (tileCount > kAbsoluteMaxTileCount) { + // Return the previous zoom level that was still under the absolute limit return math.max(minZoom, zoom - 1); } } @@ -155,14 +155,6 @@ class _DownloadAreaDialogState extends State { ), ], ), - if (_minZoom != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(locService.t('download.minZoom')), - Text('Z$_minZoom'), - ], - ), if (_maxPossibleZoom != null && _tileCount != null) Padding( padding: const EdgeInsets.only(top: 8.0), @@ -178,7 +170,9 @@ class _DownloadAreaDialogState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]), + _tileCount! > kMaxReasonableTileCount + ? 'Above recommended limit (Z${_maxPossibleZoom})' + : locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]), style: TextStyle( fontSize: 12, color: _tileCount! > kMaxReasonableTileCount @@ -190,7 +184,7 @@ class _DownloadAreaDialogState extends State { const SizedBox(height: 2), Text( _tileCount! > kMaxReasonableTileCount - ? locService.t('download.exceedsTileLimit', params: [kMaxReasonableTileCount.toString()]) + ? 'Current selection exceeds ${kMaxReasonableTileCount} recommended tile limit but is within ${kAbsoluteMaxTileCount} absolute limit' : locService.t('download.withinTileLimit', params: [kMaxReasonableTileCount.toString()]), style: TextStyle( fontSize: 11,