react-native-gifted-chat
Version:
The most complete chat UI for React Native
164 lines (161 loc) • 6.63 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Image, StyleSheet, View, useWindowDimensions, StatusBar, } from 'react-native';
import { BaseButton, GestureHandlerRootView, Text } from 'react-native-gesture-handler';
import { OverKeyboardView } from 'react-native-keyboard-controller';
import Animated, { useAnimatedStyle, useSharedValue, withTiming, Easing, runOnJS, } from 'react-native-reanimated';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import Zoom from 'react-native-zoom-reanimated';
import { TouchableOpacity } from './components/TouchableOpacity';
import commonStyles from './styles';
function ModalContent({ isVisible, imageSource, modalImageDimensions, imageProps, onClose }) {
const insets = useSafeAreaInsets();
// Animation values
const modalOpacity = useSharedValue(0);
const modalScale = useSharedValue(0.9);
const modalBorderRadius = useSharedValue(40);
const handleModalClose = useCallback(() => {
modalOpacity.value = withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) });
modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {
runOnJS(onClose)();
});
modalBorderRadius.value = withTiming(40, { duration: 200, easing: Easing.in(Easing.ease) });
}, [onClose, modalOpacity, modalScale, modalBorderRadius]);
// Animate on visibility change
useEffect(() => {
if (isVisible) {
modalOpacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) });
modalScale.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) });
modalBorderRadius.value = withTiming(0, { duration: 300, easing: Easing.out(Easing.ease) });
}
}, [isVisible, modalOpacity, modalScale, modalBorderRadius]);
const modalAnimatedStyle = useAnimatedStyle(() => ({
opacity: modalOpacity.value,
transform: [{ scale: modalScale.value }],
}), [modalOpacity, modalScale]);
const modalBorderRadiusStyle = useAnimatedStyle(() => ({
borderRadius: modalBorderRadius.value,
}), [modalBorderRadius]);
return (<>
<StatusBar animated barStyle='dark-content'/>
<Animated.View style={[styles.modalOverlay, modalAnimatedStyle, modalBorderRadiusStyle]}>
<GestureHandlerRootView style={commonStyles.fill}>
<Animated.View style={[commonStyles.fill, styles.modalContent, modalBorderRadiusStyle, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
{/* close button */}
<View style={styles.closeButtonContainer}>
<BaseButton onPress={handleModalClose}>
<View style={styles.closeButtonContent}>
<Text style={styles.closeButtonIcon}>
{'X'}
</Text>
</View>
</BaseButton>
</View>
<View style={[commonStyles.fill, commonStyles.centerItems]}>
<Zoom>
<Image style={modalImageDimensions} source={imageSource} resizeMode='contain' {...imageProps}/>
</Zoom>
</View>
</Animated.View>
</GestureHandlerRootView>
</Animated.View>
</>);
}
export function MessageImage({ containerStyle, imageProps, imageSourceProps, imageStyle, currentMessage, }) {
const [isModalVisible, setIsModalVisible] = useState(false);
const [imageDimensions, setImageDimensions] = useState();
const windowDimensions = useWindowDimensions();
const imageSource = useMemo(() => ({
...imageSourceProps,
uri: currentMessage?.image,
}), [imageSourceProps, currentMessage?.image]);
const isImageSourceChanged = useRef(true);
const computedImageStyle = useMemo(() => [
styles.image,
imageStyle,
], [imageStyle]);
const handleImagePress = useCallback(() => {
if (!imageSource.uri)
return;
setIsModalVisible(true);
if (isImageSourceChanged.current || !imageDimensions)
Image.getSize(imageSource.uri, (width, height) => {
setImageDimensions({ width, height });
});
}, [imageSource.uri, imageDimensions]);
const handleModalClose = useCallback(() => {
setIsModalVisible(false);
}, []);
const handleImageLayout = useCallback((e) => {
setImageDimensions({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height,
});
}, []);
const modalImageDimensions = useMemo(() => {
if (!imageDimensions)
return undefined;
const aspectRatio = imageDimensions.width / imageDimensions.height;
let width = windowDimensions.width;
let height = width / aspectRatio;
if (height > windowDimensions.height) {
height = windowDimensions.height;
width = height * aspectRatio;
}
return {
width,
height,
};
}, [imageDimensions, windowDimensions.height, windowDimensions.width]);
useEffect(() => {
isImageSourceChanged.current = true;
}, [imageSource.uri]);
if (currentMessage == null)
return null;
return (<View style={containerStyle}>
<TouchableOpacity onPress={handleImagePress}>
<Image {...imageProps} style={computedImageStyle} source={imageSource} onLayout={handleImageLayout} resizeMode='cover'/>
</TouchableOpacity>
<OverKeyboardView visible={isModalVisible}>
<SafeAreaProvider>
<ModalContent isVisible={isModalVisible} imageSource={imageSource} modalImageDimensions={modalImageDimensions} imageProps={imageProps} onClose={handleModalClose}/>
</SafeAreaProvider>
</OverKeyboardView>
</View>);
}
const styles = StyleSheet.create({
image: {
width: 150,
height: 100,
borderRadius: 13,
margin: 3,
},
modalOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 1000,
},
modalContent: {
backgroundColor: '#000',
overflow: 'hidden',
},
modalImageContainer: {
width: '100%',
height: '100%',
},
closeButtonContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
closeButtonContent: {
padding: 20,
},
closeButtonIcon: {
fontSize: 20,
lineHeight: 20,
color: 'white',
},
});
//# sourceMappingURL=MessageImage.js.map