diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js
index 4ae55b49ea0a..d19b3f6ce05d 100644
--- a/src/components/ImageView/index.js
+++ b/src/components/ImageView/index.js
@@ -1,35 +1,145 @@
-import React, {memo} from 'react';
+import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {View, Image} from 'react-native';
-import styles from '../../styles/styles';
+import {View, Image, Pressable} from 'react-native';
+import styles, {getZoomCursorStyle, getZoomSizingStyle} from '../../styles/styles';
+import canUseTouchScreen from '../../libs/canUseTouchscreen';
const propTypes = {
// URL to full-sized image
url: PropTypes.string.isRequired,
};
-const ImageView = props => (
-
-
-
-);
+ ];
+ this.state = {
+ isZoomed: false,
+ isDragging: false,
+ isMouseDown: false,
+ initialScrollLeft: 0,
+ initialScrollTop: 0,
+ initialX: 0,
+ initialY: 0,
+ };
+ }
-ImageView.propTypes = propTypes;
-ImageView.displayName = 'ImageView';
+ componentDidMount() {
+ if (this.canUseTouchScreen) {
+ return;
+ }
+
+ document.addEventListener('mousemove', this.trackMovement.bind(this));
+ }
+
+ componentWillUnmount() {
+ if (this.canUseTouchScreen) {
+ return;
+ }
+
+ document.removeEventListener('mousemove', this.trackMovement.bind(this));
+ }
+
+ trackMovement(e) {
+ if (!this.state.isZoomed) {
+ return;
+ }
+
+ 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;
+ }
-export default memo(ImageView);
+ this.setState(prevState => ({isDragging: prevState.isMouseDown}));
+ }
+
+ render() {
+ if (this.canUseTouchScreen) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ this.scrollableRef = el}
+ style={[
+ ...this.containerStyles,
+ styles.overflowScroll,
+ styles.noScrollbars,
+ ]}
+ >
+ {
+ const {pageX, pageY} = e.nativeEvent;
+ this.setState({
+ isMouseDown: true,
+ initialX: pageX,
+ initialY: pageY,
+ initialScrollLeft: this.scrollableRef.scrollLeft,
+ initialScrollTop: this.scrollableRef.scrollTop,
+ });
+ }}
+ onPress={(e) => {
+ if (this.state.isZoomed && !this.state.isDragging) {
+ const {offsetX, offsetY} = e.nativeEvent;
+ this.scrollableRef.scrollTop = offsetY * 1.5;
+ this.scrollableRef.scrollLeft = offsetX * 1.5;
+ }
+
+ if (this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
+ this.setState({isDragging: false, isMouseDown: false});
+ }
+ }}
+ onPressOut={() => {
+ if (this.state.isDragging) {
+ return;
+ }
+
+ this.setState(prevState => ({
+ isZoomed: !prevState.isZoomed,
+ isMouseDown: false,
+ }));
+ }}
+ >
+
+
+
+ );
+ }
+}
+
+ImageView.propTypes = propTypes;
+export default ImageView;
diff --git a/src/libs/canUseTouchscreen.js b/src/libs/canUseTouchscreen.js
new file mode 100644
index 000000000000..a3310a5d6be1
--- /dev/null
+++ b/src/libs/canUseTouchscreen.js
@@ -0,0 +1,32 @@
+/**
+ * Allows us to identify whether the platform has a touchscreen.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
+ *
+ * @returns {Boolean}
+ */
+function canUseTouchScreen() {
+ let hasTouchScreen = false;
+ if ('maxTouchPoints' in navigator) {
+ hasTouchScreen = navigator.maxTouchPoints > 0;
+ } else if ('msMaxTouchPoints' in navigator) {
+ hasTouchScreen = navigator.msMaxTouchPoints > 0;
+ } else {
+ const mQ = window.matchMedia && matchMedia('(pointer:coarse)');
+ if (mQ && mQ.media === '(pointer:coarse)') {
+ hasTouchScreen = !!mQ.matches;
+ } else if ('orientation' in window) {
+ hasTouchScreen = true; // deprecated, but good fallback
+ } else {
+ // Only as a last resort, fall back to user agent sniffing
+ const UA = navigator.userAgent;
+ hasTouchScreen = (
+ /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA)
+ || /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
+ );
+ }
+ }
+ return hasTouchScreen;
+}
+
+export default canUseTouchScreen;
diff --git a/src/styles/styles.js b/src/styles/styles.js
index c21bffe0ff83..bd35955c8538 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1185,6 +1185,10 @@ const styles = {
fontWeight: fontWeightBold,
fontSize: variables.iouAmountTextSize,
}, 0),
+
+ noScrollbars: {
+ scrollbarWidth: 'none',
+ },
};
const baseCodeTagStyles = {
@@ -1322,6 +1326,32 @@ function getNavigationModalCardStyle(isSmallScreenWidth) {
};
}
+/**
+ * @param {Boolean} isZoomed
+ * @param {Boolean} isDragging
+ * @return {Object}
+ */
+function getZoomCursorStyle(isZoomed, isDragging) {
+ if (!isZoomed) {
+ return {cursor: 'zoom-in'};
+ }
+
+ return {
+ cursor: isDragging ? 'grabbing' : 'zoom-out',
+ };
+}
+
+/**
+ * @param {Boolean} isZoomed
+ * @return {Object}
+ */
+function getZoomSizingStyle(isZoomed) {
+ return {
+ height: isZoomed ? '250%' : '100%',
+ width: isZoomed ? '250%' : '100%',
+ };
+}
+
export default styles;
export {
getSafeAreaPadding,
@@ -1330,4 +1360,6 @@ export {
getNavigationDrawerStyle,
getNavigationDrawerType,
getNavigationModalCardStyle,
+ getZoomCursorStyle,
+ getZoomSizingStyle,
};