Files
deflock-app/lib/services/deflock_tile_provider.dart
Doug Borg 037165653c Fix lint warnings and cleanup unused code after RadioGroup migration
Remove unused imports, fields, variables, and dead code introduced
during the RadioGroup widget migration and prior changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:36:18 -07:00

157 lines
5.3 KiB
Dart

import 'dart:async';
import 'dart:ui';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../app_state.dart';
import 'map_data_provider.dart';
import 'offline_area_service.dart';
/// Custom tile provider that integrates with DeFlock's offline/online architecture.
///
/// This replaces the complex HTTP interception approach with a clean TileProvider
/// implementation that directly interfaces with our MapDataProvider system.
class DeflockTileProvider extends TileProvider {
final MapDataProvider _mapDataProvider = MapDataProvider();
@override
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) {
// Get current provider info to include in cache key
final appState = AppState.instance;
final providerId = appState.selectedTileProvider?.id ?? 'unknown';
final tileTypeId = appState.selectedTileType?.id ?? 'unknown';
return DeflockTileImageProvider(
coordinates: coordinates,
options: options,
mapDataProvider: _mapDataProvider,
providerId: providerId,
tileTypeId: tileTypeId,
);
}
}
/// Image provider that fetches tiles through our MapDataProvider.
///
/// This handles the actual tile fetching using our existing offline/online
/// routing logic without any HTTP interception complexity.
class DeflockTileImageProvider extends ImageProvider<DeflockTileImageProvider> {
final TileCoordinates coordinates;
final TileLayer options;
final MapDataProvider mapDataProvider;
final String providerId;
final String tileTypeId;
const DeflockTileImageProvider({
required this.coordinates,
required this.options,
required this.mapDataProvider,
required this.providerId,
required this.tileTypeId,
});
@override
Future<DeflockTileImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<DeflockTileImageProvider>(this);
}
@override
ImageStreamCompleter loadImage(DeflockTileImageProvider key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, chunkEvents),
chunkEvents: chunkEvents.stream,
scale: 1.0,
);
}
Future<Codec> _loadAsync(
DeflockTileImageProvider key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async {
try {
// Get current tile provider and type from app state
final appState = AppState.instance;
final selectedProvider = appState.selectedTileProvider;
final selectedTileType = appState.selectedTileType;
if (selectedProvider == null || selectedTileType == null) {
throw Exception('No tile provider configured');
}
// Smart cache routing: only check offline cache when needed
final MapSource source = _shouldCheckOfflineCache(appState)
? MapSource.auto // Check offline first, then network
: MapSource.remote; // Skip offline cache, go directly to network
final tileBytes = await mapDataProvider.getTile(
z: coordinates.z,
x: coordinates.x,
y: coordinates.y,
source: source,
);
// Decode the image bytes
final buffer = await ImmutableBuffer.fromUint8List(Uint8List.fromList(tileBytes));
return await decode(buffer);
} catch (e) {
// Don't log routine offline misses to avoid console spam
if (!e.toString().contains('offline mode is enabled')) {
debugPrint('[DeflockTileProvider] Failed to load tile ${coordinates.z}/${coordinates.x}/${coordinates.y}: $e');
}
// Re-throw the exception and let FlutterMap handle missing tiles gracefully
// This is better than trying to provide fallback images
rethrow;
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is DeflockTileImageProvider &&
other.coordinates == coordinates &&
other.providerId == providerId &&
other.tileTypeId == tileTypeId;
}
@override
int get hashCode => Object.hash(coordinates, providerId, tileTypeId);
/// Determine if we should check offline cache for this tile request.
/// Only check offline cache if:
/// 1. We're in offline mode (forced), OR
/// 2. We have offline areas for the current provider/type
///
/// This avoids expensive filesystem searches when browsing online
/// with providers that have no offline areas.
bool _shouldCheckOfflineCache(AppState appState) {
// Always check offline cache in offline mode
if (appState.offlineMode) {
return true;
}
// For online mode, only check if we might actually have relevant offline data
final currentProvider = appState.selectedTileProvider;
final currentTileType = appState.selectedTileType;
if (currentProvider == null || currentTileType == null) {
return false;
}
// Quick check: do we have any offline areas for this provider/type?
// This avoids the expensive per-tile filesystem search in fetchLocalTile
final offlineService = OfflineAreaService();
final hasRelevantAreas = offlineService.hasOfflineAreasForProvider(
currentProvider.id,
currentTileType.id,
);
return hasRelevantAreas;
}
}