feat: redesign GPS workout UI with Runkeeper-style map overlay
All checks were successful
CI / update (push) Successful in 2m32s

- Full-screen fixed map with controls overlaid at the bottom
- Activity type selector (running/walking/cycling/hiking) with proper
  exercise mapping for history display
- GPS starts immediately on entering workout screen for faster lock
- GPS track attached to cardio exercise (like GPX upload) so history
  shows distance, pace, splits, and map
- Add activityType field to workout state, session model, and sync
- Cancel button appears when workout is paused
- GPS Workout button only shown in Tauri app
This commit is contained in:
2026-03-25 19:54:18 +01:00
parent d75e2354f6
commit 8b63812734
9 changed files with 705 additions and 46 deletions

View File

@@ -18,6 +18,8 @@ export interface IActiveWorkout {
userId: string;
version: number;
name: string;
mode: 'manual' | 'gps';
activityType: 'running' | 'walking' | 'cycling' | 'hiking' | null;
templateId: string | null;
exercises: IActiveWorkoutExercise[];
paused: boolean;
@@ -62,6 +64,16 @@ const ActiveWorkoutSchema = new mongoose.Schema(
trim: true,
maxlength: 100
},
mode: {
type: String,
enum: ['manual', 'gps'],
default: 'manual'
},
activityType: {
type: String,
enum: ['running', 'walking', 'cycling', 'hiking'],
default: null
},
templateId: {
type: String,
default: null

View File

@@ -41,12 +41,16 @@ export interface IWorkoutSession {
templateId?: string; // Reference to WorkoutTemplate if based on template
templateName?: string; // Snapshot of template name for history
name: string;
mode?: 'manual' | 'gps';
activityType?: 'running' | 'walking' | 'cycling' | 'hiking';
exercises: ICompletedExercise[];
startTime: Date;
endTime?: Date;
duration?: number; // Duration in minutes
totalVolume?: number; // Total weight × reps across all exercises
totalDistance?: number; // Total distance across all cardio exercises
gpsTrack?: IGpsPoint[]; // Top-level GPS track for GPS-only workouts
gpsPreview?: number[][]; // Downsampled [[lat,lng], ...] for card preview
prs?: IPr[];
notes?: string;
createdBy: string; // username/nickname of the person who performed the workout
@@ -155,15 +159,18 @@ const WorkoutSessionSchema = new mongoose.Schema(
trim: true,
maxlength: 100
},
mode: {
type: String,
enum: ['manual', 'gps'],
default: 'manual'
},
activityType: {
type: String,
enum: ['running', 'walking', 'cycling', 'hiking']
},
exercises: {
type: [CompletedExerciseSchema],
required: true,
validate: {
validator: function(exercises: ICompletedExercise[]) {
return exercises.length > 0;
},
message: 'A workout session must have at least one exercise'
}
default: []
},
startTime: {
type: Date,
@@ -185,6 +192,14 @@ const WorkoutSessionSchema = new mongoose.Schema(
type: Number,
min: 0
},
gpsTrack: {
type: [GpsPointSchema],
default: undefined
},
gpsPreview: {
type: [[Number]],
default: undefined
},
prs: [{
exerciseId: { type: String, required: true },
type: { type: String, required: true },