Fix: gelaufene km bei Routen-Navigation gehen verloren wenn nicht über

In-App-Zurück geschlossen wird (Angie-Bug)

Bisher wurde walked_km NUR in _closeNav (In-App-Zurück-Pfeil) gespeichert.
Wer die Navigation anders verlässt (Handy sperren/Home/PWA schließen, oder
nur den Dim-Entsperrpfeil + normal schließen), verlor die km.

- Fortschritt laufend in localStorage sichern (überlebt App-Kill).
- _recordNavWalk() Einmal-Guard, aufgerufen von _closeNav UND pagehide.
- _flushPendingNavWalk() trägt beim nächsten App-Start einen nicht
  gespeicherten Walk nach.
- Fehler nicht mehr still verschlucken: bleibt in localStorage → Retry.
This commit is contained in:
rene 2026-06-04 09:09:07 +02:00
parent 684ffa3b46
commit 91624dac25
6 changed files with 70 additions and 26 deletions

View file

@ -1 +1 @@
1159
1160

View file

@ -86,14 +86,14 @@
<title>Ban Yaro</title>
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
<script src="/js/boot-early.js?v=1159"></script>
<script src="/js/boot-early.js?v=1160"></script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1159">
<link rel="stylesheet" href="/css/layout.css?v=1159">
<link rel="stylesheet" href="/css/components.css?v=1159">
<link rel="stylesheet" href="/css/utilities.css?v=1159">
<link rel="stylesheet" href="/css/lists.css?v=1159">
<link rel="stylesheet" href="/css/design-system.css?v=1160">
<link rel="stylesheet" href="/css/layout.css?v=1160">
<link rel="stylesheet" href="/css/components.css?v=1160">
<link rel="stylesheet" href="/css/utilities.css?v=1160">
<link rel="stylesheet" href="/css/lists.css?v=1160">
</head>
<body>
@ -617,11 +617,11 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1159"></script>
<script src="/js/ui.js?v=1159"></script>
<script src="/js/app.js?v=1159"></script>
<script src="/js/worlds.js?v=1159"></script>
<script src="/js/offline-indicator.js?v=1159"></script>
<script src="/js/api.js?v=1160"></script>
<script src="/js/ui.js?v=1160"></script>
<script src="/js/app.js?v=1160"></script>
<script src="/js/worlds.js?v=1160"></script>
<script src="/js/offline-indicator.js?v=1160"></script>
<!-- Feature-Seiten werden lazy geladen -->
@ -631,7 +631,7 @@
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
<script src="/js/boot.js?v=1159"></script>
<script src="/js/boot.js?v=1160"></script>
</body>

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '1159'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '1160'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;

View file

@ -61,6 +61,8 @@ window.Page_routes = (() => {
let _navPois = [];
let _navMaxIdx = 0;
let _navWalkMeta = null; // { routeId, totalKm, trackLen }
let _navRecorded = false; // Einmal-Guard pro Navigation
const _PENDING_WALK_KEY = 'by_pending_nav_walk'; // localStorage-Sicherheitsnetz
let _navOrientCleanup = null;
let _navLastBearing = null;
let _navCompassHeading = null;
@ -122,6 +124,7 @@ window.Page_routes = (() => {
_render();
UI.loadLeaflet(); // fire & forget — bereit wenn Cards gerendert werden
_flushPendingNavWalk(); // nicht gespeicherten Navigations-Walk nachtragen
try { _userPos = await API.getLocation(); } catch {}
await _loadData();
@ -1574,6 +1577,7 @@ window.Page_routes = (() => {
if (track.length < 2) return;
_navMaxIdx = 0;
_navRecorded = false;
_navLastBearing = null;
_navWalkMeta = { routeId: route.id, totalKm: route.distanz_km || 0, trackLen: track.length };
@ -1791,7 +1795,15 @@ window.Page_routes = (() => {
};
const _updateStats = (idx, distToRoute, userLat, userLon) => {
if (idx > _navMaxIdx) _navMaxIdx = idx;
if (idx > _navMaxIdx) {
_navMaxIdx = idx;
// Fortschritt persistent sichern — überlebt App-Kill / beliebiges Schließen
if (_navWalkMeta && _navWalkMeta.trackLen > 1) {
const p = Math.round(_navMaxIdx / (_navWalkMeta.trackLen - 1) * 100);
const km = Math.round((_navMaxIdx / (_navWalkMeta.trackLen - 1)) * _navWalkMeta.totalKm * 100) / 100;
try { localStorage.setItem(_PENDING_WALK_KEY, JSON.stringify({ routeId: _navWalkMeta.routeId, walkedKm: km, pct: p, ts: Date.now() })); } catch {}
}
}
const pct = Math.round(idx / (track.length - 1) * 100);
const rem = _remainingKm(idx);
document.getElementById('rk-nav-pct').textContent = pct + '%';
@ -1827,6 +1839,7 @@ window.Page_routes = (() => {
// GPS-Watch
await _navAcquireWakeLock();
document.addEventListener('visibilitychange', _navOnVisibility);
window.addEventListener('pagehide', _recordNavWalk); // Schließen/Weg-Navigieren → speichern
let _navFirstFix = true;
_navWatchId = navigator.geolocation.watchPosition(pos => {
const { latitude: lat, longitude: lon } = pos.coords;
@ -1989,23 +2002,54 @@ window.Page_routes = (() => {
}
}
// Speichert den gelaufenen Walk — egal wie die Navigation verlassen wird
// (In-App-Zurück, pagehide/Schließen). Einmal-Guard; bei Fehler bleibt der
// Eintrag in localStorage und wird beim nächsten App-Start nachgetragen.
function _recordNavWalk() {
if (_navRecorded || !_navWalkMeta || _navWalkMeta.trackLen <= 1) return;
const pct = Math.round(_navMaxIdx / (_navWalkMeta.trackLen - 1) * 100);
if (pct < 50) return;
_navRecorded = true;
const walkedKm = Math.round((_navMaxIdx / (_navWalkMeta.trackLen - 1)) * _navWalkMeta.totalKm * 100) / 100;
API.routes.walked(_navWalkMeta.routeId, walkedKm, pct)
.then(res => {
try { localStorage.removeItem(_PENDING_WALK_KEY); } catch {}
if (res?.new_badges?.length) UI.toast.success(`🏅 Neues Badge: ${res.new_badges[0].name}!`);
})
.catch(() => {}); // bleibt in localStorage → Nachtrag beim nächsten Start
}
// Beim App-/Seiten-Start: nicht gespeicherten Walk nachtragen (App gekillt /
// „wie immer" geschlossen, ohne dass ein Speicher-Event lief).
function _flushPendingNavWalk() {
if (_navWatchId !== null) return; // läuft gerade eine Navigation
let p;
try { p = JSON.parse(localStorage.getItem(_PENDING_WALK_KEY) || 'null'); } catch {}
if (!p || (p.pct || 0) < 50) return;
if (Date.now() - (p.ts || 0) > 7 * 24 * 3600 * 1000) { // zu alt → verwerfen
try { localStorage.removeItem(_PENDING_WALK_KEY); } catch {}
return;
}
API.routes.walked(p.routeId, p.walkedKm, p.pct)
.then(res => {
try { localStorage.removeItem(_PENDING_WALK_KEY); } catch {}
UI.toast.success('Gelaufene Tour nachgetragen 🐾');
if (res?.new_badges?.length) UI.toast.success(`🏅 Neues Badge: ${res.new_badges[0].name}!`);
})
.catch(() => {}); // bleibt für den nächsten Versuch
}
function _closeNav() {
_recordNavWalk(); // ZUERST speichern (vor jedem Reset)
if (_navWatchId !== null) { navigator.geolocation.clearWatch(_navWatchId); _navWatchId = null; }
if (_navInactTimer) { clearTimeout(_navInactTimer); _navInactTimer = null; }
if (_navWakeLock) { try { _navWakeLock.release(); } catch {} _navWakeLock = null; }
document.removeEventListener('visibilitychange', _navOnVisibility);
if (_navWalkMeta && _navWalkMeta.trackLen > 1) {
const pct = Math.round(_navMaxIdx / (_navWalkMeta.trackLen - 1) * 100);
if (pct >= 50) {
const walkedKm = Math.round((_navMaxIdx / (_navWalkMeta.trackLen - 1)) * _navWalkMeta.totalKm * 100) / 100;
API.routes.walked(_navWalkMeta.routeId, walkedKm, pct)
.then(res => { if (res.new_badges?.length) UI.toast.success(`🏅 Neues Badge: ${res.new_badges[0].name}!`); })
.catch(() => {});
}
}
window.removeEventListener('pagehide', _recordNavWalk);
if (_navOrientCleanup) { _navOrientCleanup(); _navOrientCleanup = null; }
_navWalkMeta = null;
_navMaxIdx = 0;
_navRecorded = false;
_navLastBearing = null;
_navCompassHeading = null;
_navHeadingSmoothed = null;

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1159"></script>
<script src="/js/landing-init.js?v=1160"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">

View file

@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
const VER = '1159';
const VER = '1160';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten