fintess: WIP: interval setup and TTS

This commit is contained in:
2026-03-26 14:11:07 +01:00
parent 1e2422bbb1
commit e607c9a375
5 changed files with 852 additions and 10 deletions
+28
View File
@@ -235,6 +235,34 @@ const translations: Translations = {
workouts_per_week_goal: { en: 'workouts / week', de: 'Trainings / Woche' },
set_goal: { en: 'Set Goal', de: 'Ziel setzen' },
goal_set: { en: 'Goal set', de: 'Ziel gesetzt' },
// Intervals
intervals: { en: 'Intervals', de: 'Intervalle' },
no_intervals: { en: 'None', de: 'Keine' },
new_interval: { en: 'New Interval', de: 'Neues Intervall' },
edit_interval: { en: 'Edit Interval', de: 'Intervall bearbeiten' },
delete_interval: { en: 'Delete', de: 'Löschen' },
delete_interval_confirm: { en: 'Delete this interval template?', de: 'Diese Intervallvorlage löschen?' },
add_step: { en: '+ Add Step', de: '+ Schritt hinzufügen' },
step_label: { en: 'Label', de: 'Bezeichnung' },
meters: { en: 'meters', de: 'Meter' },
seconds: { en: 'seconds', de: 'Sekunden' },
intervals_complete: { en: 'Intervals complete', de: 'Intervalle abgeschlossen' },
select_interval: { en: 'Select Interval', de: 'Intervall wählen' },
custom: { en: 'Custom', de: 'Eigene' },
steps_count: { en: 'steps', de: 'Schritte' },
save_interval: { en: 'Save Interval', de: 'Intervall speichern' },
interval_name_placeholder: { en: 'Interval name', de: 'Intervallname' },
// Preset labels
label_easy: { en: 'Easy', de: 'Leicht' },
label_moderate: { en: 'Moderate', de: 'Moderat' },
label_hard: { en: 'Hard', de: 'Hart' },
label_sprint: { en: 'Sprint', de: 'Sprint' },
label_recovery: { en: 'Recovery', de: 'Erholung' },
label_hill_sprints: { en: 'Hill Sprints', de: 'Bergsprints' },
label_tempo: { en: 'Tempo', de: 'Tempo' },
label_warm_up: { en: 'Warm Up', de: 'Aufwärmen' },
label_cool_down: { en: 'Cool Down', de: 'Abkühlen' },
};
/** Get a translated string */
+27
View File
@@ -13,6 +13,12 @@ export interface GpsPoint {
timestamp: number;
}
export interface IntervalStep {
label: string;
durationType: 'distance' | 'time';
durationValue: number; // meters (distance) or seconds (time)
}
export interface VoiceGuidanceConfig {
enabled: boolean;
triggerType: 'distance' | 'time';
@@ -20,6 +26,15 @@ export interface VoiceGuidanceConfig {
metrics: string[];
language: string;
voiceId?: string;
intervals?: IntervalStep[];
}
export interface IntervalState {
currentIndex: number;
totalSteps: number;
currentLabel: string;
progress: number; // 0.01.0
complete: boolean;
}
interface AndroidBridge {
@@ -32,6 +47,7 @@ interface AndroidBridge {
installTtsEngine(): void;
pauseTracking(): void;
resumeTracking(): void;
getIntervalState(): string;
}
function checkTauri(): boolean {
@@ -95,6 +111,7 @@ export function createGpsTracker() {
);
let _debugMsg = $state('');
let _intervalState = $state<IntervalState | null>(null);
function pollPoints() {
const bridge = getAndroidBridge();
@@ -110,6 +127,13 @@ export function createGpsTracker() {
} catch (e) {
_debugMsg = `poll err: ${(e as Error)?.message ?? e}`;
}
// Poll interval state
try {
const stateJson = bridge.getIntervalState();
if (stateJson && stateJson !== '{}') {
_intervalState = JSON.parse(stateJson);
}
} catch { /* no interval active */ }
}
async function start(voiceGuidance?: VoiceGuidanceConfig, startPaused = false) {
@@ -182,12 +206,14 @@ export function createGpsTracker() {
}
isTracking = false;
_intervalState = null;
const result = [...track];
return result;
}
function reset() {
track = [];
_intervalState = null;
}
function getAvailableTtsVoices(): Array<{ id: string; name: string; language: string }> {
@@ -246,6 +272,7 @@ export function createGpsTracker() {
get latestPoint() { return latestPoint; },
get available() { return checkTauri(); },
get debug() { return _debugMsg; },
get intervalState() { return _intervalState; },
start,
stop,
reset,