GPS: full native re-import on stop + visible progressive import

- 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).
This commit is contained in:
4aiman 2026-07-07 23:45:11 +03:00
commit ec5937ea7e
6 changed files with 42 additions and 14 deletions

View file

@ -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 и весу.

View file

@ -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 {

View file

@ -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; }

2
src-tauri/Cargo.lock generated
View file

@ -139,7 +139,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "baldav"
version = "0.1.361"
version = "0.1.363"
dependencies = [
"base64 0.22.1",
"btleplug",

View file

@ -1,6 +1,6 @@
[package]
name = "baldav"
version = "0.1.361"
version = "0.1.363"
edition = "2021"
[lib]

View file

@ -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"