Feature: Routenaufzeichnung übersteht App-Updates (Guard + Persistenz)

Stufe 1 (Guard): Während aktiver Aufzeichnung wird der SW-/Force-Update-Reload
aufgeschoben (window._byRecording → boot.js/_bySwReload + app.js force-update);
nach Stop/Speichern via window._byReloadIfPending() nachgeholt.

Stufe 2 (Persistenz): Track wird gedrosselt nach localStorage (RecStore) gesichert
und beim nächsten Öffnen der Karten-/Routen-Seite als 'Aufzeichnung fortsetzen?'
angeboten (Resume seedet Track+km+Startzeit). Schützt auch bei Crash/OS-Kill/
manuellem Reload. Greift in map.js UND routes.js. SW v1167
This commit is contained in:
rene 2026-06-04 17:13:23 +02:00
parent ddfb9474ef
commit 78866206b4
8 changed files with 174 additions and 40 deletions

View file

@ -156,6 +156,7 @@ window.Page_map = (() => {
_initMap(); // sofort mit Deutschland-Mitte starten
_startLocationTracking();
_loadAll();
_offerResume(); // unterbrochene Aufzeichnung anbieten
// Standort im Hintergrund holen — bei Erfolg zur Position fliegen
API.getLocation().then(pos => {
_userPos = pos;
@ -1726,7 +1727,45 @@ window.Page_map = (() => {
else _stopRecording();
}
async function _startRecording() {
// Aufzeichnung gedrosselt nach localStorage sichern (Sicherheitsnetz gegen
// Datenverlust bei Reload/Crash). force=true schreibt sofort.
let _recPersistAt = 0;
function _persistRec(force) {
const now = Date.now();
if (!force && now - _recPersistAt < 8000) return;
_recPersistAt = now;
window.RecStore?.save({ source: 'map', track: _recTrack, distKm: _recDistKm, startTime: _recStartTime });
}
// Aufzeichnung endgültig abgeschlossen (gespeichert/verworfen): Speicher
// leeren, Guard lösen und einen ggf. aufgeschobenen Update-Reload nachholen.
function _recDone() {
window.RecStore?.clear();
window._byRecording = false;
window._byReloadIfPending?.();
}
// Unterbrochene Aufzeichnung (Reload/Crash/Update) zum Fortsetzen anbieten.
let _resumeOffered = false;
async function _offerResume() {
if (_recActive || _resumeOffered) return;
const saved = window.RecStore?.load();
if (!saved || saved.source !== 'map' || !Array.isArray(saved.track) || saved.track.length < 2) return;
if (Date.now() - (saved.ts || 0) > 6 * 3600 * 1000) { window.RecStore?.clear(); return; } // > 6h alt
_resumeOffered = true;
const km = (saved.distKm || 0).toFixed(2);
const ok = await UI.modal.confirm({
title: 'Aufzeichnung fortsetzen?',
message: `Eine unterbrochene Aufzeichnung wurde gefunden (${km} km, ${saved.track.length} Punkte). Möchtest du sie fortsetzen?`,
confirmText: 'Fortsetzen',
cancelText: 'Später',
});
// Nur explizites Fortsetzen resumt; sonst Track behalten (erneut anbieten /
// Staleness räumt nach 6h auf) — kein versehentlicher Datenverlust.
if (ok) _startRecording(saved);
}
async function _startRecording(resume) {
if (!_appState.user) {
UI.toast.warning('Bitte zuerst anmelden.');
App.navigate('settings');
@ -1736,11 +1775,12 @@ window.Page_map = (() => {
UI.toast.error('GPS nicht verfügbar.');
return;
}
window._byRecording = true; // Guard: Update-Reload wird aufgeschoben
_recActive = true;
_recPaused = false;
_recTrack = [];
_recDistKm = 0;
_recStartTime = Date.now();
_recTrack = (resume && Array.isArray(resume.track)) ? resume.track.slice() : [];
_recDistKm = resume?.distKm || 0;
_recStartTime = resume?.startTime || Date.now();
// FAB umschalten
const btn = document.getElementById('map-rec-btn');
@ -1775,13 +1815,24 @@ window.Page_map = (() => {
_recDistKm += d / 1000;
}
_recTrack.push({ lat, lon });
_persistRec();
_updateRecMap(lat, lon);
_updateRecStatus();
},
() => {},
{ enableHighAccuracy: true, maximumAge: 0, timeout: 10000 }
);
UI.toast.success('Aufzeichnung gestartet — los geht\'s!');
// Fortgesetzte Aufzeichnung: bestehenden Track sofort einzeichnen
if (resume && _recTrack.length && _map && window.L) {
_recPolyline = L.polyline(_recTrack.map(p => [p.lat, p.lon]), { color: '#EF4444', weight: 5, opacity: 0.9 }).addTo(_map);
const last = _recTrack[_recTrack.length - 1];
_recMarker = L.circleMarker([last.lat, last.lon], { radius: 8, color: '#EF4444', fillColor: '#fff', fillOpacity: 1, weight: 3 }).addTo(_map);
_map.panTo([last.lat, last.lon]);
_updateRecStatus();
}
_persistRec(true);
UI.toast.success(resume ? 'Aufzeichnung fortgesetzt.' : 'Aufzeichnung gestartet — los geht\'s!');
// Pocket-Modus aktivieren wenn in Einstellungen eingeschaltet
if (localStorage.getItem('by_pocket_mode') === 'true') {
@ -1882,9 +1933,13 @@ window.Page_map = (() => {
UI.toast.warning('Zu wenige GPS-Punkte — bitte etwas länger laufen.');
if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
if (_recMarker) { _recMarker.remove(); _recMarker = null; }
_recDone();
return;
}
// Guard bleibt aktiv bis gespeichert/verworfen — der Track liegt jetzt im
// Save-Modal UND (als Netz) in RecStore.
_persistRec(true);
const dauMin = Math.max(1, Math.floor((Date.now() - _recStartTime) / 1000 / 60));
_showRecSaveModal(_recTrack, _recDistKm, dauMin);
}
@ -2013,6 +2068,7 @@ window.Page_map = (() => {
UI.modal.close();
if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
if (_recMarker) { _recMarker.remove(); _recMarker = null; }
_recDone();
});
// Hund-Checkbox Toggle-Styling
@ -2050,6 +2106,7 @@ window.Page_map = (() => {
UI.modal.close();
if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
if (_recMarker) { _recMarker.remove(); _recMarker = null; }
_recDone();
if (saved.is_valid === false) {
UI.toast.warning(`Route „${saved.name}" gespeichert — wird nicht für Statistiken gewertet (Geschwindigkeit zu hoch).`);
} else {