You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
6.9 KiB
300 lines
6.9 KiB
import React, { useEffect, useState, useCallback } from 'react'; |
|
import { |
|
View, |
|
Text, |
|
StyleSheet, |
|
SafeAreaView, |
|
StatusBar, |
|
TouchableOpacity, |
|
Modal, |
|
Animated, |
|
Dimensions, |
|
BackHandler, |
|
} from 'react-native'; |
|
import Constants from 'expo-constants'; |
|
import AsyncStorage from '@react-native-async-storage/async-storage'; |
|
import { useNavigation } from '@react-navigation/native'; |
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
|
import { RootStackParamList } from '../../App'; |
|
|
|
type MainAppScreenNavigationProp = NativeStackNavigationProp< |
|
RootStackParamList, |
|
'MainApp' |
|
>; |
|
|
|
const MODAL_HEIGHT = 280; // 估计的模态框高度 |
|
|
|
export const MainApp = () => { |
|
const navigation = useNavigation<MainAppScreenNavigationProp>(); |
|
const [modalVisible, setModalVisible] = useState(true); |
|
const [slideAnim] = useState(new Animated.Value(MODAL_HEIGHT)); |
|
const [fadeAnim] = useState(new Animated.Value(0)); |
|
|
|
useEffect(() => { |
|
if (modalVisible) { |
|
slideAnim.setValue(MODAL_HEIGHT); |
|
fadeAnim.setValue(0); |
|
Animated.parallel([ |
|
Animated.spring(slideAnim, { |
|
toValue: 0, |
|
useNativeDriver: true, |
|
tension: 65, |
|
friction: 8, |
|
velocity: 0.3, |
|
}), |
|
Animated.timing(fadeAnim, { |
|
toValue: 1, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}) |
|
]).start(); |
|
} |
|
}, [modalVisible]); |
|
|
|
const handleCloseModal = useCallback(() => { |
|
Animated.parallel([ |
|
Animated.timing(slideAnim, { |
|
toValue: MODAL_HEIGHT, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}), |
|
Animated.timing(fadeAnim, { |
|
toValue: 0, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}) |
|
]).start(() => { |
|
setModalVisible(false); |
|
}); |
|
}, [slideAnim, fadeAnim]); |
|
|
|
const handleLoginPress = () => { |
|
handleCloseModal(); |
|
// 延迟导航,等待弹窗关闭动画完成 |
|
setTimeout(() => { |
|
navigation.navigate('Login'); |
|
}, 200); |
|
}; |
|
|
|
useEffect(() => { |
|
const backHandler = BackHandler.addEventListener( |
|
'hardwareBackPress', |
|
() => { |
|
if (modalVisible) { |
|
handleCloseModal(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
); |
|
|
|
return () => backHandler.remove(); |
|
}, [modalVisible, handleCloseModal]); |
|
|
|
const handleLogout = async () => { |
|
try { |
|
await AsyncStorage.removeItem('@selected_country'); |
|
navigation.replace('CountrySelect'); |
|
} catch (error) { |
|
console.error('Error logging out:', error); |
|
} |
|
}; |
|
|
|
return ( |
|
<View style={styles.container}> |
|
<StatusBar |
|
backgroundColor="#fff" |
|
barStyle="dark-content" |
|
/> |
|
<SafeAreaView style={styles.safeArea}> |
|
<View style={styles.header}> |
|
<Text style={styles.title}>{t('mainAppTitle')}</Text> |
|
</View> |
|
<View style={styles.content}> |
|
<Text style={styles.text}>{t('mainAppText')}</Text> |
|
<TouchableOpacity |
|
style={styles.button} |
|
onPress={handleLogout} |
|
> |
|
<Text style={styles.buttonText}>{t('resetCountry')}</Text> |
|
</TouchableOpacity> |
|
</View> |
|
</SafeAreaView> |
|
|
|
<Modal |
|
visible={modalVisible} |
|
transparent={true} |
|
animationType="none" |
|
statusBarTranslucent={true} |
|
onRequestClose={handleCloseModal} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalContainer, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalOverlay, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.modalOverlayTouch} |
|
activeOpacity={1} |
|
onPress={handleCloseModal} |
|
/> |
|
</Animated.View> |
|
<Animated.View |
|
style={[ |
|
styles.modalContent, |
|
{ |
|
transform: [{ translateY: slideAnim }], |
|
}, |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.closeButton} |
|
onPress={handleCloseModal} |
|
> |
|
<Text style={styles.closeButtonText}>✕</Text> |
|
</TouchableOpacity> |
|
<View style={styles.modalHeader}> |
|
<View style={styles.modalIndicator} /> |
|
</View> |
|
<Text style={styles.modalTitle}>{t('welcomeTitle')}</Text> |
|
<Text style={styles.modalText}> |
|
{t('welcomeMessage')} |
|
</Text> |
|
<TouchableOpacity |
|
style={styles.modalButton} |
|
onPress={handleLoginPress} |
|
> |
|
<Text style={styles.modalButtonText}>{t('loginNow')}</Text> |
|
</TouchableOpacity> |
|
</Animated.View> |
|
</Animated.View> |
|
</Modal> |
|
</View> |
|
); |
|
}; |
|
|
|
const styles = StyleSheet.create({ |
|
container: { |
|
flex: 1, |
|
backgroundColor: '#fff', |
|
paddingTop: Constants.statusBarHeight, |
|
}, |
|
safeArea: { |
|
flex: 1, |
|
}, |
|
header: { |
|
padding: 20, |
|
backgroundColor: '#fff', |
|
borderBottomWidth: 1, |
|
borderBottomColor: '#f0f0f0', |
|
}, |
|
title: { |
|
fontSize: 24, |
|
fontWeight: 'bold', |
|
color: '#000', |
|
}, |
|
content: { |
|
flex: 1, |
|
padding: 20, |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
}, |
|
text: { |
|
fontSize: 16, |
|
color: '#333', |
|
marginBottom: 30, |
|
}, |
|
button: { |
|
backgroundColor: '#007AFF', |
|
paddingHorizontal: 20, |
|
paddingVertical: 12, |
|
borderRadius: 8, |
|
}, |
|
buttonText: { |
|
color: '#fff', |
|
fontSize: 16, |
|
fontWeight: '500', |
|
}, |
|
modalContainer: { |
|
flex: 1, |
|
justifyContent: 'flex-end', |
|
}, |
|
modalOverlay: { |
|
position: 'absolute', |
|
top: 0, |
|
left: 0, |
|
right: 0, |
|
bottom: 0, |
|
backgroundColor: 'rgba(0, 0, 0, 0.4)', |
|
}, |
|
modalOverlayTouch: { |
|
flex: 1, |
|
}, |
|
modalContent: { |
|
backgroundColor: '#fff', |
|
borderTopLeftRadius: 20, |
|
borderTopRightRadius: 20, |
|
padding: 20, |
|
paddingBottom: 40, |
|
}, |
|
modalHeader: { |
|
alignItems: 'center', |
|
marginBottom: 20, |
|
}, |
|
modalIndicator: { |
|
width: 40, |
|
height: 4, |
|
backgroundColor: '#DDD', |
|
borderRadius: 2, |
|
marginVertical: 8, |
|
}, |
|
modalTitle: { |
|
fontSize: 22, |
|
fontWeight: 'bold', |
|
marginBottom: 16, |
|
color: '#000', |
|
}, |
|
modalText: { |
|
fontSize: 16, |
|
color: '#666', |
|
marginBottom: 24, |
|
lineHeight: 22, |
|
}, |
|
modalButton: { |
|
backgroundColor: '#007AFF', |
|
paddingVertical: 14, |
|
borderRadius: 10, |
|
alignItems: 'center', |
|
}, |
|
modalButtonText: { |
|
color: '#fff', |
|
fontSize: 16, |
|
fontWeight: '600', |
|
}, |
|
closeButton: { |
|
position: 'absolute', |
|
top: 20, |
|
right: 20, |
|
width: 24, |
|
height: 24, |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
zIndex: 1, |
|
}, |
|
closeButtonText: { |
|
fontSize: 20, |
|
color: '#999', |
|
fontWeight: '400', |
|
}, |
|
});
|