feat: close v1 feature gaps — freemium gating, video/audio infrastructure, EAS build config

- Add access control service with 3 free workouts (IDs 1, 11, 43), paywall gating on workout detail and lock indicators on explore grid
- Wire VideoPlayer into player background and workout detail preview
- Add placeholder HLS video URLs to 5 sample workouts (Apple test streams)
- Add test audio URLs to music service MOCK_TRACKS for all vibes
- Switch RevenueCat API key to env-based with sandbox fallback
- Create eas.json with development/preview/production build profiles
- Update app.json with iOS privacy descriptions (HealthKit, Camera) and non-exempt encryption flag
- Create collection detail screen (route crash fix)
- Replace hardcoded profile stats with real activity store data
- Add unlockWithPremium i18n key in EN/FR/DE/ES
This commit is contained in:
Millian Lamiaux
2026-03-24 12:20:56 +01:00
parent cd065d07c3
commit a042c348c1
16 changed files with 452 additions and 34 deletions

View File

@@ -23,6 +23,7 @@ export const WORKOUTS: Workout[] = [
restTime: 10,
equipment: ['No equipment required', 'Yoga mat optional'],
musicVibe: 'electronic',
videoUrl: 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8',
exercises: [
{ name: 'Jumping Jacks', duration: 20 },
{ name: 'Squats', duration: 20 },
@@ -45,6 +46,7 @@ export const WORKOUTS: Workout[] = [
restTime: 10,
equipment: ['Dumbbells optional'],
musicVibe: 'hip-hop',
videoUrl: 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
exercises: [
{ name: 'Burpees', duration: 20 },
{ name: 'Lunges', duration: 20 },
@@ -238,6 +240,7 @@ export const WORKOUTS: Workout[] = [
restTime: 10,
equipment: ['Yoga mat'],
musicVibe: 'electronic',
videoUrl: 'https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8',
exercises: [
{ name: 'Crunches', duration: 20 },
{ name: 'Russian Twists', duration: 20 },
@@ -880,6 +883,7 @@ export const WORKOUTS: Workout[] = [
restTime: 10,
equipment: ['No equipment required'],
musicVibe: 'electronic',
videoUrl: 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8',
exercises: [
{ name: 'Burpees', duration: 20 },
{ name: 'Mountain Climbers', duration: 20 },
@@ -922,6 +926,7 @@ export const WORKOUTS: Workout[] = [
restTime: 10,
equipment: ['No equipment required'],
musicVibe: 'pop',
videoUrl: 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8',
exercises: [
{ name: 'Grapevine', duration: 20 },
{ name: 'Step Touch', duration: 20 },

View File

@@ -189,7 +189,8 @@
"music": "Musik",
"musicMix": "{{vibe}} Mix",
"curatedForWorkout": "Zusammengestellt für dein Workout",
"startWorkout": "WORKOUT STARTEN"
"startWorkout": "WORKOUT STARTEN",
"unlockWithPremium": "MIT TABATAFIT+ FREISCHALTEN"
},
"onboarding": {

View File

@@ -189,7 +189,8 @@
"music": "Music",
"musicMix": "{{vibe}} Mix",
"curatedForWorkout": "Curated for your workout",
"startWorkout": "START WORKOUT"
"startWorkout": "START WORKOUT",
"unlockWithPremium": "UNLOCK WITH TABATAFIT+"
},
"paywall": {

View File

@@ -189,7 +189,8 @@
"music": "M\u00fasica",
"musicMix": "Mix {{vibe}}",
"curatedForWorkout": "Seleccionado para tu entreno",
"startWorkout": "EMPEZAR ENTRENO"
"startWorkout": "EMPEZAR ENTRENO",
"unlockWithPremium": "DESBLOQUEAR CON TABATAFIT+"
},
"onboarding": {

View File

@@ -189,7 +189,8 @@
"music": "Musique",
"musicMix": "Mix {{vibe}}",
"curatedForWorkout": "Sélectionné pour votre entraînement",
"startWorkout": "COMMENCER L'ENTRAÎNEMENT"
"startWorkout": "COMMENCER L'ENTRAÎNEMENT",
"unlockWithPremium": "DÉBLOQUER AVEC TABATAFIT+"
},
"paywall": {

View File

@@ -0,0 +1,32 @@
/**
* TabataFit Access Control Service
* Manages free tier vs premium workout access
*
* Freemium model: 3 free workouts, rest behind paywall
*/
/** Workout IDs available without a subscription */
export const FREE_WORKOUT_IDS: readonly string[] = [
'1', // Full Body Ignite — Beginner, 4 min (full-body)
'11', // Core Crusher — Intermediate, 4 min (core)
'43', // Dance Cardio — Beginner, 4 min (cardio)
] as const
/** Number of free workouts (for display in paywall copy) */
export const FREE_WORKOUT_COUNT = FREE_WORKOUT_IDS.length
/**
* Check if a specific workout is part of the free tier
*/
export function isFreeWorkout(workoutId: string): boolean {
return FREE_WORKOUT_IDS.includes(workoutId)
}
/**
* Check if user can access a workout
* Premium users can access everything; free users only get FREE_WORKOUT_IDS
*/
export function canAccessWorkout(workoutId: string, isPremium: boolean): boolean {
if (isPremium) return true
return isFreeWorkout(workoutId)
}

View File

@@ -10,31 +10,35 @@ export interface MusicTrack {
vibe: MusicVibe
}
// Public-domain / royalty-free test audio from Apple HLS examples & samples
// Replace with Supabase-hosted tracks in production
const TEST_AUDIO_BASE = 'https://www2.cs.uic.edu/~i101/SoundFiles'
const MOCK_TRACKS: Record<MusicVibe, MusicTrack[]> = {
electronic: [
{ id: '1', title: 'Energy Pulse', artist: 'Neon Dreams', duration: 240, url: '', vibe: 'electronic' },
{ id: '2', title: 'Cyber Sprint', artist: 'Digital Flux', duration: 180, url: '', vibe: 'electronic' },
{ id: '3', title: 'High Voltage', artist: 'Circuit Breakers', duration: 200, url: '', vibe: 'electronic' },
{ id: '1', title: 'Energy Pulse', artist: 'Neon Dreams', duration: 240, url: `${TEST_AUDIO_BASE}/StarWars60.wav`, vibe: 'electronic' },
{ id: '2', title: 'Cyber Sprint', artist: 'Digital Flux', duration: 180, url: `${TEST_AUDIO_BASE}/tapioca.wav`, vibe: 'electronic' },
{ id: '3', title: 'High Voltage', artist: 'Circuit Breakers', duration: 200, url: `${TEST_AUDIO_BASE}/preamble10.wav`, vibe: 'electronic' },
],
'hip-hop': [
{ id: '4', title: 'Street Heat', artist: 'Urban Flow', duration: 210, url: '', vibe: 'hip-hop' },
{ id: '5', title: 'Rhythm Power', artist: 'Beat Masters', duration: 195, url: '', vibe: 'hip-hop' },
{ id: '6', title: 'Flow State', artist: 'MC Dynamic', duration: 220, url: '', vibe: 'hip-hop' },
{ id: '4', title: 'Street Heat', artist: 'Urban Flow', duration: 210, url: `${TEST_AUDIO_BASE}/StarWars60.wav`, vibe: 'hip-hop' },
{ id: '5', title: 'Rhythm Power', artist: 'Beat Masters', duration: 195, url: `${TEST_AUDIO_BASE}/tapioca.wav`, vibe: 'hip-hop' },
{ id: '6', title: 'Flow State', artist: 'MC Dynamic', duration: 220, url: `${TEST_AUDIO_BASE}/preamble10.wav`, vibe: 'hip-hop' },
],
pop: [
{ id: '7', title: 'Summer Energy', artist: 'The Popstars', duration: 185, url: '', vibe: 'pop' },
{ id: '8', title: 'Upbeat Vibes', artist: 'Chart Toppers', duration: 200, url: '', vibe: 'pop' },
{ id: '9', title: 'Feel Good', artist: 'Radio Hits', duration: 175, url: '', vibe: 'pop' },
{ id: '7', title: 'Summer Energy', artist: 'The Popstars', duration: 185, url: `${TEST_AUDIO_BASE}/StarWars60.wav`, vibe: 'pop' },
{ id: '8', title: 'Upbeat Vibes', artist: 'Chart Toppers', duration: 200, url: `${TEST_AUDIO_BASE}/tapioca.wav`, vibe: 'pop' },
{ id: '9', title: 'Feel Good', artist: 'Radio Hits', duration: 175, url: `${TEST_AUDIO_BASE}/preamble10.wav`, vibe: 'pop' },
],
rock: [
{ id: '10', title: 'Power Chord', artist: 'The Amplifiers', duration: 230, url: '', vibe: 'rock' },
{ id: '11', title: 'High Gain', artist: ' distortion', duration: 205, url: '', vibe: 'rock' },
{ id: '12', title: ' adrenaline', artist: 'Thunderstruck', duration: 215, url: '', vibe: 'rock' },
{ id: '10', title: 'Power Chord', artist: 'The Amplifiers', duration: 230, url: `${TEST_AUDIO_BASE}/StarWars60.wav`, vibe: 'rock' },
{ id: '11', title: 'High Gain', artist: 'Distortion', duration: 205, url: `${TEST_AUDIO_BASE}/tapioca.wav`, vibe: 'rock' },
{ id: '12', title: 'Adrenaline', artist: 'Thunderstruck', duration: 215, url: `${TEST_AUDIO_BASE}/preamble10.wav`, vibe: 'rock' },
],
chill: [
{ id: '13', title: 'Smooth Flow', artist: 'Lo-Fi Beats', duration: 250, url: '', vibe: 'chill' },
{ id: '14', title: 'Zen Mode', artist: 'Calm Collective', duration: 240, url: '', vibe: 'chill' },
{ id: '15', title: 'Deep Breath', artist: 'Mindful Tones', duration: 260, url: '', vibe: 'chill' },
{ id: '13', title: 'Smooth Flow', artist: 'Lo-Fi Beats', duration: 250, url: `${TEST_AUDIO_BASE}/StarWars60.wav`, vibe: 'chill' },
{ id: '14', title: 'Zen Mode', artist: 'Calm Collective', duration: 240, url: `${TEST_AUDIO_BASE}/tapioca.wav`, vibe: 'chill' },
{ id: '15', title: 'Deep Breath', artist: 'Mindful Tones', duration: 260, url: `${TEST_AUDIO_BASE}/preamble10.wav`, vibe: 'chill' },
],
}

View File

@@ -12,7 +12,9 @@ import Purchases, { LOG_LEVEL } from 'react-native-purchases'
// RevenueCat configuration
// test_ prefix = sandbox mode (free transactions on simulator + device sandbox accounts)
export const REVENUECAT_API_KEY = 'test_oIJbIHWISJaUZdgxRMHlwizBHvM'
// Set EXPO_PUBLIC_REVENUECAT_API_KEY in .env for production
export const REVENUECAT_API_KEY =
process.env.EXPO_PUBLIC_REVENUECAT_API_KEY || 'test_oIJbIHWISJaUZdgxRMHlwizBHvM'
// Entitlement ID configured in RevenueCat dashboard
export const ENTITLEMENT_ID = '1000 Corp Pro'