diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js
index 538ca792b233..c92bd7738253 100644
--- a/src/components/ImageView/index.js
+++ b/src/components/ImageView/index.js
@@ -1,11 +1,10 @@
-import React, {PureComponent} from 'react';
+import React, {useState, useEffect, useRef, useCallback} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Image from '../Image';
import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
-import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import FullscreenLoadingIndicator from '../FullscreenLoadingIndicator';
import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback';
import CONST from '../../CONST';
@@ -23,159 +22,93 @@ const propTypes = {
/** image file name */
fileName: PropTypes.string.isRequired,
-
- ...windowDimensionsPropTypes,
};
const defaultProps = {
isAuthTokenRequired: false,
};
-class ImageView extends PureComponent {
- constructor(props) {
- super(props);
- this.scrollableRef = null;
- this.canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
- this.onContainerLayoutChanged = this.onContainerLayoutChanged.bind(this);
- this.onContainerPressIn = this.onContainerPressIn.bind(this);
- this.onContainerPress = this.onContainerPress.bind(this);
- this.imageLoad = this.imageLoad.bind(this);
- this.imageLoadingStart = this.imageLoadingStart.bind(this);
- this.trackMovement = this.trackMovement.bind(this);
- this.trackPointerPosition = this.trackPointerPosition.bind(this);
-
- this.state = {
- isLoading: true,
- containerHeight: 0,
- containerWidth: 0,
- isZoomed: false,
- isDragging: false,
- isMouseDown: false,
- initialScrollLeft: 0,
- initialScrollTop: 0,
- initialX: 0,
- initialY: 0,
- imgWidth: 0,
- imgHeight: 0,
- zoomScale: 0,
- };
- }
-
- componentDidMount() {
- if (this.canUseTouchScreen) {
- return;
- }
-
- document.addEventListener('mousemove', this.trackMovement);
- document.addEventListener('mouseup', this.trackPointerPosition);
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.url === this.props.url || this.state.isLoading) {
- return;
- }
-
- this.imageLoadingStart();
- }
+function ImageView({isAuthTokenRequired, url, fileName}) {
+ const [isLoading, setIsLoading] = useState(true);
+ const [containerHeight, setContainerHeight] = useState(0);
+ const [containerWidth, setContainerWidth] = useState(0);
+ const [isZoomed, setIsZoomed] = useState(false);
+ const [isDragging, setIsDragging] = useState(false);
+ const [isMouseDown, setIsMouseDown] = useState(false);
+ const [initialScrollLeft, setInitialScrollLeft] = useState(0);
+ const [initialScrollTop, setInitialScrollTop] = useState(0);
+ const [initialX, setInitialX] = useState(0);
+ const [initialY, setInitialY] = useState(0);
+ const [imgWidth, setImgWidth] = useState(0);
+ const [imgHeight, setImgHeight] = useState(0);
+ const [zoomScale, setZoomScale] = useState(0);
+ const [zoomDelta, setZoomDelta] = useState({offsetX: 0, offsetY: 0});
+
+ const scrollableRef = useRef(null);
+ const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
- componentWillUnmount() {
- if (this.canUseTouchScreen) {
+ /**
+ * @param {Number} newContainerWidth
+ * @param {Number} newContainerHeight
+ * @param {Number} newImageWidth
+ * @param {Number} newImageHeight
+ */
+ const setScale = (newContainerWidth, newContainerHeight, newImageWidth, newImageHeight) => {
+ if (!newContainerWidth || !newImageWidth || !newContainerHeight || !newImageHeight) {
return;
}
-
- document.removeEventListener('mousemove', this.trackMovement);
- document.removeEventListener('mouseup', this.trackPointerPosition);
- }
+ const newZoomScale = Math.min(newContainerWidth / newImageWidth, newContainerHeight / newImageHeight);
+ setZoomScale(newZoomScale);
+ };
/**
* @param {SyntheticEvent} e
*/
- onContainerLayoutChanged(e) {
+ const onContainerLayoutChanged = (e) => {
const {width, height} = e.nativeEvent.layout;
- this.setScale(width, height, this.state.imgWidth, this.state.imgHeight);
- this.setState({
- containerHeight: height,
- containerWidth: width,
- });
- }
+ setScale(width, height, imgWidth, imgHeight);
- /**
- * @param {SyntheticEvent} e
- */
- onContainerPressIn(e) {
- const {pageX, pageY} = e.nativeEvent;
- this.setState({
- isMouseDown: true,
- initialX: pageX,
- initialY: pageY,
- initialScrollLeft: this.scrollableRef.scrollLeft,
- initialScrollTop: this.scrollableRef.scrollTop,
- });
- }
-
- /**
- * @param {SyntheticEvent} e
- */
- onContainerPress(e) {
- let scrollX;
- let scrollY;
- if (!this.state.isZoomed && !this.state.isDragging) {
- const {offsetX, offsetY} = e.nativeEvent;
-
- // Dividing clicked positions by the zoom scale to get coordinates
- // so that once we zoom we will scroll to the clicked location.
- const delta = this.getScrollOffset(offsetX / this.state.zoomScale, offsetY / this.state.zoomScale);
- scrollX = delta.offsetX;
- scrollY = delta.offsetY;
- }
-
- if (this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
- this.setState({isDragging: false, isMouseDown: false});
- } else {
- // We first zoom and once its done then we scroll to the location the user clicked.
- this.setState(
- (prevState) => ({
- isZoomed: !prevState.isZoomed,
- isMouseDown: false,
- }),
- () => {
- this.scrollableRef.scrollTop = scrollY;
- this.scrollableRef.scrollLeft = scrollX;
- },
- );
- }
- }
+ setContainerHeight(height);
+ setContainerWidth(width);
+ };
/**
* When open image, set image width, height.
* @param {Number} imageWidth
* @param {Number} imageHeight
*/
- setImageRegion(imageWidth, imageHeight) {
+ const setImageRegion = (imageWidth, imageHeight) => {
if (imageHeight <= 0) {
return;
}
-
- this.setScale(this.state.containerWidth, this.state.containerHeight, imageWidth, imageHeight);
- this.setState({
- imgWidth: imageWidth,
- imgHeight: imageHeight,
- });
- }
+ setScale(containerWidth, containerHeight, imageWidth, imageHeight);
+ setImgWidth(imageWidth);
+ setImgHeight(imageHeight);
+ };
+
+ const imageLoadingStart = () => {
+ if (!isLoading) return;
+ setIsLoading(true);
+ setZoomScale(0);
+ setIsZoomed(false);
+ };
+
+ const imageLoad = ({nativeEvent}) => {
+ setImageRegion(nativeEvent.width, nativeEvent.height);
+ setIsLoading(false);
+ };
/**
- * @param {Number} containerWidth
- * @param {Number} containerHeight
- * @param {Number} imageWidth
- * @param {Number} imageHeight
+ * @param {SyntheticEvent} e
*/
- setScale(containerWidth, containerHeight, imageWidth, imageHeight) {
- if (!containerWidth || !imageWidth || !containerHeight || !imageHeight) {
- return;
- }
- const newZoomScale = Math.min(containerWidth / imageWidth, containerHeight / imageHeight);
- this.setState({zoomScale: newZoomScale});
- }
+ const onContainerPressIn = (e) => {
+ const {pageX, pageY} = e.nativeEvent;
+ setIsMouseDown(true);
+ setInitialX(pageX);
+ setInitialY(pageY);
+ setInitialScrollLeft(scrollableRef.current.scrollLeft);
+ setInitialScrollTop(scrollableRef.current.scrollTop);
+ };
/**
* Convert touch point to zoomed point
@@ -183,131 +116,161 @@ class ImageView extends PureComponent {
* @param {Boolean} y y point when click zoom
* @returns {Object} converted touch point
*/
- getScrollOffset(x, y) {
+ const getScrollOffset = (x, y) => {
let offsetX;
let offsetY;
// Container size bigger than clicked position offset
- if (x <= this.state.containerWidth / 2) {
+ if (x <= containerWidth / 2) {
offsetX = 0;
- } else if (x > this.state.containerWidth / 2) {
+ } else if (x > containerWidth / 2) {
// Minus half of container size because we want to be center clicked position
- offsetX = x - this.state.containerWidth / 2;
+ offsetX = x - containerWidth / 2;
}
- if (y <= this.state.containerHeight / 2) {
+ if (y <= containerHeight / 2) {
offsetY = 0;
- } else if (y > this.state.containerHeight / 2) {
+ } else if (y > containerHeight / 2) {
// Minus half of container size because we want to be center clicked position
- offsetY = y - this.state.containerHeight / 2;
+ offsetY = y - containerHeight / 2;
}
return {offsetX, offsetY};
- }
+ };
/**
* @param {SyntheticEvent} e
*/
- trackPointerPosition(e) {
- // Whether the pointer is released inside the ImageView
- const isInsideImageView = this.scrollableRef.contains(e.nativeEvent.target);
-
- if (!isInsideImageView && this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
- this.setState({isDragging: false, isMouseDown: false});
+ const onContainerPress = (e) => {
+ if (!isZoomed && !isDragging) {
+ const {offsetX, offsetY} = e.nativeEvent;
+ // Dividing clicked positions by the zoom scale to get coordinates
+ // so that once we zoom we will scroll to the clicked location.
+ const delta = getScrollOffset(offsetX / zoomScale, offsetY / zoomScale);
+ setZoomDelta(delta);
}
- }
- trackMovement(e) {
- if (!this.state.isZoomed) {
- return;
+ if (isZoomed && isDragging && isMouseDown) {
+ setIsDragging(false);
+ setIsMouseDown(false);
+ } else {
+ // We first zoom and once its done then we scroll to the location the user clicked.
+ setIsZoomed(!isZoomed);
+ setIsMouseDown(false);
}
+ };
- if (this.state.isDragging && this.state.isMouseDown) {
- const x = e.nativeEvent.x;
- const y = e.nativeEvent.y;
- const moveX = this.state.initialX - x;
- const moveY = this.state.initialY - y;
- this.scrollableRef.scrollLeft = this.state.initialScrollLeft + moveX;
- this.scrollableRef.scrollTop = this.state.initialScrollTop + moveY;
+ /**
+ * @param {SyntheticEvent} e
+ */
+ const trackPointerPosition = useCallback(
+ (e) => {
+ // Whether the pointer is released inside the ImageView
+ const isInsideImageView = scrollableRef.current.contains(e.nativeEvent.target);
+
+ if (!isInsideImageView && isZoomed && isDragging && isMouseDown) {
+ setIsDragging(false);
+ setIsMouseDown(false);
+ }
+ },
+ [isDragging, isMouseDown, isZoomed],
+ );
+
+ const trackMovement = useCallback(
+ (e) => {
+ if (!isZoomed) {
+ return;
+ }
+
+ if (isDragging && isMouseDown) {
+ const x = e.nativeEvent.x;
+ const y = e.nativeEvent.y;
+ const moveX = initialX - x;
+ const moveY = initialY - y;
+ scrollableRef.current.scrollLeft = initialScrollLeft + moveX;
+ scrollableRef.current.scrollTop = initialScrollTop + moveY;
+ }
+
+ setIsDragging(isMouseDown);
+ },
+ [initialScrollLeft, initialScrollTop, initialX, initialY, isDragging, isMouseDown, isZoomed],
+ );
+
+ useEffect(() => {
+ if (!isZoomed || !zoomDelta || !scrollableRef.current) {
+ return;
}
+ scrollableRef.current.scrollLeft = zoomDelta.offsetX;
+ scrollableRef.current.scrollTop = zoomDelta.offsetY;
+ }, [zoomDelta, isZoomed]);
- this.setState((prevState) => ({isDragging: prevState.isMouseDown}));
- }
-
- imageLoad({nativeEvent}) {
- this.setImageRegion(nativeEvent.width, nativeEvent.height);
- this.setState({isLoading: false});
- }
-
- imageLoadingStart() {
- if (this.state.isLoading) {
+ useEffect(() => {
+ if (canUseTouchScreen) {
return;
}
- this.setState({isLoading: true, zoomScale: 0, isZoomed: false});
- }
+ document.addEventListener('mousemove', trackMovement);
+ document.addEventListener('mouseup', trackPointerPosition);
- render() {
- if (this.canUseTouchScreen) {
- return (
-
- 1 ? Image.resizeMode.center : Image.resizeMode.contain}
- onLoadStart={this.imageLoadingStart}
- onLoad={this.imageLoad}
- />
- {this.state.isLoading && }
-
- );
- }
+ return () => {
+ document.removeEventListener('mousemove', trackMovement);
+ document.removeEventListener('mouseup', trackPointerPosition);
+ };
+ }, [canUseTouchScreen, trackMovement, trackPointerPosition]);
+
+ if (canUseTouchScreen) {
return (
(this.scrollableRef = el)}
- onLayout={this.onContainerLayoutChanged}
- style={[styles.imageViewContainer, styles.overflowAuto, styles.pRelative]}
+ style={[styles.imageViewContainer, styles.overflowHidden]}
+ onLayout={onContainerLayoutChanged}
>
- = 1 ? styles.pRelative : styles.pAbsolute),
- ...styles.flex1,
- }}
- onPressIn={this.onContainerPressIn}
- onPress={this.onContainerPress}
- accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGE}
- accessibilityLabel={this.props.fileName}
- >
-
-
-
- {this.state.isLoading && }
+ 1 ? Image.resizeMode.center : Image.resizeMode.contain}
+ onLoadStart={imageLoadingStart}
+ onLoad={imageLoad}
+ />
+ {isLoading && }
);
}
+ return (
+
+ = 1 ? styles.pRelative : styles.pAbsolute),
+ ...styles.flex1,
+ }}
+ onPressIn={onContainerPressIn}
+ onPress={onContainerPress}
+ accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGE}
+ accessibilityLabel={fileName}
+ >
+
+
+
+ {isLoading && }
+
+ );
}
ImageView.propTypes = propTypes;
ImageView.defaultProps = defaultProps;
-export default withWindowDimensions(ImageView);
+ImageView.displayName = 'ImageView';
+
+export default ImageView;