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:
@@ -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 },
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
32
src/shared/services/access.ts
Normal file
32
src/shared/services/access.ts
Normal 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)
|
||||
}
|
||||
@@ -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' },
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user