Make the image preview smoother (#2147)
* Make the image preview smoother
This commit is contained in:
parent
8884293b18
commit
0edcc139f4
4 changed files with 360 additions and 83 deletions
|
|
@ -0,0 +1,257 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ImagePreview should match snapshot 1`] = `
|
||||
<AnimatedComponent
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
"flex": 1,
|
||||
},
|
||||
Object {
|
||||
"opacity": NaN,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<AnimatedComponent
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#000",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Gallery
|
||||
errorComponent={[Function]}
|
||||
flatListProps={
|
||||
Object {
|
||||
"windowSize": 3,
|
||||
}
|
||||
}
|
||||
imageComponent={[Function]}
|
||||
images={
|
||||
Array [
|
||||
Object {
|
||||
"caption": "Caption 1",
|
||||
"data": "data",
|
||||
"source": "source",
|
||||
},
|
||||
Object {
|
||||
"caption": "Caption 2",
|
||||
"data": "data",
|
||||
"source": "source",
|
||||
},
|
||||
]
|
||||
}
|
||||
initialPage={0}
|
||||
onPageSelected={[Function]}
|
||||
onSingleTapConfirmed={[Function]}
|
||||
onSwipedVertical={[Function]}
|
||||
pageMargin={2}
|
||||
removeClippedSubviews={true}
|
||||
scrollViewStyle={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<AnimatedComponent
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 48,
|
||||
"overflow": "hidden",
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
},
|
||||
Object {
|
||||
"opacity": 1,
|
||||
"top": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"height": 48,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "space-around",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"width": 48,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#fff"
|
||||
name="md-close"
|
||||
size={26}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"marginHorizontal": 10,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
1/2
|
||||
</Component>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"width": 48,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#fff"
|
||||
name="ios-download"
|
||||
size={26}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Component>
|
||||
</Component>
|
||||
</AnimatedComponent>
|
||||
<AnimatedComponent
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"opacity": 1,
|
||||
},
|
||||
Object {
|
||||
"height": 64,
|
||||
"justifyContent": "center",
|
||||
"overflow": "hidden",
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={
|
||||
Array [
|
||||
"transparent",
|
||||
"#000000",
|
||||
]
|
||||
}
|
||||
end={
|
||||
Object {
|
||||
"x": 0,
|
||||
"y": 0.9,
|
||||
}
|
||||
}
|
||||
pointerEvents="none"
|
||||
start={
|
||||
Object {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"height": 64,
|
||||
"justifyContent": "flex-end",
|
||||
"left": 0,
|
||||
"paddingBottom": 5,
|
||||
"paddingHorizontal": 24,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"fontSize": 14,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
Caption 1
|
||||
</Component>
|
||||
</LinearGradient>
|
||||
</AnimatedComponent>
|
||||
</AnimatedComponent>
|
||||
<Downloader
|
||||
deviceHeight={400}
|
||||
deviceWidth={300}
|
||||
file={
|
||||
Object {
|
||||
"caption": "Caption 1",
|
||||
"data": "data",
|
||||
"source": "source",
|
||||
}
|
||||
}
|
||||
force={false}
|
||||
onCancelPress={[Function]}
|
||||
onDownloadCancel={[Function]}
|
||||
onDownloadStart={[Function]}
|
||||
onDownloadSuccess={[Function]}
|
||||
prompt={false}
|
||||
saveToCameraRoll={true}
|
||||
show={false}
|
||||
/>
|
||||
</AnimatedComponent>
|
||||
`;
|
||||
|
||||
exports[`ImagePreview should match snapshot, renderDownloadButton 1`] = `
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"width": 48,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#fff"
|
||||
name="ios-download"
|
||||
size={26}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
`;
|
||||
|
|
@ -9,7 +9,6 @@ import {
|
|||
Image,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
|
|
@ -27,7 +26,6 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|||
|
||||
import FileAttachmentDocument from 'app/components/file_attachment_list/file_attachment_document';
|
||||
import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import {DeviceTypes, NavigationTypes, PermissionTypes} from 'app/constants';
|
||||
import {getLocalFilePathFromFile, isDocument, isVideo} from 'app/utils/file';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
|
@ -104,7 +102,7 @@ export default class ImagePreview extends PureComponent {
|
|||
const {getItemMeasures, navigator} = this.props;
|
||||
const {index} = this.state;
|
||||
|
||||
this.setState({animating: true, gallery: false, hide: false});
|
||||
this.setState({animating: true});
|
||||
navigator.setStyle({
|
||||
screenBackgroundColor: 'transparent',
|
||||
});
|
||||
|
|
@ -124,10 +122,6 @@ export default class ImagePreview extends PureComponent {
|
|||
this.setState({index});
|
||||
};
|
||||
|
||||
handleGalleryLayout = () => {
|
||||
this.setState({hide: true});
|
||||
};
|
||||
|
||||
handleSwipedVertical = (evt, gestureState) => {
|
||||
if (Math.abs(gestureState.dy) > 150) {
|
||||
this.close();
|
||||
|
|
@ -178,44 +172,6 @@ export default class ImagePreview extends PureComponent {
|
|||
};
|
||||
};
|
||||
|
||||
getSwipeableStyle = () => {
|
||||
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,
|
||||
outputRange: [origin.x, target.x],
|
||||
}),
|
||||
top: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
outputRange: [origin.y, target.y],
|
||||
}),
|
||||
width: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
outputRange: [origin.width, toWidth],
|
||||
}),
|
||||
height: this.openAnim.interpolate({
|
||||
inputRange,
|
||||
outputRange: [origin.height, toHeight],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
renderAttachmentDocument = (file) => {
|
||||
const {canDownloadFiles, theme, navigator} = this.props;
|
||||
|
||||
|
|
@ -294,7 +250,7 @@ export default class ImagePreview extends PureComponent {
|
|||
return null;
|
||||
};
|
||||
|
||||
renderDownloader() {
|
||||
renderDownloader = () => {
|
||||
const {deviceHeight, deviceWidth} = this.props;
|
||||
const file = this.getCurrentFile();
|
||||
|
||||
|
|
@ -310,9 +266,9 @@ export default class ImagePreview extends PureComponent {
|
|||
onDownloadSuccess={this.hideDownloader}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderFooter() {
|
||||
renderFooter = () => {
|
||||
const {files} = this.props;
|
||||
const {index} = this.state;
|
||||
const footer = this.getHeaderFooterStyle();
|
||||
|
|
@ -331,16 +287,15 @@ export default class ImagePreview extends PureComponent {
|
|||
</LinearGradient>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderGallery() {
|
||||
renderGallery = () => {
|
||||
return (
|
||||
<Gallery
|
||||
errorComponent={this.renderOtherItems}
|
||||
imageComponent={this.renderImageComponent}
|
||||
images={this.props.files}
|
||||
initialPage={this.state.index}
|
||||
onLayout={this.handleGalleryLayout}
|
||||
onPageSelected={this.handleChangeImage}
|
||||
onSingleTapConfirmed={this.handleTapped}
|
||||
onSwipedVertical={this.handleSwipedVertical}
|
||||
|
|
@ -348,9 +303,9 @@ export default class ImagePreview extends PureComponent {
|
|||
style={style.flex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderHeader() {
|
||||
renderHeader = () => {
|
||||
const {files} = this.props;
|
||||
const {index} = this.state;
|
||||
const header = this.getHeaderFooterStyle();
|
||||
|
|
@ -377,7 +332,7 @@ export default class ImagePreview extends PureComponent {
|
|||
</View>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderImageComponent = (imageProps, imageDimensions) => {
|
||||
if (imageDimensions) {
|
||||
|
|
@ -398,7 +353,7 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
renderOtherItems = (index) => {
|
||||
const {files} = this.props;
|
||||
|
|
@ -417,28 +372,6 @@ export default class ImagePreview extends PureComponent {
|
|||
return <View/>;
|
||||
};
|
||||
|
||||
renderSelectedItem = () => {
|
||||
const {hide} = this.state;
|
||||
const file = this.getCurrentFile();
|
||||
|
||||
if (hide || !file || !file.source || !file.source.uri || isDocument(file.data) || isVideo(file.data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const containerStyle = this.getSwipeableStyle();
|
||||
return (
|
||||
<ScrollView scrollEnabled={false}>
|
||||
<Animated.View style={[style.center, style.flex, containerStyle]}>
|
||||
<ProgressiveImage
|
||||
imageUri={file.source.uri}
|
||||
style={[StyleSheet.absoluteFill, style.fullWidth]}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
renderVideoPreview = (file) => {
|
||||
const {deviceHeight, deviceWidth, theme} = this.props;
|
||||
|
||||
|
|
@ -611,9 +544,7 @@ export default class ImagePreview extends PureComponent {
|
|||
};
|
||||
|
||||
startOpenAnimation = () => {
|
||||
this.animateOpenAnimToValue(1, () => {
|
||||
this.setState({gallery: true});
|
||||
});
|
||||
this.animateOpenAnimToValue(1);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -622,8 +553,7 @@ export default class ImagePreview extends PureComponent {
|
|||
return (
|
||||
<AnimatedSafeAreaView style={[style.container, opacity]}>
|
||||
<AnimatedView style={style.container}>
|
||||
{this.renderSelectedItem()}
|
||||
{this.state.gallery && this.renderGallery()}
|
||||
{this.renderGallery()}
|
||||
{this.renderHeader()}
|
||||
{this.renderFooter()}
|
||||
</AnimatedView>
|
||||
|
|
|
|||
87
app/screens/image_preview/image_preview.test.js
Normal file
87
app/screens/image_preview/image_preview.test.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import ImagePreview from './image_preview';
|
||||
|
||||
jest.useFakeTimers();
|
||||
jest.mock('react-intl');
|
||||
jest.mock('react-native-doc-viewer', () => {
|
||||
return {
|
||||
OpenFile: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ImagePreview', () => {
|
||||
const baseProps = {
|
||||
canDownloadFiles: true,
|
||||
deviceHeight: 400,
|
||||
deviceWidth: 300,
|
||||
files: [
|
||||
{caption: 'Caption 1', source: 'source', data: 'data'},
|
||||
{caption: 'Caption 2', source: 'source', data: 'data'},
|
||||
],
|
||||
getItemMeasures: jest.fn(),
|
||||
index: 0,
|
||||
navigator: {setStyle: jest.fn()},
|
||||
origin: {},
|
||||
target: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<ImagePreview {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find(TouchableOpacity).first().exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('should match snapshot, renderDownloadButton', () => {
|
||||
const wrapper = shallow(
|
||||
<ImagePreview {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.instance().renderDownloadButton()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state on handleChangeImage', () => {
|
||||
const wrapper = shallow(
|
||||
<ImagePreview {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
wrapper.setState({index: 0});
|
||||
wrapper.instance().handleChangeImage(1);
|
||||
|
||||
expect(wrapper.state('index')).toEqual(1);
|
||||
});
|
||||
|
||||
test('should match call getItemMeasures & navigator.setStyle on close', () => {
|
||||
const getItemMeasures = jest.fn();
|
||||
const navigator = {setStyle: jest.fn()};
|
||||
const wrapper = shallow(
|
||||
<ImagePreview
|
||||
{...baseProps}
|
||||
getItemMeasures={getItemMeasures}
|
||||
navigator={navigator}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
wrapper.instance().close();
|
||||
|
||||
expect(navigator.setStyle).toHaveBeenCalledTimes(2);
|
||||
expect(navigator.setStyle).toBeCalledWith({screenBackgroundColor: 'transparent'});
|
||||
});
|
||||
});
|
||||
|
|
@ -127,6 +127,9 @@
|
|||
"setupTestFrameworkScriptFile": "<rootDir>/test/setup.js",
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/"
|
||||
]
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"assets/images/video_player/(.*).png": "<rootDir>/dist/assets/images/video_player/$1@2x.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue