Skip to content

Commit

Permalink
Merge pull request Expensify#34202 from tienifr/fix/25113
Browse files Browse the repository at this point in the history
  • Loading branch information
mountiny authored Jan 25, 2024
2 parents 17efef8 + bf30565 commit f78a92a
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 150 deletions.
52 changes: 0 additions & 52 deletions src/components/ImageView/index.native.js

This file was deleted.

39 changes: 39 additions & 0 deletions src/components/ImageView/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import Lightbox from '@components/Lightbox';
import {zoomRangeDefaultProps} from '@components/MultiGestureCanvas/propTypes';
import type {ImageViewProps} from './types';

function ImageView({
isAuthTokenRequired = false,
url,
onScaleChanged,
onPress,
style,
zoomRange = zoomRangeDefaultProps.zoomRange,
onError,
isUsedInCarousel = false,
isSingleCarouselItem = false,
carouselItemIndex = 0,
carouselActiveItemIndex = 0,
}: ImageViewProps) {
const hasSiblingCarouselItems = isUsedInCarousel && !isSingleCarouselItem;

return (
<Lightbox
source={url}
zoomRange={zoomRange}
isAuthTokenRequired={isAuthTokenRequired}
onScaleChanged={onScaleChanged}
onPress={onPress}
onError={onError}
index={carouselItemIndex}
activeIndex={carouselActiveItemIndex}
hasSiblingCarouselItems={hasSiblingCarouselItems}
style={style}
/>
);
}

ImageView.displayName = 'ImageView';

export default ImageView;
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import type {SyntheticEvent} from 'react';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import type {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent} from 'react-native';
import {View} from 'react-native';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Image from '@components/Image';
import RESIZE_MODES from '@components/Image/resizeModes';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import CONST from '@src/CONST';
import {imageViewDefaultProps, imageViewPropTypes} from './propTypes';
import viewRef from '@src/types/utils/viewRef';
import type {ImageLoadNativeEventData, ImageViewProps} from './types';

function ImageView({isAuthTokenRequired, url, fileName, onError}) {
type ZoomDelta = {offsetX: number; offsetY: number};

function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageViewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [isLoading, setIsLoading] = useState(true);
Expand All @@ -25,29 +31,20 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
const [imgWidth, setImgWidth] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
const [zoomScale, setZoomScale] = useState(0);
const [zoomDelta, setZoomDelta] = useState({offsetX: 0, offsetY: 0});
const [zoomDelta, setZoomDelta] = useState<ZoomDelta>();

const scrollableRef = useRef(null);
const scrollableRef = useRef<HTMLDivElement>(null);
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();

/**
* @param {Number} newContainerWidth
* @param {Number} newContainerHeight
* @param {Number} newImageWidth
* @param {Number} newImageHeight
*/
const setScale = (newContainerWidth, newContainerHeight, newImageWidth, newImageHeight) => {
const setScale = (newContainerWidth: number, newContainerHeight: number, newImageWidth: number, newImageHeight: number) => {
if (!newContainerWidth || !newImageWidth || !newContainerHeight || !newImageHeight) {
return;
}
const newZoomScale = Math.min(newContainerWidth / newImageWidth, newContainerHeight / newImageHeight);
setZoomScale(newZoomScale);
};

/**
* @param {SyntheticEvent} e
*/
const onContainerLayoutChanged = (e) => {
const onContainerLayoutChanged = (e: LayoutChangeEvent) => {
const {width, height} = e.nativeEvent.layout;
setScale(width, height, imgWidth, imgHeight);

Expand All @@ -57,10 +54,8 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {

/**
* When open image, set image width, height.
* @param {Number} imageWidth
* @param {Number} imageHeight
*/
const setImageRegion = (imageWidth, imageHeight) => {
const setImageRegion = (imageWidth: number, imageHeight: number) => {
if (imageHeight <= 0) {
return;
}
Expand All @@ -78,32 +73,29 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
setIsZoomed(false);
};

const imageLoad = ({nativeEvent}) => {
const imageLoad = ({nativeEvent}: NativeSyntheticEvent<ImageLoadNativeEventData>) => {
setImageRegion(nativeEvent.width, nativeEvent.height);
setIsLoading(false);
};

/**
* @param {SyntheticEvent} e
*/
const onContainerPressIn = (e) => {
const onContainerPressIn = (e: GestureResponderEvent) => {
const {pageX, pageY} = e.nativeEvent;
setIsMouseDown(true);
setInitialX(pageX);
setInitialY(pageY);
setInitialScrollLeft(scrollableRef.current.scrollLeft);
setInitialScrollTop(scrollableRef.current.scrollTop);
setInitialScrollLeft(scrollableRef.current?.scrollLeft ?? 0);
setInitialScrollTop(scrollableRef.current?.scrollTop ?? 0);
};

/**
* Convert touch point to zoomed point
* @param {Boolean} x x point when click zoom
* @param {Boolean} y y point when click zoom
* @returns {Object} converted touch point
* @param x point when click zoom
* @param y point when click zoom
* @returns converted touch point
*/
const getScrollOffset = (x, y) => {
let offsetX;
let offsetY;
const getScrollOffset = (x: number, y: number) => {
let offsetX = 0;
let offsetY = 0;

// Container size bigger than clicked position offset
if (x <= containerWidth / 2) {
Expand All @@ -121,12 +113,9 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
return {offsetX, offsetY};
};

/**
* @param {SyntheticEvent} e
*/
const onContainerPress = (e) => {
const onContainerPress = (e?: GestureResponderEvent | KeyboardEvent | SyntheticEvent<Element, PointerEvent>) => {
if (!isZoomed && !isDragging) {
if (e.nativeEvent) {
if (e && 'nativeEvent' in e && e.nativeEvent instanceof PointerEvent) {
const {offsetX, offsetY} = e.nativeEvent;

// Dividing clicked positions by the zoom scale to get coordinates
Expand All @@ -148,13 +137,10 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
}
};

/**
* @param {SyntheticEvent} e
*/
const trackPointerPosition = useCallback(
(e) => {
(event: MouseEvent) => {
// Whether the pointer is released inside the ImageView
const isInsideImageView = scrollableRef.current.contains(e.nativeEvent.target);
const isInsideImageView = scrollableRef.current?.contains(event.target as Node);

if (!isInsideImageView && isZoomed && isDragging && isMouseDown) {
setIsDragging(false);
Expand All @@ -165,14 +151,14 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
);

const trackMovement = useCallback(
(e) => {
(event: MouseEvent) => {
if (!isZoomed) {
return;
}

if (isDragging && isMouseDown) {
const x = e.nativeEvent.x;
const y = e.nativeEvent.y;
if (isDragging && isMouseDown && scrollableRef.current) {
const x = event.x;
const y = event.y;
const moveX = initialX - x;
const moveY = initialY - y;
scrollableRef.current.scrollLeft = initialScrollLeft + moveX;
Expand Down Expand Up @@ -218,7 +204,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
style={isLoading || zoomScale === 0 ? undefined : [styles.w100, styles.h100]}
// When Image dimensions are lower than the container boundary(zoomscale <= 1), use `contain` to render the image with natural dimensions.
// Both `center` and `contain` keeps the image centered on both x and y axis.
resizeMode={zoomScale > 1 ? Image.resizeMode.center : Image.resizeMode.contain}
resizeMode={zoomScale > 1 ? RESIZE_MODES.center : RESIZE_MODES.contain}
onLoadStart={imageLoadingStart}
onLoad={imageLoad}
onError={onError}
Expand All @@ -229,7 +215,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
}
return (
<View
ref={scrollableRef}
ref={viewRef(scrollableRef)}
onLayout={onContainerLayoutChanged}
style={[styles.imageViewContainer, styles.overflowAuto, styles.pRelative]}
>
Expand All @@ -249,7 +235,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
source={{uri: url}}
isAuthTokenRequired={isAuthTokenRequired}
style={[styles.h100, styles.w100]}
resizeMode={Image.resizeMode.contain}
resizeMode={RESIZE_MODES.contain}
onLoadStart={imageLoadingStart}
onLoad={imageLoad}
onError={onError}
Expand All @@ -261,8 +247,6 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
);
}

ImageView.propTypes = imageViewPropTypes;
ImageView.defaultProps = imageViewDefaultProps;
ImageView.displayName = 'ImageView';

export default ImageView;
46 changes: 0 additions & 46 deletions src/components/ImageView/propTypes.js

This file was deleted.

47 changes: 47 additions & 0 deletions src/components/ImageView/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {StyleProp, ViewStyle} from 'react-native';
import type ZoomRange from '@components/MultiGestureCanvas/types';

type ImageViewProps = {
/** Whether source url requires authentication */
isAuthTokenRequired?: boolean;

/** Handles scale changed event in image zoom component. Used on native only */
onScaleChanged: (scale: number) => void;

/** URL to full-sized image */
url: string;

/** image file name */
fileName: string;

/** Handles errors while displaying the image */
onError?: () => void;

/** Whether this AttachmentView is shown as part of a AttachmentCarousel */
isUsedInCarousel?: boolean;

/** When "isUsedInCarousel" is set to true, determines whether there is only one item in the carousel */
isSingleCarouselItem?: boolean;

/** The index of the carousel item */
carouselItemIndex?: number;

/** The index of the currently active carousel item */
carouselActiveItemIndex?: number;

/** Function for handle on press */
onPress?: () => void;

/** Additional styles to add to the component */
style?: StyleProp<ViewStyle>;

/** Range of zoom that can be applied to the content by pinching or double tapping. */
zoomRange?: ZoomRange;
};

type ImageLoadNativeEventData = {
width: number;
height: number;
};

export type {ImageViewProps, ImageLoadNativeEventData};
Loading

0 comments on commit f78a92a

Please sign in to comment.