breakup app_state

This commit is contained in:
stopflock
2025-08-21 18:39:09 -05:00
parent 865f91ea55
commit df5e26f78d
9 changed files with 767 additions and 422 deletions
+1 -2
View File
@@ -8,9 +8,8 @@ import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
/// Handles PKCE OAuth login with OpenStreetMap.
import '../app_state.dart';
import '../keys.dart';
import '../app_state.dart' show UploadMode;
class AuthService {
// Both client IDs from keys.dart
@@ -6,25 +6,47 @@ import '../offline_areas/offline_tile_utils.dart';
/// Fetch a tile from the newest offline area that plausibly contains it, or throw if not found.
Future<List<int>> fetchLocalTile({required int z, required int x, required int y}) async {
final areas = OfflineAreaService().offlineAreas;
final offlineService = OfflineAreaService();
await offlineService.ensureInitialized();
final areas = offlineService.offlineAreas;
final List<_AreaTileMatch> candidates = [];
print('[fetchLocalTile] Looking for tile $z/$x/$y in ${areas.length} offline areas');
for (final area in areas) {
if (area.status != OfflineAreaStatus.complete) continue;
if (z < area.minZoom || z > area.maxZoom) continue;
print('[fetchLocalTile] Checking area: ${area.id}, status: ${area.status}, zoom: ${area.minZoom}-${area.maxZoom}');
if (area.status != OfflineAreaStatus.complete) {
print('[fetchLocalTile] Skipping area ${area.id} - status not complete: ${area.status}');
continue;
}
if (z < area.minZoom || z > area.maxZoom) {
print('[fetchLocalTile] Skipping area ${area.id} - zoom $z outside range ${area.minZoom}-${area.maxZoom}');
continue;
}
// Get tile coverage for area at this zoom only
final coveredTiles = computeTileList(area.bounds, z, z);
final hasTile = coveredTiles.any((tile) => tile[0] == z && tile[1] == x && tile[2] == y);
print('[fetchLocalTile] Area ${area.id} covers ${coveredTiles.length} tiles at zoom $z, contains target tile: $hasTile');
if (hasTile) {
final tilePath = _tilePath(area.directory, z, x, y);
final file = File(tilePath);
if (await file.exists()) {
final exists = await file.exists();
print('[fetchLocalTile] Tile file path: $tilePath, exists: $exists');
if (exists) {
final stat = await file.stat();
candidates.add(_AreaTileMatch(area: area, file: file, modified: stat.modified));
print('[fetchLocalTile] Added candidate from area ${area.id}');
}
}
}
print('[fetchLocalTile] Found ${candidates.length} candidates for tile $z/$x/$y');
if (candidates.isEmpty) {
throw Exception('Tile $z/$x/$y not found in any offline area');
}
+77 -4
View File
@@ -17,12 +17,31 @@ import 'package:flock_map_app/dev_config.dart';
class OfflineAreaService {
static final OfflineAreaService _instance = OfflineAreaService._();
factory OfflineAreaService() => _instance;
OfflineAreaService._() {
_loadAreasFromDisk().then((_) => _ensureAndAutoDownloadWorldArea());
}
bool _initialized = false;
Future<void>? _initializationFuture;
OfflineAreaService._();
final List<OfflineArea> _areas = [];
List<OfflineArea> get offlineAreas => List.unmodifiable(_areas);
/// Ensure the service is initialized (areas loaded from disk)
Future<void> ensureInitialized() async {
if (_initialized) return;
_initializationFuture ??= _initialize();
await _initializationFuture;
}
Future<void> _initialize() async {
if (_initialized) return;
await _loadAreasFromDisk();
await _ensureAndAutoDownloadWorldArea();
_initialized = true;
print('OfflineAreaService: Initialization complete. Found ${_areas.length} offline areas.');
}
Future<Directory> getOfflineAreaDir() async {
final dir = await getApplicationDocumentsDirectory();
@@ -39,6 +58,7 @@ class OfflineAreaService {
}
Future<int> getAreaSizeBytes(OfflineArea area) async {
print('[getAreaSizeBytes] Starting for area ${area.id}, status: ${area.status}');
int total = 0;
final dir = Directory(area.directory);
if (await dir.exists()) {
@@ -49,14 +69,30 @@ class OfflineAreaService {
}
}
area.sizeBytes = total;
print('[getAreaSizeBytes] Before saveAreasToDisk, area ${area.id} status: ${area.status}');
await saveAreasToDisk();
print('[getAreaSizeBytes] After saveAreasToDisk, area ${area.id} status: ${area.status}');
return total;
}
Future<void> saveAreasToDisk() async {
try {
final file = await _getMetadataPath();
final content = jsonEncode(_areas.map((a) => a.toJson()).toList());
final offlineDir = await getOfflineAreaDir();
// Convert areas to JSON with relative paths for portability
final areaJsonList = _areas.map((area) {
final json = area.toJson();
// Convert absolute path to relative path for storage
if (json['directory'].toString().startsWith(offlineDir.path)) {
final relativePath = json['directory'].toString().replaceFirst('${offlineDir.path}/', '');
json['directory'] = relativePath;
print('[OfflineAreaService] Saving area ${area.id} with relative path: $relativePath');
}
return json;
}).toList();
final content = jsonEncode(areaJsonList);
await file.writeAsString(content);
} catch (e) {
debugPrint('Failed to save offline areas: $e');
@@ -77,12 +113,49 @@ class OfflineAreaService {
return;
}
_areas.clear();
for (final areaJson in data) {
// Migrate stored directory paths to be relative for portability
String storedDir = areaJson['directory'];
String relativePath = storedDir;
// If it's an absolute path, extract just the folder name
if (storedDir.startsWith('/')) {
if (storedDir.contains('/offline_areas/')) {
final parts = storedDir.split('/offline_areas/');
if (parts.length == 2) {
relativePath = parts[1]; // Just the folder name (e.g., "world" or "2025-08-19...")
}
}
}
// Always construct absolute path at runtime
final offlineDir = await getOfflineAreaDir();
final fullPath = '${offlineDir.path}/$relativePath';
print('[OfflineAreaService] Area ${areaJson['id']}: stored="$storedDir", relative="$relativePath", full="$fullPath"');
// Update the JSON to use the full path for this session
areaJson['directory'] = fullPath;
final area = OfflineArea.fromJson(areaJson);
if (!Directory(area.directory).existsSync()) {
print('[OfflineAreaService] Directory does not exist: ${area.directory}');
area.status = OfflineAreaStatus.error;
} else {
print('[OfflineAreaService] Directory exists, getting size...');
// Reset error status if directory now exists (fixes areas that were previously broken due to path issues)
if (area.status == OfflineAreaStatus.error) {
print('[OfflineAreaService] Resetting error status to complete for area ${area.id} since directory now exists');
area.status = OfflineAreaStatus.complete;
}
print('[OfflineAreaService] Status before getAreaSizeBytes: ${area.status}');
getAreaSizeBytes(area);
print('[OfflineAreaService] Status after getAreaSizeBytes: ${area.status}');
print('[OfflineAreaService] Area ${area.id} loaded successfully');
}
_areas.add(area);
}