settings area metadata, offline areas support for tile types

This commit is contained in:
stopflock
2025-08-24 15:31:02 -05:00
parent 4ee783793f
commit f1c73a5e55
6 changed files with 83 additions and 1 deletions

View File

@@ -41,6 +41,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
: "${(area.sizeBytes / 1024).toStringAsFixed(1)} KB"
: '--';
String subtitle =
'Provider: ${area.tileProviderDisplay}\n' +
'Z${area.minZoom}-${area.maxZoom}\n' +
'Lat: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' +
'Lat: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}';
@@ -121,6 +122,10 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
name: area.name,
onProgress: (progress) {},
onComplete: (status) {},
tileProviderId: area.tileProviderId,
tileProviderName: area.tileProviderName,
tileTypeId: area.tileTypeId,
tileTypeName: area.tileTypeName,
);
setState(() {});
},

View File

@@ -182,6 +182,10 @@ class OfflineAreaService {
void Function(double progress)? onProgress,
void Function(OfflineAreaStatus status)? onComplete,
String? name,
String? tileProviderId,
String? tileProviderName,
String? tileTypeId,
String? tileTypeName,
}) async {
OfflineArea? area;
for (final a in _areas) {
@@ -202,6 +206,10 @@ class OfflineAreaService {
maxZoom: maxZoom,
directory: directory,
isPermanent: area?.isPermanent ?? false,
tileProviderId: tileProviderId,
tileProviderName: tileProviderName,
tileTypeId: tileTypeId,
tileTypeName: tileTypeName,
);
_areas.add(area);
await saveAreasToDisk();

View File

@@ -20,6 +20,12 @@ class OfflineArea {
List<OsmCameraNode> cameras;
int sizeBytes; // Disk size in bytes
final bool isPermanent; // Not user-deletable if true
// Tile provider metadata (null for legacy areas)
final String? tileProviderId;
final String? tileProviderName;
final String? tileTypeId;
final String? tileTypeName;
OfflineArea({
required this.id,
@@ -35,6 +41,10 @@ class OfflineArea {
this.cameras = const [],
this.sizeBytes = 0,
this.isPermanent = false,
this.tileProviderId,
this.tileProviderName,
this.tileTypeId,
this.tileTypeName,
});
Map<String, dynamic> toJson() => {
@@ -54,6 +64,10 @@ class OfflineArea {
'cameras': cameras.map((c) => c.toJson()).toList(),
'sizeBytes': sizeBytes,
'isPermanent': isPermanent,
'tileProviderId': tileProviderId,
'tileProviderName': tileProviderName,
'tileTypeId': tileTypeId,
'tileTypeName': tileTypeName,
};
static OfflineArea fromJson(Map<String, dynamic> json) {
@@ -77,6 +91,27 @@ class OfflineArea {
.map((e) => OsmCameraNode.fromJson(e)).toList(),
sizeBytes: json['sizeBytes'] ?? 0,
isPermanent: json['isPermanent'] ?? false,
tileProviderId: json['tileProviderId'],
tileProviderName: json['tileProviderName'],
tileTypeId: json['tileTypeId'],
tileTypeName: json['tileTypeName'],
);
}
/// Get display text for the tile provider used in this area
String get tileProviderDisplay {
if (tileProviderName != null && tileTypeName != null) {
return '$tileProviderName - $tileTypeName';
} else if (tileTypeName != null) {
return tileTypeName!;
} else if (tileProviderName != null) {
return tileProviderName!;
} else {
// Legacy area - assume OSM
return 'OpenStreetMap (Legacy)';
}
}
/// Check if this area has tile provider metadata
bool get hasTileProviderInfo => tileProviderId != null && tileTypeId != null;
}

View File

@@ -23,6 +23,10 @@ class WorldAreaManager {
required int maxZoom,
required String directory,
String? name,
String? tileProviderId,
String? tileProviderName,
String? tileTypeId,
String? tileTypeName,
}) downloadArea,
) async {
// Find existing world area
@@ -66,6 +70,10 @@ class WorldAreaManager {
required int maxZoom,
required String directory,
String? name,
String? tileProviderId,
String? tileProviderName,
String? tileTypeId,
String? tileTypeName,
}) downloadArea,
) async {
if (world.status == OfflineAreaStatus.complete) return;
@@ -97,7 +105,7 @@ class WorldAreaManager {
world.status = OfflineAreaStatus.downloading;
debugPrint('WorldAreaManager: Starting world area download. ${world.tilesDownloaded}/${world.tilesTotal} tiles found.');
// Start download (fire and forget)
// Start download (fire and forget) - use OSM for world areas
downloadArea(
id: world.id,
bounds: world.bounds,
@@ -105,6 +113,10 @@ class WorldAreaManager {
maxZoom: world.maxZoom,
directory: world.directory,
name: world.name,
tileProviderId: 'openstreetmap',
tileProviderName: 'OpenStreetMap',
tileTypeId: 'osm_street',
tileTypeName: 'Street Map',
);
}
}

View File

@@ -236,6 +236,11 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
final appDocDir = await OfflineAreaService().getOfflineAreaDir();
final dir = "${appDocDir.path}/$id";
// Get current tile provider info
final appState = context.read<AppState>();
final selectedProvider = appState.selectedTileProvider;
final selectedTileType = appState.selectedTileType;
// Fire and forget: don't await download, so dialog closes immediately
// ignore: unawaited_futures
OfflineAreaService().downloadArea(
@@ -246,6 +251,10 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
directory: dir,
onProgress: (progress) {},
onComplete: (status) {},
tileProviderId: selectedProvider?.id,
tileProviderName: selectedProvider?.name,
tileTypeId: selectedTileType?.id,
tileTypeName: selectedTileType?.name,
);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(

View File

@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import '../../app_state.dart';
import '../../models/tile_provider.dart';
import '../../services/offline_area_service.dart';
class LayerSelectorButton extends StatelessWidget {
const LayerSelectorButton({super.key});
@@ -17,6 +18,18 @@ class LayerSelectorButton extends StatelessWidget {
}
void _showLayerSelector(BuildContext context) {
// Check if any downloads are active
final offlineService = OfflineAreaService();
if (offlineService.hasActiveDownloads) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cannot change tile types while downloading offline areas'),
duration: Duration(seconds: 3),
),
);
return;
}
showDialog(
context: context,
builder: (context) => const _LayerSelectorDialog(),