parent
c714072b9b
commit
b47c7db4dc
15 changed files with 444 additions and 374 deletions
|
|
@ -24,7 +24,6 @@ export default class FileAttachment extends PureComponent {
|
|||
file: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
onCaptureRef: PropTypes.func,
|
||||
onCapturePreviewRef: PropTypes.func,
|
||||
onInfoPress: PropTypes.func,
|
||||
onPreviewPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
|
@ -44,14 +43,6 @@ export default class FileAttachment extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleCapturePreviewRef = (ref) => {
|
||||
const {onCapturePreviewRef, index} = this.props;
|
||||
|
||||
if (onCapturePreviewRef) {
|
||||
onCapturePreviewRef(ref, index);
|
||||
}
|
||||
};
|
||||
|
||||
handlePreviewPress = () => {
|
||||
this.props.onPreviewPress(this.props.index);
|
||||
};
|
||||
|
|
@ -105,7 +96,6 @@ export default class FileAttachment extends PureComponent {
|
|||
<FileAttachmentImage
|
||||
file={data || {}}
|
||||
onCaptureRef={this.handleCaptureRef}
|
||||
onCapturePreviewRef={this.handleCapturePreviewRef}
|
||||
theme={theme}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
|
@ -124,7 +114,6 @@ export default class FileAttachment extends PureComponent {
|
|||
<FileAttachmentIcon
|
||||
file={data}
|
||||
onCaptureRef={this.handleCaptureRef}
|
||||
onCapturePreviewRef={this.handleCapturePreviewRef}
|
||||
theme={theme}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ export default class FileAttachmentIcon extends PureComponent {
|
|||
iconHeight: PropTypes.number,
|
||||
iconWidth: PropTypes.number,
|
||||
onCaptureRef: PropTypes.func,
|
||||
onCapturePreviewRef: PropTypes.func,
|
||||
wrapperHeight: PropTypes.number,
|
||||
wrapperWidth: PropTypes.number,
|
||||
};
|
||||
|
|
@ -67,14 +66,6 @@ export default class FileAttachmentIcon extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleCapturePreviewRef = (ref) => {
|
||||
const {onCapturePreviewRef} = this.props;
|
||||
|
||||
if (onCapturePreviewRef) {
|
||||
onCapturePreviewRef(ref);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {file, iconHeight, iconWidth, wrapperHeight, wrapperWidth} = this.props;
|
||||
const source = this.getFileIconPath(file);
|
||||
|
|
@ -85,7 +76,6 @@ export default class FileAttachmentIcon extends PureComponent {
|
|||
style={[styles.fileIconWrapper, {height: wrapperHeight, width: wrapperWidth}]}
|
||||
>
|
||||
<ProgressiveImage
|
||||
ref={this.handleCapturePreviewRef}
|
||||
style={[styles.icon, {height: iconHeight, width: iconWidth}]}
|
||||
defaultSource={source}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export default class FileAttachmentImage extends PureComponent {
|
|||
]),
|
||||
imageWidth: PropTypes.number,
|
||||
onCaptureRef: PropTypes.func,
|
||||
onCapturePreviewRef: PropTypes.func,
|
||||
resizeMode: PropTypes.string,
|
||||
resizeMethod: PropTypes.string,
|
||||
wrapperHeight: PropTypes.number,
|
||||
|
|
@ -92,14 +91,6 @@ export default class FileAttachmentImage extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleCapturePreviewRef = (ref) => {
|
||||
const {onCapturePreviewRef} = this.props;
|
||||
|
||||
if (onCapturePreviewRef) {
|
||||
onCapturePreviewRef(ref);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
file,
|
||||
|
|
@ -135,7 +126,6 @@ export default class FileAttachmentImage extends PureComponent {
|
|||
style={[style.fileImageWrapper, {height: wrapperHeight, width: wrapperWidth, overflow: 'hidden'}]}
|
||||
>
|
||||
<ProgressiveImage
|
||||
ref={this.handleCapturePreviewRef}
|
||||
style={imageStyle}
|
||||
defaultSource={thumb}
|
||||
tintDefaultSource={!file.localPath}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {RequestStatus} from 'mattermost-redux/constants';
|
|||
|
||||
import {isDocument, isGif, isVideo} from 'app/utils/file';
|
||||
import {getCacheFile} from 'app/utils/image_cache_manager';
|
||||
import {previewImageAtIndex} from 'app/utils/images';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
||||
import FileAttachment from './file_attachment';
|
||||
|
|
@ -119,50 +120,10 @@ export default class FileAttachmentList extends Component {
|
|||
return results;
|
||||
};
|
||||
|
||||
getItemMeasures = (index, cb) => {
|
||||
const activeComponent = this.items[index];
|
||||
|
||||
if (!activeComponent) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
activeComponent.measure((rx, ry, width, height, x, y) => {
|
||||
cb({
|
||||
origin: {x, y, width, height},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
getPreviewProps = (index) => {
|
||||
const previewComponent = this.previewItems[index];
|
||||
return previewComponent ? {...previewComponent.props} : {};
|
||||
};
|
||||
|
||||
goToImagePreview = (passProps) => {
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleCaptureRef = (ref, idx) => {
|
||||
this.items[idx] = ref;
|
||||
};
|
||||
|
||||
handleCapturePreviewRef = (ref, idx) => {
|
||||
this.previewItems[idx] = ref;
|
||||
};
|
||||
|
||||
handleInfoPress = () => {
|
||||
this.props.hideOptionsContext();
|
||||
this.props.onPress();
|
||||
|
|
@ -171,22 +132,7 @@ export default class FileAttachmentList extends Component {
|
|||
handlePreviewPress = preventDoubleTap((idx) => {
|
||||
this.props.hideOptionsContext();
|
||||
Keyboard.dismiss();
|
||||
const component = this.items[idx];
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.measure((rx, ry, width, height, x, y) => {
|
||||
this.goToImagePreview({
|
||||
index: idx,
|
||||
origin: {x, y, width, height},
|
||||
target: {x: 0, y: 0, opacity: 1},
|
||||
files: this.galleryFiles,
|
||||
getItemMeasures: this.getItemMeasures,
|
||||
getPreviewProps: this.getPreviewProps,
|
||||
});
|
||||
});
|
||||
previewImageAtIndex(this.props.navigator, this.items, idx, this.galleryFiles);
|
||||
});
|
||||
|
||||
handlePressIn = () => {
|
||||
|
|
@ -231,7 +177,6 @@ export default class FileAttachmentList extends Component {
|
|||
index={idx}
|
||||
navigator={navigator}
|
||||
onCaptureRef={this.handleCaptureRef}
|
||||
onCapturePreviewRef={this.handleCapturePreviewRef}
|
||||
onInfoPress={this.handleInfoPress}
|
||||
onPreviewPress={this.handlePreviewPress}
|
||||
theme={this.props.theme}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export default class Markdown extends PureComponent {
|
|||
baseTextStyle: CustomPropTypes.Style,
|
||||
blockStyles: PropTypes.object,
|
||||
isEdited: PropTypes.bool,
|
||||
isReplyPost: PropTypes.bool,
|
||||
isSearchResult: PropTypes.bool,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onChannelLinkPress: PropTypes.func,
|
||||
|
|
@ -166,6 +167,7 @@ export default class Markdown extends PureComponent {
|
|||
return (
|
||||
<MarkdownImage
|
||||
linkDestination={linkDestination}
|
||||
isReplyPost={this.props.isReplyPost}
|
||||
navigator={this.props.navigator}
|
||||
onLongPress={this.props.onLongPress}
|
||||
source={src}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import MarkdownImage from './markdown_image';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...getDimensions(state),
|
||||
serverURL: getCurrentUrl(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,17 +21,21 @@ import ProgressiveImage from 'app/components/progressive_image';
|
|||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import {normalizeProtocol} from 'app/utils/url';
|
||||
|
||||
const MAX_IMAGE_HEIGHT = 150;
|
||||
|
||||
const ANDROID_MAX_HEIGHT = 4096;
|
||||
const ANDROID_MAX_WIDTH = 4096;
|
||||
const VIEWPORT_IMAGE_OFFSET = 66;
|
||||
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
||||
|
||||
export default class MarkdownImage extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
linkDestination: PropTypes.string,
|
||||
isReplyPost: PropTypes.bool,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onLongPress: PropTypes.func,
|
||||
serverURL: PropTypes.string.isRequired,
|
||||
|
|
@ -48,8 +52,7 @@ export default class MarkdownImage extends React.Component {
|
|||
|
||||
this.state = {
|
||||
width: 0,
|
||||
height: MAX_IMAGE_HEIGHT,
|
||||
maxWidth: Math.MAX_INT,
|
||||
height: 0,
|
||||
failed: false,
|
||||
uri: null,
|
||||
};
|
||||
|
|
@ -82,26 +85,6 @@ export default class MarkdownImage extends React.Component {
|
|||
this.mounted = false;
|
||||
}
|
||||
|
||||
getItemMeasures = (index, cb) => {
|
||||
const activeComponent = this.refs.item;
|
||||
|
||||
if (!activeComponent) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
activeComponent.measure((rx, ry, width, height, x, y) => {
|
||||
cb({
|
||||
origin: {x, y, width, height},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
getPreviewProps = () => {
|
||||
const previewComponent = this.refs.image;
|
||||
return previewComponent ? {...previewComponent.props} : {};
|
||||
};
|
||||
|
||||
getSource = (props = this.props) => {
|
||||
let source = props.source;
|
||||
|
||||
|
|
@ -112,30 +95,20 @@ export default class MarkdownImage extends React.Component {
|
|||
return source;
|
||||
};
|
||||
|
||||
goToImagePreview = (passProps) => {
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleSizeReceived = (width, height) => {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {deviceHeight, deviceWidth, isReplyPost} = this.props;
|
||||
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
const viewPortWidth = deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
|
||||
const dimensions = calculateDimensions(height, width, viewPortWidth);
|
||||
|
||||
this.setState({
|
||||
width,
|
||||
height,
|
||||
...dimensions,
|
||||
originalHeight: height,
|
||||
originalWidth: width,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -149,16 +122,6 @@ export default class MarkdownImage extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
handleLayout = (event) => {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
maxWidth: event.nativeEvent.layout.width,
|
||||
});
|
||||
};
|
||||
|
||||
handleLinkPress = () => {
|
||||
const url = normalizeProtocol(this.props.linkDestination);
|
||||
|
||||
|
|
@ -190,40 +153,32 @@ export default class MarkdownImage extends React.Component {
|
|||
};
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const component = this.refs.item;
|
||||
const {
|
||||
originalHeight,
|
||||
originalWidth,
|
||||
uri,
|
||||
} = this.state;
|
||||
const link = this.getSource();
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
component.measure((rx, ry, width, height, x, y) => {
|
||||
const {uri} = this.state;
|
||||
const link = this.getSource();
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
const files = [{
|
||||
caption: filename,
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
|
||||
this.goToImagePreview({
|
||||
index: 0,
|
||||
origin: {x, y, width, height},
|
||||
target: {x: 0, y: 0, opacity: 1},
|
||||
files,
|
||||
getItemMeasures: this.getItemMeasures,
|
||||
getPreviewProps: this.getPreviewProps,
|
||||
});
|
||||
});
|
||||
const files = [{
|
||||
caption: filename,
|
||||
dimensions: {
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
},
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
previewImageAtIndex(this.props.navigator, [this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
loadImageSize = (source) => {
|
||||
|
|
@ -243,11 +198,9 @@ export default class MarkdownImage extends React.Component {
|
|||
|
||||
render() {
|
||||
let image = null;
|
||||
const {uri} = this.state;
|
||||
|
||||
if (this.state.width && this.state.height && this.state.maxWidth) {
|
||||
let {width, height} = this.state;
|
||||
const {height, uri, width} = this.state;
|
||||
|
||||
if (width && height) {
|
||||
if (Platform.OS === 'android' && (width > ANDROID_MAX_WIDTH || height > ANDROID_MAX_HEIGHT)) {
|
||||
// Android has a cap on the max image size that can be displayed
|
||||
|
||||
|
|
@ -266,18 +219,6 @@ export default class MarkdownImage extends React.Component {
|
|||
</Text>
|
||||
);
|
||||
} else {
|
||||
const maxWidth = this.state.maxWidth;
|
||||
if (width > maxWidth) {
|
||||
height = height * (maxWidth / width);
|
||||
width = maxWidth;
|
||||
}
|
||||
|
||||
const maxHeight = MAX_IMAGE_HEIGHT;
|
||||
if (height > maxHeight) {
|
||||
width = width * (maxHeight / height);
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
// React Native complains if we try to pass resizeMode as a style
|
||||
let source = null;
|
||||
if (uri) {
|
||||
|
|
@ -326,8 +267,7 @@ export default class MarkdownImage extends React.Component {
|
|||
return (
|
||||
<View
|
||||
ref='item'
|
||||
style={[style.container, {height: Math.min(this.state.height, MAX_IMAGE_HEIGHT)}]}
|
||||
onLayout={this.handleLayout}
|
||||
style={style.container}
|
||||
>
|
||||
{image}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -6,17 +6,24 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
Image,
|
||||
Linking,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import InteractiveAction from './interactive_action';
|
||||
|
||||
const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10;
|
||||
const VIEWPORT_IMAGE_OFFSET = 32;
|
||||
const STATUS_COLORS = {
|
||||
good: '#00c100',
|
||||
warning: '#dede01',
|
||||
|
|
@ -42,6 +49,20 @@ export default class MessageAttachment extends PureComponent {
|
|||
this.state = this.getInitState();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.attachment.image_url) {
|
||||
ImageCacheManager.cache(null, this.props.attachment.image_url, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getActionView = (style) => {
|
||||
const {attachment, postId} = this.props;
|
||||
const {actions} = attachment;
|
||||
|
|
@ -95,6 +116,7 @@ export default class MessageAttachment extends PureComponent {
|
|||
uncollapsedText,
|
||||
text: shouldCollapse ? collapsedText : uncollapsedText,
|
||||
collapsed: shouldCollapse,
|
||||
imageUri: null,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -189,12 +211,70 @@ export default class MessageAttachment extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
handleLayout = (event) => {
|
||||
if (!this.maxImageWidth) {
|
||||
const {height, width} = event.nativeEvent.layout;
|
||||
const viewPortWidth = width > height ? height : width;
|
||||
this.maxImageWidth = viewPortWidth - VIEWPORT_IMAGE_OFFSET;
|
||||
}
|
||||
};
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const {attachment, navigator} = this.props;
|
||||
const {
|
||||
imageUri: uri,
|
||||
originalHeight,
|
||||
originalWidth,
|
||||
} = this.state;
|
||||
const link = attachment.image_url;
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
const files = [{
|
||||
caption: filename,
|
||||
dimensions: {
|
||||
height: originalHeight,
|
||||
width: originalWidth,
|
||||
},
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
previewImageAtIndex(navigator, [this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
openLink = (link) => {
|
||||
if (Linking.canOpenURL(link)) {
|
||||
Linking.openURL(link);
|
||||
}
|
||||
};
|
||||
|
||||
setImageUrl = (imageURL) => {
|
||||
let imageUri = imageURL;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
imageUri = `file://${imageURL}`;
|
||||
}
|
||||
|
||||
Image.getSize(imageUri, (width, height) => {
|
||||
const dimensions = calculateDimensions(height, width, this.maxImageWidth);
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
...dimensions,
|
||||
originalWidth: width,
|
||||
originalHeight: height,
|
||||
imageUri,
|
||||
});
|
||||
}
|
||||
}, () => null);
|
||||
};
|
||||
|
||||
shouldCollapse = () => {
|
||||
const text = this.props.attachment.text || '';
|
||||
return (text.match(/\n/g) || []).length >= 5 || text.length > 400;
|
||||
|
|
@ -219,6 +299,12 @@ export default class MessageAttachment extends PureComponent {
|
|||
theme,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
height,
|
||||
imageUri,
|
||||
width,
|
||||
} = this.state;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let preText;
|
||||
|
|
@ -357,13 +443,23 @@ export default class MessageAttachment extends PureComponent {
|
|||
const actions = this.getActionView(style);
|
||||
|
||||
let image;
|
||||
if (attachment.image_url) {
|
||||
if (imageUri) {
|
||||
image = (
|
||||
<View style={style.imageContainer}>
|
||||
<Image
|
||||
source={{uri: attachment.image_url}}
|
||||
style={style.image}
|
||||
/>
|
||||
<View
|
||||
ref='item'
|
||||
style={[style.imageContainer, {width: this.maxImageWidth + VIEWPORT_IMAGE_CONTAINER_OFFSET}]}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.handlePreviewImage}
|
||||
style={{height, width}}
|
||||
>
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
style={{height, width}}
|
||||
imageUri={imageUri}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -371,7 +467,10 @@ export default class MessageAttachment extends PureComponent {
|
|||
return (
|
||||
<View>
|
||||
{preText}
|
||||
<View style={[style.container, style.border, borderStyle]}>
|
||||
<View
|
||||
onLayout={this.handleLayout}
|
||||
style={[style.container, style.border, borderStyle]}
|
||||
>
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
{author}
|
||||
</View>
|
||||
|
|
@ -459,10 +558,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
borderWidth: 1,
|
||||
borderRadius: 2,
|
||||
marginTop: 5,
|
||||
},
|
||||
image: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
padding: 5,
|
||||
},
|
||||
actionsContainer: {
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,16 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import PostAttachmentOpenGraph from './post_attachment_opengraph';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...getDimensions(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
|
|
@ -16,4 +24,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
Linking,
|
||||
Platform,
|
||||
|
|
@ -16,16 +15,21 @@ import {
|
|||
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import {getNearestPoint} from 'app/utils/opengraph';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const MAX_IMAGE_HEIGHT = 150;
|
||||
const VIEWPORT_IMAGE_OFFSET = 88;
|
||||
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
||||
|
||||
export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getOpenGraphMetadata: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
isReplyPost: PropTypes.bool,
|
||||
link: PropTypes.string.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
|
|
@ -66,27 +70,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
this.mounted = false;
|
||||
}
|
||||
|
||||
calculateLargeImageDimensions = (width, height) => {
|
||||
const {width: deviceWidth} = Dimensions.get('window');
|
||||
let maxHeight = MAX_IMAGE_HEIGHT;
|
||||
let maxWidth = deviceWidth - 88 - (this.props.isReplyPost ? 15 : 0);
|
||||
|
||||
if (height <= MAX_IMAGE_HEIGHT) {
|
||||
maxHeight = height;
|
||||
} else {
|
||||
maxHeight = (height / width) * maxWidth;
|
||||
if (maxHeight > MAX_IMAGE_HEIGHT) {
|
||||
maxHeight = MAX_IMAGE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
if (height > width) {
|
||||
maxWidth = (width / height) * maxHeight;
|
||||
}
|
||||
|
||||
return {width: maxWidth, height: maxHeight};
|
||||
};
|
||||
|
||||
fetchData(url, openGraphData) {
|
||||
if (!openGraphData) {
|
||||
this.props.actions.getOpenGraphMetadata(url);
|
||||
|
|
@ -99,7 +82,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
}
|
||||
|
||||
const bestDimensions = {
|
||||
width: Dimensions.get('window').width - 88,
|
||||
width: this.getViewPostWidth(),
|
||||
height: MAX_IMAGE_HEIGHT,
|
||||
};
|
||||
|
||||
|
|
@ -126,51 +109,25 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
const uri = `${prefix}${imageUrl}`;
|
||||
|
||||
Image.getSize(uri, (width, height) => {
|
||||
const dimensions = this.calculateLargeImageDimensions(width, height);
|
||||
const dimensions = calculateDimensions(height, width, this.getViewPostWidth());
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
...dimensions,
|
||||
originalHeight: height,
|
||||
originalWidth: width,
|
||||
imageUrl: uri,
|
||||
});
|
||||
}
|
||||
}, () => null);
|
||||
};
|
||||
|
||||
getItemMeasures = (index, cb) => {
|
||||
const activeComponent = this.refs.item;
|
||||
getViewPostWidth = () => {
|
||||
const {deviceHeight, deviceWidth, isReplyPost} = this.props;
|
||||
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
const viewPortWidth = deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
|
||||
|
||||
if (!activeComponent) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
activeComponent.measure((rx, ry, width, height, x, y) => {
|
||||
cb({
|
||||
origin: {x, y, width, height},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
getPreviewProps = () => {
|
||||
const previewComponent = this.refs.image;
|
||||
return previewComponent ? {...previewComponent.props} : {};
|
||||
};
|
||||
|
||||
goToImagePreview = (passProps) => {
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
return viewPortWidth;
|
||||
};
|
||||
|
||||
goToLink = () => {
|
||||
|
|
@ -178,39 +135,33 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
};
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const component = this.refs.item;
|
||||
const {
|
||||
imageUrl: uri,
|
||||
openGraphImageUrl: link,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
} = this.state;
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
component.measure((rx, ry, width, height, x, y) => {
|
||||
const {imageUrl: uri, openGraphImageUrl: link} = this.state;
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
const files = [{
|
||||
caption: filename,
|
||||
dimensions: {
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
},
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
const files = [{
|
||||
caption: filename,
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
|
||||
this.goToImagePreview({
|
||||
index: 0,
|
||||
origin: {x, y, width, height},
|
||||
target: {x: 0, y: 0, opacity: 1},
|
||||
files,
|
||||
getItemMeasures: this.getItemMeasures,
|
||||
getPreviewProps: this.getPreviewProps,
|
||||
});
|
||||
});
|
||||
previewImageAtIndex(this.props.navigator, [this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -274,7 +225,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
ref='image'
|
||||
style={[style.image, {width, height}]}
|
||||
imageUri={imageUrl}
|
||||
resizeMode='cover'
|
||||
resizeMode='contain'
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@ export default class PostBody extends PureComponent {
|
|||
isFailed,
|
||||
isPending,
|
||||
isPostAddChannelMember,
|
||||
isReplyPost,
|
||||
isSearchResult,
|
||||
isSystemMessage,
|
||||
message,
|
||||
|
|
@ -470,6 +471,7 @@ export default class PostBody extends PureComponent {
|
|||
baseTextStyle={messageStyle}
|
||||
blockStyles={blockStyles}
|
||||
isEdited={hasBeenEdited}
|
||||
isReplyPost={isReplyPost}
|
||||
isSearchResult={isSearchResult}
|
||||
navigator={navigator}
|
||||
onLongPress={this.showOptionsContext}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ import ProgressiveImage from 'app/components/progressive_image';
|
|||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import {getYouTubeVideoId, isImageLink, isYoutubeLink} from 'app/utils/url';
|
||||
|
||||
const MAX_IMAGE_HEIGHT = 150;
|
||||
|
||||
const VIEWPORT_IMAGE_OFFSET = 66;
|
||||
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
||||
const MAX_YOUTUBE_IMAGE_HEIGHT = 150;
|
||||
let MessageAttachments;
|
||||
let PostAttachmentOpenGraph;
|
||||
|
||||
|
|
@ -97,18 +99,18 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
calculateDimensions = (width, height) => {
|
||||
calculateYouTubeImageDimensions = (width, height) => {
|
||||
const {deviceHeight, deviceWidth} = this.props;
|
||||
let maxHeight = MAX_IMAGE_HEIGHT;
|
||||
let maxHeight = MAX_YOUTUBE_IMAGE_HEIGHT;
|
||||
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
let maxWidth = deviceSize - 78;
|
||||
|
||||
if (height <= MAX_IMAGE_HEIGHT) {
|
||||
if (height <= MAX_YOUTUBE_IMAGE_HEIGHT) {
|
||||
maxHeight = height;
|
||||
} else {
|
||||
maxHeight = (height / width) * maxWidth;
|
||||
if (maxHeight > MAX_IMAGE_HEIGHT) {
|
||||
maxHeight = MAX_IMAGE_HEIGHT;
|
||||
if (maxHeight > MAX_YOUTUBE_IMAGE_HEIGHT) {
|
||||
maxHeight = MAX_YOUTUBE_IMAGE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +153,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
generateToggleableEmbed = (isImage, isYouTube) => {
|
||||
const {link} = this.props;
|
||||
const {width, height, uri} = this.state;
|
||||
const imgHeight = height || MAX_IMAGE_HEIGHT;
|
||||
const imgHeight = height;
|
||||
|
||||
if (link) {
|
||||
if (isYouTube) {
|
||||
|
|
@ -196,7 +198,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
ref='image'
|
||||
style={[styles.image, {width, height: imgHeight}]}
|
||||
defaultSource={{uri}}
|
||||
resizeMode='cover'
|
||||
resizeMode='contain'
|
||||
onError={this.handleLinkLoadError}
|
||||
/>
|
||||
</View>
|
||||
|
|
@ -209,7 +211,9 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
};
|
||||
|
||||
getImageSize = (path) => {
|
||||
const {link} = this.props;
|
||||
const {deviceHeight, deviceWidth, link, isReplyPost} = this.props;
|
||||
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
const viewPortWidth = deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
|
||||
|
||||
if (link && path) {
|
||||
let prefix = '';
|
||||
|
|
@ -228,27 +232,24 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
const dimensions = this.calculateDimensions(width, height);
|
||||
this.setState({...dimensions, linkLoaded: true, uri});
|
||||
let dimensions;
|
||||
if (isYoutubeLink(link)) {
|
||||
dimensions = this.calculateYouTubeImageDimensions(width, height);
|
||||
} else {
|
||||
dimensions = calculateDimensions(height, width, viewPortWidth);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
...dimensions,
|
||||
originalHeight: height,
|
||||
originalWidth: width,
|
||||
linkLoaded: true,
|
||||
uri,
|
||||
});
|
||||
}, () => this.setState({linkLoadError: true}));
|
||||
}
|
||||
};
|
||||
|
||||
getItemMeasures = (index, cb) => {
|
||||
const activeComponent = this.refs.item;
|
||||
|
||||
if (!activeComponent) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
activeComponent.measure((rx, ry, width, height, x, y) => {
|
||||
cb({
|
||||
origin: {x, y, width, height},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
getMessageAttachment = () => {
|
||||
const {
|
||||
postId,
|
||||
|
|
@ -285,11 +286,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
return null;
|
||||
};
|
||||
|
||||
getPreviewProps = () => {
|
||||
const previewComponent = this.refs.image;
|
||||
return previewComponent ? {...previewComponent.props} : {};
|
||||
};
|
||||
|
||||
getYouTubeTime = (link) => {
|
||||
const timeRegex = /[\\?&](t|start|time_continue)=([0-9]+h)?([0-9]+m)?([0-9]+s?)/;
|
||||
|
||||
|
|
@ -319,54 +315,31 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
return ticks;
|
||||
};
|
||||
|
||||
goToImagePreview = (passProps) => {
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleLinkLoadError = () => {
|
||||
this.setState({linkLoadError: true});
|
||||
};
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const component = this.refs.item;
|
||||
const {link, navigator} = this.props;
|
||||
const {
|
||||
originalHeight,
|
||||
originalWidth,
|
||||
uri,
|
||||
} = this.state;
|
||||
const filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const files = [{
|
||||
caption: filename,
|
||||
source: {uri},
|
||||
dimensions: {
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.measure((rx, ry, width, height, x, y) => {
|
||||
const {link} = this.props;
|
||||
const {uri} = this.state;
|
||||
const filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const files = [{
|
||||
caption: filename,
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
|
||||
this.goToImagePreview({
|
||||
index: 0,
|
||||
origin: {x, y, width, height},
|
||||
target: {x: 0, y: 0, opacity: 1},
|
||||
files,
|
||||
getItemMeasures: this.getItemMeasures,
|
||||
getPreviewProps: this.getPreviewProps,
|
||||
});
|
||||
});
|
||||
previewImageAtIndex(navigator, [this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
playYouTubeVideo = () => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
Alert,
|
||||
Animated,
|
||||
Image,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
|
|
@ -30,6 +31,7 @@ import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachm
|
|||
import {NavigationTypes, PermissionTypes} from 'app/constants';
|
||||
import {isDocument, isVideo} from 'app/utils/file';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {calculateDimensions} from 'app/utils/images';
|
||||
|
||||
import Downloader from './downloader';
|
||||
import VideoPreview from './video_preview';
|
||||
|
|
@ -49,7 +51,6 @@ export default class ImagePreview extends PureComponent {
|
|||
deviceWidth: PropTypes.number.isRequired,
|
||||
files: PropTypes.array,
|
||||
getItemMeasures: PropTypes.func.isRequired,
|
||||
getPreviewProps: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
origin: PropTypes.object,
|
||||
|
|
@ -179,10 +180,23 @@ export default class ImagePreview extends PureComponent {
|
|||
};
|
||||
|
||||
getSwipeableStyle = () => {
|
||||
const {deviceHeight, deviceWidth} = this.props;
|
||||
const {origin, target} = this.state;
|
||||
const {deviceHeight, deviceWidth, files} = this.props;
|
||||
const {origin, target, index} = this.state;
|
||||
const inputRange = [0, 1];
|
||||
|
||||
let toHeight = deviceHeight;
|
||||
let toWidth = deviceWidth;
|
||||
if (files[index].dimensions) {
|
||||
const {height, width} = files[index].dimensions;
|
||||
if (width < deviceWidth) {
|
||||
toWidth = width;
|
||||
}
|
||||
|
||||
if (height < deviceWidth) {
|
||||
toHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
left: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
|
|
@ -194,11 +208,11 @@ export default class ImagePreview extends PureComponent {
|
|||
}),
|
||||
width: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
outputRange: [origin.width, deviceWidth],
|
||||
outputRange: [origin.width, toWidth],
|
||||
}),
|
||||
height: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
outputRange: [origin.height, deviceHeight],
|
||||
outputRange: [origin.height, toHeight],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
@ -323,6 +337,7 @@ export default class ImagePreview extends PureComponent {
|
|||
return (
|
||||
<Gallery
|
||||
errorComponent={this.renderOtherItems}
|
||||
imageComponent={this.renderImageComponent}
|
||||
images={this.props.files}
|
||||
initialPage={this.state.index}
|
||||
onLayout={this.handleGalleryLayout}
|
||||
|
|
@ -364,6 +379,27 @@ export default class ImagePreview extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderImageComponent = (imageProps, imageDimensions) => {
|
||||
if (imageDimensions) {
|
||||
const {height, width} = imageDimensions;
|
||||
const {style, ...otherProps} = imageProps;
|
||||
const flattenStyle = StyleSheet.flatten(style);
|
||||
const calculatedDimensions = calculateDimensions(height, width, flattenStyle.width, flattenStyle.height);
|
||||
const imageStyle = {...flattenStyle, ...calculatedDimensions};
|
||||
|
||||
return (
|
||||
<View style={[style, {justifyContent: 'center', alignItems: 'center'}]}>
|
||||
<Image
|
||||
{...otherProps}
|
||||
style={imageStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderOtherItems = (index) => {
|
||||
const {files} = this.props;
|
||||
const file = files[index];
|
||||
|
|
@ -382,23 +418,19 @@ export default class ImagePreview extends PureComponent {
|
|||
};
|
||||
|
||||
renderSelectedItem = () => {
|
||||
const {hide, index} = this.state;
|
||||
const {hide} = this.state;
|
||||
const file = this.getCurrentFile();
|
||||
|
||||
if (hide || isDocument(file.data) || isVideo(file.data)) {
|
||||
if (hide || !file || !file.source || !file.source.uri || isDocument(file.data) || isVideo(file.data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {getPreviewProps} = this.props;
|
||||
const containerStyle = this.getSwipeableStyle();
|
||||
const previewProps = getPreviewProps(index);
|
||||
Reflect.deleteProperty(previewProps, 'thumbnailUri');
|
||||
|
||||
return (
|
||||
<ScrollView scrollEnabled={false}>
|
||||
<Animated.View style={[style.center, style.flex, containerStyle]}>
|
||||
<ProgressiveImage
|
||||
{...previewProps}
|
||||
imageUri={file.source.uri}
|
||||
style={[StyleSheet.absoluteFill, style.fullWidth]}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
|
|
|
|||
90
app/utils/images.js
Normal file
90
app/utils/images.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
let previewComponents;
|
||||
const IMAGE_MAX_HEIGHT = 350;
|
||||
const IMAGE_MIN_DIMENSION = 50;
|
||||
|
||||
export const calculateDimensions = (height, width, viewPortWidth = 0, viewPortHeight = 0) => {
|
||||
const ratio = height / width;
|
||||
const heightRatio = width / height;
|
||||
|
||||
let imageWidth = width;
|
||||
let imageHeight = height;
|
||||
|
||||
if (width > viewPortWidth) {
|
||||
imageWidth = viewPortWidth;
|
||||
imageHeight = imageWidth * ratio;
|
||||
} else if (width < IMAGE_MIN_DIMENSION) {
|
||||
imageWidth = IMAGE_MIN_DIMENSION;
|
||||
imageHeight = imageWidth * ratio;
|
||||
}
|
||||
|
||||
if (
|
||||
(
|
||||
imageHeight > IMAGE_MAX_HEIGHT ||
|
||||
(viewPortHeight && imageHeight > viewPortHeight)
|
||||
) && viewPortHeight <= IMAGE_MAX_HEIGHT
|
||||
) {
|
||||
imageHeight = viewPortHeight || IMAGE_MAX_HEIGHT;
|
||||
imageWidth = imageHeight * heightRatio;
|
||||
} else if (imageHeight < IMAGE_MIN_DIMENSION) {
|
||||
imageHeight = IMAGE_MIN_DIMENSION;
|
||||
imageWidth = imageHeight * heightRatio;
|
||||
}
|
||||
|
||||
return {
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
};
|
||||
};
|
||||
|
||||
export function previewImageAtIndex(navigator, components, index, files) {
|
||||
previewComponents = components;
|
||||
const component = components[index];
|
||||
if (component) {
|
||||
component.measure((rx, ry, width, height, x, y) => {
|
||||
goToImagePreview(
|
||||
navigator,
|
||||
{
|
||||
index,
|
||||
origin: {x, y, width, height},
|
||||
target: {x: 0, y: 0, opacity: 1},
|
||||
files,
|
||||
getItemMeasures,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function goToImagePreview(navigator, passProps) {
|
||||
navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getItemMeasures(index, cb) {
|
||||
const activeComponent = previewComponents[index];
|
||||
|
||||
if (!activeComponent) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
activeComponent.measure((rx, ry, width, height, x, y) => {
|
||||
cb({
|
||||
origin: {x, y, width, height},
|
||||
});
|
||||
});
|
||||
}
|
||||
59
app/utils/images.test.js
Normal file
59
app/utils/images.test.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {calculateDimensions} from 'app/utils/images';
|
||||
|
||||
const PORTRAIT_VIEWPORT = 315;
|
||||
const IMAGE_MAX_HEIGHT = 350;
|
||||
const IMAGE_MIN_DIMENSION = 50;
|
||||
|
||||
describe('Images calculateDimensions', () => {
|
||||
it('image smaller than 50x50 should return 50x50', () => {
|
||||
const {height, width} = calculateDimensions(20, 20, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(IMAGE_MIN_DIMENSION);
|
||||
expect(width).toEqual(IMAGE_MIN_DIMENSION);
|
||||
});
|
||||
|
||||
it('images with height below 50 should return height of 50', () => {
|
||||
const {height} = calculateDimensions(45, 100, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(IMAGE_MIN_DIMENSION);
|
||||
});
|
||||
|
||||
it('images with width below 50 should return width of 50', () => {
|
||||
const {width} = calculateDimensions(100, 45, PORTRAIT_VIEWPORT);
|
||||
expect(width).toEqual(IMAGE_MIN_DIMENSION);
|
||||
});
|
||||
|
||||
it('images with that are 50x50 should return the same size', () => {
|
||||
const {height, width} = calculateDimensions(50, 50, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(IMAGE_MIN_DIMENSION);
|
||||
expect(width).toEqual(IMAGE_MIN_DIMENSION);
|
||||
});
|
||||
|
||||
it('images with smaller sizes than the max allowed should return the same size', () => {
|
||||
const {height, width} = calculateDimensions(75, 150, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(75);
|
||||
expect(width).toEqual(150);
|
||||
});
|
||||
|
||||
it('images with that have a width of the same size as the viewPort return the same size', () => {
|
||||
const {height, width} = calculateDimensions(75, PORTRAIT_VIEWPORT, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(75);
|
||||
expect(width).toEqual(PORTRAIT_VIEWPORT);
|
||||
});
|
||||
|
||||
it('large images with more height than width should return a MAX height of 350', () => {
|
||||
const {height} = calculateDimensions(1920, 1080, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(IMAGE_MAX_HEIGHT);
|
||||
});
|
||||
|
||||
it('large images with more width than height should return the MAX width equal to the viewPort', () => {
|
||||
const {width} = calculateDimensions(1080, 1920, PORTRAIT_VIEWPORT);
|
||||
expect(width).toEqual(PORTRAIT_VIEWPORT);
|
||||
});
|
||||
|
||||
it('large images with the viewPort height defined should return the MAX height equal to the viewPort Height', () => {
|
||||
const {height} = calculateDimensions(1920, 1080, PORTRAIT_VIEWPORT, 340);
|
||||
expect(height).toEqual(340);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue