This commit is contained in:
imeepos
2025-10-21 10:21:45 +08:00
parent 7e3f94bae3
commit 3a99ff96d5
28 changed files with 6981 additions and 176 deletions

View File

@@ -0,0 +1,247 @@
import { ThemedText } from '@/components/themed-text';
import { Image } from 'expo-image';
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Dimensions,
Modal,
PanResponder,
Platform,
StatusBar,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
export interface FullscreenImageModalProps {
visible: boolean;
onClose: () => void;
imageUrl: string;
onPrevious?: () => void;
onNext?: () => void;
hasNext?: boolean;
hasPrevious?: boolean;
}
export function FullscreenImageModal({
visible,
onClose,
imageUrl,
onPrevious,
onNext,
hasNext = false,
hasPrevious = false,
}: FullscreenImageModalProps) {
const [isLoading, setIsLoading] = useState(true);
const [showControls, setShowControls] = useState(true);
// 重置状态当模态框打开/关闭时
useEffect(() => {
if (visible) {
setIsLoading(true);
setShowControls(true);
}
}, [visible]);
// 处理图片加载完成
const handleImageLoad = () => {
setIsLoading(false);
};
// 处理图片加载错误
const handleImageError = (error: any) => {
console.error('图片加载错误:', error);
setIsLoading(false);
};
// 图片点击处理(直接关闭)
const handleImagePress = () => {
handleClose();
};
// 关闭模态框
const handleClose = () => {
onClose();
};
// 创建手势处理器
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) => {
// 只有水平移动时才响应
return Math.abs(gestureState.dx) > Math.abs(gestureState.dy) && Math.abs(gestureState.dx) > 10;
},
onPanResponderRelease: (_, gestureState) => {
const threshold = screenWidth / 4; // 滑动屏幕宽度的1/4触发切换
if (gestureState.dx > threshold && hasPrevious) {
// 向右滑动,显示上一个
onPrevious?.();
} else if (gestureState.dx < -threshold && hasNext) {
// 向左滑动,显示下一个
onNext?.();
}
},
})
).current;
// 处理返回键Android
useEffect(() => {
const backHandler = () => {
if (visible) {
handleClose();
return true;
}
return false;
};
// 在实际应用中,这里需要添加返回键监听
// 对于演示,我们只做概念性实现
return () => {
// 清理返回键监听
};
}, [visible]);
if (!visible) return null;
return (
<Modal
visible={visible}
transparent={false}
animationType="fade"
onRequestClose={handleClose}
statusBarTranslucent={true}
>
<View style={styles.container}>
{/* 状态栏处理 */}
{Platform.OS === 'android' && <StatusBar hidden />}
{/* 图片容器 */}
<TouchableOpacity
style={styles.imageContainer}
onPress={handleImagePress}
activeOpacity={1}
>
<View
style={styles.imageWrapper}
{...panResponder.panHandlers}
>
{/* 图片显示 */}
<Image
source={{ uri: imageUrl }}
style={styles.image}
contentFit="contain"
onLoad={handleImageLoad}
onError={handleImageError}
placeholder={{ blurhash: 'L6PZfSi_.AyE_3t7t7R**0o#DgR4' }}
transition={300}
/>
{/* 加载指示器 */}
{isLoading && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#4ECDC4" />
</View>
)}
{/* 左右导航指示器 */}
{hasPrevious && (
<TouchableOpacity
style={styles.leftIndicator}
onPress={(e) => {
e.stopPropagation();
onPrevious?.();
}}
activeOpacity={0.7}
>
<ThemedText style={styles.indicatorIcon}></ThemedText>
</TouchableOpacity>
)}
{hasNext && (
<TouchableOpacity
style={styles.rightIndicator}
onPress={(e) => {
e.stopPropagation();
onNext?.();
}}
activeOpacity={0.7}
>
<ThemedText style={styles.indicatorIcon}></ThemedText>
</TouchableOpacity>
)}
</View>
</TouchableOpacity>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
imageContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
imageWrapper: {
flex: 1,
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
image: {
width: screenWidth,
height: screenHeight,
backgroundColor: '#000',
},
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
},
leftIndicator: {
position: 'absolute',
left: 20,
top: '50%',
marginTop: -20,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
},
rightIndicator: {
position: 'absolute',
right: 20,
top: '50%',
marginTop: -20,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
},
indicatorIcon: {
fontSize: 24,
color: '#fff',
fontWeight: 'bold',
},
});
export default FullscreenImageModal;