diff --git a/src-tauri/gen/android/app/src/main/java/org/bocken/app/AndroidBridge.kt b/src-tauri/gen/android/app/src/main/java/org/bocken/app/AndroidBridge.kt index 5b09e4e..97463fc 100644 --- a/src-tauri/gen/android/app/src/main/java/org/bocken/app/AndroidBridge.kt +++ b/src-tauri/gen/android/app/src/main/java/org/bocken/app/AndroidBridge.kt @@ -19,7 +19,7 @@ class AndroidBridge(private val context: Context) { private var ttsForVoices: TextToSpeech? = null @JavascriptInterface - fun startLocationService(ttsConfigJson: String) { + fun startLocationService(ttsConfigJson: String, startPaused: Boolean) { if (context is Activity) { // Request notification permission on Android 13+ (required for foreground service notification) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -50,6 +50,7 @@ class AndroidBridge(private val context: Context) { val intent = Intent(context, LocationForegroundService::class.java).apply { putExtra("ttsConfig", ttsConfigJson) + putExtra("startPaused", startPaused) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) @@ -58,10 +59,16 @@ class AndroidBridge(private val context: Context) { } } - /** Backwards-compatible overload for calls without TTS config */ + /** Overload: TTS config only (not paused) */ + @JavascriptInterface + fun startLocationService(ttsConfigJson: String) { + startLocationService(ttsConfigJson, false) + } + + /** Overload: no args (not paused, no TTS) */ @JavascriptInterface fun startLocationService() { - startLocationService("{}") + startLocationService("{}", false) } @JavascriptInterface diff --git a/src-tauri/gen/android/app/src/main/java/org/bocken/app/LocationForegroundService.kt b/src-tauri/gen/android/app/src/main/java/org/bocken/app/LocationForegroundService.kt index 807d2d7..b965371 100644 --- a/src-tauri/gen/android/app/src/main/java/org/bocken/app/LocationForegroundService.kt +++ b/src-tauri/gen/android/app/src/main/java/org/bocken/app/LocationForegroundService.kt @@ -127,10 +127,11 @@ class LocationForegroundService : Service(), TextToSpeech.OnInitListener { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val startPaused = intent?.getBooleanExtra("startPaused", false) ?: false startTimeMs = System.currentTimeMillis() pausedAccumulatedMs = 0L - pausedSinceMs = 0L - paused = false + pausedSinceMs = if (startPaused) startTimeMs else 0L + paused = startPaused totalDistanceKm = 0.0 lastLat = Double.NaN lastLng = Double.NaN @@ -151,7 +152,11 @@ class LocationForegroundService : Service(), TextToSpeech.OnInitListener { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val notification = buildNotification("0:00", "0.00 km", "") + val notification = if (startPaused) { + buildNotification("Waiting to start...", "", "") + } else { + buildNotification("0:00", "0.00 km", "") + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(NOTIFICATION_ID, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION) @@ -488,7 +493,7 @@ class LocationForegroundService : Service(), TextToSpeech.OnInitListener { val channel = NotificationChannel( CHANNEL_ID, "GPS Tracking", - NotificationManager.IMPORTANCE_LOW + NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "Shows while GPS is recording your workout" } diff --git a/src/lib/js/gps.svelte.ts b/src/lib/js/gps.svelte.ts index 8d47f2a..4745c2b 100644 --- a/src/lib/js/gps.svelte.ts +++ b/src/lib/js/gps.svelte.ts @@ -23,7 +23,7 @@ export interface VoiceGuidanceConfig { } interface AndroidBridge { - startLocationService(ttsConfigJson: string): void; + startLocationService(ttsConfigJson: string, startPaused: boolean): void; stopLocationService(): void; getPoints(): string; isTracking(): boolean; @@ -112,7 +112,7 @@ export function createGpsTracker() { } } - async function start(voiceGuidance?: VoiceGuidanceConfig) { + async function start(voiceGuidance?: VoiceGuidanceConfig, startPaused = false) { _debugMsg = 'starting...'; if (!checkTauri() || isTracking) { _debugMsg = `bail: tauri=${checkTauri()} tracking=${isTracking}`; @@ -145,7 +145,7 @@ export function createGpsTracker() { if (bridge) { _debugMsg = 'starting native GPS service...'; const ttsConfig = JSON.stringify(voiceGuidance ?? {}); - bridge.startLocationService(ttsConfig); + bridge.startLocationService(ttsConfig, startPaused); // Poll the native side for collected points _pollTimer = setInterval(pollPoints, POLL_INTERVAL_MS); _debugMsg = 'native GPS service started, polling...'; diff --git a/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte b/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte index 678c603..3109b49 100644 --- a/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte +++ b/src/routes/fitness/[workout=fitnessWorkout]/[active=fitnessActive]/+page.svelte @@ -18,6 +18,7 @@ import SetTable from '$lib/components/fitness/SetTable.svelte'; import ExercisePicker from '$lib/components/fitness/ExercisePicker.svelte'; import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte'; + import Toggle from '$lib/components/Toggle.svelte'; import { onMount } from 'svelte'; const workout = getWorkout(); @@ -41,13 +42,26 @@ let useGps = $state(gps.isTracking); - // Voice guidance config + // Voice guidance config (defaults, overridden from localStorage in onMount) let vgEnabled = $state(false); let vgTriggerType = $state('distance'); let vgTriggerValue = $state(1); let vgMetrics = $state(['totalTime', 'totalDistance', 'avgPace']); - let vgLanguage = $state('en'); + const vgLanguage = $derived(lang); let vgShowPanel = $state(false); + let vgLoaded = $state(false); + + // Persist voice guidance settings to localStorage + $effect(() => { + const settings = { + enabled: vgEnabled, + triggerType: vgTriggerType, + triggerValue: vgTriggerValue, + metrics: vgMetrics, + }; + if (!vgLoaded) return; + localStorage.setItem('vg_settings', JSON.stringify(settings)); + }); // GPS workout mode state — if we're restoring a GPS workout that was already tracking, it's started let gpsStarted = $state(gps.isTracking && workout.mode === 'gps' && !workout.paused); @@ -250,11 +264,24 @@ return; } + // Restore voice guidance settings from localStorage + try { + const saved = localStorage.getItem('vg_settings'); + if (saved) { + const s = JSON.parse(saved); + if (typeof s.enabled === 'boolean') vgEnabled = s.enabled; + if (s.triggerType === 'distance' || s.triggerType === 'time') vgTriggerType = s.triggerType; + if (typeof s.triggerValue === 'number' && s.triggerValue > 0) vgTriggerValue = s.triggerValue; + if (Array.isArray(s.metrics)) vgMetrics = s.metrics; + } + } catch {} + vgLoaded = true; + // For GPS workouts in pre-start: start GPS immediately so the map // shows the user's position while they configure activity/audio. if (workout.mode === 'gps' && !gpsStarted && !gps.isTracking) { _prestartGps = true; - gps.start(); + gps.start(undefined, true); } }); @@ -873,7 +900,7 @@
- {:else} - +