mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-17 00:20:40 +02:00
Preview tile fetching on startup, offline area refresh, more cameras->nodes
This commit is contained in:
@@ -272,6 +272,115 @@ class OfflineAreaService {
|
||||
_areas.remove(area);
|
||||
await saveAreasToDisk();
|
||||
}
|
||||
|
||||
/// Refresh/update an existing offline area - tiles, nodes, or both
|
||||
Future<void> refreshArea({
|
||||
required String id,
|
||||
required bool refreshTiles,
|
||||
required bool refreshNodes,
|
||||
void Function(double progress)? onProgress,
|
||||
void Function(OfflineAreaStatus status)? onComplete,
|
||||
}) async {
|
||||
final area = _areas.firstWhere((a) => a.id == id, orElse: () => throw 'Area not found');
|
||||
|
||||
if (area.status == OfflineAreaStatus.downloading) {
|
||||
throw 'Area is already downloading';
|
||||
}
|
||||
|
||||
// Set area to downloading state
|
||||
area.status = OfflineAreaStatus.downloading;
|
||||
area.progress = 0.0;
|
||||
area.tilesDownloaded = 0;
|
||||
await saveAreasToDisk();
|
||||
|
||||
try {
|
||||
bool success = true;
|
||||
|
||||
if (refreshTiles && refreshNodes) {
|
||||
// Refresh both - use the full download process
|
||||
success = await OfflineAreaDownloader.downloadArea(
|
||||
area: area,
|
||||
bounds: area.bounds,
|
||||
minZoom: area.minZoom,
|
||||
maxZoom: area.maxZoom,
|
||||
directory: area.directory,
|
||||
onProgress: onProgress,
|
||||
saveAreasToDisk: saveAreasToDisk,
|
||||
getAreaSizeBytes: getAreaSizeBytes,
|
||||
);
|
||||
} else if (refreshTiles) {
|
||||
// Refresh tiles only
|
||||
success = await _refreshTilesOnly(area, onProgress);
|
||||
} else if (refreshNodes) {
|
||||
// Refresh nodes only
|
||||
success = await _refreshNodesOnly(area, onProgress);
|
||||
} else {
|
||||
// Neither option selected - shouldn't happen but handle gracefully
|
||||
success = true;
|
||||
area.progress = 1.0;
|
||||
}
|
||||
|
||||
await getAreaSizeBytes(area);
|
||||
|
||||
if (success) {
|
||||
area.status = OfflineAreaStatus.complete;
|
||||
area.progress = 1.0;
|
||||
debugPrint('Area $id: refresh completed successfully.');
|
||||
} else {
|
||||
area.status = OfflineAreaStatus.error;
|
||||
debugPrint('Area $id: refresh failed after maximum retry attempts.');
|
||||
}
|
||||
await saveAreasToDisk();
|
||||
onComplete?.call(area.status);
|
||||
} catch (e) {
|
||||
area.status = OfflineAreaStatus.error;
|
||||
await saveAreasToDisk();
|
||||
onComplete?.call(area.status);
|
||||
debugPrint('Area $id: refresh failed with exception: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh only the tiles for an area
|
||||
Future<bool> _refreshTilesOnly(OfflineArea area, void Function(double progress)? onProgress) async {
|
||||
final allTiles = computeTileList(area.bounds, area.minZoom, area.maxZoom);
|
||||
area.tilesTotal = allTiles.length;
|
||||
|
||||
return await OfflineAreaDownloader.downloadTilesWithRetry(
|
||||
area: area,
|
||||
allTiles: allTiles,
|
||||
directory: area.directory,
|
||||
onProgress: onProgress,
|
||||
saveAreasToDisk: saveAreasToDisk,
|
||||
getAreaSizeBytes: getAreaSizeBytes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Refresh only the nodes for an area
|
||||
Future<bool> _refreshNodesOnly(OfflineArea area, void Function(double progress)? onProgress) async {
|
||||
try {
|
||||
// Use the same logic as in the downloader for consistency
|
||||
final nodeZoom = (area.minZoom + 1).clamp(8, 16);
|
||||
final expandedNodeBounds = OfflineAreaDownloader.calculateNodeBounds(area.bounds, nodeZoom);
|
||||
|
||||
final nodes = await MapDataProvider().getAllNodesForDownload(
|
||||
bounds: expandedNodeBounds,
|
||||
profiles: AppState.instance.profiles,
|
||||
);
|
||||
|
||||
area.nodes = nodes;
|
||||
await OfflineAreaDownloader.saveNodes(nodes, area.directory);
|
||||
|
||||
// Set progress to complete for nodes-only refresh
|
||||
onProgress?.call(1.0);
|
||||
area.progress = 1.0;
|
||||
|
||||
debugPrint('Area ${area.id}: Refreshed ${nodes.length} nodes');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Area ${area.id}: Failed to refresh nodes: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove any legacy world areas from previous versions
|
||||
Future<void> _cleanupLegacyWorldAreas() async {
|
||||
|
||||
@@ -30,7 +30,7 @@ class OfflineAreaDownloader {
|
||||
area.tilesTotal = allTiles.length;
|
||||
|
||||
// Download tiles with retry logic
|
||||
final success = await _downloadTilesWithRetry(
|
||||
final success = await downloadTilesWithRetry(
|
||||
area: area,
|
||||
allTiles: allTiles,
|
||||
directory: directory,
|
||||
@@ -51,7 +51,7 @@ class OfflineAreaDownloader {
|
||||
}
|
||||
|
||||
/// Download tiles with retry logic
|
||||
static Future<bool> _downloadTilesWithRetry({
|
||||
static Future<bool> downloadTilesWithRetry({
|
||||
required OfflineArea area,
|
||||
required Set<List<int>> allTiles,
|
||||
required String directory,
|
||||
@@ -138,7 +138,7 @@ class OfflineAreaDownloader {
|
||||
// Modest expansion: use tiles at minZoom + 1 instead of minZoom
|
||||
// This gives a reasonable buffer without capturing entire states
|
||||
final nodeZoom = (minZoom + 1).clamp(8, 16); // Reasonable bounds for node fetching
|
||||
final expandedNodeBounds = _calculateNodeBounds(bounds, nodeZoom);
|
||||
final expandedNodeBounds = calculateNodeBounds(bounds, nodeZoom);
|
||||
|
||||
final nodes = await MapDataProvider().getAllNodesForDownload(
|
||||
bounds: expandedNodeBounds,
|
||||
@@ -150,7 +150,7 @@ class OfflineAreaDownloader {
|
||||
}
|
||||
|
||||
/// Calculate expanded bounds that cover the entire tile area at minimum zoom
|
||||
static LatLngBounds _calculateNodeBounds(LatLngBounds visibleBounds, int minZoom) {
|
||||
static LatLngBounds calculateNodeBounds(LatLngBounds visibleBounds, int minZoom) {
|
||||
final tiles = computeTileList(visibleBounds, minZoom, minZoom);
|
||||
if (tiles.isEmpty) return visibleBounds;
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../models/tile_provider.dart';
|
||||
import '../state/settings_state.dart';
|
||||
|
||||
/// Service for fetching missing tile preview images
|
||||
class TilePreviewService {
|
||||
static const int _previewZoom = 10;
|
||||
static const int _previewX = 512;
|
||||
static const int _previewY = 384;
|
||||
static const Duration _timeout = Duration(seconds: 10);
|
||||
|
||||
/// Attempt to fetch missing preview tiles for tile types that don't already have preview data
|
||||
/// Fails silently - no error handling or user notification on failure
|
||||
static Future<void> fetchMissingPreviews(SettingsState settingsState) async {
|
||||
try {
|
||||
bool anyUpdates = false;
|
||||
|
||||
for (final provider in settingsState.tileProviders) {
|
||||
final updatedTileTypes = <TileType>[];
|
||||
bool providerNeedsUpdate = false;
|
||||
|
||||
for (final tileType in provider.tileTypes) {
|
||||
// Only fetch if preview tile is missing
|
||||
if (tileType.previewTile == null) {
|
||||
// Skip if tile type requires API key but provider doesn't have one
|
||||
if (tileType.requiresApiKey && (provider.apiKey == null || provider.apiKey!.isEmpty)) {
|
||||
updatedTileTypes.add(tileType);
|
||||
continue;
|
||||
}
|
||||
|
||||
final previewData = await _fetchPreviewForTileType(tileType, provider.apiKey);
|
||||
if (previewData != null) {
|
||||
// Create updated tile type with preview data
|
||||
final updatedTileType = tileType.copyWith(previewTile: previewData);
|
||||
updatedTileTypes.add(updatedTileType);
|
||||
providerNeedsUpdate = true;
|
||||
} else {
|
||||
updatedTileTypes.add(tileType);
|
||||
}
|
||||
} else {
|
||||
updatedTileTypes.add(tileType);
|
||||
}
|
||||
}
|
||||
|
||||
if (providerNeedsUpdate) {
|
||||
final updatedProvider = provider.copyWith(tileTypes: updatedTileTypes);
|
||||
await settingsState.addOrUpdateTileProvider(updatedProvider);
|
||||
anyUpdates = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyUpdates) {
|
||||
debugPrint('TilePreviewService: Updated providers with new preview tiles');
|
||||
}
|
||||
} catch (e) {
|
||||
// Fail silently as requested
|
||||
debugPrint('TilePreviewService: Error during preview fetching: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Uint8List?> _fetchPreviewForTileType(TileType tileType, String? apiKey) async {
|
||||
try {
|
||||
final url = tileType.getTileUrl(_previewZoom, _previewX, _previewY, apiKey: apiKey);
|
||||
|
||||
final response = await http.get(Uri.parse(url)).timeout(_timeout);
|
||||
|
||||
if (response.statusCode == 200 && response.bodyBytes.isNotEmpty) {
|
||||
debugPrint('TilePreviewService: Fetched preview for ${tileType.name}');
|
||||
return response.bodyBytes;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fail silently - just log for debugging
|
||||
debugPrint('TilePreviewService: Failed to fetch preview for ${tileType.name}: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user