Preview tile fetching on startup, offline area refresh, more cameras->nodes

This commit is contained in:
stopflock
2025-09-29 21:28:40 -05:00
parent 1140e6300a
commit 2b26bf9188
12 changed files with 403 additions and 27 deletions
+14
View File
@@ -9,6 +9,7 @@ import 'models/pending_upload.dart';
import 'models/tile_provider.dart';
import 'services/offline_area_service.dart';
import 'services/node_cache.dart';
import 'services/tile_preview_service.dart';
import 'widgets/camera_provider_with_cache.dart';
import 'state/auth_state.dart';
import 'state/operator_profile_state.dart';
@@ -101,6 +102,10 @@ class AppState extends ChangeNotifier {
Future<void> _init() async {
// Initialize all state modules
await _settingsState.init();
// Attempt to fetch missing tile type preview tiles (fails silently)
_fetchMissingTilePreviews();
await _operatorProfileState.init();
await _profileState.init();
await _uploadQueueState.init();
@@ -297,6 +302,15 @@ class AppState extends ChangeNotifier {
}
// ---------- Private Methods ----------
/// Attempts to fetch missing tile preview images in the background (fire and forget)
void _fetchMissingTilePreviews() {
// Run asynchronously without awaiting to avoid blocking app startup
TilePreviewService.fetchMissingPreviews(_settingsState).catchError((error) {
// Silently ignore errors - this is best effort
debugPrint('AppState: Tile preview fetching failed silently: $error');
});
}
void _startUploader() {
_uploadQueueState.startUploader(
offlineMode: offlineMode,
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Innerhalb {} Kachel-Limit",
"exceedsTileLimit": "Aktuelle Auswahl überschreitet {} Kachel-Limit",
"offlineModeWarning": "Downloads im Offline-Modus deaktiviert. Deaktivieren Sie den Offline-Modus, um neue Bereiche herunterzuladen.",
"downloadStarted": "Download gestartet! Lade Kacheln und Kameras...",
"downloadStarted": "Download gestartet! Lade Kacheln und Knoten...",
"downloadFailed": "Download konnte nicht gestartet werden: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Länge",
"tiles": "Kacheln",
"size": "Größe",
"cameras": "Kameras",
"nodes": "Knoten",
"areaIdFallback": "Bereich {}...",
"renameArea": "Bereich umbenennen",
"refreshWorldTiles": "Welt-Kacheln aktualisieren/neu herunterladen",
@@ -236,7 +236,17 @@
"renameButton": "Umbenennen",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Bereich aktualisieren",
"refreshAreaDialogTitle": "Offline-Bereich aktualisieren",
"refreshAreaDialogSubtitle": "Wählen Sie aus, was für diesen Bereich aktualisiert werden soll:",
"refreshTiles": "Karten-Kacheln aktualisieren",
"refreshTilesSubtitle": "Alle Kacheln für aktualisierte Bilder erneut herunterladen",
"refreshNodes": "Knoten aktualisieren",
"refreshNodesSubtitle": "Knotendaten für diesen Bereich erneut abrufen",
"startRefresh": "Aktualisierung starten",
"refreshStarted": "Aktualisierung gestartet!",
"refreshFailed": "Aktualisierung fehlgeschlagen: {}"
},
"refineTagsSheet": {
"title": "Tags Verfeinern",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Within {} tile limit",
"exceedsTileLimit": "Current selection exceeds {} tile limit",
"offlineModeWarning": "Downloads disabled while in offline mode. Disable offline mode to download new areas.",
"downloadStarted": "Download started! Fetching tiles and cameras...",
"downloadStarted": "Download started! Fetching tiles and nodes...",
"downloadFailed": "Failed to start download: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Lon",
"tiles": "Tiles",
"size": "Size",
"cameras": "Cameras",
"nodes": "Nodes",
"areaIdFallback": "Area {}...",
"renameArea": "Rename area",
"refreshWorldTiles": "Refresh/re-download world tiles",
@@ -236,7 +236,17 @@
"renameButton": "Rename",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Refresh area",
"refreshAreaDialogTitle": "Refresh Offline Area",
"refreshAreaDialogSubtitle": "Choose what to refresh for this area:",
"refreshTiles": "Refresh Map Tiles",
"refreshTilesSubtitle": "Re-download all tiles for updated imagery",
"refreshNodes": "Refresh Nodes",
"refreshNodesSubtitle": "Re-fetch node data for this area",
"startRefresh": "Start Refresh",
"refreshStarted": "Refresh started!",
"refreshFailed": "Refresh failed: {}"
},
"refineTagsSheet": {
"title": "Refine Tags",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Dentro del límite de {} mosaicos",
"exceedsTileLimit": "La selección actual excede el límite de {} mosaicos",
"offlineModeWarning": "Descargas deshabilitadas en modo sin conexión. Deshabilite el modo sin conexión para descargar nuevas áreas.",
"downloadStarted": "¡Descarga iniciada! Obteniendo mosaicos y cámaras...",
"downloadStarted": "¡Descarga iniciada! Obteniendo mosaicos y nodos...",
"downloadFailed": "Error al iniciar la descarga: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Lon",
"tiles": "Teselas",
"size": "Tamaño",
"cameras": "Cámaras",
"nodes": "Nodos",
"areaIdFallback": "Área {}...",
"renameArea": "Renombrar área",
"refreshWorldTiles": "Actualizar/re-descargar teselas mundiales",
@@ -236,7 +236,17 @@
"renameButton": "Renombrar",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Actualizar área",
"refreshAreaDialogTitle": "Actualizar Área sin Conexión",
"refreshAreaDialogSubtitle": "Elija qué actualizar para esta área:",
"refreshTiles": "Actualizar Mosaicos del Mapa",
"refreshTilesSubtitle": "Volver a descargar todos los mosaicos para imágenes actualizadas",
"refreshNodes": "Actualizar Nodos",
"refreshNodesSubtitle": "Volver a obtener datos de nodos para esta área",
"startRefresh": "Iniciar Actualización",
"refreshStarted": "¡Actualización iniciada!",
"refreshFailed": "Actualización falló: {}"
},
"refineTagsSheet": {
"title": "Refinar Etiquetas",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Dans la limite de {} tuiles",
"exceedsTileLimit": "La sélection actuelle dépasse la limite de {} tuiles",
"offlineModeWarning": "Téléchargements désactivés en mode hors ligne. Désactivez le mode hors ligne pour télécharger de nouvelles zones.",
"downloadStarted": "Téléchargement démarré! Récupération des tuiles et caméras...",
"downloadStarted": "Téléchargement démarré! Récupération des tuiles et nœuds...",
"downloadFailed": "Échec du démarrage du téléchargement: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Lon",
"tiles": "Tuiles",
"size": "Taille",
"cameras": "Caméras",
"nodes": "Nœuds",
"areaIdFallback": "Zone {}...",
"renameArea": "Renommer la zone",
"refreshWorldTiles": "Actualiser/re-télécharger les tuiles mondiales",
@@ -236,7 +236,17 @@
"renameButton": "Renommer",
"megabytes": "Mo",
"kilobytes": "Ko",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Actualiser la zone",
"refreshAreaDialogTitle": "Actualiser la Zone Hors Ligne",
"refreshAreaDialogSubtitle": "Choisissez quoi actualiser pour cette zone :",
"refreshTiles": "Actualiser les Tuiles de Carte",
"refreshTilesSubtitle": "Télécharger à nouveau toutes les tuiles pour des images mises à jour",
"refreshNodes": "Actualiser les Nœuds",
"refreshNodesSubtitle": "Récupérer à nouveau les données de nœuds pour cette zone",
"startRefresh": "Démarrer l'Actualisation",
"refreshStarted": "Actualisation démarrée !",
"refreshFailed": "Actualisation échouée : {}"
},
"refineTagsSheet": {
"title": "Affiner les Étiquettes",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Entro il limite di {} tile",
"exceedsTileLimit": "La selezione corrente supera il limite di {} tile",
"offlineModeWarning": "Download disabilitati in modalità offline. Disabilita la modalità offline per scaricare nuove aree.",
"downloadStarted": "Download avviato! Recupero tile e telecamere...",
"downloadStarted": "Download avviato! Recupero tile e nodi...",
"downloadFailed": "Impossibile avviare il download: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Lon",
"tiles": "Tile",
"size": "Dimensione",
"cameras": "Telecamere",
"nodes": "Nodi",
"areaIdFallback": "Area {}...",
"renameArea": "Rinomina area",
"refreshWorldTiles": "Aggiorna/ri-scarica tile mondiali",
@@ -236,7 +236,17 @@
"renameButton": "Rinomina",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Aggiorna area",
"refreshAreaDialogTitle": "Aggiorna Area Offline",
"refreshAreaDialogSubtitle": "Scegli cosa aggiornare per quest'area:",
"refreshTiles": "Aggiorna Tile Mappa",
"refreshTilesSubtitle": "Riscarica tutte le tile per immagini aggiornate",
"refreshNodes": "Aggiorna Nodi",
"refreshNodesSubtitle": "Ricarica i dati dei nodi per quest'area",
"startRefresh": "Avvia Aggiornamento",
"refreshStarted": "Aggiornamento avviato!",
"refreshFailed": "Aggiornamento fallito: {}"
},
"refineTagsSheet": {
"title": "Affina Tag",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "Dentro do limite de {} tiles",
"exceedsTileLimit": "A seleção atual excede o limite de {} tiles",
"offlineModeWarning": "Downloads desabilitados no modo offline. Desative o modo offline para baixar novas áreas.",
"downloadStarted": "Download iniciado! Buscando tiles e câmeras...",
"downloadStarted": "Download iniciado! Buscando tiles e s...",
"downloadFailed": "Falha ao iniciar o download: {}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "Lon",
"tiles": "Tiles",
"size": "Tamanho",
"cameras": "Câmeras",
"nodes": "s",
"areaIdFallback": "Área {}...",
"renameArea": "Renomear área",
"refreshWorldTiles": "Atualizar/rebaixar tiles mundiais",
@@ -236,7 +236,17 @@
"renameButton": "Renomear",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "Atualizar área",
"refreshAreaDialogTitle": "Atualizar Área Offline",
"refreshAreaDialogSubtitle": "Escolha o que atualizar para esta área:",
"refreshTiles": "Atualizar Tiles do Mapa",
"refreshTilesSubtitle": "Baixar novamente todos os tiles para imagens atualizadas",
"refreshNodes": "Atualizar Nós",
"refreshNodesSubtitle": "Buscar novamente os dados dos nós para esta área",
"startRefresh": "Iniciar Atualização",
"refreshStarted": "Atualização iniciada!",
"refreshFailed": "Atualização falhou: {}"
},
"refineTagsSheet": {
"title": "Refinar Tags",
+13 -3
View File
@@ -79,7 +79,7 @@
"withinTileLimit": "在 {} 瓦片限制内",
"exceedsTileLimit": "当前选择超出 {} 瓦片限制",
"offlineModeWarning": "离线模式下禁用下载。禁用离线模式以下载新区域。",
"downloadStarted": "下载已开始!正在获取瓦片和摄像头...",
"downloadStarted": "下载已开始!正在获取瓦片和节点...",
"downloadFailed": "启动下载失败:{}"
},
"uploadMode": {
@@ -225,7 +225,7 @@
"longitude": "经度",
"tiles": "瓦片",
"size": "大小",
"cameras": "摄像头",
"nodes": "节点",
"areaIdFallback": "区域 {}...",
"renameArea": "重命名区域",
"refreshWorldTiles": "刷新/重新下载世界瓦片",
@@ -236,7 +236,17 @@
"renameButton": "重命名",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
"progress": "{}%",
"refreshArea": "刷新区域",
"refreshAreaDialogTitle": "刷新离线区域",
"refreshAreaDialogSubtitle": "选择要为此区域刷新的内容:",
"refreshTiles": "刷新地图瓦片",
"refreshTilesSubtitle": "重新下载所有瓦片以获取更新的图像",
"refreshNodes": "刷新节点",
"refreshNodesSubtitle": "重新获取此区域的节点数据",
"startRefresh": "开始刷新",
"refreshStarted": "刷新已开始!",
"refreshFailed": "刷新失败:{}"
},
"refineTagsSheet": {
"title": "细化标签",
@@ -24,6 +24,39 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
});
}
void _showRefreshDialog(OfflineArea area) {
showDialog(
context: context,
builder: (context) => _RefreshAreaDialog(
area: area,
onRefresh: (refreshTiles, refreshNodes) {
try {
// ignore: unawaited_futures
service.refreshArea(
id: area.id,
refreshTiles: refreshTiles,
refreshNodes: refreshNodes,
onProgress: (progress) => setState(() {}),
onComplete: (status) => setState(() {}),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(LocalizationService.instance.t('offlineAreas.refreshStarted')),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(LocalizationService.instance.t('offlineAreas.refreshFailed', params: [e.toString()])),
),
);
}
},
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
@@ -59,7 +92,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesTotal}';
}
subtitle += '\n${locService.t('offlineAreas.size')}: $diskStr';
subtitle += '\n${locService.t('offlineAreas.cameras')}: ${area.nodes.length}';
subtitle += '\n${locService.t('offlineAreas.nodes')}: ${area.nodes.length}';
return Card(
child: ListTile(
leading: Icon(area.status == OfflineAreaStatus.complete
@@ -113,7 +146,12 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
}
},
),
if (area.status != OfflineAreaStatus.downloading)
if (area.status != OfflineAreaStatus.downloading) ...[
IconButton(
icon: const Icon(Icons.refresh, color: Colors.blue),
tooltip: locService.t('offlineAreas.refreshArea'),
onPressed: () => _showRefreshDialog(area),
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
tooltip: locService.t('offlineAreas.deleteOfflineArea'),
@@ -122,6 +160,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
setState(() {});
},
),
],
],
),
subtitle: Text(subtitle),
@@ -168,3 +207,67 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
);
}
}
class _RefreshAreaDialog extends StatefulWidget {
final OfflineArea area;
final Function(bool refreshTiles, bool refreshNodes) onRefresh;
const _RefreshAreaDialog({
required this.area,
required this.onRefresh,
});
@override
State<_RefreshAreaDialog> createState() => _RefreshAreaDialogState();
}
class _RefreshAreaDialogState extends State<_RefreshAreaDialog> {
bool _refreshTiles = true;
bool _refreshNodes = true;
@override
Widget build(BuildContext context) {
final locService = LocalizationService.instance;
return AlertDialog(
title: Text(locService.t('offlineAreas.refreshAreaDialogTitle')),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(locService.t('offlineAreas.refreshAreaDialogSubtitle')),
const SizedBox(height: 16),
CheckboxListTile(
title: Text(locService.t('offlineAreas.refreshTiles')),
subtitle: Text(locService.t('offlineAreas.refreshTilesSubtitle')),
value: _refreshTiles,
onChanged: (value) => setState(() => _refreshTiles = value ?? true),
dense: true,
),
CheckboxListTile(
title: Text(locService.t('offlineAreas.refreshNodes')),
subtitle: Text(locService.t('offlineAreas.refreshNodesSubtitle')),
value: _refreshNodes,
onChanged: (value) => setState(() => _refreshNodes = value ?? true),
dense: true,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(locService.t('actions.cancel')),
),
ElevatedButton(
onPressed: (_refreshTiles || _refreshNodes)
? () {
Navigator.of(context).pop();
widget.onRefresh(_refreshTiles, _refreshNodes);
}
: null,
child: Text(locService.t('offlineAreas.startRefresh')),
),
],
);
}
}
+109
View File
@@ -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;
+80
View File
@@ -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;
}
}