UNPKG

react-native-media-viewing

Version:

React Native modal component for viewing images and video as a sliding gallery

79 lines (78 loc) 4.24 kB
import React, { useCallback, useRef, useState } from "react"; import VideoPlayer from "react-native-video-controls"; import { Animated, Dimensions, ScrollView, StyleSheet, View, TouchableWithoutFeedback, } from "react-native"; import useDoubleTapToZoom from "../../hooks/useDoubleTapToZoom"; import useImageDimensions from "../../hooks/useImageDimensions"; import { getImageStyles, getImageTransform } from "../../utils"; import { MediaLoading } from "./MediaLoading"; const SWIPE_CLOSE_OFFSET = 75; const SWIPE_CLOSE_VELOCITY = 1.55; const SCREEN = Dimensions.get("screen"); const SCREEN_WIDTH = SCREEN.width; const SCREEN_HEIGHT = SCREEN.height; const MediaItem = ({ mediaSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled = true, doubleTapToZoomEnabled = true, }) => { const scrollViewRef = useRef(null); const [loaded, setLoaded] = useState(false); const [scaled, setScaled] = useState(false); const imageDimensions = useImageDimensions(mediaSrc); const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN); const [translate, scale] = getImageTransform(imageDimensions, SCREEN); const scrollValueY = new Animated.Value(0); const scaleValue = new Animated.Value(scale || 1); const translateValue = new Animated.ValueXY(translate); const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1; const imageOpacity = scrollValueY.interpolate({ inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], outputRange: [0.5, 1, 0.5], }); const imagesStyles = getImageStyles(imageDimensions, translateValue, scaleValue); const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity }; const onScrollEndDrag = useCallback(({ nativeEvent }) => { var _a, _b, _c, _d; const velocityY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.velocity) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0)); const scaled = ((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1; onZoom(scaled); setScaled(scaled); if (!scaled && swipeToCloseEnabled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) { onRequestClose(); } }, [scaled]); const onScroll = ({ nativeEvent, }) => { var _a, _b, _c, _d; const offsetY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.contentOffset) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0)); if (((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1) { return; } scrollValueY.setValue(offsetY); }; const onLongPressHandler = useCallback((event) => { onLongPress(mediaSrc); }, [mediaSrc, onLongPress]); return (<View> <ScrollView ref={scrollViewRef} style={styles.listItem} pinchGestureEnabled showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} maximumZoomScale={maxScale} contentContainerStyle={styles.imageScrollContainer} scrollEnabled={swipeToCloseEnabled} onScrollEndDrag={onScrollEndDrag} scrollEventThrottle={1} {...(swipeToCloseEnabled && { onScroll, })}> {(!loaded || !imageDimensions) && <MediaLoading />} {mediaSrc.mediaType === "image" && (<TouchableWithoutFeedback onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined} onLongPress={onLongPressHandler} delayLongPress={delayLongPress}> <Animated.Image source={mediaSrc} style={imageStylesWithOpacity} onLoad={() => setLoaded(true)}/> </TouchableWithoutFeedback>)} {mediaSrc.mediaType === "video" && (<VideoPlayer source={mediaSrc} navigator={null} disableBack disableVolume disableFullscreen style={styles.containerMedia} onLoad={() => setLoaded(true)} paused/>)} </ScrollView> </View>); }; const styles = StyleSheet.create({ listItem: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT, }, imageScrollContainer: { height: SCREEN_HEIGHT, }, containerMedia: { width: SCREEN_WIDTH, height: "100%", }, }); export default React.memo(MediaItem);