fitst commit
This commit is contained in:
190
screens/welcome/ui/LanguageSelect.tsx
Normal file
190
screens/welcome/ui/LanguageSelect.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import Feather from '@expo/vector-icons/Feather';
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Animated, Modal, TouchableOpacity, View } from 'react-native';
|
||||
import { styles } from '../styles/welcomeStyle';
|
||||
|
||||
export default function LanguageSelect() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [current, setCurrent] = useState('uz');
|
||||
const [menuPos, setMenuPos] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
||||
const triggerRef = useRef<View>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLang = async () => {
|
||||
const lang = await getLang();
|
||||
if (lang) setCurrent(lang);
|
||||
};
|
||||
fetchLang();
|
||||
}, []);
|
||||
|
||||
const backdropAnim = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
const slideAnim = useRef(new Animated.Value(-20)).current;
|
||||
const scaleAnim = useRef(new Animated.Value(0.9)).current;
|
||||
|
||||
const languages = [
|
||||
{ key: 'uz', label: "🇺🇿 O'zbek" },
|
||||
{ key: 'ru', label: '🇷🇺 Русский' },
|
||||
{ key: 'en', label: '🇺🇸 English' },
|
||||
];
|
||||
|
||||
const openMenu = () => {
|
||||
triggerRef.current?.measureInWindow((x, y, width, height) => {
|
||||
setMenuPos({
|
||||
x,
|
||||
y: y + height + 60,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
setVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
Animated.parallel([
|
||||
Animated.timing(backdropAnim, {
|
||||
toValue: 0,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(slideAnim, {
|
||||
toValue: -20,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(scaleAnim, {
|
||||
toValue: 0.9,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start(() => {
|
||||
setVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
backdropAnim.setValue(0);
|
||||
fadeAnim.setValue(0);
|
||||
slideAnim.setValue(-20);
|
||||
scaleAnim.setValue(0.9);
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(backdropAnim, {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.spring(slideAnim, {
|
||||
toValue: 0,
|
||||
tension: 100,
|
||||
friction: 8,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.spring(scaleAnim, {
|
||||
toValue: 1,
|
||||
tension: 100,
|
||||
friction: 8,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const selectLanguage = async (lang: string) => {
|
||||
closeMenu();
|
||||
setCurrent(lang);
|
||||
await i18n.changeLanguage(lang);
|
||||
await saveLang(lang);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.langContainer} ref={triggerRef}>
|
||||
<TouchableOpacity style={styles.langTrigger} onPress={openMenu} activeOpacity={0.7}>
|
||||
<Feather name="globe" size={18} color="black" />
|
||||
<ThemedText style={{ ...styles.langText }}>{current.toUpperCase()}</ThemedText>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Modal
|
||||
transparent
|
||||
visible={visible}
|
||||
animationType="none"
|
||||
statusBarTranslucent
|
||||
onRequestClose={closeMenu}
|
||||
>
|
||||
<TouchableOpacity style={{ flex: 1 }} activeOpacity={1} onPress={closeMenu}>
|
||||
<Animated.View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
opacity: backdropAnim,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Animated.View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: menuPos.x - menuPos.width - 20,
|
||||
top: menuPos.y,
|
||||
width: 160,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 16,
|
||||
elevation: 12,
|
||||
overflow: 'hidden',
|
||||
opacity: fadeAnim,
|
||||
transform: [{ translateY: slideAnim }, { scale: scaleAnim }],
|
||||
}}
|
||||
>
|
||||
{languages.map((l, index) => (
|
||||
<TouchableOpacity
|
||||
key={l.key}
|
||||
onPress={() => selectLanguage(l.key)}
|
||||
activeOpacity={0.7}
|
||||
style={{
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
backgroundColor: current === l.key ? '#EEF2FF' : 'transparent',
|
||||
borderBottomWidth: index < languages.length - 1 ? 1 : 0,
|
||||
borderBottomColor: '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<ThemedText
|
||||
style={[styles.langItemText, current === l.key && styles.langItemTextActive]}
|
||||
>
|
||||
{l.label}
|
||||
</ThemedText>
|
||||
{current === l.key && <MaterialIcons name="done" size={20} color="#6366F1" />}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// StyleSheet.absoluteFillObject uchun import qo'shing:
|
||||
import { getLang, saveLang } from '@/hooks/storage.native';
|
||||
import i18n from '@/i18n/i18n';
|
||||
import { StyleSheet } from 'react-native';
|
||||
Reference in New Issue
Block a user