finally getting close. actually loading offline areas is the last thing remaining afaict

This commit is contained in:
stopflock
2025-08-10 15:45:21 -05:00
parent 16ad49198f
commit 3596d4ee99
5 changed files with 31 additions and 15 deletions
+6
View File
@@ -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<void> 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();
}
+5 -1
View File
@@ -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<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ChangeNotifierProvider(
create: (_) => AppState(),
+1 -6
View File
@@ -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.');
+1
View File
@@ -250,6 +250,7 @@ class _MapViewState extends State<MapView> {
return Stack(
children: [
FlutterMap(
key: ValueKey(appState.offlineMode),
mapController: _controller,
options: MapOptions(
initialCenter: _currentLatLng ?? LatLng(37.7749, -122.4194),
+18 -8
View File
@@ -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.
}
}
}