feat: redesign player with Dynamic Island, compact timer, and fix Live Activity timer drift
Some checks failed
CI / TypeScript (pull_request) Failing after 5s
CI / ESLint (pull_request) Failing after 3s
CI / Tests (pull_request) Failing after 5s
CI / Build Check (pull_request) Has been skipped
CI / Admin Web Tests (pull_request) Successful in 2m5s
CI / Deploy Edge Functions (pull_request) Has been skipped
Some checks failed
CI / TypeScript (pull_request) Failing after 5s
CI / ESLint (pull_request) Failing after 3s
CI / Tests (pull_request) Failing after 5s
CI / Build Check (pull_request) Has been skipped
CI / Admin Web Tests (pull_request) Successful in 2m5s
CI / Deploy Edge Functions (pull_request) Has been skipped
## What changed ### Player Redesign (video-first layout) - New compact timer ring (110pt) with phase label, replaces 240pt ring - Auto-hide top bar with block progress dots (3s auto-dismiss) - Expandable now-playing music pill with skip control - Bottom control bar with heart rate, play/pause, and skip - Exercise caption with 'Next' preview during rest phases - Compact round counter (capsule dots) ### Dynamic Island & Live Activities - WorkoutLiveActivity widget: expanded, compact, and minimal views - Phase-colored timers with Text(timerInterval:) countdown - Shows exercise name, round progress, heart rate, music track - MusicLiveActivity: standalone music now-playing widget - LiveActivityMusicBars animated component - Deep link from Dynamic Island back to app ### Timer Drift Fix (critical) - Store a stable phaseEndDate once per phase instead of recalculating Date() + timeRemaining on every update - Prevents dynamic island countdown from rubber-banding due to 5-second periodic update recalculation drift - Reset phaseEndDate on phase change and resume from pause - Guard Live Activity updates behind vm.isRunning to prevent premature creation when music track loads before workout start - Fixes timer showing 0 in Dynamic Island when expanding from home screen ### New PlayerViewModel timer engine - Full phase support: prep, warmup, work, rest, interBlockRest, cooldown, complete - 1-second countdown with audio cues at 3-2-1 - Phase transitions with spring animation and haptics - HealthKit live session integration - Workout session recording with completion ### Music Service - New MusicPlayerViewModel with vibe-based playlist loading - Track info exposed for Dynamic Island display - Skip track support from Dynamic Island notification action - Automatic play/pause based on phase and running state ### Additional - ZoneHighlightIcon component for HomeTab zone cards - Updated watchOS localizations with complication strings - Info.plist updated for widget extension
This commit is contained in:
@@ -20,12 +20,23 @@
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tabatago</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>TabataGo reads your health data to show fitness stats and personalize your workouts.</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>TabataGo saves your Tabata workouts to Apple Health to track calories, heart rate, and contribute to your Activity Rings.</string>
|
||||
<key>NSMotionUsageDescription</key>
|
||||
<string>TabataGo uses motion data to improve calorie estimates during workouts.</string>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>POSTHOG_API_KEY</key>
|
||||
<string>$(POSTHOG_API_KEY)</string>
|
||||
<key>REVENUECAT_API_KEY</key>
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
},
|
||||
"%@ / year — save 40%%" : {
|
||||
|
||||
},
|
||||
"%@ bpm" : {
|
||||
|
||||
},
|
||||
"%@ kcal" : {
|
||||
|
||||
@@ -2202,6 +2199,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Next" : {
|
||||
|
||||
},
|
||||
"No workouts yet" : {
|
||||
"extractionState" : "manual",
|
||||
@@ -5725,6 +5725,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Skip" : {
|
||||
|
||||
},
|
||||
"Subscription purchases are handled by Apple's App Store and RevenueCat. We do not store your payment information." : {
|
||||
|
||||
@@ -6284,25 +6287,25 @@
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ganzkörpertraining von Kopf bis Fuß"
|
||||
"value" : "Zielt auf alle großen Muskelgruppen — das ultimative Ganzkörper-Tabata-Workout"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Total body burn, head to toe"
|
||||
"value" : "Targets every major muscle group — the ultimate full-body Tabata burn"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Quema total del cuerpo, de pies a cabeza"
|
||||
"value" : "Dirige a todos los grupos musculares principales — la quema Tabata de cuerpo completo definitiva"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Brûlure totale du corps, de la tête aux pieds"
|
||||
"value" : "Cible tous les groupes musculaires — le brûleur Tabata corps entier ultime"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6342,25 +6345,25 @@
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Beine, Gesäß und Körpermitte"
|
||||
"value" : "Zielt auf Quads, Gesäß, Beinbeuger und Waden mit explosiven Intervallen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Legs, glutes & core stability"
|
||||
"value" : "Targets quads, glutes, hamstrings & calves with explosive intervals"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Piernas, glúteos y estabilidad del core"
|
||||
"value" : "Dirige a cuádriceps, glúteos, isquiotibiales y pantorrillas con intervalos explosivos"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Jambes, fessiers et gainage"
|
||||
"value" : "Cible quadriceps, fessiers, ischio-jambiers et mollets avec des intervals explosifs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6400,25 +6403,25 @@
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Arme, Brust, Schultern und Rücken"
|
||||
"value" : "Zielt auf Bizeps, Schultern, Brust und Rücken mit High-Intensity-Intervallen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Arms, chest, shoulders & back"
|
||||
"value" : "Targets biceps, shoulders, chest & back with high-intensity intervals"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Brazos, pecho, hombros y espalda"
|
||||
"value" : "Dirige a bíceps, hombros, pecho y espalda con intervalos de alta intensidad"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bras, poitrine, épaules et dos"
|
||||
"value" : "Cible biceps, épaules, poitrine et dos avec des intervals haute intensité"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user