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:
Millian Lamiaux
2026-03-25 23:28:51 +01:00
parent f11eb6b9ae
commit b833198e9d
42 changed files with 2006 additions and 1594 deletions

View File

@@ -22,7 +22,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { LinearGradient } from 'expo-linear-gradient'
import { BlurView } from 'expo-blur'
import { useKeepAwake } from 'expo-keep-awake'
import Ionicons from '@expo/vector-icons/Ionicons'
import { Icon, type IconName } from '@/src/shared/components/Icon'
import { useTranslation } from 'react-i18next'
@@ -187,7 +187,7 @@ function ControlButton({
size = 64,
variant = 'primary',
}: {
icon: keyof typeof Ionicons.glyphMap
icon: IconName
onPress: () => void
size?: number
variant?: 'primary' | 'secondary' | 'danger'
@@ -228,7 +228,7 @@ function ControlButton({
style={[timerStyles.controlButton, { width: size, height: size, borderRadius: size / 2 }]}
>
<View style={[timerStyles.controlButtonBg, { backgroundColor }]} />
<Ionicons name={icon} size={size * 0.4} color={colors.text.primary} />
<Icon name={icon} size={size * 0.4} tintColor={colors.text.primary} />
</Pressable>
</Animated.View>
)
@@ -423,10 +423,11 @@ export default function PlayerScreen() {
}
}, [timer.phase])
// Countdown beep for last 3 seconds
// Countdown beep + haptic for last 3 seconds
useEffect(() => {
if (timer.isRunning && timer.timeRemaining <= 3 && timer.timeRemaining > 0) {
audio.countdownBeep()
haptics.countdownTick()
}
}, [timer.timeRemaining])
@@ -481,7 +482,7 @@ export default function PlayerScreen() {
<View style={[styles.header, { paddingTop: insets.top + SPACING[4] }]}>
<Pressable onPress={stopWorkout} style={styles.closeButton}>
<BlurView intensity={colors.glass.blurLight} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
<Ionicons name="close" size={24} color={colors.text.primary} />
<Icon name="xmark" size={24} tintColor={colors.text.primary} />
</Pressable>
<View style={styles.headerCenter}>
<Text style={styles.workoutTitle}>{workout?.title ?? 'Workout'}</Text>
@@ -541,22 +542,22 @@ export default function PlayerScreen() {
{showControls && !timer.isComplete && (
<View style={[styles.controls, { paddingBottom: insets.bottom + SPACING[6] }]}>
{!timer.isRunning ? (
<ControlButton icon="play" onPress={startTimer} size={80} />
<ControlButton icon="play.fill" onPress={startTimer} size={80} />
) : (
<View style={styles.controlsRow}>
<ControlButton
icon="stop"
icon="stop.fill"
onPress={stopWorkout}
size={56}
variant="danger"
/>
<ControlButton
icon={timer.isPaused ? 'play' : 'pause'}
icon={timer.isPaused ? 'play.fill' : 'pause.fill'}
onPress={togglePause}
size={80}
/>
<ControlButton
icon="play-skip-forward"
icon="forward.end.fill"
onPress={handleSkip}
size={56}
variant="secondary"
@@ -576,7 +577,7 @@ export default function PlayerScreen() {
end={{ x: 1, y: 1 }}
style={StyleSheet.absoluteFill}
/>
<Text style={styles.doneButtonText}>Done</Text>
<Text style={styles.doneButtonText}>{t('common:done')}</Text>
</Pressable>
</View>
)}