Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add onPress and hide bars on press #193

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/ImageViewing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
VirtualizedList,
ModalProps,
Modal,
TouchableWithoutFeedback,
} from "react-native";

import ImageItem from "./components/ImageItem/ImageItem";
Expand All @@ -33,6 +34,7 @@ type Props = {
visible: boolean;
onRequestClose: () => void;
onLongPress?: (image: ImageSource) => void;
onPress?: (image: ImageSource) => void;
onImageIndexChange?: (imageIndex: number) => void;
presentationStyle?: ModalProps["presentationStyle"];
animationType?: ModalProps["animationType"];
Expand All @@ -57,6 +59,7 @@ function ImageViewing({
visible,
onRequestClose,
onLongPress = () => {},
onPress = () => {},
onImageIndexChange,
animationType = DEFAULT_ANIMATION_TYPE,
backgroundColor = DEFAULT_BG_COLOR,
Expand All @@ -70,7 +73,7 @@ function ImageViewing({
const imageList = useRef<VirtualizedList<ImageSource>>(null);
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose);
const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN);
const [headerTransform, footerTransform, toggleBarsVisible] =
const [headerTransform, footerTransform, setBarsVisible, toggleBarsVisible] =
useAnimatedComponents();

useEffect(() => {
Expand All @@ -83,11 +86,16 @@ function ImageViewing({
(isScaled: boolean) => {
// @ts-ignore
imageList?.current?.setNativeProps({ scrollEnabled: !isScaled });
toggleBarsVisible(!isScaled);
setBarsVisible(!isScaled);
},
[imageList]
);

const onPressHandler = (src: ImageSource) => {
onPress(src);
toggleBarsVisible();
};

if (!visible) {
return null;
}
Expand All @@ -103,6 +111,7 @@ function ImageViewing({
hardwareAccelerated
>
<StatusBarManager presentationStyle={presentationStyle} />

<View style={[styles.container, { opacity, backgroundColor }]}>
<Animated.View style={[styles.header, { transform: headerTransform }]}>
{typeof HeaderComponent !== "undefined" ? (
Expand Down Expand Up @@ -137,6 +146,7 @@ function ImageViewing({
imageSrc={imageSrc}
onRequestClose={onRequestCloseEnhanced}
onLongPress={onLongPress}
onPress={onPressHandler}
delayLongPress={delayLongPress}
swipeToCloseEnabled={swipeToCloseEnabled}
doubleTapToZoomEnabled={doubleTapToZoomEnabled}
Expand Down
7 changes: 7 additions & 0 deletions src/components/ImageItem/ImageItem.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Props = {
onRequestClose: () => void;
onZoom: (isZoomed: boolean) => void;
onLongPress: (image: ImageSource) => void;
onPress: (image: ImageSource) => void;
delayLongPress: number;
swipeToCloseEnabled?: boolean;
doubleTapToZoomEnabled?: boolean;
Expand All @@ -46,6 +47,7 @@ const ImageItem = ({
onZoom,
onRequestClose,
onLongPress,
onPress,
delayLongPress,
swipeToCloseEnabled = true,
doubleTapToZoomEnabled = true,
Expand Down Expand Up @@ -73,13 +75,18 @@ const ImageItem = ({
onLongPress(imageSrc);
}, [imageSrc, onLongPress]);

const onPressHandler = useCallback(() => {
onPress(imageSrc);
}, [imageSrc, onLongPress]);

const [panHandlers, scaleValue, translateValue] = usePanResponder({
initialScale: scale || 1,
initialTranslate: translate || { x: 0, y: 0 },
onZoom: onZoomPerformed,
doubleTapToZoomEnabled,
onLongPress: onLongPressHandler,
delayLongPress,
onPress: onPressHandler,
});

const imagesStyles = getImageStyles(
Expand Down
21 changes: 13 additions & 8 deletions src/components/ImageItem/ImageItem.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@ declare type Props = {
onRequestClose: () => void;
onZoom: (isZoomed: boolean) => void;
onLongPress: (image: ImageSource) => void;
onPress: (image: ImageSource) => void;
delayLongPress: number;
swipeToCloseEnabled?: boolean;
doubleTapToZoomEnabled?: boolean;
};

declare const _default: React.MemoExoticComponent<({
imageSrc,
onZoom,
onRequestClose,
onLongPress,
delayLongPress,
swipeToCloseEnabled,
}: Props) => JSX.Element>;
declare const _default: React.MemoExoticComponent<
({
imageSrc,
onZoom,
onRequestClose,
onLongPress,
delayLongPress,
currentImageIndex,
onPress,
swipeToCloseEnabled,
}: Props) => JSX.Element
>;

export default _default;
14 changes: 13 additions & 1 deletion src/components/ImageItem/ImageItem.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Props = {
onRequestClose: () => void;
onZoom: (scaled: boolean) => void;
onLongPress: (image: ImageSource) => void;
onPress: (image: ImageSource) => void;
delayLongPress: number;
swipeToCloseEnabled?: boolean;
doubleTapToZoomEnabled?: boolean;
Expand All @@ -48,6 +49,7 @@ const ImageItem = ({
onZoom,
onRequestClose,
onLongPress,
onPress,
delayLongPress,
swipeToCloseEnabled = true,
doubleTapToZoomEnabled = true,
Expand All @@ -56,7 +58,17 @@ const ImageItem = ({
const [loaded, setLoaded] = useState(false);
const [scaled, setScaled] = useState(false);
const imageDimensions = useImageDimensions(imageSrc);
const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN);

const onPressHandler = useCallback(() => {
onPress(imageSrc);
}, [imageSrc, onPress]);

const handleDoubleTap = useDoubleTapToZoom(
scrollViewRef,
scaled,
SCREEN,
onPressHandler
);

const [translate, scale] = getImageTransform(imageDimensions, SCREEN);
const scrollValueY = new Animated.Value(0);
Expand Down
44 changes: 31 additions & 13 deletions src/hooks/useAnimatedComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*
*/

import { useRef } from "react";
import { Animated } from "react-native";

const INITIAL_POSITION = { x: 0, y: 0 };
Expand All @@ -15,33 +16,50 @@ const ANIMATION_CONFIG = {
};

const useAnimatedComponents = () => {
const headerTranslate = new Animated.ValueXY(INITIAL_POSITION);
const footerTranslate = new Animated.ValueXY(INITIAL_POSITION);
const headerTranslate = useRef(new Animated.ValueXY(INITIAL_POSITION));
const footerTranslate = useRef(new Animated.ValueXY(INITIAL_POSITION));

const toggleVisible = (isVisible: boolean) => {
if (isVisible) {
const isVisible = useRef(true);

const setIsVisible = (shouldMakeVisible: boolean) => {
if (shouldMakeVisible) {
Animated.parallel([
Animated.timing(headerTranslate.y, { ...ANIMATION_CONFIG, toValue: 0 }),
Animated.timing(footerTranslate.y, { ...ANIMATION_CONFIG, toValue: 0 }),
]).start();
Animated.timing(headerTranslate.current.y, {
...ANIMATION_CONFIG,
toValue: 0,
}),
Animated.timing(footerTranslate.current.y, {
...ANIMATION_CONFIG,
toValue: 0,
}),
]).start(() => (isVisible.current = true));
} else {
Animated.parallel([
Animated.timing(headerTranslate.y, {
Animated.timing(headerTranslate.current.y, {
...ANIMATION_CONFIG,
toValue: -300,
}),
Animated.timing(footerTranslate.y, {
Animated.timing(footerTranslate.current.y, {
...ANIMATION_CONFIG,
toValue: 300,
}),
]).start();
]).start(() => (isVisible.current = false));
}
};

const headerTransform = headerTranslate.getTranslateTransform();
const footerTransform = footerTranslate.getTranslateTransform();
const toggleIsVisible = () => {
setIsVisible(!isVisible.current);
};

const headerTransform = headerTranslate.current.getTranslateTransform();
const footerTransform = footerTranslate.current.getTranslateTransform();

return [headerTransform, footerTransform, toggleVisible] as const;
return [
headerTransform,
footerTransform,
setIsVisible,
toggleIsVisible,
] as const;
};

export default useAnimatedComponents;
15 changes: 14 additions & 1 deletion src/hooks/useDoubleTapToZoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,25 @@ import { Dimensions } from "../@types";
const DOUBLE_TAP_DELAY = 300;
let lastTapTS: number | null = null;

let isWaitingToSendSinglePress = false;

/**
* This is iOS only.
* Same functionality for Android implemented inside usePanResponder hook.
*/
function useDoubleTapToZoom(
scrollViewRef: React.RefObject<ScrollView>,
scaled: boolean,
screen: Dimensions
screen: Dimensions,
onPress: () => void
) {
const handleDoubleTap = useCallback(
(event: NativeSyntheticEvent<NativeTouchEvent>) => {
const nowTS = new Date().getTime();
const scrollResponderRef = scrollViewRef?.current?.getScrollResponder();

if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) {
isWaitingToSendSinglePress = false;
const { pageX, pageY } = event.nativeEvent;
let targetX = 0;
let targetY = 0;
Expand All @@ -58,6 +62,15 @@ function useDoubleTapToZoom(
});
} else {
lastTapTS = nowTS;

if (!isWaitingToSendSinglePress) {
isWaitingToSendSinglePress = true;
setTimeout(() => {
if (!isWaitingToSendSinglePress) return;
isWaitingToSendSinglePress = false;
onPress();
}, DOUBLE_TAP_DELAY);
}
}
},
[scaled]
Expand Down
28 changes: 22 additions & 6 deletions src/hooks/usePanResponder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ const SCALE_MAX = 2;
const DOUBLE_TAP_DELAY = 300;
const OUT_BOUND_MULTIPLIER = 0.75;

let isWaitingToSendSinglePress = false;

type Props = {
initialScale: number;
initialTranslate: Position;
onZoom: (isZoomed: boolean) => void;
doubleTapToZoomEnabled: boolean;
onLongPress: () => void;
onPress: () => void;
delayLongPress: number;
};

Expand All @@ -48,6 +51,7 @@ const usePanResponder = ({
onZoom,
doubleTapToZoomEnabled,
onLongPress,
onPress,
delayLongPress,
}: Props): Readonly<
[GestureResponderHandlers, Animated.Value, Animated.ValueXY]
Expand Down Expand Up @@ -152,6 +156,7 @@ const usePanResponder = ({
);

if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
isWaitingToSendSinglePress = false;
const isScaled = currentTranslate.x !== initialTranslate.x; // currentScale !== initialScale;
const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0];
const targetScale = SCALE_MAX;
Expand Down Expand Up @@ -199,6 +204,15 @@ const usePanResponder = ({
lastTapTS = null;
} else {
lastTapTS = Date.now();

if (!isWaitingToSendSinglePress) {
isWaitingToSendSinglePress = true;
setTimeout(() => {
if (!isWaitingToSendSinglePress) return;
isWaitingToSendSinglePress = false;
onPress();
}, DOUBLE_TAP_DELAY);
}
}
},
onMove: (
Expand All @@ -208,11 +222,13 @@ const usePanResponder = ({
const { dx, dy } = gestureState;

if (Math.abs(dx) >= meaningfulShift || Math.abs(dy) >= meaningfulShift) {
isWaitingToSendSinglePress = false;
cancelLongPressHandle();
}

// Don't need to handle move because double tap in progress (was handled in onStart)
if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
isWaitingToSendSinglePress = false;
cancelLongPressHandle();
return;
}
Expand All @@ -232,6 +248,7 @@ const usePanResponder = ({

if (isPinchGesture) {
cancelLongPressHandle();
isWaitingToSendSinglePress = false;

const initialDistance = getDistanceBetweenTouches(initialTouches);
const currentDistance = getDistanceBetweenTouches(
Expand Down Expand Up @@ -280,9 +297,8 @@ const usePanResponder = ({
if (isTapGesture && currentScale > initialScale) {
const { x, y } = currentTranslate;
const { dx, dy } = gestureState;
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
currentScale
);
const [topBound, leftBound, bottomBound, rightBound] =
getBounds(currentScale);

let nextTranslateX = x + dx;
let nextTranslateY = y + dy;
Expand Down Expand Up @@ -324,6 +340,7 @@ const usePanResponder = ({
tmpTranslate = { x: nextTranslateX, y: nextTranslateY };
}
},

onRelease: () => {
cancelLongPressHandle();

Expand All @@ -347,9 +364,8 @@ const usePanResponder = ({

if (tmpTranslate) {
const { x, y } = tmpTranslate;
const [topBound, leftBound, bottomBound, rightBound] = getBounds(
currentScale
);
const [topBound, leftBound, bottomBound, rightBound] =
getBounds(currentScale);

let nextTranslateX = x;
let nextTranslateY = y;
Expand Down