feat: integrate theme and i18n across all screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Celebration with real data from activity store
|
||||
*/
|
||||
|
||||
import { useRef, useEffect } from 'react'
|
||||
import { useRef, useEffect, useMemo } from 'react'
|
||||
import {
|
||||
View,
|
||||
Text as RNText,
|
||||
@@ -20,17 +20,15 @@ import { BlurView } from 'expo-blur'
|
||||
import Ionicons from '@expo/vector-icons/Ionicons'
|
||||
|
||||
import * as Sharing from 'expo-sharing'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useHaptics } from '@/src/shared/hooks'
|
||||
import { useActivityStore } from '@/src/shared/stores'
|
||||
import { getWorkoutById, getTrainerById, getPopularWorkouts } from '@/src/shared/data'
|
||||
import { getWorkoutById, getPopularWorkouts } from '@/src/shared/data'
|
||||
import { useTranslatedWorkout, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
|
||||
|
||||
import {
|
||||
BRAND,
|
||||
DARK,
|
||||
TEXT,
|
||||
GLASS,
|
||||
SHADOW,
|
||||
} from '@/src/shared/constants/colors'
|
||||
import { useThemeColors, BRAND } from '@/src/shared/theme'
|
||||
import type { ThemeColors } from '@/src/shared/theme/types'
|
||||
import { TYPOGRAPHY } from '@/src/shared/constants/typography'
|
||||
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
|
||||
import { RADIUS } from '@/src/shared/constants/borderRadius'
|
||||
@@ -51,6 +49,8 @@ function SecondaryButton({
|
||||
children: React.ReactNode
|
||||
icon?: keyof typeof Ionicons.glyphMap
|
||||
}) {
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current
|
||||
|
||||
const handlePressIn = () => {
|
||||
@@ -77,7 +77,7 @@ function SecondaryButton({
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Animated.View style={[styles.secondaryButton, { transform: [{ scale: scaleAnim }] }]}>
|
||||
{icon && <Ionicons name={icon} size={18} color={TEXT.PRIMARY} style={styles.buttonIcon} />}
|
||||
{icon && <Ionicons name={icon} size={18} color={colors.text.primary} style={styles.buttonIcon} />}
|
||||
<RNText style={styles.secondaryButtonText}>{children}</RNText>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
@@ -91,6 +91,8 @@ function PrimaryButton({
|
||||
onPress: () => void
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current
|
||||
|
||||
const handlePressIn = () => {
|
||||
@@ -134,6 +136,8 @@ function PrimaryButton({
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
function CelebrationRings() {
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const ring1Anim = useRef(new Animated.Value(0)).current
|
||||
const ring2Anim = useRef(new Animated.Value(0)).current
|
||||
const ring3Anim = useRef(new Animated.Value(0)).current
|
||||
@@ -190,6 +194,8 @@ function StatCard({
|
||||
icon: keyof typeof Ionicons.glyphMap
|
||||
delay?: number
|
||||
}) {
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const scaleAnim = useRef(new Animated.Value(0)).current
|
||||
|
||||
useEffect(() => {
|
||||
@@ -205,7 +211,7 @@ function StatCard({
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.statCard, { transform: [{ scale: scaleAnim }] }]}>
|
||||
<BlurView intensity={GLASS.BLUR_MEDIUM} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
<BlurView intensity={colors.glass.blurMedium} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<Ionicons name={icon} size={24} color={BRAND.PRIMARY} />
|
||||
<RNText style={styles.statValue}>{value}</RNText>
|
||||
<RNText style={styles.statLabel}>{label}</RNText>
|
||||
@@ -214,6 +220,9 @@ function StatCard({
|
||||
}
|
||||
|
||||
function BurnBarResult({ percentile }: { percentile: number }) {
|
||||
const { t } = useTranslation()
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const barAnim = useRef(new Animated.Value(0)).current
|
||||
|
||||
useEffect(() => {
|
||||
@@ -232,8 +241,8 @@ function BurnBarResult({ percentile }: { percentile: number }) {
|
||||
|
||||
return (
|
||||
<View style={styles.burnBarContainer}>
|
||||
<RNText style={styles.burnBarTitle}>Burn Bar</RNText>
|
||||
<RNText style={styles.burnBarResult}>You beat {percentile}% of users!</RNText>
|
||||
<RNText style={styles.burnBarTitle}>{t('screens:complete.burnBar')}</RNText>
|
||||
<RNText style={styles.burnBarResult}>{t('screens:complete.burnBarResult', { percentile })}</RNText>
|
||||
<View style={styles.burnBarTrack}>
|
||||
<Animated.View style={[styles.burnBarFill, { width: barWidth }]} />
|
||||
</View>
|
||||
@@ -249,9 +258,14 @@ export default function WorkoutCompleteScreen() {
|
||||
const insets = useSafeAreaInsets()
|
||||
const router = useRouter()
|
||||
const haptics = useHaptics()
|
||||
const { t } = useTranslation()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
|
||||
const workout = getWorkoutById(id ?? '1')
|
||||
const colors = useThemeColors()
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
|
||||
const rawWorkout = getWorkoutById(id ?? '1')
|
||||
const workout = useTranslatedWorkout(rawWorkout)
|
||||
const streak = useActivityStore((s) => s.streak)
|
||||
const history = useActivityStore((s) => s.history)
|
||||
const recentWorkouts = history.slice(0, 1)
|
||||
@@ -262,7 +276,8 @@ export default function WorkoutCompleteScreen() {
|
||||
const resultMinutes = latestResult?.durationMinutes ?? workout?.duration ?? 4
|
||||
|
||||
// Recommended workouts (different from current)
|
||||
const recommended = getPopularWorkouts(4).filter(w => w.id !== id).slice(0, 3)
|
||||
const rawRecommended = getPopularWorkouts(4).filter(w => w.id !== id).slice(0, 3)
|
||||
const recommended = useTranslatedWorkouts(rawRecommended)
|
||||
|
||||
const handleGoHome = () => {
|
||||
haptics.buttonTap()
|
||||
@@ -274,7 +289,7 @@ export default function WorkoutCompleteScreen() {
|
||||
const isAvailable = await Sharing.isAvailableAsync()
|
||||
if (isAvailable) {
|
||||
await Sharing.shareAsync('https://tabatafit.app', {
|
||||
dialogTitle: `I just completed ${workout?.title ?? 'a workout'}! 🔥 ${resultCalories} calories in ${resultMinutes} minutes.`,
|
||||
dialogTitle: t('screens:complete.shareText', { title: workout?.title ?? 'a workout', calories: resultCalories, duration: resultMinutes }),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -297,15 +312,15 @@ export default function WorkoutCompleteScreen() {
|
||||
{/* Celebration */}
|
||||
<View style={styles.celebrationSection}>
|
||||
<RNText style={styles.celebrationEmoji}>🎉</RNText>
|
||||
<RNText style={styles.celebrationTitle}>WORKOUT COMPLETE</RNText>
|
||||
<RNText style={styles.celebrationTitle}>{t('screens:complete.title')}</RNText>
|
||||
<CelebrationRings />
|
||||
</View>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<View style={styles.statsGrid}>
|
||||
<StatCard value={resultCalories} label="CALORIES" icon="flame" delay={100} />
|
||||
<StatCard value={resultMinutes} label="MINUTES" icon="time" delay={200} />
|
||||
<StatCard value="100%" label="COMPLETE" icon="checkmark-circle" delay={300} />
|
||||
<StatCard value={resultCalories} label={t('screens:complete.caloriesLabel')} icon="flame" delay={100} />
|
||||
<StatCard value={resultMinutes} label={t('screens:complete.minutesLabel')} icon="time" delay={200} />
|
||||
<StatCard value="100%" label={t('screens:complete.completeLabel')} icon="checkmark-circle" delay={300} />
|
||||
</View>
|
||||
|
||||
{/* Burn Bar */}
|
||||
@@ -319,8 +334,8 @@ export default function WorkoutCompleteScreen() {
|
||||
<Ionicons name="flame" size={32} color={BRAND.PRIMARY} />
|
||||
</View>
|
||||
<View style={styles.streakInfo}>
|
||||
<RNText style={styles.streakTitle}>{streak.current} Day Streak!</RNText>
|
||||
<RNText style={styles.streakSubtitle}>Keep the momentum going!</RNText>
|
||||
<RNText style={styles.streakTitle}>{t('screens:complete.streakTitle', { count: streak.current })}</RNText>
|
||||
<RNText style={styles.streakSubtitle}>{t('screens:complete.streakSubtitle')}</RNText>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -329,7 +344,7 @@ export default function WorkoutCompleteScreen() {
|
||||
{/* Share Button */}
|
||||
<View style={styles.shareSection}>
|
||||
<SecondaryButton onPress={handleShare} icon="share-outline">
|
||||
Share Your Workout
|
||||
{t('screens:complete.shareWorkout')}
|
||||
</SecondaryButton>
|
||||
</View>
|
||||
|
||||
@@ -337,39 +352,36 @@ export default function WorkoutCompleteScreen() {
|
||||
|
||||
{/* Recommended */}
|
||||
<View style={styles.recommendedSection}>
|
||||
<RNText style={styles.recommendedTitle}>Recommended Next</RNText>
|
||||
<RNText style={styles.recommendedTitle}>{t('screens:complete.recommendedNext')}</RNText>
|
||||
<View style={styles.recommendedGrid}>
|
||||
{recommended.map((w) => {
|
||||
const trainer = getTrainerById(w.trainerId)
|
||||
return (
|
||||
<Pressable
|
||||
key={w.id}
|
||||
onPress={() => handleWorkoutPress(w.id)}
|
||||
style={styles.recommendedCard}
|
||||
>
|
||||
<BlurView intensity={GLASS.BLUR_LIGHT} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
<View style={styles.recommendedThumb}>
|
||||
<LinearGradient
|
||||
colors={[trainer?.color ?? BRAND.PRIMARY, BRAND.PRIMARY_LIGHT]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<RNText style={styles.recommendedInitial}>{trainer?.name[0] ?? 'T'}</RNText>
|
||||
</View>
|
||||
<RNText style={styles.recommendedTitleText} numberOfLines={1}>{w.title}</RNText>
|
||||
<RNText style={styles.recommendedDurationText}>{w.duration} min</RNText>
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
{recommended.map((w) => (
|
||||
<Pressable
|
||||
key={w.id}
|
||||
onPress={() => handleWorkoutPress(w.id)}
|
||||
style={styles.recommendedCard}
|
||||
>
|
||||
<BlurView intensity={colors.glass.blurLight} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<View style={styles.recommendedThumb}>
|
||||
<LinearGradient
|
||||
colors={[BRAND.PRIMARY, BRAND.PRIMARY_LIGHT]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<Ionicons name="flame" size={24} color="#FFFFFF" />
|
||||
</View>
|
||||
<RNText style={styles.recommendedTitleText} numberOfLines={1}>{w.title}</RNText>
|
||||
<RNText style={styles.recommendedDurationText}>{t('units.minUnit', { count: w.duration })}</RNText>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Fixed Bottom Button */}
|
||||
<View style={[styles.bottomBar, { paddingBottom: insets.bottom + SPACING[4] }]}>
|
||||
<BlurView intensity={GLASS.BLUR_HEAVY} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
<BlurView intensity={colors.glass.blurHeavy} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<View style={styles.homeButtonContainer}>
|
||||
<PrimaryButton onPress={handleGoHome}>
|
||||
Back to Home
|
||||
{t('screens:complete.backToHome')}
|
||||
</PrimaryButton>
|
||||
</View>
|
||||
</View>
|
||||
@@ -381,246 +393,248 @@ export default function WorkoutCompleteScreen() {
|
||||
// STYLES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: DARK.BASE,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: LAYOUT.SCREEN_PADDING,
|
||||
},
|
||||
function createStyles(colors: ThemeColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.bg.base,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: LAYOUT.SCREEN_PADDING,
|
||||
},
|
||||
|
||||
// Buttons
|
||||
secondaryButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[3],
|
||||
paddingHorizontal: SPACING[4],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: TEXT.PRIMARY,
|
||||
fontWeight: '600',
|
||||
},
|
||||
primaryButton: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[4],
|
||||
paddingHorizontal: SPACING[6],
|
||||
borderRadius: RADIUS.LG,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
primaryButtonText: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: TEXT.PRIMARY,
|
||||
fontWeight: '700',
|
||||
},
|
||||
buttonIcon: {
|
||||
marginRight: SPACING[2],
|
||||
},
|
||||
// Buttons
|
||||
secondaryButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[3],
|
||||
paddingHorizontal: SPACING[4],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glassStrong,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: colors.text.primary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
primaryButton: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[4],
|
||||
paddingHorizontal: SPACING[6],
|
||||
borderRadius: RADIUS.LG,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
primaryButtonText: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: '#FFFFFF',
|
||||
fontWeight: '700',
|
||||
},
|
||||
buttonIcon: {
|
||||
marginRight: SPACING[2],
|
||||
},
|
||||
|
||||
// Celebration
|
||||
celebrationSection: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: SPACING[8],
|
||||
},
|
||||
celebrationEmoji: {
|
||||
fontSize: 64,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
celebrationTitle: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: TEXT.PRIMARY,
|
||||
letterSpacing: 2,
|
||||
},
|
||||
ringsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginTop: SPACING[6],
|
||||
gap: SPACING[4],
|
||||
},
|
||||
ring: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
},
|
||||
ring1: {
|
||||
borderColor: BRAND.PRIMARY,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.15)',
|
||||
},
|
||||
ring2: {
|
||||
borderColor: '#30D158',
|
||||
backgroundColor: 'rgba(48, 209, 88, 0.15)',
|
||||
},
|
||||
ring3: {
|
||||
borderColor: '#5AC8FA',
|
||||
backgroundColor: 'rgba(90, 200, 250, 0.15)',
|
||||
},
|
||||
ringEmoji: {
|
||||
fontSize: 28,
|
||||
},
|
||||
// Celebration
|
||||
celebrationSection: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: SPACING[8],
|
||||
},
|
||||
celebrationEmoji: {
|
||||
fontSize: 64,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
celebrationTitle: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
letterSpacing: 2,
|
||||
},
|
||||
ringsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginTop: SPACING[6],
|
||||
gap: SPACING[4],
|
||||
},
|
||||
ring: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: colors.border.glass,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.border.glassStrong,
|
||||
},
|
||||
ring1: {
|
||||
borderColor: BRAND.PRIMARY,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.15)',
|
||||
},
|
||||
ring2: {
|
||||
borderColor: '#30D158',
|
||||
backgroundColor: 'rgba(48, 209, 88, 0.15)',
|
||||
},
|
||||
ring3: {
|
||||
borderColor: '#5AC8FA',
|
||||
backgroundColor: 'rgba(90, 200, 250, 0.15)',
|
||||
},
|
||||
ringEmoji: {
|
||||
fontSize: 28,
|
||||
},
|
||||
|
||||
// Stats Grid
|
||||
statsGrid: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: SPACING[6],
|
||||
},
|
||||
statCard: {
|
||||
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[4]) / 3,
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
statValue: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: TEXT.PRIMARY,
|
||||
marginTop: SPACING[2],
|
||||
},
|
||||
statLabel: {
|
||||
...TYPOGRAPHY.CAPTION_2,
|
||||
color: TEXT.TERTIARY,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
// Stats Grid
|
||||
statsGrid: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: SPACING[6],
|
||||
},
|
||||
statCard: {
|
||||
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[4]) / 3,
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
statValue: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
marginTop: SPACING[2],
|
||||
},
|
||||
statLabel: {
|
||||
...TYPOGRAPHY.CAPTION_2,
|
||||
color: colors.text.tertiary,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
|
||||
// Burn Bar
|
||||
burnBarContainer: {
|
||||
marginBottom: SPACING[6],
|
||||
},
|
||||
burnBarTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
burnBarResult: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: BRAND.PRIMARY,
|
||||
marginTop: SPACING[1],
|
||||
marginBottom: SPACING[3],
|
||||
},
|
||||
burnBarTrack: {
|
||||
height: 8,
|
||||
backgroundColor: DARK.SURFACE,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
burnBarFill: {
|
||||
height: '100%',
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
borderRadius: 4,
|
||||
},
|
||||
// Burn Bar
|
||||
burnBarContainer: {
|
||||
marginBottom: SPACING[6],
|
||||
},
|
||||
burnBarTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: colors.text.tertiary,
|
||||
},
|
||||
burnBarResult: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: BRAND.PRIMARY,
|
||||
marginTop: SPACING[1],
|
||||
marginBottom: SPACING[3],
|
||||
},
|
||||
burnBarTrack: {
|
||||
height: 8,
|
||||
backgroundColor: colors.bg.surface,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
burnBarFill: {
|
||||
height: '100%',
|
||||
backgroundColor: BRAND.PRIMARY,
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
// Divider
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
marginVertical: SPACING[2],
|
||||
},
|
||||
// Divider
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.border.glass,
|
||||
marginVertical: SPACING[2],
|
||||
},
|
||||
|
||||
// Streak
|
||||
streakSection: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: SPACING[4],
|
||||
gap: SPACING[4],
|
||||
},
|
||||
streakBadge: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.15)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
streakInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
streakTitle: {
|
||||
...TYPOGRAPHY.TITLE_2,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
streakSubtitle: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: TEXT.TERTIARY,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
// Streak
|
||||
streakSection: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: SPACING[4],
|
||||
gap: SPACING[4],
|
||||
},
|
||||
streakBadge: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.15)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
streakInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
streakTitle: {
|
||||
...TYPOGRAPHY.TITLE_2,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
streakSubtitle: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: colors.text.tertiary,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
|
||||
// Share
|
||||
shareSection: {
|
||||
paddingVertical: SPACING[4],
|
||||
alignItems: 'center',
|
||||
},
|
||||
// Share
|
||||
shareSection: {
|
||||
paddingVertical: SPACING[4],
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Recommended
|
||||
recommendedSection: {
|
||||
paddingVertical: SPACING[4],
|
||||
},
|
||||
recommendedTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: TEXT.PRIMARY,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
recommendedGrid: {
|
||||
flexDirection: 'row',
|
||||
gap: SPACING[3],
|
||||
},
|
||||
recommendedCard: {
|
||||
flex: 1,
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
recommendedThumb: {
|
||||
width: '100%',
|
||||
aspectRatio: 1,
|
||||
borderRadius: RADIUS.MD,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: SPACING[2],
|
||||
overflow: 'hidden',
|
||||
},
|
||||
recommendedInitial: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
recommendedTitleText: {
|
||||
...TYPOGRAPHY.CARD_TITLE,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
recommendedDurationText: {
|
||||
...TYPOGRAPHY.CARD_METADATA,
|
||||
color: TEXT.TERTIARY,
|
||||
},
|
||||
// Recommended
|
||||
recommendedSection: {
|
||||
paddingVertical: SPACING[4],
|
||||
},
|
||||
recommendedTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: colors.text.primary,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
recommendedGrid: {
|
||||
flexDirection: 'row',
|
||||
gap: SPACING[3],
|
||||
},
|
||||
recommendedCard: {
|
||||
flex: 1,
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
recommendedThumb: {
|
||||
width: '100%',
|
||||
aspectRatio: 1,
|
||||
borderRadius: RADIUS.MD,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: SPACING[2],
|
||||
overflow: 'hidden',
|
||||
},
|
||||
recommendedInitial: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
recommendedTitleText: {
|
||||
...TYPOGRAPHY.CARD_TITLE,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
recommendedDurationText: {
|
||||
...TYPOGRAPHY.CARD_METADATA,
|
||||
color: colors.text.tertiary,
|
||||
},
|
||||
|
||||
// Bottom Bar
|
||||
bottomBar: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
paddingHorizontal: LAYOUT.SCREEN_PADDING,
|
||||
paddingTop: SPACING[4],
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
homeButtonContainer: {
|
||||
height: 56,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
// Bottom Bar
|
||||
bottomBar: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
paddingHorizontal: LAYOUT.SCREEN_PADDING,
|
||||
paddingTop: SPACING[4],
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border.glass,
|
||||
},
|
||||
homeButtonContainer: {
|
||||
height: 56,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user