offline areas localization

This commit is contained in:
stopflock
2025-09-26 14:43:57 -05:00
parent 175bc8831a
commit a2b842fb67
5 changed files with 138 additions and 40 deletions

View File

@@ -211,5 +211,27 @@
"deleteOperatorProfile": "Betreiber-Profil Löschen",
"deleteOperatorProfileConfirm": "Sind Sie sicher, dass Sie \"{}\" löschen möchten?",
"operatorProfileDeleted": "Betreiber-Profil gelöscht"
},
"offlineAreas": {
"noAreasTitle": "Keine Offline-Bereiche",
"noAreasSubtitle": "Laden Sie einen Kartenbereich für die Offline-Nutzung herunter.",
"provider": "Anbieter",
"zoomLevels": "Z{}-{}",
"latitude": "Breite",
"longitude": "Länge",
"tiles": "Kacheln",
"size": "Größe",
"cameras": "Kameras",
"areaIdFallback": "Bereich {}...",
"renameArea": "Bereich umbenennen",
"refreshWorldTiles": "Welt-Kacheln aktualisieren/neu herunterladen",
"deleteOfflineArea": "Offline-Bereich löschen",
"cancelDownload": "Download abbrechen",
"renameAreaDialogTitle": "Offline-Bereich Umbenennen",
"areaNameLabel": "Bereichsname",
"renameButton": "Umbenennen",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
}
}

View File

@@ -211,5 +211,27 @@
"deleteOperatorProfile": "Delete Operator Profile",
"deleteOperatorProfileConfirm": "Are you sure you want to delete \"{}\"?",
"operatorProfileDeleted": "Operator profile deleted"
},
"offlineAreas": {
"noAreasTitle": "No offline areas",
"noAreasSubtitle": "Download a map area for offline use.",
"provider": "Provider",
"zoomLevels": "Z{}-{}",
"latitude": "Lat",
"longitude": "Lon",
"tiles": "Tiles",
"size": "Size",
"cameras": "Cameras",
"areaIdFallback": "Area {}...",
"renameArea": "Rename area",
"refreshWorldTiles": "Refresh/re-download world tiles",
"deleteOfflineArea": "Delete offline area",
"cancelDownload": "Cancel download",
"renameAreaDialogTitle": "Rename Offline Area",
"areaNameLabel": "Area Name",
"renameButton": "Rename",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
}
}

View File

@@ -211,5 +211,27 @@
"deleteOperatorProfile": "Eliminar Perfil de Operador",
"deleteOperatorProfileConfirm": "¿Está seguro de que desea eliminar \"{}\"?",
"operatorProfileDeleted": "Perfil de operador eliminado"
},
"offlineAreas": {
"noAreasTitle": "Sin áreas sin conexión",
"noAreasSubtitle": "Descarga un área del mapa para uso sin conexión.",
"provider": "Proveedor",
"zoomLevels": "Z{}-{}",
"latitude": "Lat",
"longitude": "Lon",
"tiles": "Teselas",
"size": "Tamaño",
"cameras": "Cámaras",
"areaIdFallback": "Área {}...",
"renameArea": "Renombrar área",
"refreshWorldTiles": "Actualizar/re-descargar teselas mundiales",
"deleteOfflineArea": "Eliminar área sin conexión",
"cancelDownload": "Cancelar descarga",
"renameAreaDialogTitle": "Renombrar Área Sin Conexión",
"areaNameLabel": "Nombre del Área",
"renameButton": "Renombrar",
"megabytes": "MB",
"kilobytes": "KB",
"progress": "{}%"
}
}

View File

@@ -211,5 +211,27 @@
"deleteOperatorProfile": "Supprimer Profil d'Opérateur",
"deleteOperatorProfileConfirm": "Êtes-vous sûr de vouloir supprimer \"{}\"?",
"operatorProfileDeleted": "Profil d'opérateur supprimé"
},
"offlineAreas": {
"noAreasTitle": "Aucune zone hors ligne",
"noAreasSubtitle": "Téléchargez une zone de carte pour utilisation hors ligne.",
"provider": "Fournisseur",
"zoomLevels": "Z{}-{}",
"latitude": "Lat",
"longitude": "Lon",
"tiles": "Tuiles",
"size": "Taille",
"cameras": "Caméras",
"areaIdFallback": "Zone {}...",
"renameArea": "Renommer la zone",
"refreshWorldTiles": "Actualiser/re-télécharger les tuiles mondiales",
"deleteOfflineArea": "Supprimer la zone hors ligne",
"cancelDownload": "Annuler le téléchargement",
"renameAreaDialogTitle": "Renommer la Zone Hors Ligne",
"areaNameLabel": "Nom de la Zone",
"renameButton": "Renommer",
"megabytes": "Mo",
"kilobytes": "Ko",
"progress": "{}%"
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../../services/offline_area_service.dart';
import '../../services/offline_areas/offline_area_models.dart';
import '../../services/localization_service.dart';
class OfflineAreasSection extends StatefulWidget {
const OfflineAreasSection({super.key});
@@ -25,35 +26,42 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
@override
Widget build(BuildContext context) {
final areas = service.offlineAreas;
if (areas.isEmpty) {
return const ListTile(
leading: Icon(Icons.download_for_offline),
title: Text('No offline areas'),
subtitle: Text('Download a map area for offline use.'),
);
}
return Column(
children: areas.map((area) {
String diskStr = area.sizeBytes > 0
? area.sizeBytes > 1024 * 1024
? "${(area.sizeBytes / (1024 * 1024)).toStringAsFixed(2)} MB"
: "${(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)}';
if (area.status == OfflineAreaStatus.downloading) {
subtitle += '\nTiles: ${area.tilesDownloaded} / ${area.tilesTotal}';
} else {
subtitle += '\nTiles: ${area.tilesTotal}';
}
subtitle += '\nSize: $diskStr';
if (!area.isPermanent) {
subtitle += '\nCameras: ${area.cameras.length}';
return AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) {
final locService = LocalizationService.instance;
final areas = service.offlineAreas;
if (areas.isEmpty) {
return ListTile(
leading: const Icon(Icons.download_for_offline),
title: Text(locService.t('offlineAreas.noAreasTitle')),
subtitle: Text(locService.t('offlineAreas.noAreasSubtitle')),
);
}
return Column(
children: areas.map((area) {
String diskStr = area.sizeBytes > 0
? area.sizeBytes > 1024 * 1024
? "${(area.sizeBytes / (1024 * 1024)).toStringAsFixed(2)} ${locService.t('offlineAreas.megabytes')}"
: "${(area.sizeBytes / 1024).toStringAsFixed(1)} ${locService.t('offlineAreas.kilobytes')}"
: '--';
String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' +
locService.t('offlineAreas.zoomLevels', params: [area.minZoom.toString(), area.maxZoom.toString()]) + '\n' +
'${locService.t('offlineAreas.latitude')}: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' +
'${locService.t('offlineAreas.latitude')}: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}';
if (area.status == OfflineAreaStatus.downloading) {
subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesDownloaded} / ${area.tilesTotal}';
} else {
subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesTotal}';
}
subtitle += '\n${locService.t('offlineAreas.size')}: $diskStr';
if (!area.isPermanent) {
subtitle += '\n${locService.t('offlineAreas.cameras')}: ${area.cameras.length}';
}
return Card(
child: ListTile(
leading: Icon(area.status == OfflineAreaStatus.complete
@@ -66,35 +74,35 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
Expanded(
child: Text(area.name.isNotEmpty
? area.name
: 'Area ${area.id.substring(0, 6)}...'),
: locService.t('offlineAreas.areaIdFallback', params: [area.id.substring(0, 6)])),
),
if (!area.isPermanent)
IconButton(
icon: const Icon(Icons.edit, size: 20),
tooltip: 'Rename area',
tooltip: locService.t('offlineAreas.renameArea'),
onPressed: () async {
String? newName = await showDialog<String>(
context: context,
builder: (ctx) {
final ctrl = TextEditingController(text: area.name);
return AlertDialog(
title: const Text('Rename Offline Area'),
title: Text(locService.t('offlineAreas.renameAreaDialogTitle')),
content: TextField(
controller: ctrl,
maxLength: 40,
decoration: const InputDecoration(labelText: 'Area Name'),
decoration: InputDecoration(labelText: locService.t('offlineAreas.areaNameLabel')),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Cancel'),
child: Text(locService.t('actions.cancel')),
),
ElevatedButton(
onPressed: () {
Navigator.pop(ctx, ctrl.text.trim());
},
child: const Text('Rename'),
child: Text(locService.t('offlineAreas.renameButton')),
),
],
);
@@ -111,7 +119,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
if (area.isPermanent && area.status != OfflineAreaStatus.downloading)
IconButton(
icon: const Icon(Icons.refresh, color: Colors.blue),
tooltip: 'Refresh/re-download world tiles',
tooltip: locService.t('offlineAreas.refreshWorldTiles'),
onPressed: () async {
await service.downloadArea(
id: area.id,
@@ -133,7 +141,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
else if (!area.isPermanent && area.status != OfflineAreaStatus.downloading)
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
tooltip: 'Delete offline area',
tooltip: locService.t('offlineAreas.deleteOfflineArea'),
onPressed: () async {
service.deleteArea(area.id);
setState(() {});
@@ -154,7 +162,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
children: [
LinearProgressIndicator(value: area.progress),
Text(
'${(area.progress * 100).toStringAsFixed(0)}%',
locService.t('offlineAreas.progress', params: [(area.progress * 100).toStringAsFixed(0)]),
style: const TextStyle(fontSize: 12),
)
],
@@ -162,7 +170,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
),
IconButton(
icon: const Icon(Icons.cancel, color: Colors.orange),
tooltip: 'Cancel download',
tooltip: locService.t('offlineAreas.cancelDownload'),
onPressed: () {
service.cancelDownload(area.id);
setState(() {});
@@ -178,8 +186,10 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
}
: null,
),
);
}).toList(),
);
}).toList(),
);
},
);
}
}