From 3596d4ee99c1567a0500ede5b7ce0c7162cdb7d4 Mon Sep 17 00:00:00 2001 From: stopflock Date: Sun, 10 Aug 2025 15:45:21 -0500 Subject: [PATCH] finally getting close. actually loading offline areas is the last thing remaining afaict --- lib/app_state.dart | 6 ++++++ lib/main.dart | 6 +++++- lib/services/map_data_provider.dart | 7 +----- lib/widgets/map_view.dart | 1 + lib/widgets/tile_provider_with_cache.dart | 26 ++++++++++++++++------- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/app_state.dart b/lib/app_state.dart index 6243154..c12c987 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -9,6 +9,7 @@ import 'models/pending_upload.dart'; import 'services/auth_service.dart'; import 'services/uploader.dart'; import 'services/profile_service.dart'; +import 'widgets/tile_provider_with_cache.dart'; // Enum for upload mode (Production, OSM Sandbox, Simulate) enum UploadMode { production, sandbox, simulate } @@ -35,9 +36,14 @@ class AppState extends ChangeNotifier { bool _offlineMode = false; bool get offlineMode => _offlineMode; Future setOfflineMode(bool enabled) async { + final wasOffline = _offlineMode; _offlineMode = enabled; final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_offlineModePrefsKey, enabled); + if (wasOffline && !enabled) { + // Transitioning from offline to online: clear tile cache! + TileProviderWithCache.clearCache(); + } notifyListeners(); } diff --git a/lib/main.dart b/lib/main.dart index d72e78f..db56c14 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,11 @@ import 'app_state.dart'; import 'screens/home_screen.dart'; import 'screens/settings_screen.dart'; -void main() { +import 'widgets/tile_provider_with_cache.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + runApp( ChangeNotifierProvider( create: (_) => AppState(), diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index a19f606..6f93549 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -62,12 +62,7 @@ class MapDataProvider { required int y, MapSource source = MapSource.auto, }) async { - final offline = AppState.instance.offlineMode; - print('[MapDataProvider] getTile called for $z/$x/$y, source=$source, offlineMode=$offline'); - if (offline && source != MapSource.local) { - print('[MapDataProvider] BLOCKED by offlineMode for $z/$x/$y'); - throw OfflineModeException("Cannot fetch remote tiles in offline mode."); - } + print('[MapDataProvider] getTile called for $z/$x/$y, source=$source'); if (source == MapSource.local) { // TODO: implement local tile loading throw UnimplementedError('Local tile loading not yet implemented.'); diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 877a639..04817a8 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -250,6 +250,7 @@ class _MapViewState extends State { return Stack( children: [ FlutterMap( + key: ValueKey(appState.offlineMode), mapController: _controller, options: MapOptions( initialCenter: _currentLatLng ?? LatLng(37.7749, -122.4194), diff --git a/lib/widgets/tile_provider_with_cache.dart b/lib/widgets/tile_provider_with_cache.dart index f43aecd..ba2c019 100644 --- a/lib/widgets/tile_provider_with_cache.dart +++ b/lib/widgets/tile_provider_with_cache.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; import '../services/map_data_provider.dart'; import '../app_state.dart'; @@ -15,23 +15,33 @@ class TileProviderWithCache extends TileProvider { TileProviderWithCache({this.onTileCacheUpdated}); @override - ImageProvider getImage(TileCoordinates coords, TileLayer options) { + ImageProvider getImage(TileCoordinates coords, TileLayer options, {MapSource source = MapSource.auto}) { final key = '${coords.z}/${coords.x}/${coords.y}'; if (_tileCache.containsKey(key)) { return MemoryImage(_tileCache[key]!); } else { - _fetchAndCacheTile(coords, key); - // Use asset (robust, cross-platform) for non-existing tiles. + _fetchAndCacheTile(coords, key, source: source); + // Always return a placeholder until the real tile is cached, regardless of source/offline/online. return const AssetImage('assets/transparent_1x1.png'); } } - void _fetchAndCacheTile(TileCoordinates coords, String key) async { + static void clearCache() { + _tileCache.clear(); + print('[TileProviderWithCache] Tile cache cleared'); + } + + void _fetchAndCacheTile(TileCoordinates coords, String key, {MapSource source = MapSource.auto}) async { // Don't fire multiple fetches for the same tile simultaneously if (_tileCache.containsKey(key)) return; + // Only block REMOTE fetch in offline mode, but allow local/offline sources in the future. + if (AppState.instance.offlineMode && source != MapSource.local) { + print('[TileProviderWithCache] BLOCKED tile $key due to offline mode'); + return; + } try { final bytes = await MapDataProvider().getTile( - z: coords.z, x: coords.x, y: coords.y, + z: coords.z, x: coords.x, y: coords.y, source: source, ); if (bytes.isNotEmpty) { _tileCache[key] = Uint8List.fromList(bytes); @@ -40,10 +50,10 @@ class TileProviderWithCache extends TileProvider { SchedulerBinding.instance.addPostFrameCallback((_) => onTileCacheUpdated!()); } } - // If bytes were empty, don't cache anything (will re-attempt next time) + // If bytes were empty, don't cache (will re-attempt next time) } catch (e) { print('[TileProviderWithCache] Error fetching tile $key: $e'); - // Do NOT cache a failed/placeholder/empty tile! + // Do NOT cache a failed or empty tile! Placeholder tiles will be evicted on online transition. } } }