@gyjshow/react-native-image-viewing 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/@types/index.d.ts +17 -0
- package/dist/@types/index.js +8 -0
- package/dist/ImageViewing.d.ts +33 -0
- package/dist/ImageViewing.js +86 -0
- package/dist/components/ImageDefaultHeader.d.ts +13 -0
- package/dist/components/ImageDefaultHeader.js +38 -0
- package/dist/components/ImageItem/ImageItem.android.d.ts +20 -0
- package/dist/components/ImageItem/ImageItem.android.js +83 -0
- package/dist/components/ImageItem/ImageItem.ios.d.ts +20 -0
- package/dist/components/ImageItem/ImageItem.ios.js +79 -0
- package/dist/components/ImageItem/ImageLoading.d.ts +9 -0
- package/dist/components/ImageItem/ImageLoading.js +30 -0
- package/dist/components/StatusBarManager.d.ts +5 -0
- package/dist/components/StatusBarManager.js +14 -0
- package/dist/hooks/useAnimatedComponents.d.ts +18 -0
- package/dist/hooks/useAnimatedComponents.js +41 -0
- package/dist/hooks/useDoubleTapToZoom.d.ts +16 -0
- package/dist/hooks/useDoubleTapToZoom.js +49 -0
- package/dist/hooks/useImageDimensions.d.ts +10 -0
- package/dist/hooks/useImageDimensions.js +69 -0
- package/dist/hooks/useImageIndexChange.d.ts +11 -0
- package/dist/hooks/useImageIndexChange.js +20 -0
- package/dist/hooks/useImagePrefetch.d.ts +10 -0
- package/dist/hooks/useImagePrefetch.js +21 -0
- package/dist/hooks/usePanResponder.d.ts +19 -0
- package/dist/hooks/usePanResponder.js +273 -0
- package/dist/hooks/useRequestClose.d.ts +9 -0
- package/dist/hooks/useRequestClose.js +20 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/utils.d.ts +46 -0
- package/dist/utils.js +99 -0
- package/package.json +45 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { ScrollView, NativeTouchEvent, NativeSyntheticEvent } from "react-native";
|
|
10
|
+
import { Dimensions } from "../@types";
|
|
11
|
+
/**
|
|
12
|
+
* This is iOS only.
|
|
13
|
+
* Same functionality for Android implemented inside usePanResponder hook.
|
|
14
|
+
*/
|
|
15
|
+
declare function useDoubleTapToZoom(scrollViewRef: React.RefObject<ScrollView | null>, scaled: boolean, screen: Dimensions): (event: NativeSyntheticEvent<NativeTouchEvent>) => void;
|
|
16
|
+
export default useDoubleTapToZoom;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
const DOUBLE_TAP_DELAY = 300;
|
|
10
|
+
let lastTapTS = null;
|
|
11
|
+
/**
|
|
12
|
+
* This is iOS only.
|
|
13
|
+
* Same functionality for Android implemented inside usePanResponder hook.
|
|
14
|
+
*/
|
|
15
|
+
function useDoubleTapToZoom(scrollViewRef, scaled, screen) {
|
|
16
|
+
const handleDoubleTap = useCallback((event) => {
|
|
17
|
+
var _a;
|
|
18
|
+
const nowTS = new Date().getTime();
|
|
19
|
+
const scrollResponderRef = (_a = scrollViewRef === null || scrollViewRef === void 0 ? void 0 : scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.getScrollResponder();
|
|
20
|
+
if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) {
|
|
21
|
+
const { pageX, pageY } = event.nativeEvent;
|
|
22
|
+
let targetX = 0;
|
|
23
|
+
let targetY = 0;
|
|
24
|
+
let targetWidth = screen.width;
|
|
25
|
+
let targetHeight = screen.height;
|
|
26
|
+
// Zooming in
|
|
27
|
+
// TODO: Add more precise calculation of targetX, targetY based on touch
|
|
28
|
+
if (!scaled) {
|
|
29
|
+
targetX = pageX / 2;
|
|
30
|
+
targetY = pageY / 2;
|
|
31
|
+
targetWidth = screen.width / 2;
|
|
32
|
+
targetHeight = screen.height / 2;
|
|
33
|
+
}
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
scrollResponderRef === null || scrollResponderRef === void 0 ? void 0 : scrollResponderRef.scrollResponderZoomTo({
|
|
36
|
+
x: targetX,
|
|
37
|
+
y: targetY,
|
|
38
|
+
width: targetWidth,
|
|
39
|
+
height: targetHeight,
|
|
40
|
+
animated: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lastTapTS = nowTS;
|
|
45
|
+
}
|
|
46
|
+
}, [scaled]);
|
|
47
|
+
return handleDoubleTap;
|
|
48
|
+
}
|
|
49
|
+
export default useDoubleTapToZoom;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { Dimensions, ImageSource } from "../@types";
|
|
9
|
+
declare const useImageDimensions: (image: ImageSource) => Dimensions | null;
|
|
10
|
+
export default useImageDimensions;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import { Image } from "react-native";
|
|
10
|
+
import { createCache } from "../utils";
|
|
11
|
+
const CACHE_SIZE = 50;
|
|
12
|
+
const imageDimensionsCache = createCache(CACHE_SIZE);
|
|
13
|
+
const useImageDimensions = (image) => {
|
|
14
|
+
const [dimensions, setDimensions] = useState(null);
|
|
15
|
+
const getImageDimensions = (image) => {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
var _a;
|
|
18
|
+
if (typeof image == "number") {
|
|
19
|
+
const cacheKey = `${image}`;
|
|
20
|
+
let imageDimensions = imageDimensionsCache.get(cacheKey);
|
|
21
|
+
if (!imageDimensions) {
|
|
22
|
+
const { width, height } = Image.resolveAssetSource(image);
|
|
23
|
+
imageDimensions = { width, height };
|
|
24
|
+
imageDimensionsCache.set(cacheKey, imageDimensions);
|
|
25
|
+
}
|
|
26
|
+
resolve(imageDimensions);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
if (image.uri) {
|
|
31
|
+
const source = image;
|
|
32
|
+
const cacheKey = source.uri;
|
|
33
|
+
const imageDimensions = imageDimensionsCache.get(cacheKey);
|
|
34
|
+
if (imageDimensions) {
|
|
35
|
+
resolve(imageDimensions);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const uri = source.uri;
|
|
39
|
+
if (!uri) {
|
|
40
|
+
resolve({ width: 0, height: 0 });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
Image.getSizeWithHeaders(uri, (_a = source.headers) !== null && _a !== void 0 ? _a : {}, (width, height) => {
|
|
44
|
+
imageDimensionsCache.set(cacheKey, { width, height });
|
|
45
|
+
resolve({ width, height });
|
|
46
|
+
}, () => {
|
|
47
|
+
resolve({ width: 0, height: 0 });
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
resolve({ width: 0, height: 0 });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
let isImageUnmounted = false;
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
getImageDimensions(image).then((dimensions) => {
|
|
59
|
+
if (!isImageUnmounted) {
|
|
60
|
+
setDimensions(dimensions);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return () => {
|
|
64
|
+
isImageUnmounted = true;
|
|
65
|
+
};
|
|
66
|
+
}, [image]);
|
|
67
|
+
return dimensions;
|
|
68
|
+
};
|
|
69
|
+
export default useImageDimensions;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { NativeSyntheticEvent, NativeScrollEvent } from "react-native";
|
|
9
|
+
import { Dimensions } from "../@types";
|
|
10
|
+
declare const useImageIndexChange: (imageIndex: number, screen: Dimensions) => readonly [number, (event: NativeSyntheticEvent<NativeScrollEvent>) => void];
|
|
11
|
+
export default useImageIndexChange;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
const useImageIndexChange = (imageIndex, screen) => {
|
|
10
|
+
const [currentImageIndex, setImageIndex] = useState(imageIndex);
|
|
11
|
+
const onScroll = (event) => {
|
|
12
|
+
const { nativeEvent: { contentOffset: { x: scrollX }, }, } = event;
|
|
13
|
+
if (screen.width) {
|
|
14
|
+
const nextIndex = Math.round(scrollX / screen.width);
|
|
15
|
+
setImageIndex(nextIndex < 0 ? 0 : nextIndex);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return [currentImageIndex, onScroll];
|
|
19
|
+
};
|
|
20
|
+
export default useImageIndexChange;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { ImageSource } from "../@types";
|
|
9
|
+
declare const useImagePrefetch: (images: ImageSource[]) => void;
|
|
10
|
+
export default useImagePrefetch;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect } from "react";
|
|
9
|
+
import { Image } from "react-native";
|
|
10
|
+
const useImagePrefetch = (images) => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
images.forEach((image) => {
|
|
13
|
+
//@ts-ignore
|
|
14
|
+
if (image.uri) {
|
|
15
|
+
//@ts-ignore
|
|
16
|
+
return Image.prefetch(image.uri);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}, []);
|
|
20
|
+
};
|
|
21
|
+
export default useImagePrefetch;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { Animated, GestureResponderHandlers } from "react-native";
|
|
9
|
+
import { Position } from "../@types";
|
|
10
|
+
type Props = {
|
|
11
|
+
initialScale: number;
|
|
12
|
+
initialTranslate: Position;
|
|
13
|
+
onZoom: (isZoomed: boolean) => void;
|
|
14
|
+
doubleTapToZoomEnabled: boolean;
|
|
15
|
+
onLongPress: () => void;
|
|
16
|
+
delayLongPress: number;
|
|
17
|
+
};
|
|
18
|
+
declare const usePanResponder: ({ initialScale, initialTranslate, onZoom, doubleTapToZoomEnabled, onLongPress, delayLongPress, }: Props) => Readonly<[GestureResponderHandlers, Animated.Value, Animated.ValueXY]>;
|
|
19
|
+
export default usePanResponder;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useMemo, useEffect } from "react";
|
|
9
|
+
import { Animated, Dimensions, } from "react-native";
|
|
10
|
+
import { createPanResponder, getDistanceBetweenTouches, getImageTranslate, getImageDimensionsByTranslate, } from "../utils";
|
|
11
|
+
const SCREEN = Dimensions.get("window");
|
|
12
|
+
const SCREEN_WIDTH = SCREEN.width;
|
|
13
|
+
const SCREEN_HEIGHT = SCREEN.height;
|
|
14
|
+
const MIN_DIMENSION = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
15
|
+
const SCALE_MAX = 2;
|
|
16
|
+
const DOUBLE_TAP_DELAY = 300;
|
|
17
|
+
const OUT_BOUND_MULTIPLIER = 0.75;
|
|
18
|
+
const usePanResponder = ({ initialScale, initialTranslate, onZoom, doubleTapToZoomEnabled, onLongPress, delayLongPress, }) => {
|
|
19
|
+
let numberInitialTouches = 1;
|
|
20
|
+
let initialTouches = [];
|
|
21
|
+
let currentScale = initialScale;
|
|
22
|
+
let currentTranslate = initialTranslate;
|
|
23
|
+
let tmpScale = 0;
|
|
24
|
+
let tmpTranslate = null;
|
|
25
|
+
let isDoubleTapPerformed = false;
|
|
26
|
+
let lastTapTS = null;
|
|
27
|
+
let longPressHandlerRef = null;
|
|
28
|
+
const meaningfulShift = MIN_DIMENSION * 0.01;
|
|
29
|
+
const scaleValue = new Animated.Value(initialScale);
|
|
30
|
+
const translateValue = new Animated.ValueXY(initialTranslate);
|
|
31
|
+
const imageDimensions = getImageDimensionsByTranslate(initialTranslate, SCREEN);
|
|
32
|
+
const getBounds = (scale) => {
|
|
33
|
+
const scaledImageDimensions = {
|
|
34
|
+
width: imageDimensions.width * scale,
|
|
35
|
+
height: imageDimensions.height * scale,
|
|
36
|
+
};
|
|
37
|
+
const translateDelta = getImageTranslate(scaledImageDimensions, SCREEN);
|
|
38
|
+
const left = initialTranslate.x - translateDelta.x;
|
|
39
|
+
const right = left - (scaledImageDimensions.width - SCREEN.width);
|
|
40
|
+
const top = initialTranslate.y - translateDelta.y;
|
|
41
|
+
const bottom = top - (scaledImageDimensions.height - SCREEN.height);
|
|
42
|
+
return [top, left, bottom, right];
|
|
43
|
+
};
|
|
44
|
+
const getTranslateInBounds = (translate, scale) => {
|
|
45
|
+
const inBoundTranslate = { x: translate.x, y: translate.y };
|
|
46
|
+
const [topBound, leftBound, bottomBound, rightBound] = getBounds(scale);
|
|
47
|
+
if (translate.x > leftBound) {
|
|
48
|
+
inBoundTranslate.x = leftBound;
|
|
49
|
+
}
|
|
50
|
+
else if (translate.x < rightBound) {
|
|
51
|
+
inBoundTranslate.x = rightBound;
|
|
52
|
+
}
|
|
53
|
+
if (translate.y > topBound) {
|
|
54
|
+
inBoundTranslate.y = topBound;
|
|
55
|
+
}
|
|
56
|
+
else if (translate.y < bottomBound) {
|
|
57
|
+
inBoundTranslate.y = bottomBound;
|
|
58
|
+
}
|
|
59
|
+
return inBoundTranslate;
|
|
60
|
+
};
|
|
61
|
+
const fitsScreenByWidth = () => imageDimensions.width * currentScale < SCREEN_WIDTH;
|
|
62
|
+
const fitsScreenByHeight = () => imageDimensions.height * currentScale < SCREEN_HEIGHT;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
scaleValue.addListener(({ value }) => {
|
|
65
|
+
if (typeof onZoom === "function") {
|
|
66
|
+
onZoom(value !== initialScale);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return () => scaleValue.removeAllListeners();
|
|
70
|
+
});
|
|
71
|
+
const cancelLongPressHandle = () => {
|
|
72
|
+
longPressHandlerRef && clearTimeout(longPressHandlerRef);
|
|
73
|
+
};
|
|
74
|
+
const handlers = {
|
|
75
|
+
onGrant: (_, gestureState) => {
|
|
76
|
+
numberInitialTouches = gestureState.numberActiveTouches;
|
|
77
|
+
if (gestureState.numberActiveTouches > 1)
|
|
78
|
+
return;
|
|
79
|
+
longPressHandlerRef = setTimeout(onLongPress, delayLongPress);
|
|
80
|
+
},
|
|
81
|
+
onStart: (event, gestureState) => {
|
|
82
|
+
initialTouches = event.nativeEvent.touches;
|
|
83
|
+
numberInitialTouches = gestureState.numberActiveTouches;
|
|
84
|
+
if (gestureState.numberActiveTouches > 1)
|
|
85
|
+
return;
|
|
86
|
+
const tapTS = Date.now();
|
|
87
|
+
// Handle double tap event by calculating diff between first and second taps timestamps
|
|
88
|
+
isDoubleTapPerformed = Boolean(lastTapTS && tapTS - lastTapTS < DOUBLE_TAP_DELAY);
|
|
89
|
+
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
|
|
90
|
+
const isScaled = currentTranslate.x !== initialTranslate.x; // currentScale !== initialScale;
|
|
91
|
+
const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0];
|
|
92
|
+
const targetScale = SCALE_MAX;
|
|
93
|
+
const nextScale = isScaled ? initialScale : targetScale;
|
|
94
|
+
const nextTranslate = isScaled
|
|
95
|
+
? initialTranslate
|
|
96
|
+
: getTranslateInBounds({
|
|
97
|
+
x: initialTranslate.x +
|
|
98
|
+
(SCREEN_WIDTH / 2 - touchX) * (targetScale / currentScale),
|
|
99
|
+
y: initialTranslate.y +
|
|
100
|
+
(SCREEN_HEIGHT / 2 - touchY) * (targetScale / currentScale),
|
|
101
|
+
}, targetScale);
|
|
102
|
+
onZoom(!isScaled);
|
|
103
|
+
Animated.parallel([
|
|
104
|
+
Animated.timing(translateValue.x, {
|
|
105
|
+
toValue: nextTranslate.x,
|
|
106
|
+
duration: 300,
|
|
107
|
+
useNativeDriver: true,
|
|
108
|
+
}),
|
|
109
|
+
Animated.timing(translateValue.y, {
|
|
110
|
+
toValue: nextTranslate.y,
|
|
111
|
+
duration: 300,
|
|
112
|
+
useNativeDriver: true,
|
|
113
|
+
}),
|
|
114
|
+
Animated.timing(scaleValue, {
|
|
115
|
+
toValue: nextScale,
|
|
116
|
+
duration: 300,
|
|
117
|
+
useNativeDriver: true,
|
|
118
|
+
}),
|
|
119
|
+
], { stopTogether: false }).start(() => {
|
|
120
|
+
currentScale = nextScale;
|
|
121
|
+
currentTranslate = nextTranslate;
|
|
122
|
+
});
|
|
123
|
+
lastTapTS = null;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
lastTapTS = Date.now();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
onMove: (event, gestureState) => {
|
|
130
|
+
const { dx, dy } = gestureState;
|
|
131
|
+
if (Math.abs(dx) >= meaningfulShift || Math.abs(dy) >= meaningfulShift) {
|
|
132
|
+
cancelLongPressHandle();
|
|
133
|
+
}
|
|
134
|
+
// Don't need to handle move because double tap in progress (was handled in onStart)
|
|
135
|
+
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
|
|
136
|
+
cancelLongPressHandle();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (numberInitialTouches === 1 &&
|
|
140
|
+
gestureState.numberActiveTouches === 2) {
|
|
141
|
+
numberInitialTouches = 2;
|
|
142
|
+
initialTouches = event.nativeEvent.touches;
|
|
143
|
+
}
|
|
144
|
+
const isTapGesture = numberInitialTouches == 1 && gestureState.numberActiveTouches === 1;
|
|
145
|
+
const isPinchGesture = numberInitialTouches === 2 && gestureState.numberActiveTouches === 2;
|
|
146
|
+
if (isPinchGesture) {
|
|
147
|
+
cancelLongPressHandle();
|
|
148
|
+
const initialDistance = getDistanceBetweenTouches(initialTouches);
|
|
149
|
+
const currentDistance = getDistanceBetweenTouches(event.nativeEvent.touches);
|
|
150
|
+
let nextScale = (currentDistance / initialDistance) * currentScale;
|
|
151
|
+
/**
|
|
152
|
+
* In case image is scaling smaller than initial size ->
|
|
153
|
+
* slow down this transition by applying OUT_BOUND_MULTIPLIER
|
|
154
|
+
*/
|
|
155
|
+
if (nextScale < initialScale) {
|
|
156
|
+
nextScale =
|
|
157
|
+
nextScale + (initialScale - nextScale) * OUT_BOUND_MULTIPLIER;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* In case image is scaling down -> move it in direction of initial position
|
|
161
|
+
*/
|
|
162
|
+
if (currentScale > initialScale && currentScale > nextScale) {
|
|
163
|
+
const k = (currentScale - initialScale) / (currentScale - nextScale);
|
|
164
|
+
const nextTranslateX = nextScale < initialScale
|
|
165
|
+
? initialTranslate.x
|
|
166
|
+
: currentTranslate.x -
|
|
167
|
+
(currentTranslate.x - initialTranslate.x) / k;
|
|
168
|
+
const nextTranslateY = nextScale < initialScale
|
|
169
|
+
? initialTranslate.y
|
|
170
|
+
: currentTranslate.y -
|
|
171
|
+
(currentTranslate.y - initialTranslate.y) / k;
|
|
172
|
+
translateValue.x.setValue(nextTranslateX);
|
|
173
|
+
translateValue.y.setValue(nextTranslateY);
|
|
174
|
+
tmpTranslate = { x: nextTranslateX, y: nextTranslateY };
|
|
175
|
+
}
|
|
176
|
+
scaleValue.setValue(nextScale);
|
|
177
|
+
tmpScale = nextScale;
|
|
178
|
+
}
|
|
179
|
+
if (isTapGesture && currentScale > initialScale) {
|
|
180
|
+
const { x, y } = currentTranslate;
|
|
181
|
+
const { dx, dy } = gestureState;
|
|
182
|
+
const [topBound, leftBound, bottomBound, rightBound] = getBounds(currentScale);
|
|
183
|
+
let nextTranslateX = x + dx;
|
|
184
|
+
let nextTranslateY = y + dy;
|
|
185
|
+
if (nextTranslateX > leftBound) {
|
|
186
|
+
nextTranslateX =
|
|
187
|
+
nextTranslateX -
|
|
188
|
+
(nextTranslateX - leftBound) * OUT_BOUND_MULTIPLIER;
|
|
189
|
+
}
|
|
190
|
+
if (nextTranslateX < rightBound) {
|
|
191
|
+
nextTranslateX =
|
|
192
|
+
nextTranslateX -
|
|
193
|
+
(nextTranslateX - rightBound) * OUT_BOUND_MULTIPLIER;
|
|
194
|
+
}
|
|
195
|
+
if (nextTranslateY > topBound) {
|
|
196
|
+
nextTranslateY =
|
|
197
|
+
nextTranslateY - (nextTranslateY - topBound) * OUT_BOUND_MULTIPLIER;
|
|
198
|
+
}
|
|
199
|
+
if (nextTranslateY < bottomBound) {
|
|
200
|
+
nextTranslateY =
|
|
201
|
+
nextTranslateY -
|
|
202
|
+
(nextTranslateY - bottomBound) * OUT_BOUND_MULTIPLIER;
|
|
203
|
+
}
|
|
204
|
+
if (fitsScreenByWidth()) {
|
|
205
|
+
nextTranslateX = x;
|
|
206
|
+
}
|
|
207
|
+
if (fitsScreenByHeight()) {
|
|
208
|
+
nextTranslateY = y;
|
|
209
|
+
}
|
|
210
|
+
translateValue.x.setValue(nextTranslateX);
|
|
211
|
+
translateValue.y.setValue(nextTranslateY);
|
|
212
|
+
tmpTranslate = { x: nextTranslateX, y: nextTranslateY };
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onRelease: () => {
|
|
216
|
+
cancelLongPressHandle();
|
|
217
|
+
if (isDoubleTapPerformed) {
|
|
218
|
+
isDoubleTapPerformed = false;
|
|
219
|
+
}
|
|
220
|
+
if (tmpScale > 0) {
|
|
221
|
+
if (tmpScale < initialScale || tmpScale > SCALE_MAX) {
|
|
222
|
+
tmpScale = tmpScale < initialScale ? initialScale : SCALE_MAX;
|
|
223
|
+
Animated.timing(scaleValue, {
|
|
224
|
+
toValue: tmpScale,
|
|
225
|
+
duration: 100,
|
|
226
|
+
useNativeDriver: true,
|
|
227
|
+
}).start();
|
|
228
|
+
}
|
|
229
|
+
currentScale = tmpScale;
|
|
230
|
+
tmpScale = 0;
|
|
231
|
+
}
|
|
232
|
+
if (tmpTranslate) {
|
|
233
|
+
const { x, y } = tmpTranslate;
|
|
234
|
+
const [topBound, leftBound, bottomBound, rightBound] = getBounds(currentScale);
|
|
235
|
+
let nextTranslateX = x;
|
|
236
|
+
let nextTranslateY = y;
|
|
237
|
+
if (!fitsScreenByWidth()) {
|
|
238
|
+
if (nextTranslateX > leftBound) {
|
|
239
|
+
nextTranslateX = leftBound;
|
|
240
|
+
}
|
|
241
|
+
else if (nextTranslateX < rightBound) {
|
|
242
|
+
nextTranslateX = rightBound;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!fitsScreenByHeight()) {
|
|
246
|
+
if (nextTranslateY > topBound) {
|
|
247
|
+
nextTranslateY = topBound;
|
|
248
|
+
}
|
|
249
|
+
else if (nextTranslateY < bottomBound) {
|
|
250
|
+
nextTranslateY = bottomBound;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
Animated.parallel([
|
|
254
|
+
Animated.timing(translateValue.x, {
|
|
255
|
+
toValue: nextTranslateX,
|
|
256
|
+
duration: 100,
|
|
257
|
+
useNativeDriver: true,
|
|
258
|
+
}),
|
|
259
|
+
Animated.timing(translateValue.y, {
|
|
260
|
+
toValue: nextTranslateY,
|
|
261
|
+
duration: 100,
|
|
262
|
+
useNativeDriver: true,
|
|
263
|
+
}),
|
|
264
|
+
]).start();
|
|
265
|
+
currentTranslate = { x: nextTranslateX, y: nextTranslateY };
|
|
266
|
+
tmpTranslate = null;
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
const panResponder = useMemo(() => createPanResponder(handlers), [handlers]);
|
|
271
|
+
return [panResponder.panHandlers, scaleValue, translateValue];
|
|
272
|
+
};
|
|
273
|
+
export default usePanResponder;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
declare const useRequestClose: (onRequestClose: () => void) => readonly [number, () => void];
|
|
9
|
+
export default useRequestClose;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
const useRequestClose = (onRequestClose) => {
|
|
10
|
+
const [opacity, setOpacity] = useState(1);
|
|
11
|
+
return [
|
|
12
|
+
opacity,
|
|
13
|
+
() => {
|
|
14
|
+
setOpacity(0);
|
|
15
|
+
onRequestClose();
|
|
16
|
+
setTimeout(() => setOpacity(1), 0);
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
};
|
|
20
|
+
export default useRequestClose;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) JOB TODAY S.A. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
import { Animated, GestureResponderEvent, PanResponderGestureState, PanResponderInstance, NativeTouchEvent } from "react-native";
|
|
9
|
+
import { Dimensions, Position } from "./@types";
|
|
10
|
+
type CacheStorageItem = {
|
|
11
|
+
key: string;
|
|
12
|
+
value: any;
|
|
13
|
+
};
|
|
14
|
+
export declare const createCache: (cacheSize: number) => {
|
|
15
|
+
_storage: CacheStorageItem[];
|
|
16
|
+
get(key: string): any;
|
|
17
|
+
set(key: string, value: any): void;
|
|
18
|
+
};
|
|
19
|
+
export declare const splitArrayIntoBatches: (arr: any[], batchSize: number) => any[];
|
|
20
|
+
export declare const getImageTransform: (image: Dimensions | null, screen: Dimensions) => readonly [] | readonly [{
|
|
21
|
+
readonly x: number;
|
|
22
|
+
readonly y: number;
|
|
23
|
+
}, number];
|
|
24
|
+
export declare const getImageStyles: (image: Dimensions | null, translate: Animated.ValueXY, scale?: Animated.Value) => {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
transform?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
transform: any[];
|
|
32
|
+
};
|
|
33
|
+
export declare const getImageTranslate: (image: Dimensions, screen: Dimensions) => Position;
|
|
34
|
+
export declare const getImageDimensionsByTranslate: (translate: Position, screen: Dimensions) => Dimensions;
|
|
35
|
+
export declare const getImageTranslateForScale: (currentTranslate: Position, targetScale: number, screen: Dimensions) => Position;
|
|
36
|
+
type HandlerType = (event: GestureResponderEvent, state: PanResponderGestureState) => void;
|
|
37
|
+
type PanResponderProps = {
|
|
38
|
+
onGrant: HandlerType;
|
|
39
|
+
onStart?: HandlerType;
|
|
40
|
+
onMove: HandlerType;
|
|
41
|
+
onRelease?: HandlerType;
|
|
42
|
+
onTerminate?: HandlerType;
|
|
43
|
+
};
|
|
44
|
+
export declare const createPanResponder: ({ onGrant, onStart, onMove, onRelease, onTerminate, }: PanResponderProps) => PanResponderInstance;
|
|
45
|
+
export declare const getDistanceBetweenTouches: (touches: NativeTouchEvent[]) => number;
|
|
46
|
+
export {};
|