* Use bottom sheet over modal * check-style fixes
This commit is contained in:
parent
4b58f01597
commit
dfc50b7dff
2 changed files with 147 additions and 26 deletions
|
|
@ -13,6 +13,7 @@ import {
|
|||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
findNodeHandle,
|
||||
} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
|
@ -32,8 +33,8 @@ import {getLocalFilePathFromFile, isDocument, isVideo} from 'app/utils/file';
|
|||
import {emptyFunction} from 'app/utils/general';
|
||||
import {calculateDimensions} from 'app/utils/images';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {
|
||||
showModalOverCurrentContext,
|
||||
dismissModal,
|
||||
mergeNavigationOptions,
|
||||
} from 'app/actions/navigation';
|
||||
|
|
@ -89,6 +90,8 @@ export default class ImagePreview extends PureComponent {
|
|||
showDownloader: false,
|
||||
target: props.target,
|
||||
};
|
||||
|
||||
this.headerRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -267,6 +270,7 @@ export default class ImagePreview extends PureComponent {
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
ref={this.headerRef}
|
||||
onPress={action}
|
||||
style={style.headerIcon}
|
||||
>
|
||||
|
|
@ -434,7 +438,9 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
|
||||
this.setState({showHeaderFooter: show});
|
||||
StatusBar.setHidden(!show, 'slide');
|
||||
if (Platform.OS === 'ios') {
|
||||
StatusBar.setHidden(!show, 'slide');
|
||||
}
|
||||
|
||||
Animated.timing(this.headerFooterAnim, {
|
||||
...ANIM_CONFIG,
|
||||
|
|
@ -465,7 +471,8 @@ export default class ImagePreview extends PureComponent {
|
|||
showDownloadOptionsIOS = async () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const file = this.getCurrentFile();
|
||||
const items = [];
|
||||
const options = [];
|
||||
const actions = [];
|
||||
let permissionRequest;
|
||||
|
||||
const hasPermissionToStorage = await Permissions.check('photo');
|
||||
|
|
@ -510,37 +517,37 @@ export default class ImagePreview extends PureComponent {
|
|||
const path = getLocalFilePathFromFile(VIDEOS_PATH, file);
|
||||
const exist = await RNFetchBlob.fs.exists(path);
|
||||
if (exist) {
|
||||
items.push({
|
||||
action: this.saveVideoIOS,
|
||||
text: {
|
||||
id: t('mobile.image_preview.save_video'),
|
||||
defaultMessage: 'Save Video',
|
||||
},
|
||||
});
|
||||
options.push(formatMessage({
|
||||
id: t('mobile.image_preview.save_video'),
|
||||
defaultMessage: 'Save Video',
|
||||
}));
|
||||
actions.push(this.saveVideoIOS);
|
||||
} else {
|
||||
this.showVideoDownloadRequiredAlertIOS();
|
||||
}
|
||||
} else {
|
||||
items.push({
|
||||
action: this.showDownloader,
|
||||
text: {
|
||||
id: t('mobile.image_preview.save'),
|
||||
defaultMessage: 'Save Image',
|
||||
},
|
||||
});
|
||||
options.push(formatMessage({
|
||||
id: t('mobile.image_preview.save'),
|
||||
defaultMessage: 'Save Image',
|
||||
}));
|
||||
actions.push(this.showDownloader);
|
||||
}
|
||||
|
||||
if (items.length) {
|
||||
this.setHeaderAndFooterVisible(false);
|
||||
if (options.length) {
|
||||
options.push(formatMessage({
|
||||
id: 'mobile.post.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
}));
|
||||
actions.push(emptyFunction);
|
||||
|
||||
const screen = 'OptionsModal';
|
||||
const passProps = {
|
||||
BottomSheet.showBottomSheetWithOptions({
|
||||
options,
|
||||
cancelButtonIndex: options.length - 1,
|
||||
title: file.caption,
|
||||
items,
|
||||
onCancelPress: () => this.setHeaderAndFooterVisible(true),
|
||||
};
|
||||
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
anchor: this.headerRef.current ? findNodeHandle(this.headerRef.current) : null,
|
||||
}, (buttonIndex) => {
|
||||
actions[buttonIndex]();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import {shallow} from 'enzyme';
|
|||
import {
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
|
||||
import ImagePreview from './image_preview';
|
||||
|
||||
|
|
@ -20,6 +22,11 @@ jest.mock('react-native-doc-viewer', () => {
|
|||
OpenFile: jest.fn(),
|
||||
};
|
||||
});
|
||||
jest.mock('react-native-permissions', () => {
|
||||
return {
|
||||
check: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ImagePreview', () => {
|
||||
const baseProps = {
|
||||
|
|
@ -108,4 +115,111 @@ describe('ImagePreview', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should show bottom sheet when showDownloadOptionsIOS is called for existing video', async () => {
|
||||
BottomSheet.showBottomSheetWithOptions = jest.fn();
|
||||
RNFetchBlob.fs.exists = jest.fn().mockReturnValue(true);
|
||||
const formatMessage = jest.fn(({defaultMessage}) => {
|
||||
return defaultMessage;
|
||||
});
|
||||
|
||||
const index = 0;
|
||||
const files = [{
|
||||
caption: 'caption',
|
||||
data: {
|
||||
mime_type: 'video/mp4',
|
||||
},
|
||||
}];
|
||||
const props = {
|
||||
...baseProps,
|
||||
index,
|
||||
files,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ImagePreview
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: {formatMessage}}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
await instance.showDownloadOptionsIOS();
|
||||
|
||||
const expectedOptions = {
|
||||
options: ['Save Video', 'Cancel'],
|
||||
cancelButtonIndex: 1,
|
||||
anchor: null,
|
||||
title: files[index].caption,
|
||||
};
|
||||
expect(BottomSheet.showBottomSheetWithOptions).
|
||||
toHaveBeenCalledWith(expectedOptions, expect.any(Function));
|
||||
});
|
||||
|
||||
test('should not show bottom sheet when showDownloadOptionsIOS is called for non-existing video', async () => {
|
||||
BottomSheet.showBottomSheetWithOptions = jest.fn();
|
||||
RNFetchBlob.fs.exists = jest.fn().mockReturnValue(false);
|
||||
|
||||
const index = 0;
|
||||
const files = [{
|
||||
caption: 'caption',
|
||||
data: {
|
||||
mime_type: 'video/mp4',
|
||||
},
|
||||
}];
|
||||
const props = {
|
||||
...baseProps,
|
||||
index,
|
||||
files,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ImagePreview
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
await instance.showDownloadOptionsIOS();
|
||||
|
||||
expect(BottomSheet.showBottomSheetWithOptions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should show bottom sheet when showDownloadOptionsIOS is called for image', async () => {
|
||||
BottomSheet.showBottomSheetWithOptions = jest.fn();
|
||||
RNFetchBlob.fs.exists = jest.fn().mockReturnValue(true);
|
||||
const formatMessage = jest.fn(({defaultMessage}) => {
|
||||
return defaultMessage;
|
||||
});
|
||||
|
||||
const index = 0;
|
||||
const files = [{
|
||||
caption: 'caption',
|
||||
data: {
|
||||
mime_type: 'image/jpeg',
|
||||
},
|
||||
}];
|
||||
const props = {
|
||||
...baseProps,
|
||||
index,
|
||||
files,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ImagePreview
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: {formatMessage}}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
await instance.showDownloadOptionsIOS();
|
||||
|
||||
const expectedOptions = {
|
||||
options: ['Save Image', 'Cancel'],
|
||||
cancelButtonIndex: 1,
|
||||
anchor: null,
|
||||
title: files[index].caption,
|
||||
};
|
||||
expect(BottomSheet.showBottomSheetWithOptions).
|
||||
toHaveBeenCalledWith(expectedOptions, expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue