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:
@@ -14,9 +14,12 @@ import { Host, Button, HStack } from '@expo/ui/swift-ui'
|
||||
import { glassEffect, padding } from '@expo/ui/swift-ui/modifiers'
|
||||
|
||||
import { useHaptics } from '@/src/shared/hooks'
|
||||
import { usePurchases } from '@/src/shared/hooks/usePurchases'
|
||||
import { track } from '@/src/shared/services/analytics'
|
||||
import { canAccessWorkout } from '@/src/shared/services/access'
|
||||
import { getWorkoutById } from '@/src/shared/data'
|
||||
import { useTranslatedWorkout, useMusicVibeLabel } from '@/src/shared/data/useTranslatedData'
|
||||
import { VideoPlayer } from '@/src/shared/components/VideoPlayer'
|
||||
|
||||
import { useThemeColors, BRAND } from '@/src/shared/theme'
|
||||
import type { ThemeColors } from '@/src/shared/theme/types'
|
||||
@@ -35,6 +38,7 @@ export default function WorkoutDetailScreen() {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [isSaved, setIsSaved] = useState(false)
|
||||
const { isPremium } = usePurchases()
|
||||
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
@@ -62,7 +66,18 @@ export default function WorkoutDetailScreen() {
|
||||
)
|
||||
}
|
||||
|
||||
const isLocked = !canAccessWorkout(workout.id, isPremium)
|
||||
|
||||
const handleStartWorkout = () => {
|
||||
if (isLocked) {
|
||||
haptics.buttonTap()
|
||||
track('paywall_triggered', {
|
||||
source: 'workout_detail',
|
||||
workout_id: workout.id,
|
||||
})
|
||||
router.push('/paywall')
|
||||
return
|
||||
}
|
||||
haptics.phaseChange()
|
||||
router.push(`/player/${workout.id}`)
|
||||
}
|
||||
@@ -110,6 +125,13 @@ export default function WorkoutDetailScreen() {
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 100 }]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Video Preview Hero */}
|
||||
<VideoPlayer
|
||||
videoUrl={workout.videoUrl}
|
||||
mode="preview"
|
||||
isPlaying={true}
|
||||
style={styles.videoPreview}
|
||||
/>
|
||||
{/* Quick stats */}
|
||||
<View style={styles.quickStats}>
|
||||
<View style={[styles.statBadge, { backgroundColor: 'rgba(255, 107, 53, 0.15)' }]}>
|
||||
@@ -184,11 +206,17 @@ export default function WorkoutDetailScreen() {
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.startButton,
|
||||
isLocked && styles.lockedButton,
|
||||
pressed && styles.startButtonPressed,
|
||||
]}
|
||||
onPress={handleStartWorkout}
|
||||
>
|
||||
<RNText style={styles.startButtonText}>{t('screens:workout.startWorkout')}</RNText>
|
||||
{isLocked && (
|
||||
<Ionicons name="lock-closed" size={18} color="#FFFFFF" style={{ marginRight: 8 }} />
|
||||
)}
|
||||
<RNText style={styles.startButtonText}>
|
||||
{isLocked ? t('screens:workout.unlockWithPremium') : t('screens:workout.startWorkout')}
|
||||
</RNText>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -235,6 +263,14 @@ function createStyles(colors: ThemeColors) {
|
||||
height: 44,
|
||||
},
|
||||
|
||||
// Video Preview
|
||||
videoPreview: {
|
||||
height: 220,
|
||||
borderRadius: RADIUS.XL,
|
||||
overflow: 'hidden' as const,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
|
||||
// Quick Stats
|
||||
quickStats: {
|
||||
flexDirection: 'row',
|
||||
@@ -380,6 +416,12 @@ function createStyles(colors: ThemeColors) {
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
lockedButton: {
|
||||
backgroundColor: colors.bg.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: BRAND.PRIMARY,
|
||||
},
|
||||
startButtonPressed: {
|
||||
backgroundColor: BRAND.PRIMARY_DARK,
|
||||
|
||||
Reference in New Issue
Block a user