Compare commits

3 Commits

Author SHA1 Message Date
9e95179175 fix: auto-zoom to street level when first GPS point arrives
All checks were successful
CI / update (push) Successful in 2m19s
When the map starts zoomed out (level 5), snap to zoom 16 on the first
real GPS position instead of keeping the overview level.
2026-03-26 10:08:28 +01:00
c997e74806 feat: add debug mode to Android build script
Adds a `debug` command that temporarily enables cleartext traffic and
points frontendDist at the local dev server, then restores release
config on exit via trap.
2026-03-26 10:05:59 +01:00
c8dafa7c8a fix: live-update GPS position marker and distance during tracking
- Make map variables reactive ($state) so effects fire when map initializes
- Split single effect into polyline update + marker/view tracking
- Marker now always follows latestPoint instead of staying at start position
- Reset prevTrackLen on GPS restart to avoid skipping points
- Hide marker until real GPS position arrives
- Round saved distance to nearest 10m to avoid long floating-point values
2026-03-26 10:05:41 +01:00
2 changed files with 70 additions and 33 deletions

View File

@@ -12,11 +12,17 @@ APK_SIGNED="$APK_DIR/app-universal-release-signed.apk"
KEYSTORE="src-tauri/debug.keystore"
PACKAGE="org.bocken.app"
MANIFEST="src-tauri/gen/android/app/src/main/AndroidManifest.xml"
TAURI_CONF="src-tauri/tauri.conf.json"
DEV_SERVER="http://192.168.1.4:5173"
PROD_DIST="https://bocken.org"
usage() {
echo "Usage: $0 [build|deploy|run]"
echo "Usage: $0 [build|deploy|run|debug]"
echo " build - Build and sign the APK"
echo " deploy - Build + install on connected device"
echo " run - Build + install + launch on device"
echo " debug - Deploy pointing at local dev server (cleartext enabled)"
exit 1
}
@@ -82,9 +88,28 @@ run() {
echo ":: App launched."
}
enable_debug() {
echo ":: Enabling debug config (cleartext + local dev server)..."
sed -i 's|\${usesCleartextTraffic}|true|' "$MANIFEST"
sed -i "s|\"frontendDist\": \"$PROD_DIST\"|\"frontendDist\": \"$DEV_SERVER\"|" "$TAURI_CONF"
}
restore_release() {
echo ":: Restoring release config..."
sed -i 's|android:usesCleartextTraffic="true"|android:usesCleartextTraffic="${usesCleartextTraffic}"|' "$MANIFEST"
sed -i "s|\"frontendDist\": \"$DEV_SERVER\"|\"frontendDist\": \"$PROD_DIST\"|" "$TAURI_CONF"
}
debug() {
enable_debug
trap restore_release EXIT
deploy
}
case "${1:-}" in
build) build ;;
deploy) deploy ;;
run) run ;;
debug) debug ;;
*) usage ;;
esac

View File

@@ -101,11 +101,11 @@
}
/** @type {any} */
let liveMap = null;
let liveMap = $state(null);
/** @type {any} */
let livePolyline = null;
let livePolyline = $state(null);
/** @type {any} */
let liveMarker = null;
let liveMarker = $state(null);
/** @type {any} */
let leafletLib = null;
let prevTrackLen = 0;
@@ -117,13 +117,13 @@
destroy() {
if (liveMap) {
liveMap.remove();
}
liveMap = null;
livePolyline = null;
liveMarker = null;
leafletLib = null;
prevTrackLen = 0;
}
}
};
}
@@ -140,25 +140,28 @@
}).addTo(liveMap);
livePolyline = leafletLib.polyline([], { color: '#88c0d0', weight: 3 }).addTo(liveMap);
liveMarker = leafletLib.circleMarker([0, 0], {
radius: 6, fillColor: '#a3be8c', fillOpacity: 1, color: '#fff', weight: 2
radius: 6, fillColor: '#a3be8c', fillOpacity: 1, color: '#fff', weight: 2, opacity: 0, fillOpacity: 0
}).addTo(liveMap);
if (gps.track.length > 0) {
// Restore existing trail on the polyline
if (gpsStarted) {
const pts = gps.track.map((/** @type {any} */ p) => [p.lat, p.lng]);
livePolyline.setLatLngs(pts);
liveMarker.setLatLng(pts[pts.length - 1]);
liveMap.setView(pts[pts.length - 1], 16);
}
// Center on latest point — the marker $effect will also kick in
const last = gps.track[gps.track.length - 1];
liveMap.setView([last.lat, last.lng], 16);
liveMarker.setLatLng([last.lat, last.lng]);
prevTrackLen = gps.track.length;
} else {
// No track yet — show fallback until GPS kicks in
liveMap.setView([51.5, 10], 16);
// No track yet — get current position to center the map
liveMap.setView([51.5, 10], 5);
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
(pos) => {
if (liveMap) {
const ll = [pos.coords.latitude, pos.coords.longitude];
liveMap.setView(ll, 16);
liveMarker.setLatLng(ll);
liveMap.setView([pos.coords.latitude, pos.coords.longitude], 16);
}
},
() => {},
@@ -204,25 +207,33 @@
}
});
// Update polyline incrementally when new track points arrive
$effect(() => {
const len = gps.track.length;
if (len > prevTrackLen && liveMap && gps.latestPoint) {
if (gpsStarted) {
// Only draw the trail once the workout has actually started
if (len > prevTrackLen && liveMap && livePolyline && gpsStarted) {
for (let i = prevTrackLen; i < len; i++) {
const p = gps.track[i];
livePolyline.addLatLng([p.lat, p.lng]);
}
}
// Always update the position marker
const pt = [gps.latestPoint.lat, gps.latestPoint.lng];
liveMarker.setLatLng(pt);
const zoom = liveMap.getZoom() || 16;
liveMap.setView(pt, zoom);
// Always sync prevTrackLen even if we didn't draw (e.g. pre-start)
if (len > prevTrackLen) {
prevTrackLen = len;
}
});
// Always keep marker and map view centered on the latest GPS position
$effect(() => {
const pt = gps.latestPoint;
if (pt && liveMap && liveMarker) {
const ll = [pt.lat, pt.lng];
liveMarker.setLatLng(ll);
liveMarker.setStyle({ opacity: 1, fillOpacity: 1 });
const zoom = liveMap.getZoom();
liveMap.setView(ll, zoom < 14 ? 16 : zoom);
}
});
/** Check if any exercise in the workout is cardio */
function hasCardioExercise() {
return workout.exercises.some((/** @type {any} */ e) => {
@@ -285,6 +296,7 @@
// so the native service resets time/distance to zero
await gps.stop();
gps.reset();
prevTrackLen = 0;
}
const started = await gps.start(getVoiceGuidanceConfig());
if (started) {
@@ -317,7 +329,7 @@
if (wasGpsMode && gpsTrack.length >= 2) {
// GPS workout: create a cardio exercise entry with the track attached,
// just like a manually-added workout with GPX upload
const filteredDistance = trackDistance(gpsTrack);
const filteredDistance = Math.round(trackDistance(gpsTrack) * 100) / 100;
const durationMin = (gpsTrack[gpsTrack.length - 1].timestamp - gpsTrack[0].timestamp) / 60000;
const exerciseId = ACTIVITY_EXERCISE_MAP[actType ?? 'running'] ?? 'running';
const exerciseName = getExerciseById(exerciseId)?.name ?? exerciseId;
@@ -343,7 +355,7 @@
// Manual workout: attach GPS to cardio exercises
const workoutStart = new Date(sessionData.startTime).getTime();
const filteredTrack = gpsTrack.filter((/** @type {any} */ p) => p.timestamp >= workoutStart);
const filteredDistance = trackDistance(filteredTrack);
const filteredDistance = Math.round(trackDistance(filteredTrack) * 100) / 100;
if (filteredTrack.length > 0) {
for (const ex of sessionData.exercises) {