feat: migrate icons to SF Symbols, refactor explore tab, add collections/programs data layer
- Replace all Ionicons with native SF Symbols via expo-symbols SymbolView - Create reusable Icon wrapper component (src/shared/components/Icon.tsx) - Remove @expo/vector-icons and lucide-react dependencies - Refactor explore tab with filters, search, and category browsing - Add collections and programs data with Supabase integration - Add explore filter store and filter sheet - Update i18n strings (en, de, es, fr) for new explore features - Update test mocks and remove stale snapshots - Add user fitness level to user store and types
This commit is contained in:
@@ -8,7 +8,7 @@ import { useRouter } from 'expo-router'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import { BlurView } from 'expo-blur'
|
||||
import Ionicons from '@expo/vector-icons/Ionicons'
|
||||
import { Icon, type IconName } from '@/src/shared/components/Icon'
|
||||
|
||||
import { useMemo, useRef, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -23,6 +23,11 @@ import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
|
||||
import { RADIUS } from '@/src/shared/constants/borderRadius'
|
||||
import type { ProgramId } from '@/src/shared/types'
|
||||
|
||||
// Feature flags — disable incomplete features
|
||||
const FEATURE_FLAGS = {
|
||||
ASSESSMENT_ENABLED: false, // Assessment player not yet implemented
|
||||
}
|
||||
|
||||
const FONTS = {
|
||||
LARGE_TITLE: 34,
|
||||
TITLE: 28,
|
||||
@@ -33,19 +38,19 @@ const FONTS = {
|
||||
}
|
||||
|
||||
// Program metadata for display
|
||||
const PROGRAM_META: Record<ProgramId, { icon: keyof typeof Ionicons.glyphMap; gradient: [string, string]; accent: string }> = {
|
||||
const PROGRAM_META: Record<ProgramId, { icon: IconName; gradient: [string, string]; accent: string }> = {
|
||||
'upper-body': {
|
||||
icon: 'barbell-outline',
|
||||
icon: 'dumbbell',
|
||||
gradient: ['#FF6B35', '#FF3B30'],
|
||||
accent: '#FF6B35',
|
||||
},
|
||||
'lower-body': {
|
||||
icon: 'footsteps-outline',
|
||||
icon: 'figure.walk',
|
||||
gradient: ['#30D158', '#28A745'],
|
||||
accent: '#30D158',
|
||||
},
|
||||
'full-body': {
|
||||
icon: 'flame-outline',
|
||||
icon: 'flame',
|
||||
gradient: ['#5AC8FA', '#007AFF'],
|
||||
accent: '#5AC8FA',
|
||||
},
|
||||
@@ -143,7 +148,7 @@ function ProgramCard({
|
||||
style={styles.programIconGradient}
|
||||
/>
|
||||
<View style={styles.programIconInner}>
|
||||
<Ionicons name={meta.icon} size={24} color="#FFFFFF" />
|
||||
<Icon name={meta.icon} size={24} tintColor="#FFFFFF" />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.programHeaderText}>
|
||||
@@ -220,10 +225,10 @@ function ProgramCard({
|
||||
: t('programs.continue')
|
||||
}
|
||||
</StyledText>
|
||||
<Ionicons
|
||||
name={programStatus === 'completed' ? 'refresh' : 'arrow-forward'}
|
||||
<Icon
|
||||
name={programStatus === 'completed' ? 'arrow.clockwise' : 'arrow.right'}
|
||||
size={17}
|
||||
color="#FFFFFF"
|
||||
tintColor="#FFFFFF"
|
||||
style={styles.ctaIcon}
|
||||
/>
|
||||
</LinearGradient>
|
||||
@@ -248,9 +253,9 @@ function QuickStats() {
|
||||
const totalMinutes = useMemo(() => history.reduce((sum, r) => sum + r.durationMinutes, 0), [history])
|
||||
|
||||
const stats = [
|
||||
{ icon: 'flame' as const, value: streak.current, label: t('home.statsStreak'), color: BRAND.PRIMARY },
|
||||
{ icon: 'calendar-outline' as const, value: `${thisWeekCount}/7`, label: t('home.statsThisWeek'), color: '#5AC8FA' },
|
||||
{ icon: 'time-outline' as const, value: totalMinutes, label: t('home.statsMinutes'), color: '#30D158' },
|
||||
{ icon: 'flame.fill' as const, value: streak.current, label: t('home.statsStreak'), color: BRAND.PRIMARY },
|
||||
{ icon: 'calendar' as const, value: `${thisWeekCount}/7`, label: t('home.statsThisWeek'), color: '#5AC8FA' },
|
||||
{ icon: 'clock' as const, value: totalMinutes, label: t('home.statsMinutes'), color: '#30D158' },
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -262,7 +267,7 @@ function QuickStats() {
|
||||
tint={colors.glass.blurTint}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<Ionicons name={stat.icon} size={16} color={stat.color} />
|
||||
<Icon name={stat.icon} size={16} tintColor={stat.color} />
|
||||
<StyledText size={17} weight="bold" color={colors.text.primary} style={{ fontVariant: ['tabular-nums'] }}>
|
||||
{String(stat.value)}
|
||||
</StyledText>
|
||||
@@ -323,7 +328,7 @@ function AssessmentCard({ onPress }: { onPress: () => void }) {
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={styles.assessmentIconInner}>
|
||||
<Ionicons name="clipboard-outline" size={22} color="#FFFFFF" />
|
||||
<Icon name="clipboard" size={22} tintColor="#FFFFFF" />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.assessmentText}>
|
||||
@@ -335,7 +340,7 @@ function AssessmentCard({ onPress }: { onPress: () => void }) {
|
||||
</StyledText>
|
||||
</View>
|
||||
<View style={styles.assessmentArrow}>
|
||||
<Ionicons name="arrow-forward" size={16} color={BRAND.PRIMARY} />
|
||||
<Icon name="arrow.right" size={16} tintColor={BRAND.PRIMARY} />
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -405,7 +410,7 @@ export default function HomeScreen() {
|
||||
{/* Inline streak badge */}
|
||||
{streak.current > 0 && (
|
||||
<View style={styles.streakBadge}>
|
||||
<Ionicons name="flame" size={13} color={BRAND.PRIMARY} />
|
||||
<Icon name="flame.fill" size={13} tintColor={BRAND.PRIMARY} />
|
||||
<StyledText size={12} weight="bold" color={BRAND.PRIMARY} style={{ fontVariant: ['tabular-nums'] }}>
|
||||
{streak.current}
|
||||
</StyledText>
|
||||
@@ -426,8 +431,10 @@ export default function HomeScreen() {
|
||||
{/* Quick Stats Row */}
|
||||
<QuickStats />
|
||||
|
||||
{/* Assessment Card (if not completed) */}
|
||||
<AssessmentCard onPress={handleAssessmentPress} />
|
||||
{/* Assessment Card (if not completed and feature enabled) */}
|
||||
{FEATURE_FLAGS.ASSESSMENT_ENABLED && (
|
||||
<AssessmentCard onPress={handleAssessmentPress} />
|
||||
)}
|
||||
|
||||
{/* Program Cards */}
|
||||
<View style={styles.programsSection}>
|
||||
@@ -460,7 +467,7 @@ export default function HomeScreen() {
|
||||
tint={colors.glass.blurTint}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<Ionicons name="shuffle-outline" size={16} color={colors.text.secondary} />
|
||||
<Icon name="shuffle" size={16} tintColor={colors.text.secondary} />
|
||||
<StyledText size={14} weight="medium" color={colors.text.secondary}>
|
||||
{t('home.switchProgram')}
|
||||
</StyledText>
|
||||
|
||||
Reference in New Issue
Block a user