Plt 5991 pinch zoom (#414)
* Rename and get correct image URL * Add pinch and zoom * Fix pinch and zoom on device * Fix dismiss drag and landscape
This commit is contained in:
parent
5c5a4f5102
commit
eb99aac45c
5 changed files with 428 additions and 34 deletions
|
|
@ -18,7 +18,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
import * as Utils from 'mattermost-redux/utils/file_utils.js';
|
||||
|
||||
import FileAttachmentIcon from './file_attachment_icon';
|
||||
import FileAttachmentPreview from './file_attachment_preview';
|
||||
import FileAttachmentImage from './file_attachment_image';
|
||||
|
||||
export default class FileAttachment extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -61,7 +61,7 @@ export default class FileAttachment extends PureComponent {
|
|||
let fileAttachmentComponent;
|
||||
if (file.has_preview_image) {
|
||||
fileAttachmentComponent = (
|
||||
<FileAttachmentPreview
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={this.props.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,23 @@ import imageIcon from 'assets/images/icons/image.png';
|
|||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class FileAttachmentPreview extends PureComponent {
|
||||
const IMAGE_SIZE = {
|
||||
Fullsize: 'fullsize',
|
||||
Preview: 'preview',
|
||||
Thumbnail: 'thumbnail'
|
||||
};
|
||||
|
||||
export default class FileAttachmentImage extends PureComponent {
|
||||
static propTypes = {
|
||||
addFileToFetchCache: PropTypes.func.isRequired,
|
||||
fetchCache: PropTypes.object.isRequired,
|
||||
file: PropTypes.object,
|
||||
imageHeight: PropTypes.number,
|
||||
imageSize: PropTypes.oneOf([
|
||||
IMAGE_SIZE.Fullsize,
|
||||
IMAGE_SIZE.Preview,
|
||||
IMAGE_SIZE.Thumbnail
|
||||
]),
|
||||
imageWidth: PropTypes.number,
|
||||
resizeMode: PropTypes.string,
|
||||
resizeMethod: PropTypes.string,
|
||||
|
|
@ -36,6 +47,7 @@ export default class FileAttachmentPreview extends PureComponent {
|
|||
static defaultProps = {
|
||||
fadeInOnLoad: false,
|
||||
imageHeight: 100,
|
||||
imageSize: IMAGE_SIZE.Thumbnail,
|
||||
imageWidth: 100,
|
||||
loading: false,
|
||||
resizeMode: 'cover',
|
||||
|
|
@ -74,7 +86,7 @@ export default class FileAttachmentPreview extends PureComponent {
|
|||
toValue: 1,
|
||||
duration: 300
|
||||
}).start(() => {
|
||||
this.props.addFileToFetchCache(Client.getFilePreviewUrl(this.props.file.id, this.state.timestamp));
|
||||
this.props.addFileToFetchCache(this.handleGetImageURL());
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -84,6 +96,20 @@ export default class FileAttachmentPreview extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleGetImageURL = () => {
|
||||
const {file, imageSize} = this.props;
|
||||
|
||||
switch (imageSize) {
|
||||
case IMAGE_SIZE.Fullsize:
|
||||
return Client.getFileUrl(file.id, this.state.timestamp);
|
||||
case IMAGE_SIZE.Preview:
|
||||
return Client.getFilePreviewUrl(file.id, this.state.timestamp);
|
||||
case IMAGE_SIZE.Thumbnail:
|
||||
default:
|
||||
return Client.getFileThumbnailUrl(file.id, this.state.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fetchCache,
|
||||
|
|
@ -97,12 +123,16 @@ export default class FileAttachmentPreview extends PureComponent {
|
|||
wrapperWidth
|
||||
} = this.props;
|
||||
|
||||
let source = file.id ? {uri: Client.getFilePreviewUrl(file.id, this.state.timestamp)} : {};
|
||||
let source = {};
|
||||
|
||||
if (this.state.retry === 4) {
|
||||
source = imageIcon;
|
||||
} else if (file.id) {
|
||||
source = {uri: this.handleGetImageURL()};
|
||||
}
|
||||
|
||||
const isInFetchCache = fetchCache[source.uri];
|
||||
|
||||
const imageComponentLoaders = {
|
||||
onError: isInFetchCache ? null : this.handleLoadError,
|
||||
onLoadStart: isInFetchCache ? null : this.handleLoadStart,
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
import Font from 'react-native-vector-icons/Ionicons';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import FileAttachmentPreview from 'app/components/file_attachment_list/file_attachment_preview';
|
||||
import FileAttachmentImage from 'app/components/file_attachment_list/file_attachment_image';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
|
||||
const {height: deviceHeight} = Dimensions.get('window');
|
||||
|
|
@ -47,7 +47,7 @@ export default class FileUploadPreview extends PureComponent {
|
|||
key={file.clientId}
|
||||
style={style.preview}
|
||||
>
|
||||
<FileAttachmentPreview
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
|
@ -21,7 +20,8 @@ import LinearGradient from 'react-native-linear-gradient';
|
|||
import Orientation from 'react-native-orientation';
|
||||
|
||||
import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
|
||||
import FileAttachmentPreview from 'app/components/file_attachment_list/file_attachment_preview';
|
||||
|
||||
import ZoomableImage from './zoomable_image';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
|
||||
|
|
@ -29,6 +29,10 @@ const DRAG_VERTICAL_THRESHOLD_START = 25; // When do we want to start capturing
|
|||
const DRAG_VERTICAL_THRESHOLD_END = 100; // When do we want to navigate back
|
||||
const DRAG_HORIZONTAL_THRESHOLD = 50; // Make sure that it's not a sloppy horizontal swipe
|
||||
const HEADER_HEIGHT = 64;
|
||||
const STATUSBAR_HEIGHT = Platform.select({
|
||||
ios: 0,
|
||||
android: 20
|
||||
});
|
||||
|
||||
export default class ImagePreview extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -51,6 +55,8 @@ export default class ImagePreview extends PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.zoomableImages = {};
|
||||
|
||||
const currentFile = props.files.findIndex((file) => file.id === props.fileId);
|
||||
this.state = {
|
||||
currentFile,
|
||||
|
|
@ -65,14 +71,14 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.panResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponderCapture: this.onMoveShouldSetPanResponderCapture,
|
||||
this.mainViewPanResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponderCapture: this.mainViewMoveShouldSetPanResponderCapture,
|
||||
onPanResponderMove: Animated.event([null, {
|
||||
dx: 0,
|
||||
dy: this.state.drag.y
|
||||
}]),
|
||||
onPanResponderRelease: this.onPanResponderRelease,
|
||||
onPanResponderTerminate: this.onPanResponderRelease
|
||||
onPanResponderRelease: this.mainViewPanResponderRelease,
|
||||
onPanResponderTerminate: this.mainViewPanResponderRelease
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -100,14 +106,21 @@ export default class ImagePreview extends PureComponent {
|
|||
|
||||
componentWillUnmount() {
|
||||
Orientation.lockToPortrait();
|
||||
if (Platform.OS === 'ios') {
|
||||
StatusBar.setHidden(false, 'fade');
|
||||
}
|
||||
}
|
||||
|
||||
onMoveShouldSetPanResponderCapture = (evt, gestureState) => {
|
||||
mainViewMoveShouldSetPanResponderCapture = (evt, gestureState) => {
|
||||
if (gestureState.numberActiveTouches === 2 || this.state.isZooming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {dx, dy} = gestureState;
|
||||
return (Math.abs(dy) > DRAG_VERTICAL_THRESHOLD_START && dx < DRAG_HORIZONTAL_THRESHOLD);
|
||||
}
|
||||
|
||||
onPanResponderRelease = (evt, gestureState) => {
|
||||
mainViewPanResponderRelease = (evt, gestureState) => {
|
||||
if (Math.abs(gestureState.dy) > DRAG_VERTICAL_THRESHOLD_END) {
|
||||
this.props.actions.goBack();
|
||||
} else {
|
||||
|
|
@ -117,15 +130,32 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
toggleHeaderAndFileInfo = () => {
|
||||
const showFileInfo = !this.state.showFileInfo;
|
||||
handleImageTap = () => {
|
||||
/*if (!this.lastPress) {
|
||||
this.lastPress = Date.now();
|
||||
} else if (Date.now() - this.lastPress < 400) {
|
||||
if (this.zoomableImages.hasOwnProperty(this.state.currentFile)) {
|
||||
this.zoomableImages[this.state.currentFile].zoomIn();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.lastPress = Date.now();
|
||||
|
||||
}*/
|
||||
|
||||
this.setHeaderAndFileInfoVisible(!this.state.showFileInfo);
|
||||
}
|
||||
|
||||
setHeaderAndFileInfoVisible = (show) => {
|
||||
this.setState({
|
||||
showFileInfo
|
||||
showFileInfo: show
|
||||
});
|
||||
|
||||
StatusBar.setHidden(!showFileInfo, 'fade');
|
||||
if (Platform.OS === 'ios') {
|
||||
StatusBar.setHidden(!show, 'fade');
|
||||
}
|
||||
|
||||
const opacity = showFileInfo ? 1 : 0;
|
||||
const opacity = show ? 1 : 0;
|
||||
|
||||
Animated.timing(this.state.footerOpacity, {
|
||||
toValue: opacity,
|
||||
|
|
@ -155,8 +185,17 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
imageIsZooming = (zooming) => {
|
||||
if (zooming !== this.state.isZooming) {
|
||||
this.setHeaderAndFileInfoVisible(!zooming);
|
||||
this.setState({
|
||||
isZooming: zooming
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const maxImageHeight = this.state.deviceHeight;
|
||||
const maxImageHeight = this.state.deviceHeight - STATUSBAR_HEIGHT;
|
||||
|
||||
return (
|
||||
<View
|
||||
|
|
@ -165,34 +204,38 @@ export default class ImagePreview extends PureComponent {
|
|||
>
|
||||
<AnimatedView
|
||||
style={[this.state.drag.getLayout(), {opacity: this.state.wrapperViewOpacity}]}
|
||||
{...this.panResponder.panHandlers}
|
||||
{...this.mainViewPanResponder.panHandlers}
|
||||
>
|
||||
<ScrollView
|
||||
ref={this.attachScrollView}
|
||||
style={[style.ScrollView]}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
scrollEnabled={!this.state.isZooming}
|
||||
horizontal={true}
|
||||
pagingEnabled={this.state.pagingEnabled}
|
||||
pagingEnabled={!this.state.isZooming}
|
||||
bounces={false}
|
||||
onScroll={this.handleScroll}
|
||||
scrollEventThrottle={1}
|
||||
contentOffset={{x: (this.state.currentFile) * this.state.deviceWidth}}
|
||||
>
|
||||
{this.props.files.map((file) => {
|
||||
{this.props.files.map((file, index) => {
|
||||
let component;
|
||||
if (file.has_preview_image) {
|
||||
component = (
|
||||
<FileAttachmentPreview
|
||||
<ZoomableImage
|
||||
ref={(c) => {
|
||||
this.zoomableImages[index] = c;
|
||||
}}
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
theme={this.props.theme}
|
||||
imageHeight={Math.min(maxImageHeight, file.height)}
|
||||
imageWidth={Math.min(this.state.deviceWidth, file.width)}
|
||||
resizeMode='contain'
|
||||
wrapperBackgroundColor='#000'
|
||||
wrapperHeight={maxImageHeight}
|
||||
wrapperHeight={this.state.deviceHeight}
|
||||
wrapperWidth={this.state.deviceWidth}
|
||||
onImageTap={this.handleImageTap}
|
||||
onZoom={this.imageIsZooming}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
@ -209,14 +252,12 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
<View
|
||||
key={file.id}
|
||||
onPress={this.toggleHeaderAndFileInfo}
|
||||
style={[style.pageWrapper, {height: this.state.deviceHeight, width: this.state.deviceWidth}]}
|
||||
>
|
||||
<View style={[style.pageWrapper, {height: maxImageHeight, width: this.state.deviceWidth}]}>
|
||||
{component}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
{component}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
|
|
|||
323
app/scenes/image_preview/zoomable_image.js
Normal file
323
app/scenes/image_preview/zoomable_image.js
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
// This control is based on Leonti's Stack Overflow post:
|
||||
// http://stackoverflow.com/users/219449/leonti
|
||||
// http://stackoverflow.com/questions/36368919/scrollable-image-with-pinch-to-zoom
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {
|
||||
View,
|
||||
PanResponder
|
||||
} from 'react-native';
|
||||
|
||||
import FileAttachmentImage from 'app/components/file_attachment_list/file_attachment_image';
|
||||
|
||||
function calcDistance(x1, y1, x2, y2) {
|
||||
const dx = Math.abs(x1 - x2);
|
||||
const dy = Math.abs(y1 - y2);
|
||||
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
|
||||
}
|
||||
|
||||
function calcCenter(x1, y1, x2, y2) {
|
||||
function middle(p1, p2) {
|
||||
return p1 > p2 ? p1 - (p1 - p2) / 2 : p2 - (p2 - p1) / 2; // eslint-disable-line
|
||||
}
|
||||
|
||||
return {
|
||||
x: middle(x1, x2),
|
||||
y: middle(y1, y2)
|
||||
};
|
||||
}
|
||||
|
||||
function maxOffset(offset, windowDimension, imageDimension) {
|
||||
const max = windowDimension - imageDimension;
|
||||
if (max >= 0) {
|
||||
return 0;
|
||||
}
|
||||
return offset < max ? max : offset;
|
||||
}
|
||||
|
||||
function calcOffsetByZoom(width, height, imageWidth, imageHeight, zoom) {
|
||||
const xDiff = (imageWidth * zoom) - width;
|
||||
const yDiff = (imageHeight * zoom) - height;
|
||||
return {
|
||||
left: -xDiff / 2,
|
||||
top: -yDiff / 2
|
||||
};
|
||||
}
|
||||
|
||||
class ZoomableImage extends Component {
|
||||
static propTypes = {
|
||||
addFileToFetchCache: PropTypes.func.isRequired,
|
||||
fetchCache: PropTypes.object.isRequired,
|
||||
file: PropTypes.object.isRequired,
|
||||
imageHeight: PropTypes.number.isRequired,
|
||||
imageWidth: PropTypes.number.isRequired,
|
||||
onImageTap: PropTypes.func,
|
||||
onZoom: PropTypes.func.isRequired,
|
||||
wrapperHeight: PropTypes.number.isRequired,
|
||||
wrapperWidth: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onImageTap: () => false
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onLayout = this.onLayout.bind(this);
|
||||
|
||||
this.state = {
|
||||
zoom: 1,
|
||||
maxZoom: 3,
|
||||
minZoom: 1,
|
||||
layoutKnown: false,
|
||||
isZooming: false,
|
||||
isMoving: false,
|
||||
initialDistance: 0,
|
||||
initialX: 0,
|
||||
initalY: 0,
|
||||
offsetTop: 0,
|
||||
offsetLeft: 0,
|
||||
initialTop: 0,
|
||||
initialLeft: 0,
|
||||
initialTopWithoutZoom: 0,
|
||||
initialLeftWithoutZoom: 0,
|
||||
initialZoom: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: props.wrapperHeight,
|
||||
width: props.wrapperWidth
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => {
|
||||
this.tap = Date.now();
|
||||
|
||||
return true;
|
||||
},
|
||||
onStartShouldSetPanResponderCapture: (evt, gestureState) => {
|
||||
if (gestureState.numberActiveTouches === 2 || this.state.zoom > 1) {
|
||||
// Store each press for double tap detection
|
||||
/*if (!this.lastPress) {
|
||||
this.lastPress = Date.now();
|
||||
} else if (Date.now() - this.lastPress < 400 && Date.now() - this.lastPress > 100) {
|
||||
this.setState({
|
||||
zoom: 1,
|
||||
top: 0,
|
||||
offsetTop: 0,
|
||||
left: 0,
|
||||
offsetLeft: 0
|
||||
});
|
||||
this.props.onZoom(false);
|
||||
this.lastPress = null;
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
this.lastPress = Date.now();
|
||||
|
||||
this.props.onZoom(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
onMoveShouldSetPanResponder: (evt, gestureState) => {
|
||||
return gestureState.numberActiveTouches === 2 || this.state.zoom > 1;
|
||||
},
|
||||
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
|
||||
return gestureState.numberActiveTouches === 2 || this.state.zoom > 1;
|
||||
},
|
||||
onPanResponderGrant: () => {
|
||||
return;
|
||||
},
|
||||
onPanResponderMove: (evt) => {
|
||||
const touches = evt.nativeEvent.touches;
|
||||
|
||||
if (touches.length === 2) {
|
||||
const touch1 = touches[0];
|
||||
const touch2 = touches[1];
|
||||
|
||||
this.processPinch(touch1.pageX, touch1.pageY,
|
||||
touch2.pageX, touch2.pageY);
|
||||
} else if (touches.length === 1 && !this.state.isZooming) {
|
||||
this.processTouch(touches[0].pageX, touches[0].pageY);
|
||||
}
|
||||
},
|
||||
onPanResponderTerminationRequest: () => {
|
||||
return this.state.zoom === 1;
|
||||
},
|
||||
onPanResponderRelease: () => {
|
||||
this.props.onZoom(this.state.zoom > 1);
|
||||
this.setState({
|
||||
isZooming: false,
|
||||
isMoving: false
|
||||
});
|
||||
},
|
||||
onPanResponderTerminate: () => {
|
||||
return;
|
||||
},
|
||||
onShouldBlockNativeResponder: () => false
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.wrapperWidth !== this.state.width || nextProps.wrapperHeight !== this.state.height) {
|
||||
this.setState({
|
||||
height: nextProps.wrapperHeight,
|
||||
width: nextProps.wrapperWidth
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
zoomIn = (zoom = 2) => {
|
||||
const offsetByZoom = calcOffsetByZoom(this.state.width, this.state.height,
|
||||
this.props.wrapperWidth, this.props.wrapperHeight, zoom);
|
||||
|
||||
this.setState({
|
||||
zoom,
|
||||
left: offsetByZoom.left,
|
||||
top: offsetByZoom.top,
|
||||
initialX: this.state.width / 2,
|
||||
initialY: this.state.height / 2,
|
||||
initialZoom: zoom,
|
||||
initialTopWithoutZoom: this.state.top - offsetByZoom.top,
|
||||
initialLeftWithoutZoom: this.state.left - offsetByZoom.left
|
||||
});
|
||||
this.props.onZoom(true);
|
||||
}
|
||||
|
||||
processPinch(x1, y1, x2, y2) {
|
||||
const distance = calcDistance(x1, y1, x2, y2);
|
||||
const center = calcCenter(x1, y1, x2, y2);
|
||||
|
||||
if (this.state.isZooming) {
|
||||
const touchZoom = distance / this.state.initialDistance;
|
||||
const zoom = touchZoom * this.state.initialZoom > this.state.minZoom ? touchZoom * this.state.initialZoom : this.state.minZoom;
|
||||
if (zoom > this.state.maxZoom) {
|
||||
return;
|
||||
}
|
||||
const offsetByZoom = calcOffsetByZoom(this.state.width, this.state.height,
|
||||
this.props.wrapperWidth, this.props.wrapperHeight, zoom);
|
||||
const left = (this.state.initialLeftWithoutZoom * touchZoom) + offsetByZoom.left;
|
||||
const top = (this.state.initialTopWithoutZoom * touchZoom) + offsetByZoom.top;
|
||||
|
||||
this.setState({
|
||||
zoom,
|
||||
left: left > 0 ? 0 : maxOffset(left, this.state.width, this.props.wrapperWidth * zoom),
|
||||
top: top > 0 ? 0 : maxOffset(top, this.state.height, this.props.wrapperHeight * zoom)
|
||||
});
|
||||
} else {
|
||||
const offsetByZoom = calcOffsetByZoom(this.state.width, this.state.height,
|
||||
this.props.wrapperWidth, this.props.wrapperHeight, this.state.zoom);
|
||||
this.setState({
|
||||
isZooming: true,
|
||||
initialDistance: distance,
|
||||
initialX: center.x,
|
||||
initialY: center.y,
|
||||
initialTop: this.state.top,
|
||||
initialLeft: this.state.left,
|
||||
initialZoom: this.state.zoom,
|
||||
initialTopWithoutZoom: this.state.top - offsetByZoom.top,
|
||||
initialLeftWithoutZoom: this.state.left - offsetByZoom.left
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
processTouch(x, y) {
|
||||
if (this.state.isMoving) {
|
||||
const left = this.state.initialLeft + x - this.state.initialX; // eslint-disable-line
|
||||
const top = this.state.initialTop + y - this.state.initialY; // eslint-disable-line
|
||||
|
||||
this.setState({
|
||||
left: left > 0 ? 0 : maxOffset(left, this.state.width, this.props.wrapperWidth * this.state.zoom),
|
||||
top: top > 0 ? 0 : maxOffset(top, this.state.height, this.props.wrapperHeight * this.state.zoom)
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
isMoving: true,
|
||||
initialX: x,
|
||||
initialY: y,
|
||||
initialTop: this.state.top,
|
||||
initialLeft: this.state.left
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onLayout(event) {
|
||||
if (this.state.layoutKnown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const layout = event.nativeEvent.layout;
|
||||
|
||||
if (layout.width === this.state.width && layout.height === this.state.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetTop = 0; // eslint-disable-line
|
||||
|
||||
this.setState({
|
||||
layoutKnown: true,
|
||||
width: this.props.wrapperWidth,
|
||||
height: this.props.wrapperHeight,
|
||||
offsetTop
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
addFileToFetchCache,
|
||||
fetchCache,
|
||||
file,
|
||||
imageHeight,
|
||||
imageWidth,
|
||||
theme,
|
||||
wrapperHeight,
|
||||
wrapperWidth
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
{...this.panResponder.panHandlers}
|
||||
onResponderRelease={() => {
|
||||
if (Date.now() - this.tap < 100) {
|
||||
this.props.onImageTap();
|
||||
}
|
||||
|
||||
this.props.onZoom(this.state.zoom > 1);
|
||||
this.setState({
|
||||
isZooming: false,
|
||||
isMoving: false
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: this.state.offsetTop + this.state.top,
|
||||
left: this.state.offsetLeft + this.state.left,
|
||||
width: this.state.width * this.state.zoom,
|
||||
height: this.state.height * this.state.zoom
|
||||
}}
|
||||
>
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={addFileToFetchCache}
|
||||
fetchCache={fetchCache}
|
||||
file={file}
|
||||
theme={theme}
|
||||
imageHeight={imageHeight * this.state.zoom}
|
||||
imageSize='fullsize'
|
||||
imageWidth={imageWidth * this.state.zoom}
|
||||
resizeMode='contain'
|
||||
wrapperBackgroundColor='#000'
|
||||
wrapperHeight={wrapperHeight * this.state.zoom}
|
||||
wrapperWidth={wrapperWidth * this.state.zoom}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ZoomableImage;
|
||||
Loading…
Reference in a new issue