import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { Pressable } from 'react-native'; import { useTranslation } from 'react-i18next'; import { useNavigation } from 'expo-router'; import Animated, { Easing, runOnJS, useAnimatedStyle, useSharedValue, withSequence, withTiming, } from 'react-native-reanimated'; import { MOCK_CONTENT } from '@/src/constants/mockContent'; import { addFavorite, getThemeMode, getUserProfile, setReaction, setThemeMode, type ThemeMode, } from '@/src/storage/appStorage'; import ProfileModal from '@/components/home/ProfileModal'; import ThemeModal from '@/components/home/ThemeModal'; import ThemeIcon from '@/assets/images/home/theme.svg'; import MyIcon from '@/assets/images/home/my.svg'; import LikeOutlineIcon from '@/assets/images/home/like.svg'; import LikeFilledIcon from '@/assets/images/home/like_filled.svg'; import HateIcon from '@/assets/images/home/hate.svg'; export default function HomeScreen() { const { t } = useTranslation(); const navigation = useNavigation(); const [index, setIndex] = useState(0); const [themeMode, setThemeModeState] = useState('scenery'); const [themeOpen, setThemeOpen] = useState(false); const [profileOpen, setProfileOpen] = useState(false); const [profileName, setProfileName] = useState(undefined); const [busy, setBusy] = useState(false); const [likeFilled, setLikeFilled] = useState(false); const item = useMemo(() => MOCK_CONTENT[index % MOCK_CONTENT.length], [index]); useEffect(() => { let cancelled = false; (async () => { const mode = await getThemeMode(); const profile = await getUserProfile(); if (cancelled) return; setThemeModeState(mode); setProfileName(profile.name); })(); return () => { cancelled = true; }; }, []); const backgroundColor = themeMode === 'color' ? '#F3D0E1' : '#F4D6C2'; useLayoutEffect(() => { navigation.setOptions({ headerShadowVisible: false, headerStyle: { backgroundColor }, headerRight: () => ( setThemeOpen(true)} accessibilityLabel={t('home.theme')} > setProfileOpen(true)} accessibilityLabel={t('home.profile')} > ), }); }, [backgroundColor, navigation, t]); const likeScale = useSharedValue(1); const hateScale = useSharedValue(1); const likeAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: likeScale.value }], })); const hateAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: hateScale.value }], })); function playLikeAnimationAndThen(next: () => void) { setLikeFilled(true); likeScale.value = withSequence( withTiming(0.92, { duration: 90, easing: Easing.out(Easing.cubic) }), withTiming(1.14, { duration: 120, easing: Easing.out(Easing.cubic) }), withTiming(1, { duration: 140, easing: Easing.out(Easing.cubic) }, (finished) => { if (finished) runOnJS(next)(); }) ); } function playHateAnimationAndThen(next: () => void) { hateScale.value = withSequence( withTiming(0.92, { duration: 90, easing: Easing.out(Easing.cubic) }), withTiming(1.06, { duration: 110, easing: Easing.out(Easing.cubic) }), withTiming(1, { duration: 120, easing: Easing.out(Easing.cubic) }, (finished) => { if (finished) runOnJS(next)(); }) ); } function onPressLike() { if (busy) return; setBusy(true); playLikeAnimationAndThen(async () => { await setReaction(item.id, 'like'); await addFavorite(item.id); setIndex((i) => i + 1); setTimeout(() => setLikeFilled(false), 220); setBusy(false); }); } function onPressHate() { if (busy) return; setBusy(true); playHateAnimationAndThen(async () => { await setReaction(item.id, 'dislike'); setIndex((i) => i + 1); setBusy(false); }); } async function onSelectTheme(next: ThemeMode) { setThemeModeState(next); await setThemeMode(next); setThemeOpen(false); } return ( {item.text} (hateScale.value = withTiming(0.92, { duration: 80 }))} onPressOut={() => (hateScale.value = withTiming(1, { duration: 120 }))} accessibilityRole="button" accessibilityLabel={t('home.dislike')} hitSlop={10} style={styles.reactionInner} > (likeScale.value = withTiming(0.92, { duration: 80 }))} onPressOut={() => (likeScale.value = withTiming(1, { duration: 120 }))} accessibilityRole="button" accessibilityLabel={t('home.like')} hitSlop={10} style={styles.reactionInner} > {likeFilled ? ( ) : ( )} setThemeOpen(false)} /> setProfileOpen(false)} /> ); } function CircleIconButton({ onPress, accessibilityLabel, children, }: { onPress: () => void; accessibilityLabel: string; children: React.ReactNode; }) { return ( {children} ); } const styles = StyleSheet.create({ container: { flex: 1, padding: 20, justifyContent: 'center', gap: 16, }, headerRight: { flexDirection: 'row', gap: 10, paddingRight: 10, }, circleBtn: { width: 34, height: 34, borderRadius: 17, backgroundColor: 'rgba(255,255,255,0.75)', alignItems: 'center', justifyContent: 'center', }, card: { borderRadius: 16, padding: 20, backgroundColor: 'rgba(255,255,255,0.65)', borderWidth: StyleSheet.hairlineWidth, borderColor: '#E5E7EB', }, text: { fontSize: 20, lineHeight: 28, color: '#5E2A28', fontWeight: '700', }, actions: { flexDirection: 'row', gap: 12, justifyContent: 'center', }, reactionButton: { width: 58, height: 58, borderRadius: 29, backgroundColor: 'rgba(255,255,255,0.75)', alignItems: 'center', justifyContent: 'center', }, reactionInner: { width: 58, height: 58, borderRadius: 29, alignItems: 'center', justifyContent: 'center', }, });