mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-02 19:05:51 +02:00
finalize code paths for offline areas, caching, in light of multiple tile providers
This commit is contained in:
@@ -3,9 +3,14 @@ import 'package:latlong2/latlong.dart';
|
||||
import '../offline_area_service.dart';
|
||||
import '../offline_areas/offline_area_models.dart';
|
||||
import '../offline_areas/offline_tile_utils.dart';
|
||||
import '../../app_state.dart';
|
||||
|
||||
/// Fetch a tile from the newest offline area that plausibly contains it, or throw if not found.
|
||||
/// Fetch a tile from the newest offline area that matches the current provider, or throw if not found.
|
||||
Future<List<int>> fetchLocalTile({required int z, required int x, required int y}) async {
|
||||
final appState = AppState.instance;
|
||||
final currentProvider = appState.selectedTileProvider;
|
||||
final currentTileType = appState.selectedTileType;
|
||||
|
||||
final offlineService = OfflineAreaService();
|
||||
await offlineService.ensureInitialized();
|
||||
final areas = offlineService.offlineAreas;
|
||||
@@ -14,6 +19,9 @@ Future<List<int>> fetchLocalTile({required int z, required int x, required int y
|
||||
for (final area in areas) {
|
||||
if (area.status != OfflineAreaStatus.complete) continue;
|
||||
if (z < area.minZoom || z > area.maxZoom) continue;
|
||||
|
||||
// Only consider areas that match the current provider/type
|
||||
if (area.tileProviderId != currentProvider?.id || area.tileTypeId != currentTileType?.id) continue;
|
||||
|
||||
// Get tile coverage for area at this zoom only
|
||||
final coveredTiles = computeTileList(area.bounds, z, z);
|
||||
@@ -28,7 +36,7 @@ Future<List<int>> fetchLocalTile({required int z, required int x, required int y
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty) {
|
||||
throw Exception('Tile $z/$x/$y not found in any offline area');
|
||||
throw Exception('Tile $z/$x/$y from current provider ${currentProvider?.id}/${currentTileType?.id} not found in any offline area');
|
||||
}
|
||||
candidates.sort((a, b) => b.modified.compareTo(a.modified)); // newest first
|
||||
return await candidates.first.file.readAsBytes();
|
||||
|
||||
@@ -60,6 +60,7 @@ class OfflineAreaService {
|
||||
|
||||
await _loadAreasFromDisk();
|
||||
await WorldAreaManager.ensureWorldArea(_areas, getOfflineAreaDir, downloadArea);
|
||||
await saveAreasToDisk(); // Save any world area updates
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ class OfflineAreaDownloader {
|
||||
) async {
|
||||
try {
|
||||
// Use the same unified path as live tiles: always go through MapDataProvider
|
||||
// MapDataProvider will use current AppState provider for downloads
|
||||
final bytes = await MapDataProvider().getTile(
|
||||
z: tile[0],
|
||||
x: tile[1],
|
||||
|
||||
@@ -38,7 +38,7 @@ class WorldAreaManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Create world area if it doesn't exist
|
||||
// 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";
|
||||
@@ -51,8 +51,38 @@ class WorldAreaManager {
|
||||
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,
|
||||
cameras: world.cameras,
|
||||
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
|
||||
|
||||
@@ -13,10 +13,10 @@ class SimpleTileHttpClient extends http.BaseClient {
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
// Extract tile coordinates from the URL using our standard pattern
|
||||
// Extract tile coordinates from our custom URL scheme
|
||||
final tileCoords = _extractTileCoords(request.url);
|
||||
if (tileCoords != null) {
|
||||
final z = tileCoords['z']!; // We know these are not null from _extractTileCoords
|
||||
final z = tileCoords['z']!;
|
||||
final x = tileCoords['x']!;
|
||||
final y = tileCoords['y']!;
|
||||
return _handleTileRequest(z, x, y);
|
||||
@@ -26,21 +26,22 @@ class SimpleTileHttpClient extends http.BaseClient {
|
||||
return _inner.send(request);
|
||||
}
|
||||
|
||||
/// Extract z/x/y coordinates from our standard tile URL pattern
|
||||
/// Extract z/x/y coordinates from our fake domain: https://tiles.local/provider/type/z/x/y
|
||||
/// We ignore the provider/type in the URL since we use current AppState for actual fetching
|
||||
Map<String, int>? _extractTileCoords(Uri url) {
|
||||
// We'll use a simple standard pattern: /{z}/{x}/{y}.png
|
||||
// This will be the format we use in map_view.dart
|
||||
final pathSegments = url.pathSegments;
|
||||
if (url.host != 'tiles.local') return null;
|
||||
|
||||
if (pathSegments.length == 3) {
|
||||
final z = int.tryParse(pathSegments[0]);
|
||||
final x = int.tryParse(pathSegments[1]);
|
||||
final yWithExt = pathSegments[2];
|
||||
final y = int.tryParse(yWithExt.replaceAll(RegExp(r'\.[^.]*$'), '')); // Remove .png
|
||||
|
||||
if (z != null && x != null && y != null) {
|
||||
return {'z': z, 'x': x, 'y': y};
|
||||
}
|
||||
final pathSegments = url.pathSegments;
|
||||
if (pathSegments.length != 5) return null;
|
||||
|
||||
// pathSegments[0] = providerId (for cache separation only)
|
||||
// pathSegments[1] = tileTypeId (for cache separation only)
|
||||
final z = int.tryParse(pathSegments[2]);
|
||||
final x = int.tryParse(pathSegments[3]);
|
||||
final y = int.tryParse(pathSegments[4]);
|
||||
|
||||
if (z != null && x != null && y != null) {
|
||||
return {'z': z, 'x': x, 'y': y};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -49,6 +50,7 @@ class SimpleTileHttpClient extends http.BaseClient {
|
||||
Future<http.StreamedResponse> _handleTileRequest(int z, int x, int y) async {
|
||||
try {
|
||||
// Always go through MapDataProvider - it handles offline/online routing
|
||||
// MapDataProvider will get current provider from AppState
|
||||
final tileBytes = await _mapDataProvider.getTile(z: z, x: x, y: y, source: MapSource.auto);
|
||||
|
||||
// Clear waiting status - we got data
|
||||
|
||||
Reference in New Issue
Block a user