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:
@@ -15,7 +15,7 @@ import {
|
||||
} from 'react-native'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import Ionicons from '@expo/vector-icons/Ionicons'
|
||||
import { Icon } from '@/src/shared/components/Icon'
|
||||
|
||||
import { Alert } from 'react-native'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -85,7 +85,7 @@ function ProblemScreen({ onNext }: { onNext: () => void }) {
|
||||
marginBottom: SPACING[8],
|
||||
}}
|
||||
>
|
||||
<Ionicons name="time" size={80} color={BRAND.PRIMARY} />
|
||||
<Icon name="clock.fill" size={80} color={BRAND.PRIMARY} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View style={{ opacity: textOpacity, alignItems: 'center' }}>
|
||||
@@ -136,10 +136,10 @@ function ProblemScreen({ onNext }: { onNext: () => void }) {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const BARRIERS = [
|
||||
{ id: 'no-time', labelKey: 'onboarding.empathy.noTime' as const, icon: 'time-outline' as const },
|
||||
{ id: 'low-motivation', labelKey: 'onboarding.empathy.lowMotivation' as const, icon: 'battery-dead-outline' as const },
|
||||
{ id: 'no-knowledge', labelKey: 'onboarding.empathy.noKnowledge' as const, icon: 'help-circle-outline' as const },
|
||||
{ id: 'no-gym', labelKey: 'onboarding.empathy.noGym' as const, icon: 'home-outline' as const },
|
||||
{ id: 'no-time', labelKey: 'onboarding.empathy.noTime' as const, icon: 'clock' as const },
|
||||
{ id: 'low-motivation', labelKey: 'onboarding.empathy.lowMotivation' as const, icon: 'battery.0percent' as const },
|
||||
{ id: 'no-knowledge', labelKey: 'onboarding.empathy.noKnowledge' as const, icon: 'questionmark.circle' as const },
|
||||
{ id: 'no-gym', labelKey: 'onboarding.empathy.noGym' as const, icon: 'house' as const },
|
||||
]
|
||||
|
||||
function EmpathyScreen({
|
||||
@@ -187,7 +187,7 @@ function EmpathyScreen({
|
||||
]}
|
||||
onPress={() => toggleBarrier(item.id)}
|
||||
>
|
||||
<Ionicons
|
||||
<Icon
|
||||
name={item.icon}
|
||||
size={28}
|
||||
color={selected ? BRAND.PRIMARY : colors.text.tertiary}
|
||||
@@ -373,10 +373,10 @@ function SolutionScreen({ onNext }: { onNext: () => void }) {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const WOW_FEATURES = [
|
||||
{ icon: 'timer-outline' as const, iconColor: BRAND.PRIMARY, titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
|
||||
{ icon: 'barbell-outline' as const, iconColor: PHASE.REST, titleKey: 'onboarding.wow.card2Title', subtitleKey: 'onboarding.wow.card2Subtitle' },
|
||||
{ icon: 'mic-outline' as const, iconColor: PHASE.PREP, titleKey: 'onboarding.wow.card3Title', subtitleKey: 'onboarding.wow.card3Subtitle' },
|
||||
{ icon: 'trending-up-outline' as const, iconColor: PHASE.COMPLETE, titleKey: 'onboarding.wow.card4Title', subtitleKey: 'onboarding.wow.card4Subtitle' },
|
||||
{ icon: 'timer' as const, iconColor: BRAND.PRIMARY, titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
|
||||
{ icon: 'dumbbell' as const, iconColor: PHASE.REST, titleKey: 'onboarding.wow.card2Title', subtitleKey: 'onboarding.wow.card2Subtitle' },
|
||||
{ icon: 'mic' as const, iconColor: PHASE.PREP, titleKey: 'onboarding.wow.card3Title', subtitleKey: 'onboarding.wow.card3Subtitle' },
|
||||
{ icon: 'arrow.up.right' as const, iconColor: PHASE.COMPLETE, titleKey: 'onboarding.wow.card4Title', subtitleKey: 'onboarding.wow.card4Subtitle' },
|
||||
] as const
|
||||
|
||||
function WowScreen({ onNext }: { onNext: () => void }) {
|
||||
@@ -453,7 +453,7 @@ function WowScreen({ onNext }: { onNext: () => void }) {
|
||||
]}
|
||||
>
|
||||
<View style={[wowStyles.iconCircle, { backgroundColor: `${feature.iconColor}26` }]}>
|
||||
<Ionicons name={feature.icon} size={22} color={feature.iconColor} />
|
||||
<Icon name={feature.icon} size={22} color={feature.iconColor} />
|
||||
</View>
|
||||
<View style={wowStyles.textCol}>
|
||||
<StyledText size={17} weight="semibold" color={colors.text.primary}>
|
||||
@@ -822,7 +822,7 @@ function PaywallScreen({
|
||||
key={featureKey}
|
||||
style={[styles.featureRow, { opacity: featureAnims[i] }]}
|
||||
>
|
||||
<Ionicons name="checkmark-circle" size={22} color={BRAND.SUCCESS} />
|
||||
<Icon name="checkmark.circle.fill" size={22} color={BRAND.SUCCESS} />
|
||||
<StyledText
|
||||
size={16}
|
||||
color={colors.text.primary}
|
||||
@@ -1036,6 +1036,15 @@ export default function OnboardingScreen() {
|
||||
setStep(next)
|
||||
}, [step, barriers, name, level, goal, frequency])
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
if (step > 1) {
|
||||
const prev = step - 1
|
||||
stepStartTime.current = Date.now()
|
||||
track('onboarding_step_back', { from_step: step, to_step: prev })
|
||||
setStep(prev)
|
||||
}
|
||||
}, [step])
|
||||
|
||||
const renderStep = () => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
@@ -1079,7 +1088,7 @@ export default function OnboardingScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<OnboardingStep step={step} totalSteps={TOTAL_STEPS}>
|
||||
<OnboardingStep step={step} totalSteps={TOTAL_STEPS} onBack={prevStep}>
|
||||
{renderStep()}
|
||||
</OnboardingStep>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user