From ec5937ea7e26df40b6cc44639559908544ac102e Mon Sep 17 00:00:00 2001 From: 4aiman Date: Tue, 7 Jul 2026 23:45:11 +0300 Subject: [PATCH] GPS: full native re-import on stop + visible progressive import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stopRecording: replace the dead _lastNativeCount backfill (the variable is never assigned → guard is always false → missed points were never saved) with a full timestamp-based re-import from the native points file, then recompute stats over the complete set. Fixes losing ~90% of a track when recording with the screen off — the JS import poll is frozen by the WebView while the screen is off, so only the native file had all points. - Poll: draw each imported point via a lightweight onImportPoint and yield a frame every ~20 points so the track visibly grows as points transfer on screen-wake catch-up (restores pre-batch behaviour, kept cheap). --- README.md | 2 +- frontend/js/gps.js | 31 ++++++++++++++++++++++--------- frontend/js/main.js | 17 ++++++++++++++++- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4e318ea..fe4b2ba 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Возможности - **Дневник питания** — 24400+ продуктов (Магнит, 5ка, Health-Diet.ru, Ашан, Макдоналдс, KFC, Додо Пицца, KDV, ИОН, Гриднев). 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50). -- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN). +- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN). - **BLE пульсометр** — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека. - **Расчёт ккал** — физическая модель для велосипеда (мощность из gradient + Crr + CdA). MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности. - **Тренировки** — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу. diff --git a/frontend/js/gps.js b/frontend/js/gps.js index a3b2ce5..cc82b13 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -787,18 +787,24 @@ GPS.startRecording = async function(activityType, weightKg) { try { const nativePoints = await GPS._readNativePoints(); let newCount = 0; - // Suppress per-point UI updates during batch — onBatchUpdate handles it + // Keep the heavy per-point path off (onPoint runs computeStats over all + // points + DB writes → O(n²) on a big backlog). Draw each imported point + // cheaply via onImportPoint so the track visibly GROWS as points transfer + // from the native file, and yield a frame every ~20 points so Leaflet + // repaints — this makes the screen-wake catch-up visible again. const origOnPoint = GPS.recording.onPoint; + GPS.recording.onPoint = null; for (const p of nativePoints) { const pTs = new Date(p.ts).getTime(); if (pTs <= GPS.recording._lastProcessedTs) continue; - GPS.recording.onPoint = null; // suppress addLatLng per point await GPS._processIncomingPoint(p); GPS.recording._lastProcessedTs = pTs; newCount++; + if (GPS.recording.onImportPoint) GPS.recording.onImportPoint(p); + if (newCount % 20 === 0) await new Promise(r => requestAnimationFrame(r)); } GPS.recording.onPoint = origOnPoint; - // Batch UI update — rebuild polyline from sorted points + // Final sync — marker, pan, course and stats from the full point set. if (newCount > 0 && GPS.recording.onBatchUpdate) { GPS.recording.onBatchUpdate(); } @@ -845,14 +851,21 @@ GPS.stopRecording = async function() { // Stop native service — read remaining points but DON'T delete the file yet let nativePointsFile = null; if (GPS.recording.nativeService) { - // Import any remaining points from native service + // Import EVERY native point not yet in the DB. The live poll is screen-gated + // (WebView freezes timers while the screen is off), so a walk with the screen + // off leaves most points only in the native file. Idempotent by timestamp — + // same pattern as recoverSession. Then recompute stats over the COMPLETE set. const nativePoints = await GPS._readNativePoints(); - if (nativePoints.length > GPS.recording._lastNativeCount && GPS.recording.sessionId) { - for (let i = GPS.recording._lastNativeCount; i < nativePoints.length; i++) { - const p = nativePoints[i]; - GPS.recording.points.push(p); - await DB.addGpsPoint(GPS.recording.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, null, null); + if (GPS.recording.sessionId && nativePoints.length) { + const existing = await DB.getGpsPoints(GPS.recording.sessionId); + const dbTs = new Set(existing.map(p => new Date(p.ts).getTime())); + let seq = existing.length; + for (const p of nativePoints) { + if (dbTs.has(new Date(p.ts).getTime())) continue; + seq++; + await DB.addGpsPoint(GPS.recording.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, seq, null, p); } + GPS.recording.points = await DB.getGpsPoints(GPS.recording.sessionId); } // Remember the file name for cleanup after DB save try { diff --git a/frontend/js/main.js b/frontend/js/main.js index bbc5aea..a24815b 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -3296,7 +3296,16 @@ function renderRecordingInline(container) { let stats = GPS.computeStats(GPS.recording.points, GPS.recording.weightKg, GPS.getRecordingOpts()); if (distEl) distEl.textContent = GPS.formatDistance(stats.distance); }; - + + // Lightweight per-point draw for progressive import (screen-wake catch-up): + // only extends the polyline so the track visibly grows. Heavy work (stats, + // marker, course) is done periodically + at the end by onBatchUpdate. + GPS.recording.onImportPoint = (p) => { + let ll = [p.lat, p.lon]; + borderLine.addLatLng(ll); + trailLine.addLatLng(ll); + }; + } function setupLiveUpdates(container) { @@ -3330,6 +3339,12 @@ function setupLiveUpdates(container) { if (spdEl) spdEl.textContent = stats.avgSpeed + " км/ч"; }; + // Lightweight per-point draw for progressive import (see recording screen). + GPS.recording.onImportPoint = (p) => { + if (!gpsTrailLine) return; + gpsTrailLine.addLatLng([p.lat, p.lon]); + }; + // Timer for duration display + auto-finish watchdog for a forgotten session timer = setInterval(async () => { if (!GPS.recording.active) { clearInterval(timer); return; } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a46d007..55b83e7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -139,7 +139,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "baldav" -version = "0.1.361" +version = "0.1.363" dependencies = [ "base64 0.22.1", "btleplug", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9afeb74..1ea280a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baldav" -version = "0.1.361" +version = "0.1.363" edition = "2021" [lib] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7767888..4f619dd 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicegui/tauri-v2/refs/heads/main/tooling/cli/schema.json", "productName": "BalDav", - "version": "0.1.361", + "version": "0.1.363", "identifier": "dev.baldav.app", "build": { "frontendDist": "../frontend"