diff --git a/Makefile b/Makefile index fc9716e35..99d340836 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,11 @@ POD := $(shell command -v pod 2> /dev/null) .podinstall: ifdef POD - @echo Getting CocoaPods dependencies; + @echo Getting Cocoapods dependencies; @cd ios && pod install; +else + @echo "Cocoapods is not installed https://cocoapods.org/" + @exit 1 endif @touch $@ @@ -117,6 +120,7 @@ post-install: @sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json @sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json @sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json + @sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_SENSOR);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java start-packager: @if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index be5c57796..ba36bb1a4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -53,6 +53,9 @@ + diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java index 8faba1f5a..66568d287 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java @@ -1,6 +1,7 @@ package com.mattermost.rnbeta; import android.app.Activity; +import android.content.pm.ActivityInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.RestrictionsManager; @@ -10,6 +11,7 @@ import android.os.Bundle; import android.util.Log; import android.util.ArraySet; import android.view.WindowManager.LayoutParams; +import android.content.res.Configuration; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; @@ -143,6 +145,15 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A mListeners.remove(listener); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (mVisibleActivity != null) { + Intent intent = new Intent("onConfigurationChanged"); + intent.putExtra("newConfig", newConfig); + mVisibleActivity.sendBroadcast(intent); + } + } + private synchronized void switchToVisible(Activity activity) { if (mVisibleActivity == null) { mVisibleActivity = activity; diff --git a/app/actions/device/index.js b/app/actions/device/index.js new file mode 100644 index 000000000..faf5f214d --- /dev/null +++ b/app/actions/device/index.js @@ -0,0 +1,55 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {Dimensions} from 'react-native'; + +import {DeviceTypes} from 'app/constants'; + +export function calculateDeviceDimensions() { + const {height, width} = Dimensions.get('window'); + return { + type: DeviceTypes.DEVICE_DIMENSIONS_CHANGED, + data: { + deviceHeight: height, + deviceWidth: width + } + }; +} + +export function connection(isOnline) { + return async (dispatch, getState) => { + dispatch({ + type: DeviceTypes.CONNECTION_CHANGED, + data: isOnline + }, getState); + }; +} + +export function setStatusBarHeight(height = 20) { + return { + type: DeviceTypes.STATUSBAR_HEIGHT_CHANGED, + data: height + }; +} + +export function setDeviceOrientation(orientation) { + return { + type: DeviceTypes.DEVICE_ORIENTATION_CHANGED, + data: orientation + }; +} + +export function setDeviceAsTablet() { + return { + type: DeviceTypes.DEVICE_TYPE_CHANGED, + data: true + }; +} + +export default { + calculateDeviceDimensions, + connection, + setDeviceOrientation, + setDeviceAsTablet, + setStatusBarHeight +}; diff --git a/app/actions/views/connection.js b/app/actions/views/connection.js deleted file mode 100644 index 67c9d9e09..000000000 --- a/app/actions/views/connection.js +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {ViewTypes} from 'app/constants'; - -export function connection(isOnline) { - return async (dispatch, getState) => { - dispatch({ - type: ViewTypes.CONNECTION_CHANGED, - data: isOnline - }, getState); - }; -} diff --git a/app/actions/views/root.js b/app/actions/views/root.js index e60dfb8ba..9caed27b4 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -68,13 +68,6 @@ export function goToNotification(notification) { }; } -export function setStatusBarHeight(height = 20) { - return { - type: ViewTypes.STATUSBAR_HEIGHT_CHANGED, - data: height - }; -} - export function purgeOfflineStore() { return {type: General.OFFLINE_STORE_PURGE}; } @@ -84,5 +77,5 @@ export default { queueNotification, clearNotification, goToNotification, - setStatusBarHeight + purgeOfflineStore }; diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 4b67a273f..9c254c69d 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -22,6 +22,7 @@ import {General} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; const DRAWER_INITIAL_OFFSET = 40; +const DRAWER_LANDSCAPE_OFFSET = 150; export default class ChannelDrawer extends PureComponent { static propTypes = { @@ -39,19 +40,29 @@ export default class ChannelDrawer extends PureComponent { currentChannelId: PropTypes.string.isRequired, currentTeamId: PropTypes.string.isRequired, currentUserId: PropTypes.string.isRequired, + isLandscape: PropTypes.bool.isRequired, + isTablet: PropTypes.bool.isRequired, intl: PropTypes.object.isRequired, navigator: PropTypes.object, teamsCount: PropTypes.number.isRequired, theme: PropTypes.object.isRequired }; - state = { - openDrawer: false, - openDrawerOffset: DRAWER_INITIAL_OFFSET - }; - swiperIndex = 1; + constructor(props) { + super(props); + + let openDrawerOffset = DRAWER_INITIAL_OFFSET; + if (props.isLandscape || props.isTablet) { + openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; + } + this.state = { + openDrawer: false, + openDrawerOffset + }; + } + componentWillMount() { this.props.actions.getTeams(); } @@ -62,6 +73,17 @@ export default class ChannelDrawer extends PureComponent { BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack); } + componentWillReceiveProps(nextProps) { + const {isLandscape, isTablet} = this.props; + if (nextProps.isLandscape !== isLandscape || nextProps.isTablet || isTablet) { + let openDrawerOffset = DRAWER_INITIAL_OFFSET; + if (nextProps.isLandscape || nextProps.isTablet) { + openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; + } + this.setState({openDrawerOffset}); + } + } + componentWillUnmount() { EventEmitter.off('open_channel_drawer', this.openChannelDrawer); EventEmitter.off('close_channel_drawer', this.closeChannelDrawer); @@ -81,6 +103,10 @@ export default class ChannelDrawer extends PureComponent { this.setState({openDrawer: false}); }; + drawerSwiperRef = (ref) => { + this.drawerSwiper = ref; + }; + handleDrawerClose = () => { this.resetDrawer(); @@ -93,7 +119,7 @@ export default class ChannelDrawer extends PureComponent { }; handleDrawerOpen = () => { - if (this.state.openDrawerOffset === DRAWER_INITIAL_OFFSET) { + if (this.state.openDrawerOffset !== 0) { Keyboard.dismiss(); } }; @@ -210,8 +236,13 @@ export default class ChannelDrawer extends PureComponent { onSearchEnds = () => { //hack to update the drawer when the offset changes + const {isLandscape, isTablet} = this.props; this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle - this.setState({openDrawerOffset: DRAWER_INITIAL_OFFSET}); + let openDrawerOffset = DRAWER_INITIAL_OFFSET; + if (isLandscape || isTablet) { + openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; + } + this.setState({openDrawerOffset}); }; onSearchStart = () => { @@ -221,13 +252,13 @@ export default class ChannelDrawer extends PureComponent { showTeams = () => { if (this.swiperIndex === 1 && this.props.teamsCount > 1) { - this.refs.swiper.showTeamsPage(); + this.drawerSwiper.getWrappedInstance().showTeamsPage(); } }; resetDrawer = () => { if (this.swiperIndex !== 1) { - this.refs.swiper.resetPage(); + this.drawerSwiper.getWrappedInstance().resetPage(); } }; @@ -242,7 +273,7 @@ export default class ChannelDrawer extends PureComponent { openDrawerOffset } = this.state; - const showTeams = openDrawerOffset === DRAWER_INITIAL_OFFSET && teamsCount > 1; + const showTeams = openDrawerOffset !== 0 && teamsCount > 1; const teams = ( @@ -268,7 +299,7 @@ export default class ChannelDrawer extends PureComponent { return ( true, + openDrawerOffset: 0 + }; + + state = { + index: 1 + }; + + componentWillReceiveProps(nextProps) { + if (this.refs.swiper) { + if (nextProps.openDrawerOffset !== this.props.openDrawerOffset || nextProps.isLandscape !== this.props.isLandscape) { + this.refs.swiper.initialRender = true; + if (this.state.index === 1) { + this.resetPage(); + } + } + } + } + + swiperPageSelected = (e, state, context) => { + this.props.onPageSelected(context.state.index); + this.setState({index: context.state.index}); + }; + + showTeamsPage = () => { + this.refs.swiper.scrollBy(-1, true); + }; + + resetPage = () => { + this.refs.swiper.scrollBy(1, false); + }; + + render() { + const { + children, + deviceHeight, + deviceWidth, + openDrawerOffset, + showTeams, + theme + } = this.props; + + const pagination = {bottom: 0}; + if (showTeams) { + pagination.bottom = 0; + } + + return ( + + {children} + + ); + } +} diff --git a/app/components/channel_drawer/drawer_swipper/index.js b/app/components/channel_drawer/drawer_swipper/index.js index faea416c3..9b2bc08d2 100644 --- a/app/components/channel_drawer/drawer_swipper/index.js +++ b/app/components/channel_drawer/drawer_swipper/index.js @@ -1,82 +1,18 @@ // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {Dimensions} from 'react-native'; -import Swiper from 'react-native-swiper'; +import {connect} from 'react-redux'; -import {changeOpacity} from 'app/utils/theme'; +import {getDimensions, isLandscape} from 'app/selectors/device'; -export default class DrawerSwiper extends PureComponent { - static propTypes = { - children: PropTypes.node.isRequired, - onPageSelected: PropTypes.func, - openDrawerOffset: PropTypes.number, - showTeams: PropTypes.bool.isRequired, - theme: PropTypes.object.isRequired +import DraweSwiper from './drawer_swiper'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + ...getDimensions(state), + isLandscape: isLandscape(state) }; - - static defaultProps = { - onPageSelected: () => true, - openDrawerOffset: 0 - }; - - componentWillReceiveProps(nextProps) { - if (nextProps.openDrawerOffset !== this.props.openDrawerOffset && this.refs.swiper) { - this.refs.swiper.initialRender = true; - } - } - - swiperPageSelected = (e, state, context) => { - this.props.onPageSelected(context.state.index); - }; - - showTeamsPage = () => { - this.refs.swiper.scrollBy(-1, true); - }; - - resetPage = () => { - this.refs.swiper.scrollBy(1, false); - }; - - render() { - const { - children, - openDrawerOffset, - showTeams, - theme - } = this.props; - - const pagination = {bottom: 0}; - if (showTeams) { - pagination.bottom = 0; - } - - // Get the dimensions here so when the orientation changes we get the right dimensions - const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window'); - - return ( - - {children} - - ); - } } + +export default connect(mapStateToProps, null, null, {withRef: true})(DraweSwiper); diff --git a/app/components/channel_drawer/index.js b/app/components/channel_drawer/index.js index 97da0ed02..7858984e6 100644 --- a/app/components/channel_drawer/index.js +++ b/app/components/channel_drawer/index.js @@ -11,6 +11,7 @@ import {getCurrentTeamId, getTeamMemberships} from 'mattermost-redux/selectors/e import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel'; import {makeDirectChannel} from 'app/actions/views/more_dms'; +import {isLandscape, isTablet} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import ChannelDrawer from './channel_drawer.js'; @@ -23,6 +24,8 @@ function mapStateToProps(state, ownProps) { currentTeamId: getCurrentTeamId(state), currentChannelId: getCurrentChannelId(state), currentUserId, + isLandscape: isLandscape(state), + isTablet: isTablet(state), teamsCount: Object.keys(getTeamMemberships(state)).length, theme: getTheme(state) }; diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js index d23dd7986..27c22dd5e 100644 --- a/app/components/emoji_picker/emoji_picker.js +++ b/app/components/emoji_picker/emoji_picker.js @@ -5,7 +5,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {injectIntl, intlShape} from 'react-intl'; import { - Dimensions, SectionList, TouchableOpacity, View @@ -17,7 +16,6 @@ import SearchBar from 'app/components/search_bar'; import {emptyFunction} from 'app/utils/general'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; -const {width: deviceWidth} = Dimensions.get('window'); const EMOJI_SIZE = 30; const EMOJI_GUTTER = 7.5; const SECTION_MARGIN = 15; @@ -25,6 +23,7 @@ const SECTION_MARGIN = 15; class EmojiPicker extends PureComponent { static propTypes = { emojis: PropTypes.array.isRequired, + deviceWidth: PropTypes.number.isRequired, intl: intlShape.isRequired, onEmojiPress: PropTypes.func, theme: PropTypes.object.isRequired @@ -43,10 +42,19 @@ class EmojiPicker extends PureComponent { this.state = { emojis: props.emojis, - searchTerm: '' + searchTerm: '', + width: props.deviceWidth - (SECTION_MARGIN * 2) }; } + componentWillReceiveProps(nextProps) { + if (nextProps.deviceWidth !== this.props.deviceWidth) { + this.setState({ + width: nextProps.deviceWidth - (SECTION_MARGIN * 2) + }); + } + } + changeSearchTerm = (text) => { this.setState({ searchTerm: text @@ -67,11 +75,11 @@ class EmojiPicker extends PureComponent { emojis: this.props.emojis, searchTerm: '' }); - } + }; filterEmojiAliases = (aliases, searchTerm) => { return aliases.findIndex((alias) => alias.includes(searchTerm)) !== -1; - } + }; searchEmojis = (searchTerm) => { const {emojis} = this.props; @@ -106,7 +114,7 @@ class EmojiPicker extends PureComponent { }); return nextEmojis; - } + }; renderSectionHeader = ({section}) => { const {theme} = this.props; @@ -121,7 +129,7 @@ class EmojiPicker extends PureComponent { /> ); - } + }; renderEmojis = (emojis, index) => { const {theme} = this.props; @@ -157,13 +165,13 @@ class EmojiPicker extends PureComponent { })} ); - } + }; renderItem = ({item}) => { const {theme} = this.props; const styles = getStyleSheetFromTheme(theme); - const numColumns = Number(((deviceWidth - (SECTION_MARGIN * 2)) / (EMOJI_SIZE + (EMOJI_GUTTER * 2))).toFixed(0)); + const numColumns = Number((this.state.width / (EMOJI_SIZE + (EMOJI_GUTTER * 2))).toFixed(0)); const slices = item.items.reduce((slice, emoji, emojiIndex) => { if (emojiIndex % numColumns === 0 && emojiIndex !== 0) { @@ -180,7 +188,7 @@ class EmojiPicker extends PureComponent { {slices.map(this.renderEmojis)} ); - } + }; render() { const {intl, theme} = this.props; @@ -253,8 +261,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => { marginRight: 0 }, listView: { - backgroundColor: theme.centerChannelBg, - width: deviceWidth - (SECTION_MARGIN * 2) + backgroundColor: theme.centerChannelBg }, searchBar: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), diff --git a/app/components/emoji_picker/index.js b/app/components/emoji_picker/index.js index 38d05a9ef..e43943835 100644 --- a/app/components/emoji_picker/index.js +++ b/app/components/emoji_picker/index.js @@ -6,6 +6,7 @@ import {createSelector} from 'reselect'; import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis'; +import {getDimensions} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import {CategoryNames, Emojis, EmojiIndicesByCategory} from 'app/utils/emojis'; @@ -98,9 +99,11 @@ const getEmojisBySection = createSelector( function mapStateToProps(state) { const emojis = getEmojisBySection(state); + const {deviceWidth} = getDimensions(state); return { emojis, + deviceWidth, theme: getTheme(state) }; } diff --git a/app/components/file_upload_preview/file_upload_preview.js b/app/components/file_upload_preview/file_upload_preview.js index 9844c00a0..246c9bf04 100644 --- a/app/components/file_upload_preview/file_upload_preview.js +++ b/app/components/file_upload_preview/file_upload_preview.js @@ -4,7 +4,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Dimensions, Platform, ScrollView, StyleSheet, @@ -17,8 +16,6 @@ import FileAttachmentImage from 'app/components/file_attachment_list/file_attach import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon'; import KeyboardLayout from 'app/components/layout/keyboard_layout'; -const {height: deviceHeight} = Dimensions.get('window'); - export default class FileUploadPreview extends PureComponent { static propTypes = { actions: PropTypes.shape({ @@ -29,6 +26,7 @@ export default class FileUploadPreview extends PureComponent { channelId: PropTypes.string.isRequired, channelIsLoading: PropTypes.bool, createPostRequestStatus: PropTypes.string.isRequired, + deviceHeight: PropTypes.number.isRequired, fetchCache: PropTypes.object.isRequired, files: PropTypes.array.isRequired, inputHeight: PropTypes.number.isRequired, @@ -98,7 +96,7 @@ export default class FileUploadPreview extends PureComponent { ); }); - } + }; render() { if (this.props.channelIsLoading || (!this.props.files.length && !this.props.filesUploadingForCurrentChannel)) { @@ -107,7 +105,7 @@ export default class FileUploadPreview extends PureComponent { return ( - + = 2; return { appState, isConnecting, - isOnline: connection, + isOnline: getConnection(state), webSocketStatus, ...ownProps }; diff --git a/app/components/post_body_additional_content/index.js b/app/components/post_body_additional_content/index.js index 5f5c95dbf..03700017d 100644 --- a/app/components/post_body_additional_content/index.js +++ b/app/components/post_body_additional_content/index.js @@ -12,6 +12,7 @@ import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/po import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {ViewTypes} from 'app/constants'; +import {getDimensions} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import {extractFirstLink} from 'app/utils/url'; @@ -24,6 +25,7 @@ function mapStateToProps(state, ownProps) { return { ...ownProps, + ...getDimensions(state), config, link, openGraphData: getOpenGraphMetadataForUrl(state, link), diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js index 6b27be4fd..8fa69f9af 100644 --- a/app/components/post_body_additional_content/post_body_additional_content.js +++ b/app/components/post_body_additional_content/post_body_additional_content.js @@ -4,7 +4,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Dimensions, Image, Linking, Platform, @@ -12,7 +11,7 @@ import { TouchableWithoutFeedback, View } from 'react-native'; -import Orientation from 'react-native-orientation'; + import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube'; import youTubeVideoId from 'youtube-video-id'; import youtubePlayIcon from 'assets/images/icons/youtube-play-icon.png'; @@ -29,6 +28,8 @@ export default class PostBodyAdditionalContent extends PureComponent { baseTextStyle: CustomPropTypes.Style, blockStyles: PropTypes.object, config: PropTypes.object, + deviceHeight: PropTypes.number.isRequired, + deviceWidth: PropTypes.number.isRequired, link: PropTypes.string, message: PropTypes.string.isRequired, navigator: PropTypes.object.isRequired, @@ -71,9 +72,10 @@ export default class PostBodyAdditionalContent extends PureComponent { } calculateDimensions = (width, height) => { - const {width: deviceWidth} = Dimensions.get('window'); + const {deviceHeight, deviceWidth} = this.props; let maxHeight = MAX_IMAGE_HEIGHT; - let maxWidth = deviceWidth - 68; + const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth; + let maxWidth = deviceSize - 78; if (height <= MAX_IMAGE_HEIGHT) { maxHeight = height; @@ -218,11 +220,8 @@ export default class PostBodyAdditionalContent extends PureComponent { const {link} = this.props; const videoId = youTubeVideoId(link); - Orientation.unlockAllOrientations(); if (Platform.OS === 'ios') { - YouTubeStandaloneIOS.playVideo(videoId).then(() => { - Orientation.lockToPortrait(); - }); + YouTubeStandaloneIOS.playVideo(videoId); } else { const {config} = this.props; @@ -231,12 +230,9 @@ export default class PostBodyAdditionalContent extends PureComponent { apiKey: config.GoogleDeveloperKey, videoId, autoplay: true - }).then(() => { - Orientation.lockToPortrait(); }); } else { Linking.openURL(link); - Orientation.lockToPortrait(); } } }; diff --git a/app/components/search_preview/search_preview.js b/app/components/search_preview/search_preview.js index 09eb9d0a1..96c2b53d4 100644 --- a/app/components/search_preview/search_preview.js +++ b/app/components/search_preview/search_preview.js @@ -4,7 +4,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Dimensions, Platform, Text, TouchableOpacity, @@ -90,7 +89,6 @@ export default class SearchPreview extends PureComponent { render() { const {channelName, currentUserId, posts, theme} = this.props; - const {height, width} = Dimensions.get('window'); const style = getStyleSheet(theme); let postList; @@ -113,7 +111,7 @@ export default class SearchPreview extends PureComponent { return ( { container: { position: 'absolute', backgroundColor: changeOpacity('#000', 0.3), + height: '100%', top: 0, left: 0, - zIndex: 10 + zIndex: 10, + width: '100%' }, wrapper: { flex: 1, diff --git a/app/constants/device.js b/app/constants/device.js new file mode 100644 index 000000000..8f0b096b7 --- /dev/null +++ b/app/constants/device.js @@ -0,0 +1,12 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import keyMirror from 'mattermost-redux/utils/key_mirror'; + +export default keyMirror({ + CONNECTION_CHANGED: null, + DEVICE_DIMENSIONS_CHANGED: null, + DEVICE_TYPE_CHANGED: null, + DEVICE_ORIENTATION_CHANGED: null, + STATUSBAR_HEIGHT_CHANGED: null +}); diff --git a/app/constants/index.js b/app/constants/index.js index 91bc7f280..7269a537d 100644 --- a/app/constants/index.js +++ b/app/constants/index.js @@ -1,10 +1,12 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import DeviceTypes from './device'; import NavigationTypes from './navigation'; import ViewTypes from './view'; export { + DeviceTypes, NavigationTypes, ViewTypes }; diff --git a/app/constants/view.js b/app/constants/view.js index f2e5dc6f0..14bf61412 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -20,8 +20,6 @@ const ViewTypes = keyMirror({ NOTIFICATION_IN_APP: null, NOTIFICATION_TAPPED: null, - CONNECTION_CHANGED: null, - SET_POST_DRAFT: null, SET_COMMENT_DRAFT: null, @@ -47,9 +45,7 @@ const ViewTypes = keyMirror({ INCREASE_POST_VISIBILITY: null, RECEIVED_FOCUSED_POST: null, - LOADING_POSTS: null, - - STATUSBAR_HEIGHT_CHANGED: null + LOADING_POSTS: null }); export default { diff --git a/app/initial_state.js b/app/initial_state.js index c3d869a61..9e57b2905 100644 --- a/app/initial_state.js +++ b/app/initial_state.js @@ -246,6 +246,9 @@ const state = { } } }, + device: { + connection: true + }, navigation: '', views: { channel: { @@ -253,7 +256,6 @@ const state = { loading: false, tooltipVisible: false }, - connection: true, fetchCache: {}, i18n: { locale: '' diff --git a/app/mattermost.js b/app/mattermost.js index e7dfc262d..3adbb7eb3 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -29,11 +29,16 @@ import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; import {Client, Client4} from 'mattermost-redux/client'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; +import { + calculateDeviceDimensions, + setDeviceOrientation, + setDeviceAsTablet, + setStatusBarHeight +} from 'app/actions/device'; import { goToNotification, loadConfigAndLicense, queueNotification, - setStatusBarHeight, purgeOfflineStore } from 'app/actions/views/root'; import {setChannelDisplayName} from 'app/actions/views/channel'; @@ -69,7 +74,7 @@ export default class Mattermost { this.isConfigured = false; this.allowOtherServers = true; - Orientation.lockToPortrait(); + Orientation.unlockAllOrientations(); initializeSentry(); @@ -80,7 +85,7 @@ export default class Mattermost { EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.handleReset); EventEmitter.on(General.DEFAULT_CHANNEL, this.handleResetDisplayName); EventEmitter.on(NavigationTypes.RESTART_APP, this.restartApp); - + Orientation.addOrientationListener(this.orientationDidChange); mattermostManaged.addEventListener('managedConfigDidChange', this.handleManagedConfig); this.handleAppStateChange(AppState.currentState); @@ -373,6 +378,10 @@ export default class Mattermost { // We need to wait for hydration to occur before load the router. listenForHydration = () => { const state = this.store.getState(); + Orientation.getOrientation((orientation) => { + this.orientationDidChange(orientation); + }); + if (state.views.root.hydrationComplete) { this.unsubscribeFromStore(); @@ -498,6 +507,17 @@ export default class Mattermost { } }; + orientationDidChange = (orientation) => { + const {dispatch} = this.store; + dispatch(setDeviceOrientation(orientation)); + if (DeviceInfo.isTablet()) { + dispatch(setDeviceAsTablet()); + } + setTimeout(() => { + dispatch(calculateDeviceDimensions()); + }, 100); + }; + resetBadgeAndVersion = () => { const {dispatch, getState} = this.store; Client4.serverVersion = ''; @@ -558,6 +578,9 @@ export default class Mattermost { passProps: { allowOtherServers: this.allowOtherServers }, + appStyle: { + orientation: 'auto' + }, animationType }); }; diff --git a/app/reducers/views/connection.js b/app/reducers/device/connection.js similarity index 81% rename from app/reducers/views/connection.js rename to app/reducers/device/connection.js index 76fbb34a9..76f7fbe28 100644 --- a/app/reducers/views/connection.js +++ b/app/reducers/device/connection.js @@ -2,11 +2,11 @@ // See License.txt for license information. import {UserTypes} from 'mattermost-redux/action_types'; -import {ViewTypes} from 'app/constants'; +import {DeviceTypes} from 'app/constants'; export default function connection(state = true, action) { switch (action.type) { - case ViewTypes.CONNECTION_CHANGED: + case DeviceTypes.CONNECTION_CHANGED: return action.data; case UserTypes.LOGOUT_SUCCESS: diff --git a/app/reducers/device/dimension.js b/app/reducers/device/dimension.js new file mode 100644 index 000000000..16b2b668a --- /dev/null +++ b/app/reducers/device/dimension.js @@ -0,0 +1,30 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {Dimensions} from 'react-native'; + +import {UserTypes} from 'mattermost-redux/action_types'; +import {DeviceTypes} from 'app/constants'; + +const {height, width} = Dimensions.get('window'); +const initialState = { + deviceHeight: height, + deviceWidth: width +}; + +export default function dimension(state = initialState, action) { + switch (action.type) { + case DeviceTypes.DEVICE_DIMENSIONS_CHANGED: { + const {data} = action; + if (state.deviceWidth !== data.deviceWidth || state.deviceHeight !== data.deviceHeight) { + return {...data}; + } + break; + } + case UserTypes.LOGOUT_SUCCESS: + return initialState; + } + + return state; +} + diff --git a/app/reducers/device/index.js b/app/reducers/device/index.js new file mode 100644 index 000000000..afbf13377 --- /dev/null +++ b/app/reducers/device/index.js @@ -0,0 +1,18 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {combineReducers} from 'redux'; + +import connection from './connection'; +import dimension from './dimension'; +import isTablet from './is_tablet'; +import orientation from './orientation'; +import statusBarHeight from './status_bar'; + +export default combineReducers({ + connection, + dimension, + isTablet, + orientation, + statusBarHeight +}); diff --git a/app/reducers/device/is_tablet.js b/app/reducers/device/is_tablet.js new file mode 100644 index 000000000..d079d73ee --- /dev/null +++ b/app/reducers/device/is_tablet.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {DeviceTypes} from 'app/constants'; + +export default function isTablet(state = false, action) { + switch (action.type) { + case DeviceTypes.DEVICE_TYPE_CHANGED: + return action.data; + } + + return state; +} + diff --git a/app/reducers/device/orientation.js b/app/reducers/device/orientation.js new file mode 100644 index 000000000..8b1ebab2e --- /dev/null +++ b/app/reducers/device/orientation.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {DeviceTypes} from 'app/constants'; + +export default function orientation(state = 'PORTRAIT', action) { + switch (action.type) { + case DeviceTypes.DEVICE_ORIENTATION_CHANGED: + return action.data; + } + + return state; +} + diff --git a/app/reducers/device/status_bar.js b/app/reducers/device/status_bar.js new file mode 100644 index 000000000..1b1859ec2 --- /dev/null +++ b/app/reducers/device/status_bar.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {DeviceTypes} from 'app/constants'; + +export default function height(state = 0, action) { + switch (action.type) { + case DeviceTypes.STATUSBAR_HEIGHT_CHANGED: + return action.data; + default: + return state; + } +} + diff --git a/app/reducers/index.js b/app/reducers/index.js index 430da8526..e80bcabcf 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -1,10 +1,12 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import device from './device'; import navigation from './navigation'; import views from './views'; export default { + device, navigation, views }; diff --git a/app/reducers/views/index.js b/app/reducers/views/index.js index a0caf1b15..38ed2978f 100644 --- a/app/reducers/views/index.js +++ b/app/reducers/views/index.js @@ -4,7 +4,6 @@ import {combineReducers} from 'redux'; import channel from './channel'; -import connection from './connection'; import fetchCache from './fetch_cache'; import i18n from './i18n'; import login from './login'; @@ -16,7 +15,6 @@ import team from './team'; import thread from './thread'; export default combineReducers({ - connection, channel, fetchCache, i18n, diff --git a/app/reducers/views/root.js b/app/reducers/views/root.js index c07316acd..bd24bdba7 100644 --- a/app/reducers/views/root.js +++ b/app/reducers/views/root.js @@ -37,18 +37,8 @@ function purge(state = false, action) { } } -function statusBarHeight(state = 20, action) { - switch (action.type) { - case ViewTypes.STATUSBAR_HEIGHT_CHANGED: - return action.data; - default: - return state; - } -} - export default combineReducers({ appInitializing, hydrationComplete, - purge, - statusBarHeight + purge }); diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 29bcf1253..2ce355ffe 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -5,7 +5,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {injectIntl, intlShape} from 'react-intl'; import { - Dimensions, NetInfo, Platform, View @@ -199,6 +198,16 @@ class Channel extends PureComponent { navigator={navigator} > + + + + + + + + - - - - - - - - ); } @@ -233,17 +232,12 @@ class Channel extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { - headerContainer: { - flex: 1, - position: 'absolute' - }, header: { backgroundColor: theme.sidebarHeaderBg, flexDirection: 'row', justifyContent: 'flex-start', - width: Dimensions.get('window').width, + width: '100%', zIndex: 10, - elevation: 2, ...Platform.select({ android: { height: 46 @@ -255,15 +249,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { }) }, postList: { - flex: 1, - ...Platform.select({ - android: { - marginTop: 46 - }, - ios: { - marginTop: 64 - } - }) + flex: 1 }, loading: { backgroundColor: theme.centerChannelBg, @@ -272,6 +258,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { keyboardLayout: { backgroundColor: theme.centerChannelBg, flex: 1, + zIndex: -1, paddingBottom: 0 } }; diff --git a/app/screens/channel/channel_drawer_button.js b/app/screens/channel/channel_drawer_button.js index 858a71aa8..6d79b0a17 100644 --- a/app/screens/channel/channel_drawer_button.js +++ b/app/screens/channel/channel_drawer_button.js @@ -25,7 +25,6 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; class ChannelDrawerButton extends PureComponent { static propTypes = { - applicationInitializing: PropTypes.bool.isRequired, currentTeamId: PropTypes.string.isRequired, myTeamMembers: PropTypes.object, theme: PropTypes.object, @@ -80,7 +79,6 @@ class ChannelDrawerButton extends PureComponent { render() { const { - applicationInitializing, currentTeamId, mentionCount, messageCount, @@ -89,10 +87,6 @@ class ChannelDrawerButton extends PureComponent { } = this.props; const style = getStyleFromTheme(theme); - if (applicationInitializing) { - return null; - } - let mentions = mentionCount; let messages = messageCount; @@ -184,7 +178,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { function mapStateToProps(state) { return { - applicationInitializing: state.views.root.appInitializing, currentTeamId: getCurrentTeamId(state), myTeamMembers: getTeamMemberships(state), theme: getTheme(state), diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js index ce29c4306..485ced103 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -6,7 +6,6 @@ import PropTypes from 'prop-types'; import {injectIntl, intlShape} from 'react-intl'; import { Animated, - Dimensions, Platform, StyleSheet, View @@ -207,7 +206,6 @@ class ChannelPostList extends PureComponent { } const refreshIndicatorDimensions = { - width: Dimensions.get('window').width, height: retryMessageHeight }; @@ -234,7 +232,8 @@ const style = StyleSheet.create({ paddingHorizontal: 10, position: 'absolute', top: 0, - overflow: 'hidden' + overflow: 'hidden', + width: '100%' } }); diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js index 3317d1611..cbfa4bd34 100644 --- a/app/screens/channel/channel_post_list/index.js +++ b/app/screens/channel/channel_post_list/index.js @@ -9,6 +9,7 @@ import {RequestStatus} from 'mattermost-redux/constants'; import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts'; import {getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel'; +import {getConnection} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import ChannelPostList from './channel_post_list'; @@ -22,7 +23,7 @@ function makeMapStateToProps() { const {getPosts, getPostsRetryAttempts, getPostsSince, getPostsSinceRetryAttempts} = state.requests.posts; const posts = getPostsInChannel(state, channelId) || []; const {websocket: websocketRequest} = state.requests.general; - const {connection: networkOnline} = state.views; + const networkOnline = getConnection(state); const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS; let getPostsStatus; diff --git a/app/screens/channel/channel_search_button.js b/app/screens/channel/channel_search_button.js index 4946d54cc..ec9615eaa 100644 --- a/app/screens/channel/channel_search_button.js +++ b/app/screens/channel/channel_search_button.js @@ -29,7 +29,6 @@ class ChannelSearchButton extends PureComponent { clearSearch: PropTypes.func.isRequired, handlePostDraftChanged: PropTypes.func.isRequired }).isRequired, - applicationInitializing: PropTypes.bool.isRequired, navigator: PropTypes.object, theme: PropTypes.object }; @@ -57,14 +56,9 @@ class ChannelSearchButton extends PureComponent { render() { const { - applicationInitializing, theme } = this.props; - if (applicationInitializing) { - return null; - } - return ( preventDoubleTap(this.handlePress, this)} @@ -98,7 +92,6 @@ const style = StyleSheet.create({ function mapStateToProps(state, ownProps) { return { - applicationInitializing: state.views.root.appInitializing, theme: getTheme(state), ...ownProps }; diff --git a/app/screens/channel/channel_title.js b/app/screens/channel/channel_title.js index a3d943a69..6a35fca16 100644 --- a/app/screens/channel/channel_title.js +++ b/app/screens/channel/channel_title.js @@ -16,10 +16,6 @@ import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels'; import {getTheme} from 'app/selectors/preferences'; function ChannelTitle(props) { - if (props.applicationInitializing) { - return null; - } - const channelName = props.displayName || props.currentChannel.display_name; let icon; if (channelName) { @@ -56,7 +52,6 @@ function ChannelTitle(props) { } ChannelTitle.propTypes = { - applicationInitializing: PropTypes.bool.isRequired, currentChannel: PropTypes.object, displayName: PropTypes.string, onPress: PropTypes.func, @@ -71,7 +66,6 @@ ChannelTitle.defaultProps = { function mapStateToProps(state) { return { - applicationInitializing: state.views.root.appInitializing, currentChannel: getCurrentChannel(state) || {}, displayName: state.views.channel.displayName, theme: getTheme(state) diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js index 34907a99e..2b013ac24 100644 --- a/app/screens/channel/index.js +++ b/app/screens/channel/index.js @@ -9,8 +9,9 @@ import { loadProfilesAndTeamMembersForDMSidebar, selectInitialChannel } from 'app/actions/views/channel'; -import {connection} from 'app/actions/views/connection'; +import {connection} from 'app/actions/device'; import {selectFirstAvailableTeam} from 'app/actions/views/select_team'; +import {getStatusBarHeight} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users'; @@ -27,7 +28,6 @@ import Channel from './channel'; function mapStateToProps(state, ownProps) { const {websocket} = state.requests.general; const {myChannels: channelsRequest} = state.requests.channels; - const {statusBarHeight} = state.views.root; return { ...ownProps, @@ -35,7 +35,7 @@ function mapStateToProps(state, ownProps) { currentChannelId: getCurrentChannelId(state), theme: getTheme(state), webSocketRequest: websocket, - statusBarHeight, + statusBarHeight: getStatusBarHeight(state), channelsRequestStatus: channelsRequest.status }; } diff --git a/app/screens/code/code.js b/app/screens/code/code.js index 9551db6c1..ff4f36efe 100644 --- a/app/screens/code/code.js +++ b/app/screens/code/code.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import { - Dimensions, ScrollView, StyleSheet, Text, @@ -14,10 +13,6 @@ import { import {getCodeFont} from 'app/utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -const { - width: deviceWidth -} = Dimensions.get('window'); - export default class Code extends React.PureComponent { static propTypes = { theme: PropTypes.object.isRequired, @@ -26,7 +21,7 @@ export default class Code extends React.PureComponent { countLines = (content) => { return content.split('\n').length; - } + }; render() { const style = getStyleSheet(this.props.theme); @@ -100,7 +95,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { codeContainer: { flexGrow: 0, flexShrink: 1, - width: deviceWidth + width: '100%' }, code: { paddingHorizontal: 6, diff --git a/app/screens/image_preview/downloader.ios.js b/app/screens/image_preview/downloader.ios.js index a523d331f..f08e4ebe1 100644 --- a/app/screens/image_preview/downloader.ios.js +++ b/app/screens/image_preview/downloader.ios.js @@ -6,7 +6,6 @@ import PropTypes from 'prop-types'; import { Animated, CameraRoll, - Dimensions, StyleSheet, Text, TouchableOpacity, @@ -22,10 +21,11 @@ import FormattedText from 'app/components/formatted_text'; import {emptyFunction} from 'app/utils/general'; const {View: AnimatedView} = Animated; -const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window'); export default class Downloader extends PureComponent { static propTypes = { + deviceHeight: PropTypes.number.isRequired, + deviceWidth: PropTypes.number.isRequired, file: PropTypes.object.isRequired, onDownloadCancel: PropTypes.func, onDownloadSuccess: PropTypes.func, @@ -38,9 +38,13 @@ export default class Downloader extends PureComponent { show: false }; - state = { - downloaderTop: new Animated.Value(deviceHeight), - progress: 0 + constructor(props) { + super(props); + + this.state = { + downloaderTop: new Animated.Value(props.deviceHeight), + progress: 0 + }; } componentWillReceiveProps(nextProps) { @@ -52,10 +56,27 @@ export default class Downloader extends PureComponent { }); } else if (!nextProps.show && this.props.show) { this.toggleDownloader(false); + } else if (this.props.deviceHeight !== nextProps.deviceHeight) { + this.recenterDownloader(nextProps); } } + recenterDownloader = (props) => { + const {deviceHeight, show} = props; + const top = show ? (deviceHeight / 2) - 100 : deviceHeight; + + Animated.sequence([ + Animated.delay(200), + Animated.spring(this.state.downloaderTop, { + toValue: top, + tension: 8, + friction: 5 + }) + ]).start(); + }; + toggleDownloader = (show = true) => { + const {deviceHeight} = this.props; const top = show ? (deviceHeight / 2) - 100 : deviceHeight; Animated.spring(this.state.downloaderTop, { @@ -67,7 +88,7 @@ export default class Downloader extends PureComponent { this.startDownload(); } }); - } + }; startDownload = async () => { try { @@ -181,15 +202,19 @@ export default class Downloader extends PureComponent { ); - } + }; render() { const {show} = this.props; + if (!show) { + return null; + } + const {didCancel, progress} = this.state; const trueProgress = didCancel ? 0 : progress; - const containerHeight = show ? deviceHeight : 0; + const containerHeight = show ? '100%' : 0; return ( @@ -241,11 +266,11 @@ const styles = StyleSheet.create({ position: 'absolute', top: 0, left: 0, - width: deviceWidth + width: '100%' }, downloader: { alignItems: 'center', - left: (deviceWidth / 2) - 118, + alignSelf: 'center', height: 220, width: 236, borderRadius: 8, diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index 40a35d046..b4d2352e7 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -5,7 +5,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { Animated, - Dimensions, InteractionManager, PanResponder, Platform, @@ -18,7 +17,6 @@ import { } from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import LinearGradient from 'react-native-linear-gradient'; -import Orientation from 'react-native-orientation'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -30,7 +28,6 @@ import Downloader from './downloader'; import Previewer from './previewer'; const {View: AnimatedView} = Animated; -const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window'); const DRAG_VERTICAL_THRESHOLD_START = 25; // When do we want to start capturing the drag 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 @@ -46,6 +43,8 @@ export default class ImagePreview extends PureComponent { addFileToFetchCache: PropTypes.func.isRequired }), canDownloadFiles: PropTypes.bool.isRequired, + deviceHeight: PropTypes.number.isRequired, + deviceWidth: PropTypes.number.isRequired, fetchCache: PropTypes.object.isRequired, fileId: PropTypes.string.isRequired, files: PropTypes.array.isRequired, @@ -60,10 +59,9 @@ export default class ImagePreview extends PureComponent { this.zoomableImages = {}; const currentFile = props.files.findIndex((file) => file.id === props.fileId); + this.state = { currentFile, - deviceHeight: deviceHeight - STATUSBAR_HEIGHT, - deviceWidth, drag: new Animated.ValueXY(), footerOpacity: new Animated.Value(1), pagingEnabled: true, @@ -85,7 +83,6 @@ export default class ImagePreview extends PureComponent { } componentDidMount() { - Orientation.unlockAllOrientations(); InteractionManager.runAfterInteractions(() => { this.scrollView.scrollTo({x: (this.state.currentFile) * this.state.deviceWidth, animated: false}); Animated.timing(this.state.wrapperViewOpacity, { @@ -95,8 +92,15 @@ export default class ImagePreview extends PureComponent { }); } + componentWillReceiveProps(nextProps) { + if (this.props.deviceWidth !== nextProps.deviceWidth && Platform.OS === 'android') { + InteractionManager.runAfterInteractions(() => { + this.scrollView.scrollTo({x: (this.state.currentFile * nextProps.deviceWidth), animated: false}); + }); + } + } + componentWillUnmount() { - Orientation.lockToPortrait(); if (Platform.OS === 'ios') { StatusBar.setHidden(false, 'fade'); } @@ -145,7 +149,7 @@ export default class ImagePreview extends PureComponent { handleImageDoubleTap = (x, y) => { this.zoomableImages[this.state.currentFile].toggleZoom(x, y); - } + }; setHeaderAndFileInfoVisible = (show) => { this.setState({ @@ -165,7 +169,7 @@ export default class ImagePreview extends PureComponent { }; handleScroll = (event) => { - const offset = event.nativeEvent.contentOffset.x / this.state.deviceWidth; + const offset = event.nativeEvent.contentOffset.x / this.props.deviceWidth; const wholeNumber = Number((offset).toFixed(0)); if (Math.abs(offset - wholeNumber) < 0.01) { this.setState({ @@ -184,15 +188,6 @@ export default class ImagePreview extends PureComponent { this.scrollView = c; }; - onLayout = (event) => { - if (event.nativeEvent.layout.width !== this.state.deviceWidth) { - this.setState({ - deviceHeight: event.nativeEvent.layout.height, - deviceWidth: event.nativeEvent.layout.width - }); - } - }; - imageIsZooming = (zooming) => { if (zooming !== this.state.isZooming) { this.setHeaderAndFileInfoVisible(!zooming); @@ -212,7 +207,7 @@ export default class ImagePreview extends PureComponent { } else { this.showIOSDownloadOptions(); } - } + }; showIOSDownloadOptions = () => { this.setHeaderAndFileInfoVisible(false); @@ -244,7 +239,7 @@ export default class ImagePreview extends PureComponent { modalPresentationStyle: 'overCurrentContext' } }); - } + }; showDownloader = () => { EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL); @@ -252,14 +247,14 @@ export default class ImagePreview extends PureComponent { this.setState({ showDownloader: true }); - } + }; hideDownloader = (hideFileInfo = true) => { this.setState({showDownloader: false}); if (hideFileInfo) { this.setHeaderAndFileInfoVisible(true); } - } + }; renderDownloadButton = () => { const {canDownloadFiles, files} = this.props; @@ -297,10 +292,10 @@ export default class ImagePreview extends PureComponent { {icon} ); - } + }; render() { - const maxImageHeight = this.state.deviceHeight - STATUSBAR_HEIGHT; + const maxImageHeight = this.props.deviceHeight - STATUSBAR_HEIGHT; const marginStyle = { ...Platform.select({ @@ -314,10 +309,7 @@ export default class ImagePreview extends PureComponent { }; return ( - + {this.props.files.map((file, index) => { let component; @@ -347,10 +338,10 @@ export default class ImagePreview extends PureComponent { file={file} theme={this.props.theme} imageHeight={Math.min(maxImageHeight, file.height)} - imageWidth={Math.min(this.state.deviceWidth, file.width)} + imageWidth={Math.min(this.props.deviceWidth, file.width)} shrink={this.state.shouldShrinkImages} - wrapperHeight={this.state.deviceHeight} - wrapperWidth={this.state.deviceWidth} + wrapperHeight={this.props.deviceHeight} + wrapperWidth={this.props.deviceWidth} onImageTap={this.handleImageTap} onImageDoubleTap={this.handleImageDoubleTap} onZoom={this.imageIsZooming} @@ -372,7 +363,7 @@ export default class ImagePreview extends PureComponent { return ( {component} @@ -380,7 +371,7 @@ export default class ImagePreview extends PureComponent { })} @@ -417,6 +408,8 @@ export default class ImagePreview extends PureComponent { { //eslint-disable-line react/display-name const {navigator} = props; //eslint-disable-line react/prop-types return ( @@ -58,7 +58,7 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('About', () => wrapWithContextProvider(About), store, Provider); Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(AddReaction), store, Provider); Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(AdvancedSettings), store, Provider); - Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel), store, Provider); + Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel, false), store, Provider); Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider); Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(ChannelInfo), store, Provider); Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider); @@ -73,7 +73,7 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(MoreChannels), store, Provider); Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(MoreDirectMessages), store, Provider); Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider); - Navigation.registerComponent('Notification', () => wrapWithContextProvider(Notification, true), store, Provider); + Navigation.registerComponent('Notification', () => wrapWithContextProvider(Notification), store, Provider); Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(NotificationSettings), store, Provider); Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(NotificationSettingsEmail), store, Provider); Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(NotificationSettingsMentions), store, Provider); diff --git a/app/screens/login/login.js b/app/screens/login/login.js index 1dd537733..5e30feb77 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -9,14 +9,15 @@ import { Image, InteractionManager, Keyboard, - KeyboardAvoidingView, - Platform, + StyleSheet, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native'; import Button from 'react-native-button'; +import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; +import Orientation from 'react-native-orientation'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; @@ -57,10 +58,8 @@ class Login extends PureComponent { }; } - componentDidMount() { - if (Platform.OS === 'android') { - Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard); - } + componentWillMount() { + Orientation.addOrientationListener(this.orientationDidChange); } componentWillReceiveProps(nextProps) { @@ -70,9 +69,7 @@ class Login extends PureComponent { } componentWillUnmount() { - if (Platform.OS === 'android') { - Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard); - } + Orientation.removeOrientationListener(this.orientationDidChange); } goToLoadTeam = (expiresAt) => { @@ -123,10 +120,6 @@ class Login extends PureComponent { }); }; - handleAndroidKeyboard = () => { - this.blur(); - }; - blur = () => { this.loginId.blur(); this.passwd.blur(); @@ -286,6 +279,14 @@ class Login extends PureComponent { this.passwd.focus(); }; + orientationDidChange = () => { + this.scroll.scrollToPosition(0, 0, true); + }; + + scrollRef = (ref) => { + this.scroll = ref; + }; + render() { const isLoading = this.props.loginRequest.status === RequestStatus.STARTED; @@ -313,14 +314,14 @@ class Login extends PureComponent { } return ( - + - + @@ -347,6 +348,7 @@ class Login extends PureComponent { returnKeyType='next' underlineColorAndroid='transparent' onSubmitEditing={this.passwordFocus} + blurOnSubmit={false} /> {proceed} - + - + ); } } +const style = StyleSheet.create({ + container: { + backgroundColor: '#FFFFFF', + flex: 1 + }, + innerContainer: { + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 50 + } +}); + export default injectIntl(Login); diff --git a/app/screens/login_options/login_options.js b/app/screens/login_options/login_options.js index 8fada460f..6c846c29e 100644 --- a/app/screens/login_options/login_options.js +++ b/app/screens/login_options/login_options.js @@ -6,10 +6,12 @@ import PropTypes from 'prop-types'; import {injectIntl, intlShape} from 'react-intl'; import { Image, - Text, - View + ScrollView, + StyleSheet, + Text } from 'react-native'; import Button from 'react-native-button'; +import Orientation from 'react-native-orientation'; import semver from 'semver'; import {ViewTypes} from 'app/constants'; @@ -31,6 +33,14 @@ class LoginOptions extends PureComponent { theme: PropTypes.object }; + componentWillMount() { + Orientation.addOrientationListener(this.orientationDidChange); + } + + componentWillUnmount() { + Orientation.removeOrientationListener(this.orientationDidChange); + } + goToLogin = () => { const {intl, navigator, theme} = this.props; navigator.push({ @@ -66,6 +76,10 @@ class LoginOptions extends PureComponent { }); }; + orientationDidChange = () => { + this.scroll.scrollTo({x: 0, y: 0, animated: true}); + }; + renderEmailOption = () => { const {config} = this.props; if (config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true') { @@ -172,9 +186,17 @@ class LoginOptions extends PureComponent { return null; }; + scrollRef = (ref) => { + this.scroll = ref; + }; + render() { return ( - + + ); } } +const style = StyleSheet.create({ + container: { + backgroundColor: '#FFFFFF', + flex: 1 + }, + innerContainer: { + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 50 + } +}); + export default injectIntl(LoginOptions); diff --git a/app/screens/notification/index.js b/app/screens/notification/index.js index 3514e0b5b..e233f283b 100644 --- a/app/screens/notification/index.js +++ b/app/screens/notification/index.js @@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {goToNotification} from 'app/actions/views/root'; +import {getDimensions} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import {getChannel} from 'mattermost-redux/selectors/entities/channels'; @@ -15,6 +16,7 @@ import Notification from './notification'; function mapStateToProps(state, ownProps) { const {data} = ownProps.notification; + const {deviceWidth} = getDimensions(state); let user; if (data.sender_id) { @@ -30,6 +32,7 @@ function mapStateToProps(state, ownProps) { ...ownProps, config: state.entities.general.config, channel, + deviceWidth, user, teammateNameDisplay: getTeammateNameDisplaySetting(state), theme: getTheme(state) diff --git a/app/screens/notification/notification.js b/app/screens/notification/notification.js index 330f7dbed..4b2966797 100644 --- a/app/screens/notification/notification.js +++ b/app/screens/notification/notification.js @@ -4,7 +4,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Dimensions, Image, Platform, StyleSheet, @@ -32,6 +31,7 @@ export default class Notification extends PureComponent { }).isRequired, channel: PropTypes.object, config: PropTypes.object, + deviceWidth: PropTypes.number.isRequired, notification: PropTypes.object.isRequired, teammateNameDisplay: PropTypes.string, navigator: PropTypes.object, @@ -161,7 +161,8 @@ export default class Notification extends PureComponent { }; render() { - const {message} = this.props.notification; + const {deviceWidth, notification} = this.props; + const {message} = notification; if (message) { const msg = message.split(':'); @@ -172,7 +173,7 @@ export default class Notification extends PureComponent { const icon = this.getNotificationIcon(); return ( - + { this.props.onCancelPress(); this.close(); - } + }; close = () => { Animated.timing(this.state.top, { - toValue: deviceHeight, + toValue: this.props.deviceHeight, duration: DURATION }).start(() => { this.props.navigator.dismissModal({ @@ -78,7 +82,7 @@ export default class OptionsModal extends PureComponent { return ( - + { return { header: { backgroundColor: theme.sidebarHeaderBg, - width: Dimensions.get('window').width, + width: '100%', ...Platform.select({ android: { height: 46, diff --git a/app/screens/thread/index.js b/app/screens/thread/index.js index a5dc30f5b..f91deab2d 100644 --- a/app/screens/thread/index.js +++ b/app/screens/thread/index.js @@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {handleCommentDraftChanged} from 'app/actions/views/thread'; +import {getStatusBarHeight} from 'app/selectors/device'; import {getTheme} from 'app/selectors/preferences'; import {selectPost} from 'mattermost-redux/actions/posts'; @@ -21,7 +22,6 @@ function makeMapStateToProps() { return function mapStateToProps(state, ownProps) { const posts = getPostsForThread(state, ownProps); const threadDraft = state.views.thread.drafts[ownProps.rootId]; - const {statusBarHeight} = state.views.root; return { ...ownProps, @@ -31,7 +31,7 @@ function makeMapStateToProps() { draft: threadDraft.draft, files: threadDraft.files, posts, - statusBarHeight, + statusBarHeight: getStatusBarHeight(state), theme: getTheme(state) }; }; diff --git a/app/selectors/device.js b/app/selectors/device.js new file mode 100644 index 000000000..566779cdc --- /dev/null +++ b/app/selectors/device.js @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +export function getDimensions(state) { + return state.device.dimension; +} + +export function getConnection(state) { + return state.device.connection; +} + +export function getStatusBarHeight(state) { + return state.device.statusBarHeight; +} + +export function isLandscape(state) { + return state.device.orientation === 'LANDSCAPE'; +} + +export function isTablet(state) { + return state.device.isTablet; +} diff --git a/app/store/index.js b/app/store/index.js index b99af2981..7c45644cc 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -160,7 +160,7 @@ export default function configureAppStore(initialState) { autoRehydrate: { log: false }, - blacklist: ['navigation', 'offline', 'requests'], + blacklist: ['device', 'navigation', 'offline', 'requests'], debounce: 500, transforms: [ setTransformer, diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 16f86dc15..a16900fd3 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -24,7 +24,6 @@ 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 1BCA51319AC6442991C6A208 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0A091BF1A3D04650AD306A0D /* Zocial.ttf */; }; - 2A167658767AAB5161E3271B /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */; }; 2B4C9B708010475DA575B81D /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */; }; 2D5296A8926B4D7FBAF2D6E2 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */; }; 374634801E8480C2005E1244 /* libRCTOrientation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 374634671E848085005E1244 /* libRCTOrientation.a */; }; @@ -60,6 +59,7 @@ 7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; }; 7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; }; 7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */; }; + 7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */; }; 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB10971F6101710039A015 /* BlurAppScreen.m */; }; 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; }; 7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */; }; @@ -67,6 +67,7 @@ 895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */; }; 9358B95F95184EE0A4DCE629 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; }; A08D512E7ADC40CCAD055A9E /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */; }; + A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */; }; C035DB50ED2045F09923FFAE /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 04AA5E8EF3B54735A11E3B95 /* MaterialCommunityIcons.ttf */; }; C337E8708D2845C6A8DBB47E /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */; }; D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */; }; @@ -441,6 +442,13 @@ remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D; remoteInfo = ReactNativeNavigation; }; + 7FDB92A61F706F45006CDFD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 014A3B5C1C6CF33500B6D375; + remoteInfo = RNImagePicker; + }; 7FDF28E01E1F4B1F00DBBE56 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */; @@ -543,6 +551,7 @@ 7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = ""; }; 7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ToolTipMenu.xcodeproj; path = "../node_modules/react-native-tooltip/ToolTipMenu.xcodeproj"; sourceTree = ""; }; 7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; + 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = ""; }; 7FEB10961F6101710039A015 /* BlurAppScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BlurAppScreen.h; path = Mattermost/BlurAppScreen.h; sourceTree = ""; }; 7FEB10971F6101710039A015 /* BlurAppScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BlurAppScreen.m; path = Mattermost/BlurAppScreen.m; sourceTree = ""; }; 7FEB10991F61019C0039A015 /* MattermostManaged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MattermostManaged.h; path = Mattermost/MattermostManaged.h; sourceTree = ""; }; @@ -615,9 +624,10 @@ DEBCF44F1F46413BB407D316 /* libz.tbd in Frameworks */, 7F43D5DD1F6BF95F001FC614 /* libRNCookieManagerIOS.a in Frameworks */, 7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */, + 7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */, 7F43D5DE1F6BF96A001FC614 /* libRCTYouTube.a in Frameworks */, 7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */, - 2A167658767AAB5161E3271B /* libPods-Mattermost.a in Frameworks */, + A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -975,6 +985,14 @@ name = Products; sourceTree = ""; }; + 7FDB92761F706F45006CDFD1 /* Products */ = { + isa = PBXGroup; + children = ( + 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */, + ); + name = Products; + sourceTree = ""; + }; 7FDF28C41E1F4B1F00DBBE56 /* Products */ = { isa = PBXGroup; children = ( @@ -995,6 +1013,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */, 37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */, 3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */, 7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */, @@ -1244,6 +1263,10 @@ ProductGroup = 3752181C1F4B9E320035444B /* Products */; ProjectRef = 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */; }, + { + ProductGroup = 7FDB92761F706F45006CDFD1 /* Products */; + ProjectRef = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */; + }, { ProductGroup = 7F8C49621F3DFC30003A22BA /* Products */; ProjectRef = D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */; @@ -1639,6 +1662,13 @@ remoteRef = 7FC200DE1EBB65100099331B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNImagePicker.a; + remoteRef = 7FDB92A61F706F45006CDFD1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1957,6 +1987,7 @@ PRODUCT_NAME = Mattermost; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -2002,6 +2033,7 @@ PRODUCT_NAME = Mattermost; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/test/setup.js b/test/setup.js index 7f2cf7c68..9464a8e50 100644 --- a/test/setup.js +++ b/test/setup.js @@ -16,6 +16,11 @@ mockery.enable({ warnOnUnregistered: false }); mockery.registerMock('react-native', { + Dimensions: { + get: () => { + return {width: 0, height: 0} + } + }, NativeModules: {} }); mockery.registerMock('react-native-device-info', {