2022-06-23 20:19:42 +00:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
import { LayoutChangeEvent, StyleSheet, StyleProp, ViewStyle, ImageStyle, View } from 'react-native';
|
|
|
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
|
|
import Animated, { withTiming, useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
|
|
|
|
|
|
|
|
import { useTheme } from '../../theme';
|
|
|
|
import { ImageComponent } from './ImageComponent';
|
|
|
|
|
|
|
|
interface ImageViewerProps {
|
|
|
|
style?: StyleProp<ImageStyle>;
|
|
|
|
containerStyle?: StyleProp<ViewStyle>;
|
|
|
|
imageContainerStyle?: StyleProp<ViewStyle>;
|
|
|
|
|
|
|
|
uri: string;
|
|
|
|
imageComponentType?: string;
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
onLoadEnd?: () => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
flex: {
|
|
|
|
flex: 1
|
|
|
|
},
|
|
|
|
image: {
|
|
|
|
flex: 1
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
export const ImageViewer = ({ uri = '', imageComponentType, width, height, ...props }: ImageViewerProps): React.ReactElement => {
|
|
|
|
const [centerX, setCenterX] = useState(0);
|
|
|
|
const [centerY, setCenterY] = useState(0);
|
|
|
|
|
|
|
|
const onLayout = ({
|
|
|
|
nativeEvent: {
|
|
|
|
layout: { x, y, width, height }
|
|
|
|
}
|
|
|
|
}: LayoutChangeEvent) => {
|
|
|
|
setCenterX(x + width / 2);
|
|
|
|
setCenterY(y + height / 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
const translationX = useSharedValue<number>(0);
|
|
|
|
const translationY = useSharedValue<number>(0);
|
|
|
|
const offsetX = useSharedValue<number>(0);
|
|
|
|
const offsetY = useSharedValue<number>(0);
|
|
|
|
const scale = useSharedValue<number>(1);
|
|
|
|
const scaleOffset = useSharedValue<number>(1);
|
|
|
|
|
|
|
|
const style = useAnimatedStyle(() => ({
|
|
|
|
transform: [{ translateX: translationX.value }, { translateY: translationY.value }, { scale: scale.value }]
|
|
|
|
}));
|
|
|
|
|
|
|
|
const resetScaleAnimation = () => {
|
2022-08-08 21:02:08 +00:00
|
|
|
'worklet';
|
|
|
|
|
2022-06-23 20:19:42 +00:00
|
|
|
scaleOffset.value = 1;
|
|
|
|
offsetX.value = 0;
|
|
|
|
offsetY.value = 0;
|
|
|
|
scale.value = withSpring(1);
|
|
|
|
translationX.value = withSpring(0, { overshootClamping: true });
|
|
|
|
translationY.value = withSpring(0, { overshootClamping: true });
|
|
|
|
};
|
|
|
|
|
2022-08-08 21:02:08 +00:00
|
|
|
const clamp = (value: number, min: number, max: number) => {
|
|
|
|
'worklet';
|
|
|
|
|
|
|
|
return Math.max(Math.min(value, max), min);
|
|
|
|
};
|
2022-06-23 20:19:42 +00:00
|
|
|
|
|
|
|
const pinchGesture = Gesture.Pinch()
|
|
|
|
.onUpdate(event => {
|
|
|
|
scale.value = clamp(scaleOffset.value * (event.scale > 0 ? event.scale : 1), 1, 4);
|
|
|
|
})
|
|
|
|
.onEnd(() => {
|
|
|
|
scaleOffset.value = scale.value > 0 ? scale.value : 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
const panGesture = Gesture.Pan()
|
|
|
|
.maxPointers(2)
|
|
|
|
.onStart(() => {
|
|
|
|
translationX.value = offsetX.value;
|
|
|
|
translationY.value = offsetY.value;
|
|
|
|
})
|
|
|
|
.onUpdate(event => {
|
|
|
|
const scaleFactor = scale.value - 1;
|
|
|
|
translationX.value = clamp(event.translationX + offsetX.value, -scaleFactor * centerX, scaleFactor * centerX);
|
|
|
|
translationY.value = clamp(event.translationY + offsetY.value, -scaleFactor * centerY, scaleFactor * centerY);
|
|
|
|
})
|
|
|
|
.onEnd(() => {
|
|
|
|
offsetX.value = translationX.value;
|
|
|
|
offsetY.value = translationY.value;
|
|
|
|
if (scale.value === 1) resetScaleAnimation();
|
|
|
|
});
|
|
|
|
|
|
|
|
const doubleTapGesture = Gesture.Tap()
|
|
|
|
.numberOfTaps(2)
|
|
|
|
.maxDelay(120)
|
|
|
|
.maxDistance(70)
|
|
|
|
.onEnd(event => {
|
|
|
|
if (scaleOffset.value > 1) resetScaleAnimation();
|
|
|
|
else {
|
|
|
|
scale.value = withTiming(2, { duration: 200 });
|
|
|
|
translationX.value = withTiming(centerX - event.x, { duration: 200 });
|
|
|
|
offsetX.value = centerX - event.x;
|
|
|
|
scaleOffset.value = 2;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const gesture = Gesture.Simultaneous(pinchGesture, panGesture, doubleTapGesture);
|
|
|
|
|
feat: add media auto-download (#5076)
* feat: media auto-download view
* media auto download view completed and saving the settings in mmkv
* audio download preference
* audio auto download when the user who sent the audio is the same logged on mobile
* creation of isAutoDownloadEnabled, evaluate hist hook, Image Full Size preload done
* minor tweak audio show play button after download
* refactor audioFile to handleMediaDownload and fixed the audio download
* desestructured params to download too
* image download and autoDownload, algo fix the formatAttachmentUrl to show the image from local
* add the possibility to cancel image download and clear local images
* refactor blur component
* video download and auto download, also keeped the behavior to download unsuportted videos to the gallery
* add the possibility to start downloading a video, then exit the room, back again to room and cancel the video previously downloading
* remove the custom hook for autoDownload
* remove blurcomponent, fix the blur style in image.tsx, minor tweak video function name
* send messageId to video
* introducing the reducer to keep the downloads in progress
* create a media download selector
* remove all the redux stuff and do the same as file upload
* video download behavior
* done for image and audio
* fix the try catch download media
* clean up
* image container uiKit
* fix lint
* change rn-fetch-blob to expo-filesystem
* add pt-br
* pass the correct message id when there is an attachment on reply
* refactor some changes requested
* fix audio and move the netInfo from autoDownloadPreference to redux
* variable isAutoDownloadEnable name and handleMediaDownload getExtension
* message/Image refactored, change the component to show the image from FastImage to Image
* refactor handleMediaDownload and deleteMedia
* minor tweak
* refactor audio
* refactor video
* fix the type on the messagesView(the view of files)
* minor tweak
* fix the name of searchMediaFIleAsync's result
* minor tweak, add the default behavior, add the OFF as label
* minor tweaks
* verify if the media auto download exists on settings view
* fix media auto download view layout and minor tweak wifi
* avoid auto download from reply
* minor tweak at comment
* tweak list.section
* change the name to netInfoState and Local_document_directory
* remove mediaType and refactor audio and image
* separate blurview
* thumbnail video and video behavior
* add Audio to i18n and minor tweak
* set the blur as always dark and add the possibility to overlay
* don't need to controle the filepath in the view
* fix the loading in image and video at begin
* save the file with a similar filename as expected
* removed the necessity of messageId or id
* minor tweak
* switch useLayoutEffect to useEffect
* avoid onpress do some edge case because of cached at video
* minor tweak
* tweak at audio comment extension
* minor tweak type userpreferences
* remove test id from mediaAutoDownloadView
* change action's name to SET_NET_INFO_STATE
* caching and deleting video's thumbnails
* remove generate thumbnail
* minor tweak in image
* update camera-roll and save the file from local url
* remove local_cache_directory and deleteThumbnail
* update blur to fix error on android
* fix blur is hiding the file description
* avoid download unsupported video
* return void when it is loading the audio
2023-08-07 14:02:30 +00:00
|
|
|
const Component = ImageComponent({ type: imageComponentType, uri });
|
2022-06-23 20:19:42 +00:00
|
|
|
|
|
|
|
const { colors } = useTheme();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<View style={[styles.flex, { width, height, backgroundColor: colors.previewBackground }]}>
|
|
|
|
<GestureDetector gesture={gesture}>
|
|
|
|
<Animated.View onLayout={onLayout} style={[styles.flex, style]}>
|
|
|
|
<Component
|
|
|
|
// @ts-ignore
|
|
|
|
style={styles.image}
|
|
|
|
resizeMode='contain'
|
|
|
|
source={{ uri }}
|
|
|
|
{...props}
|
|
|
|
/>
|
|
|
|
</Animated.View>
|
|
|
|
</GestureDetector>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
};
|