diff --git a/NOTICE.txt b/NOTICE.txt index 0300a52b9..4512bea57 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1511,3 +1511,36 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + +## react-native-safe-area + +This product contains 'react-native-safe-area', React Native module to retrieve safe area insets for iOS 11 or later. + +* HOMEPAGE: + * https://github.com/miyabi/react-native-safe-area + +* LICENSE: + +MIT License + +Copyright (c) 2017 Masayuki Iwai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index aa3a0628f..1eb2ddfef 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -11,6 +11,7 @@ import { StyleSheet, View } from 'react-native'; +import SafeAreaView from 'app/components/safe_area_view'; import Drawer from 'app/components/drawer'; import {alertErrorWithFallback} from 'app/utils/general'; @@ -362,14 +363,20 @@ export default class ChannelDrawer extends Component { ); return ( - - {lists} - + + {lists} + + ); }; diff --git a/app/components/channel_drawer/channels_list/channels_list.js b/app/components/channel_drawer/channels_list/channels_list.js index d95603327..66e056fae 100644 --- a/app/components/channel_drawer/channels_list/channels_list.js +++ b/app/components/channel_drawer/channels_list/channels_list.js @@ -34,7 +34,7 @@ class ChannelsList extends React.PureComponent { constructor(props) { super(props); - this.firstUnreadChannel = null; + this.state = { searching: false, term: '' @@ -192,12 +192,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1 }, statusBar: { - backgroundColor: theme.sidebarHeaderBg, - ...Platform.select({ - ios: { - paddingTop: 20 - } - }) + backgroundColor: theme.sidebarHeaderBg }, headerContainer: { alignItems: 'center', diff --git a/app/components/channel_drawer/teams_list/teams_list.js b/app/components/channel_drawer/teams_list/teams_list.js index 216550bc3..12c5936e9 100644 --- a/app/components/channel_drawer/teams_list/teams_list.js +++ b/app/components/channel_drawer/teams_list/teams_list.js @@ -148,12 +148,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1 }, statusBar: { - backgroundColor: theme.sidebarHeaderBg, - ...Platform.select({ - ios: { - paddingTop: 20 - } - }) + backgroundColor: theme.sidebarHeaderBg }, headerContainer: { alignItems: 'center', diff --git a/app/components/client_upgrade_listener/client_upgrade_listener.js b/app/components/client_upgrade_listener/client_upgrade_listener.js index 9073a3f61..af90752f4 100644 --- a/app/components/client_upgrade_listener/client_upgrade_listener.js +++ b/app/components/client_upgrade_listener/client_upgrade_listener.js @@ -10,7 +10,9 @@ import { TouchableOpacity, View } from 'react-native'; -import {injectIntl, intlShape} from 'react-intl'; +import {intlShape} from 'react-intl'; +import DeviceInfo from 'react-native-device-info'; +import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import FormattedText from 'app/components/formatted_text'; import {UpgradeTypes} from 'app/constants/view'; @@ -21,7 +23,7 @@ const {View: AnimatedView} = Animated; const UPDATE_TIMEOUT = 60000; -class ClientUpgradeListener extends PureComponent { +export default class ClientUpgradeListener extends PureComponent { static propTypes = { actions: PropTypes.shape({ logError: PropTypes.func.isRequired, @@ -30,7 +32,7 @@ class ClientUpgradeListener extends PureComponent { currentVersion: PropTypes.string, downloadLink: PropTypes.string, forceUpgrade: PropTypes.bool, - intl: intlShape.isRequired, + isLandscape: PropTypes.bool, lastUpgradeCheck: PropTypes.number, latestVersion: PropTypes.string, minVersion: PropTypes.string, @@ -38,14 +40,28 @@ class ClientUpgradeListener extends PureComponent { theme: PropTypes.object.isRequired }; - state = { - top: new Animated.Value(-100) + static contextTypes = { + intl: intlShape + }; + + constructor(props) { + super(props); + + this.isX = DeviceInfo.getModel() === 'iPhone X'; + + MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => { + this.closeButton = source; + }); + + this.state = { + top: new Animated.Value(-100) + }; } componentDidMount() { - const {forceUpgrade, lastUpgradeCheck, latestVersion, minVersion} = this.props; + const {forceUpgrade, isLandscape, lastUpgradeCheck, latestVersion, minVersion} = this.props; if (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT) { - this.checkUpgrade(minVersion, latestVersion); + this.checkUpgrade(minVersion, latestVersion, isLandscape); } } @@ -55,40 +71,54 @@ class ClientUpgradeListener extends PureComponent { const versionMismatch = latestVersion !== nextLatestVersion || minVersion !== nextMinVersion; if (versionMismatch && (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT)) { - this.checkUpgrade(minVersion, latestVersion); + this.checkUpgrade(minVersion, latestVersion, nextProps.isLandscape); + } else if (this.props.isLandscape !== nextProps.isLandscape && + this.state.upgradeType !== UpgradeTypes.NO_UPGRADE && this.isX) { + const newTop = nextProps.isLandscape ? 45 : 100; + this.setState({top: new Animated.Value(newTop)}); } } - checkUpgrade = (minVersion, latestVersion) => { + checkUpgrade = (minVersion, latestVersion, isLandscape) => { const {actions, currentVersion} = this.props; const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion, actions.logError); + this.setState({upgradeType}); + if (upgradeType === UpgradeTypes.NO_UPGRADE) { return; } - this.setState({upgradeType}); - - setTimeout(this.toggleUpgradeMessage, 500); + setTimeout(() => { + this.toggleUpgradeMessage(true, isLandscape); + }, 500); actions.setLastUpgradeCheck(); - } + }; - toggleUpgradeMessage = (show = true) => { - const toValue = show ? 75 : -100; + toggleUpgradeMessage = (show = true, isLandscape) => { + let toValue = -100; + if (show) { + if (this.isX && isLandscape) { + toValue = 45; + } else { + toValue = this.isX ? 100 : 75; + } + } Animated.timing(this.state.top, { toValue, duration: 300 }).start(); - } + }; handleDismiss = () => { this.toggleUpgradeMessage(false); - } + }; handleDownload = () => { - const {downloadLink, intl} = this.props; + const {downloadLink} = this.props; + const {intl} = this.context; Linking.canOpenURL(downloadLink).then((supported) => { if (supported) { @@ -110,17 +140,25 @@ class ClientUpgradeListener extends PureComponent { }); this.toggleUpgradeMessage(false); - } + }; handleLearnMore = () => { - this.props.navigator.dismissAllModals({animationType: 'none'}); + const {intl} = this.context; + this.props.navigator.dismissModal({animationType: 'none'}); this.props.navigator.showModal({ screen: 'ClientUpgrade', + title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}), navigatorStyle: { - navBarHidden: true, - statusBarHidden: true, - statusBarHideWithNavBar: true + navBarHidden: false, + statusBarHidden: false, + statusBarHideWithNavBar: false + }, + navigatorButtons: { + leftButtons: [{ + id: 'close-upgrade', + icon: this.closeButton + }] }, passProps: { upgradeType: this.state.upgradeType @@ -128,9 +166,13 @@ class ClientUpgradeListener extends PureComponent { }); this.toggleUpgradeMessage(false); - } + }; render() { + if (this.state.upgradeType === UpgradeTypes.NO_UPGRADE) { + return null; + } + const {forceUpgrade, theme} = this.props; const styles = getStyleSheet(theme); @@ -227,5 +269,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { } }; }); - -export default injectIntl(ClientUpgradeListener); diff --git a/app/components/client_upgrade_listener/index.js b/app/components/client_upgrade_listener/index.js index 38b66afce..5924e96dd 100644 --- a/app/components/client_upgrade_listener/index.js +++ b/app/components/client_upgrade_listener/index.js @@ -5,6 +5,7 @@ import {logError} from 'mattermost-redux/actions/errors'; import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade'; import getClientUpgrade from 'app/selectors/client_upgrade'; +import {isLandscape} from 'app/selectors/device'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import ClientUpgradeListener from './client_upgrade_listener'; @@ -16,6 +17,7 @@ function mapStateToProps(state) { currentVersion, downloadLink, forceUpgrade, + isLandscape: isLandscape(state), lastUpgradeCheck: state.views.clientUpgrade.lastUpdateCheck, latestVersion, minVersion, diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js index b3432eb7e..a2fc98203 100644 --- a/app/components/emoji_picker/emoji_picker.js +++ b/app/components/emoji_picker/emoji_picker.js @@ -13,11 +13,13 @@ import { TouchableOpacity, View } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome'; import sectionListGetItemLayout from 'react-native-section-list-get-item-layout'; import Emoji from 'app/components/emoji'; import FormattedText from 'app/components/formatted_text'; +import SafeAreaView from 'app/components/safe_area_view'; import SearchBar from 'app/components/search_bar'; import {emptyFunction} from 'app/utils/general'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; @@ -45,7 +47,7 @@ export default class EmojiPicker extends PureComponent { static contextTypes = { intl: intlShape.isRequired - } + }; constructor(props) { super(props); @@ -60,6 +62,7 @@ export default class EmojiPicker extends PureComponent { const emojis = this.renderableEmojis(props.emojisBySection, props.deviceWidth); const emojiSectionIndexByOffset = this.measureEmojiSections(emojis); + this.isX = DeviceInfo.getModel() === 'iPhone X'; this.state = { emojis, emojiSectionIndexByOffset, @@ -74,6 +77,10 @@ export default class EmojiPicker extends PureComponent { this.setState({ emojis: this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth) }); + + if (this.refs.search_bar) { + this.refs.search_bar.blur(); + } } } @@ -116,7 +123,7 @@ export default class EmojiPicker extends PureComponent { }); return nextEmojis; - } + }; measureEmojiSections = (emojiSections) => { let lastOffset = 0; @@ -127,7 +134,7 @@ export default class EmojiPicker extends PureComponent { return start; }); - } + }; changeSearchTerm = (text) => { this.setState({ @@ -149,11 +156,11 @@ export default class EmojiPicker extends PureComponent { filteredEmojis: [], searchTerm: '' }); - } + }; filterEmojiAliases = (aliases, searchTerm) => { return aliases.findIndex((alias) => alias.includes(searchTerm)) !== -1; - } + }; searchEmojis = (searchTerm) => { const {emojisByName} = this.props; @@ -174,11 +181,11 @@ export default class EmojiPicker extends PureComponent { }); return [...startsWith.sort(), ...includes.sort()]; - } + }; getNumberOfColumns = (deviceWidth) => { return Math.floor(Number(((deviceWidth - (SECTION_MARGIN * 2)) / (EMOJI_SIZE + (EMOJI_GUTTER * 2))))); - } + }; renderItem = ({item}) => { return ( @@ -190,7 +197,7 @@ export default class EmojiPicker extends PureComponent { onEmojiPress={this.props.onEmojiPress} /> ); - } + }; flatListKeyExtractor = (item) => item; @@ -234,7 +241,7 @@ export default class EmojiPicker extends PureComponent { currentSectionIndex: nextIndex }); } - } + }; onMomentumScrollEnd = () => { if (this.state.jumpToSection) { @@ -242,7 +249,7 @@ export default class EmojiPicker extends PureComponent { jumpToSection: false }); } - } + }; scrollToSection = (index) => { this.setState({ @@ -255,7 +262,7 @@ export default class EmojiPicker extends PureComponent { viewOffset: 25 }); }); - } + }; renderSectionHeader = ({section}) => { const {theme} = this.props; @@ -273,7 +280,7 @@ export default class EmojiPicker extends PureComponent { /> ); - } + }; renderSectionIcons = () => { const {theme} = this.props; @@ -296,13 +303,11 @@ export default class EmojiPicker extends PureComponent { ); }); - } - - sectionListKeyExtractor = (item) => item.key + }; attachSectionList = (c) => { this.sectionList = c; - } + }; render() { const {deviceWidth, isLandscape, theme} = this.props; @@ -346,48 +351,55 @@ export default class EmojiPicker extends PureComponent { let keyboardOffset = 64; if (Platform.OS === 'android') { keyboardOffset = -200; + } else if (this.isX) { + keyboardOffset = isLandscape ? 35 : 107; } else if (isLandscape) { - keyboardOffset = 51; + keyboardOffset = 52; } return ( - - - - - - {listComponent} - {!searchTerm && + + + + + + {listComponent} + {!searchTerm && {this.renderSectionIcons()} - } - - + } + + + ); } } @@ -407,6 +419,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => { left: 0, right: 0, height: 35, + width: '100%', backgroundColor: 'white' }, columnStyle: { diff --git a/app/components/offline_indicator/index.js b/app/components/offline_indicator/index.js index d4294c671..5fd1e461f 100644 --- a/app/components/offline_indicator/index.js +++ b/app/components/offline_indicator/index.js @@ -7,7 +7,7 @@ import {connect} from 'react-redux'; import {init as initWebSocket} from 'mattermost-redux/actions/websocket'; import {connection} from 'app/actions/device'; -import {getConnection} from 'app/selectors/device'; +import {getConnection, isLandscape} from 'app/selectors/device'; import OfflineIndicator from './offline_indicator'; @@ -18,6 +18,7 @@ function mapStateToProps(state) { return { isConnecting, + isLandscape: isLandscape(state), isOnline: getConnection(state), webSocketStatus }; diff --git a/app/components/offline_indicator/offline_indicator.js b/app/components/offline_indicator/offline_indicator.js index 57b776abc..4f836d615 100644 --- a/app/components/offline_indicator/offline_indicator.js +++ b/app/components/offline_indicator/offline_indicator.js @@ -11,6 +11,7 @@ import { TouchableOpacity, View } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; import IonIcon from 'react-native-vector-icons/Ionicons'; import FormattedText from 'app/components/formatted_text'; @@ -19,8 +20,6 @@ import checkNetwork from 'app/utils/network'; import {RequestStatus} from 'mattermost-redux/constants'; const HEIGHT = 38; -const NAVBAR = Platform.OS === 'ios' ? 64 : 46; -const INITIAL_TOP = NAVBAR - HEIGHT; const OFFLINE = 'offline'; const CONNECTING = 'connecting'; const CONNECTED = 'connected'; @@ -32,6 +31,7 @@ export default class OfflineIndicator extends Component { initWebSocket: PropTypes.func.isRequired }).isRequired, isConnecting: PropTypes.bool, + isLandscape: PropTypes.bool, isOnline: PropTypes.bool, webSocketStatus: PropTypes.string }; @@ -43,16 +43,27 @@ export default class OfflineIndicator extends Component { constructor(props) { super(props); + this.isX = DeviceInfo.getModel() === 'iPhone X'; + const navBar = this.getNavBarHeight(props.isLandscape); + this.state = { network: null, - top: new Animated.Value(INITIAL_TOP) + navBar, + top: new Animated.Value(navBar - HEIGHT) }; this.backgroundColor = new Animated.Value(0); } componentWillReceiveProps(nextProps) { - const {webSocketStatus} = this.props; + const {isLandscape, webSocketStatus} = this.props; + + if (nextProps.isLandscape !== isLandscape && this.state.network) { + const navBar = this.getNavBarHeight(nextProps.isLandscape); + const top = new Animated.Value(navBar - HEIGHT); + this.setState({navBar, top}); + } + if (nextProps.isOnline) { if (this.state.network && webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.SUCCESS) { // Show the connected animation only if we had a previous network status @@ -67,7 +78,7 @@ export default class OfflineIndicator extends Component { } shouldComponentUpdate(nextProps, nextState) { - return nextState.network !== this.state.network && nextState.network; + return (nextState.network !== this.state.network || nextProps.isLandscape !== this.props.isLandscape); } connect = () => { @@ -94,7 +105,7 @@ export default class OfflineIndicator extends Component { ), Animated.timing( this.state.top, { - toValue: INITIAL_TOP, + toValue: (this.state.navBar - HEIGHT), duration: 300, delay: 500 } @@ -114,6 +125,23 @@ export default class OfflineIndicator extends Component { }); }; + getNavBarHeight = (isLandscape) => { + let navBar = 46; + if (Platform.OS === 'ios') { + if (this.isX && isLandscape) { + navBar = 32; + } else if (this.isX) { + navBar = 88; + } else if (isLandscape) { + navBar = 52; + } else { + navBar = 64; + } + } + + return navBar; + }; + offline = () => { this.setState({network: OFFLINE}, () => { this.show(); @@ -123,7 +151,7 @@ export default class OfflineIndicator extends Component { show = () => { Animated.timing( this.state.top, { - toValue: NAVBAR, + toValue: this.state.navBar, duration: 300 } ).start(); diff --git a/app/components/safe_area_view/index.js b/app/components/safe_area_view/index.js new file mode 100644 index 000000000..4a902fa2a --- /dev/null +++ b/app/components/safe_area_view/index.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import SafeAreaView from './safe_area_view'; +export default SafeAreaView; diff --git a/app/components/safe_area_view/safe_area_view.android.js b/app/components/safe_area_view/safe_area_view.android.js new file mode 100644 index 000000000..2bbdc2dac --- /dev/null +++ b/app/components/safe_area_view/safe_area_view.android.js @@ -0,0 +1,26 @@ +// 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 {StyleSheet, View} from 'react-native'; + +export default class SafeAreaAndroid extends PureComponent { + static propTypes = { + children: PropTypes.node.isRequired + }; + + render() { + return ( + + {this.props.children} + + ); + } +} + +const style = StyleSheet.create({ + container: { + flex: 1 + } +}); diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js new file mode 100644 index 000000000..b9a523e3d --- /dev/null +++ b/app/components/safe_area_view/safe_area_view.ios.js @@ -0,0 +1,131 @@ +// 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 {Keyboard, View} from 'react-native'; +import DeviceInfo from 'react-native-device-info'; +import SafeArea from 'react-native-safe-area'; +import Orientation from 'react-native-orientation'; + +export default class SafeAreaIos extends PureComponent { + static propTypes = { + backgroundColor: PropTypes.string, + children: PropTypes.node.isRequired, + excludeHeader: PropTypes.bool, + forceTop: PropTypes.number, + keyboardOffset: PropTypes.number.isRequired, + navBarBackgroundColor: PropTypes.string, + navigator: PropTypes.object, + theme: PropTypes.object.isRequired + }; + + static defaultProps = { + keyboardOffset: 0 + }; + + constructor(props) { + super(props); + + this.isX = DeviceInfo.getModel() === 'iPhone X'; + + if (props.navigator) { + props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); + } + + this.state = { + keyboard: false, + safeAreaInsets: { + top: 20, left: 0, bottom: 15, right: 0 + } + }; + } + + componentWillMount() { + this.getSafeAreaInsets(); + } + + componentDidMount() { + Orientation.addOrientationListener(this.getSafeAreaInsets); + this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); + this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); + } + + componentWillUnmount() { + Orientation.removeOrientationListener(this.getSafeAreaInsets); + this.keyboardDidShowListener.remove(); + this.keyboardDidHideListener.remove(); + } + + getSafeAreaInsets = () => { + if (this.isX) { + SafeArea.getSafeAreaInsetsForRootView().then((result) => { + const {safeAreaInsets} = result; + this.setState({safeAreaInsets}); + }); + } + }; + + keyboardWillHide = () => { + this.setState({keyboard: false}); + }; + + keyboardWillShow = () => { + this.setState({keyboard: true}); + }; + + onNavigatorEvent = (event) => { + switch (event.id) { + case 'willAppear': + case 'didDisappear': + this.getSafeAreaInsets(); + break; + } + }; + + render() { + const {backgroundColor, children, excludeHeader, forceTop, keyboardOffset, navBarBackgroundColor, theme} = this.props; + const {keyboard, safeAreaInsets} = this.state; + + let bgColor = theme.centerChannelBg; + if (backgroundColor) { + bgColor = backgroundColor; + } + + let topColor = theme.sidebarHeaderBg; + if (navBarBackgroundColor) { + topColor = navBarBackgroundColor; + } + + let offset = 0; + if (keyboardOffset && this.isX) { + offset = keyboardOffset; + } + + let top = safeAreaInsets.top; + if (forceTop && this.isX && !excludeHeader) { + top = forceTop; + } + + return ( + + {!excludeHeader && + + } + {children} + + ); + } +} diff --git a/app/components/search_bar/search_box.js b/app/components/search_bar/search_box.js index 803337e5e..cebfbba4d 100644 --- a/app/components/search_bar/search_box.js +++ b/app/components/search_bar/search_box.js @@ -134,6 +134,8 @@ export default class Search extends Component { blur = () => { this.refs.input_keyword.getNode().blur(); + this.setState({expanded: false}); + this.collapseAnimation(); }; focus = () => { diff --git a/app/mattermost.js b/app/mattermost.js index 43ef3e91b..6a763fe2c 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -410,13 +410,14 @@ export default class Mattermost { this.configurePushNotifications(); } - Orientation.getOrientation((orientation) => { - this.orientationDidChange(orientation); - }); - if (state.views.root.hydrationComplete) { this.unsubscribeFromStore(); + const orientation = Orientation.getInitialOrientation(); + if (orientation) { + this.orientationDidChange(orientation); + } + const isNotActive = AppState.currentState !== 'active'; const notification = PushNotifications.getNotification(); if (notification) { diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index e64095fde..9d2df5a9d 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -8,7 +8,6 @@ import { Platform, View } from 'react-native'; - import EventEmitter from 'mattermost-redux/utils/event_emitter'; import ClientUpgradeListener from 'app/components/client_upgrade_listener'; @@ -18,6 +17,7 @@ import KeyboardLayout from 'app/components/layout/keyboard_layout'; import Loading from 'app/components/loading'; import OfflineIndicator from 'app/components/offline_indicator'; import PostListRetry from 'app/components/post_list_retry'; +import SafeAreaView from 'app/components/safe_area_view'; import StatusBar from 'app/components/status_bar'; import {wrapWithPreventDoubleTap} from 'app/utils/tap'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -26,10 +26,8 @@ import networkConnectionListener from 'app/utils/network'; import LocalConfig from 'assets/config'; -import ChannelDrawerButton from './channel_drawer_button'; +import ChannelNavBar from './channel_nav_bar'; import ChannelPostList from './channel_post_list'; -import ChannelSearchButton from './channel_search_button'; -import ChannelTitle from './channel_title'; class Channel extends PureComponent { static propTypes = { @@ -184,33 +182,32 @@ class Channel extends PureComponent { intl={intl} navigator={navigator} > - - - - - - - - - - - - - - - + + - - - {LocalConfig.EnableMobileClientUpgrade && } + + + + + + + + + {LocalConfig.EnableMobileClientUpgrade && } + ); } @@ -218,22 +215,6 @@ class Channel extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { - header: { - backgroundColor: theme.sidebarHeaderBg, - flexDirection: 'row', - justifyContent: 'flex-start', - width: '100%', - zIndex: 10, - ...Platform.select({ - android: { - height: 46 - }, - ios: { - height: 64, - paddingTop: 20 - } - }) - }, postList: { flex: 1 }, @@ -244,7 +225,6 @@ 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_nav_bar/channel_drawer_button.js similarity index 100% rename from app/screens/channel/channel_drawer_button.js rename to app/screens/channel/channel_nav_bar/channel_drawer_button.js diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js new file mode 100644 index 000000000..63e0e2296 --- /dev/null +++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js @@ -0,0 +1,69 @@ +// 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 {Platform, View} from 'react-native'; +import DeviceInfo from 'react-native-device-info'; + +import {makeStyleSheetFromTheme} from 'app/utils/theme'; + +import ChannelDrawerButton from './channel_drawer_button'; +import ChannelSearchButton from './channel_search_button'; +import ChannelTitle from './channel_title'; + +export default class ChannelNavBar extends PureComponent { + static propTypes = { + isLandscape: PropTypes.bool.isRequired, + navigator: PropTypes.object.isRequired, + onPress: PropTypes.func.isRequired, + theme: PropTypes.object.isRequired + }; + + constructor(props) { + super(props); + + this.isX = DeviceInfo.getModel() === 'iPhone X'; + } + + render() { + const {isLandscape, navigator, theme, onPress} = this.props; + const style = getStyleFromTheme(theme); + const padding = {paddingHorizontal: 0}; + + let height = 46; + if (Platform.OS === 'ios') { + height = 44; + if (isLandscape) { + height = 32; + } + + if (this.isX && isLandscape) { + padding.paddingHorizontal = 10; + } + } + + return ( + + + + + + ); + } +} + +const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { + return { + header: { + backgroundColor: theme.sidebarHeaderBg, + flexDirection: 'row', + justifyContent: 'flex-start', + width: '100%', + zIndex: 10 + } + }; +}); diff --git a/app/screens/channel/channel_search_button/channel_search_button.js b/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js similarity index 100% rename from app/screens/channel/channel_search_button/channel_search_button.js rename to app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js diff --git a/app/screens/channel/channel_search_button/index.js b/app/screens/channel/channel_nav_bar/channel_search_button/index.js similarity index 100% rename from app/screens/channel/channel_search_button/index.js rename to app/screens/channel/channel_nav_bar/channel_search_button/index.js diff --git a/app/screens/channel/channel_title/channel_title.js b/app/screens/channel/channel_nav_bar/channel_title/channel_title.js similarity index 100% rename from app/screens/channel/channel_title/channel_title.js rename to app/screens/channel/channel_nav_bar/channel_title/channel_title.js diff --git a/app/screens/channel/channel_title/index.js b/app/screens/channel/channel_nav_bar/channel_title/index.js similarity index 100% rename from app/screens/channel/channel_title/index.js rename to app/screens/channel/channel_nav_bar/channel_title/index.js diff --git a/app/screens/channel/channel_nav_bar/index.js b/app/screens/channel/channel_nav_bar/index.js new file mode 100644 index 000000000..769f1e88c --- /dev/null +++ b/app/screens/channel/channel_nav_bar/index.js @@ -0,0 +1,18 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; + +import {isLandscape} from 'app/selectors/device'; +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; + +import ChannelNavBar from './channel_nav_bar'; + +function mapStateToProps(state) { + return { + isLandscape: isLandscape(state), + theme: getTheme(state) + }; +} + +export default connect(mapStateToProps)(ChannelNavBar); diff --git a/app/screens/client_upgrade/client_upgrade.js b/app/screens/client_upgrade/client_upgrade.js index 148ed27d2..0c9f7edf8 100644 --- a/app/screens/client_upgrade/client_upgrade.js +++ b/app/screens/client_upgrade/client_upgrade.js @@ -11,7 +11,7 @@ import { TouchableOpacity, View } from 'react-native'; -import {injectIntl, intlShape} from 'react-intl'; +import {intlShape} from 'react-intl'; import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; @@ -20,7 +20,7 @@ import logo from 'assets/images/logo.png'; import checkUpgradeType from 'app/utils/client_upgrade'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; -class ClientUpgrade extends PureComponent { +export default class ClientUpgrade extends PureComponent { static propTypes = { actions: PropTypes.shape({ logError: PropTypes.func.isRequired, @@ -31,15 +31,23 @@ class ClientUpgrade extends PureComponent { userCheckedForUpgrade: PropTypes.bool, downloadLink: PropTypes.string.isRequired, forceUpgrade: PropTypes.bool, - intl: intlShape.isRequired, latestVersion: PropTypes.string, navigator: PropTypes.object, theme: PropTypes.object.isRequired, upgradeType: PropTypes.string }; - state = { - upgradeType: UpgradeTypes.NO_UPGRADE + static contextTypes = { + intl: intlShape + }; + + constructor(props) { + super(props); + + this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); + this.state = { + upgradeType: UpgradeTypes.NO_UPGRADE + }; } componentDidMount() { @@ -77,10 +85,11 @@ class ClientUpgrade extends PureComponent { } else { this.props.navigator.dismissModal(); } - } + }; handleDownload = () => { - const {downloadLink, intl} = this.props; + const {downloadLink} = this.props; + const {intl} = this.context; Linking.canOpenURL(downloadLink).then((supported) => { if (supported) { @@ -100,17 +109,17 @@ class ClientUpgrade extends PureComponent { return false; }); - } + }; - handleUpgrade = () => { - const {actions, downloadLink} = this.props; - - try { - Linking.openURL(downloadLink); - } catch (error) { - actions.logError(error); + onNavigatorEvent = (event) => { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'close-upgrade') { + this.props.navigator.dismissModal({ + animationType: 'slide-down' + }); + } } - } + }; renderMustUpgrade() { const {theme} = this.props; @@ -318,5 +327,3 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { } }; }); - -export default injectIntl(ClientUpgrade); diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index e7dd98b15..74ef4b5e1 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -26,6 +26,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {DeviceTypes} from 'app/constants/'; import FileAttachmentDocument, {SUPPORTED_DOCS_FORMAT} from 'app/components/file_attachment_list/file_attachment_document'; import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon'; +import SafeAreaView from 'app/components/safe_area_view'; import {NavigationTypes} from 'app/constants'; import {emptyFunction} from 'app/utils/general'; @@ -476,116 +477,156 @@ export default class ImagePreview extends PureComponent { }; render() { - const marginStyle = { - ...Platform.select({ - ios: { - marginTop: this.props.statusBarHeight - }, - android: { - marginTop: 10 - } - }) - }; - return ( - - - - {this.state.files.map((file, index) => { - let mime = file.mime_type; - if (mime && mime.includes(';')) { - mime = mime.split(';')[0]; - } - - let component; - if (file.has_preview_image || file.mime_type === 'image/gif') { - component = this.renderPreviewer(file, index); - } else if (SUPPORTED_DOCS_FORMAT.includes(mime)) { - component = this.renderAttachmentDocument(file); - } else if (SUPPORTED_VIDEO_FORMAT.includes(file.mime_type)) { - component = this.renderVideoPreview(file); - } else { - component = this.renderAttachmentIcon(file); - } - - return ( - - {component} - - ); - })} - + + - - - - - - - {`${this.state.currentFile + 1}/${this.state.files.length}`} - - {this.renderDownloadButton()} - - - - - {this.state.files[this.state.currentFile].name} - - + {this.state.files.map((file, index) => { + let mime = file.mime_type; + if (mime && mime.includes(';')) { + mime = mime.split(';')[0]; + } + + let component; + if (file.has_preview_image || file.mime_type === 'image/gif') { + component = this.renderPreviewer(file, index); + } else if (SUPPORTED_DOCS_FORMAT.includes(mime)) { + component = this.renderAttachmentDocument(file); + } else if (SUPPORTED_VIDEO_FORMAT.includes(file.mime_type)) { + component = this.renderVideoPreview(file); + } else { + component = this.renderAttachmentIcon(file); + } + + return ( + + {component} + + ); + })} + + + + + + + + + {`${this.state.currentFile + 1}/${this.state.files.length}`} + + {this.renderDownloadButton()} + + + + + + + {this.state.files[this.state.currentFile].name} + + + - - - + + + ); } } const style = StyleSheet.create({ - filename: { + wrapper: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.8)' + }, + scrollView: { + flex: 1, + backgroundColor: '#000' + }, + scrollViewContent: { + backgroundColor: '#000' + }, + pageWrapper: { + alignItems: 'center', + justifyContent: 'center', + flex: 1 + }, + headerContainer: { + position: 'absolute', + top: 0 + }, + header: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + height: HEADER_HEIGHT, + position: 'absolute', + top: 0, + left: 0, + width: '100%' + }, + headerControls: { + alignItems: 'center', + justifyContent: 'space-around', + flexDirection: 'row' + }, + headerIcon: { + height: 44, + width: 48, + alignItems: 'center', + justifyContent: 'center' + }, + title: { + flex: 1, + marginHorizontal: 10, color: 'white', - fontSize: 15 + fontSize: 15, + textAlign: 'center' + }, + footerContainer: { + position: 'absolute', + bottom: 0 }, footer: { position: 'absolute', @@ -599,66 +640,11 @@ const style = StyleSheet.create({ ...Platform.select({ android: { marginBottom: 13 - }, - ios: { - marginBottom: 0 } }) }, - footerHeaderWrapper: { - position: 'absolute', - bottom: 0, - left: 0 - }, - header: { - backgroundColor: 'rgba(0, 0, 0, 0.8)', - height: HEADER_HEIGHT, - position: 'absolute', - top: 0, - left: 0, - width: '100%' - }, - headerControls: { - alignItems: 'center', - justifyContent: 'space-around', - flexDirection: 'row', - ...Platform.select({ - android: { - marginTop: 0 - }, - ios: { - marginTop: 5 - } - }) - }, - headerIcon: { - height: 44, - width: 48, - alignItems: 'center', - justifyContent: 'center' - }, - pageWrapper: { - alignItems: 'center', - justifyContent: 'center' - }, - scrollView: { - flex: 1, - backgroundColor: '#000' - }, - scrollViewContent: { - backgroundColor: '#000' - }, - title: { - flex: 1, - marginHorizontal: 10, + filename: { color: 'white', - fontSize: 15, - textAlign: 'center' - }, - wrapper: { - position: 'absolute', - top: 0, - left: 0, - backgroundColor: 'rgba(0, 0, 0, 0.8)' + fontSize: 15 } }); diff --git a/app/screens/search/index.js b/app/screens/search/index.js index 400163221..4a2348872 100644 --- a/app/screens/search/index.js +++ b/app/screens/search/index.js @@ -16,6 +16,7 @@ import { setChannelDisplayName, setChannelLoading } from 'app/actions/views/channel'; +import {isLandscape} from 'app/selectors/device'; import {handleSearchDraftChanged} from 'app/actions/views/search'; import Search from './search'; @@ -29,6 +30,7 @@ function mapStateToProps(state) { return { currentTeamId, currentChannelId, + isLandscape: isLandscape(state), postIds: state.entities.search.results, recent: recent[currentTeamId], searchingStatus: searchRequest.status diff --git a/app/screens/search/search.js b/app/screens/search/search.js index e0c296216..d2d6b79f0 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -14,6 +14,7 @@ import { TouchableOpacity, View } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; import IonIcon from 'react-native-vector-icons/Ionicons'; import AwesomeIcon from 'react-native-vector-icons/FontAwesome'; @@ -23,6 +24,7 @@ import Autocomplete from 'app/components/autocomplete'; import FormattedText from 'app/components/formatted_text'; import Loading from 'app/components/loading'; import PostListRetry from 'app/components/post_list_retry'; +import SafeAreaView from 'app/components/safe_area_view'; import SearchBar from 'app/components/search_bar'; import SearchPreview from 'app/components/search_preview'; import StatusBar from 'app/components/status_bar'; @@ -55,6 +57,7 @@ class Search extends PureComponent { currentTeamId: PropTypes.string.isRequired, currentChannelId: PropTypes.string.isRequired, intl: intlShape.isRequired, + isLandscape: PropTypes.bool.isRequired, navigator: PropTypes.object, postIds: PropTypes.array, recent: PropTypes.array.isRequired, @@ -71,6 +74,7 @@ class Search extends PureComponent { super(props); props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); + this.isX = DeviceInfo.getModel() === 'iPhone X'; this.state = { channelName: '', postId: null, @@ -87,7 +91,9 @@ class Search extends PureComponent { componentDidMount() { this.setManagedConfig(); if (this.refs.searchBar) { - this.refs.searchBar.focus(); + setTimeout(() => { + this.refs.searchBar.focus(); + }, 150); } } @@ -97,6 +103,10 @@ class Search extends PureComponent { const recentLength = recent.length; const shouldScroll = prevStatus !== status && (status === RequestStatus.SUCCESS || status === RequestStatus.STARTED); + if (this.props.isLandscape !== prevProps.isLandscape) { + this.refs.searchBar.blur(); + } + if (shouldScroll) { requestAnimationFrame(() => { this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle @@ -468,6 +478,7 @@ class Search extends PureComponent { render() { const { intl, + isLandscape, navigator, postIds, recent, @@ -580,48 +591,54 @@ class Search extends PureComponent { } return ( - - - - + + + + + + + + {previewComponent} - - - {previewComponent} - + ); } } @@ -637,8 +654,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { justifyContent: 'center' }, ios: { - height: 64, - paddingTop: 20 + height: 44 } }) }, diff --git a/app/screens/settings/general/settings.js b/app/screens/settings/general/settings.js index 50a8d2734..fbe7a835f 100644 --- a/app/screens/settings/general/settings.js +++ b/app/screens/settings/general/settings.js @@ -14,7 +14,7 @@ import DeviceInfo from 'react-native-device-info'; import SettingsItem from 'app/screens/settings/settings_item'; import StatusBar from 'app/components/status_bar'; -import {preventDoubleTap} from 'app/utils/tap'; +import {wrapWithPreventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {isValidUrl} from 'app/utils/url'; @@ -68,7 +68,7 @@ class Settings extends PureComponent { return contents.join('\n'); }; - goToAbout = () => { + goToAbout = wrapWithPreventDoubleTap(() => { const {intl, navigator, theme} = this.props; navigator.push({ screen: 'About', @@ -81,9 +81,9 @@ class Settings extends PureComponent { navBarButtonColor: theme.sidebarHeaderTextColor } }); - }; + }); - goToNotifications = () => { + goToNotifications = wrapWithPreventDoubleTap(() => { const {intl, navigator, theme} = this.props; navigator.push({ screen: 'NotificationSettings', @@ -97,9 +97,9 @@ class Settings extends PureComponent { screenBackgroundColor: theme.centerChannelBg } }); - }; + }); - goToAdvancedSettings = () => { + goToAdvancedSettings = wrapWithPreventDoubleTap(() => { const {intl, navigator, theme} = this.props; navigator.push({ screen: 'AdvancedSettings', @@ -113,9 +113,9 @@ class Settings extends PureComponent { screenBackgroundColor: theme.centerChannelBg } }); - }; + }); - goToSelectTeam = () => { + goToSelectTeam = wrapWithPreventDoubleTap(() => { const {currentUrl, intl, navigator, theme} = this.props; navigator.push({ @@ -134,14 +134,16 @@ class Settings extends PureComponent { theme } }); - }; + }); - goToClientUpgrade = () => { + goToClientUpgrade = wrapWithPreventDoubleTap(() => { const {intl, theme} = this.props; this.props.navigator.push({ screen: 'ClientUpgrade', - title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Client Upgrade'}), + title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}), + animated: true, + backButtonTitle: '', navigatorStyle: { navBarHidden: false, statusBarHidden: true, @@ -154,16 +156,12 @@ class Settings extends PureComponent { userCheckedForUpgrade: true } }); - } + }); - handlePress = (action) => { - preventDoubleTap(action, this); - }; - - logout = () => { + logout = wrapWithPreventDoubleTap(() => { const {logout} = this.props.actions; InteractionManager.runAfterInteractions(logout); - }; + }); onNavigatorEvent = (event) => { if (event.type === 'NavBarButtonPress') { @@ -175,20 +173,30 @@ class Settings extends PureComponent { } }; - openErrorEmail = () => { + openErrorEmail = wrapWithPreventDoubleTap(() => { const {config} = this.props; const recipient = config.SupportEmail; const subject = `Problem with ${config.SiteName} React Native app`; - Linking.openURL( - `mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}` - ); - this.props.actions.clearErrors(); - }; + const mailTo = `mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}`; - openHelp = () => { + Linking.canOpenURL(mailTo).then((supported) => { + if (supported) { + Linking.openURL(mailTo); + this.props.actions.clearErrors(); + } + }); + }); + + openHelp = wrapWithPreventDoubleTap(() => { const {config} = this.props; - Linking.openURL(config.HelpLink.toLowerCase()); - }; + const link = config.HelpLink ? config.HelpLink.toLowerCase() : ''; + + Linking.canOpenURL(link).then((supported) => { + if (supported) { + Linking.openURL(link); + } + }); + }); render() { const {config, joinableTeams, theme} = this.props; @@ -207,7 +215,7 @@ class Settings extends PureComponent { i18nId='user.settings.modal.notifications' iconName='ios-notifications' iconType='ion' - onPress={() => this.handlePress(this.goToNotifications)} + onPress={this.goToNotifications} showArrow={showArrow} theme={theme} /> @@ -217,7 +225,7 @@ class Settings extends PureComponent { i18nId='mobile.select_team.join_open' iconName='list' iconType='foundation' - onPress={() => this.handlePress(this.goToSelectTeam)} + onPress={this.goToSelectTeam} showArrow={showArrow} theme={theme} /> @@ -228,7 +236,7 @@ class Settings extends PureComponent { i18nId='mobile.help.title' iconName='md-help' iconType='ion' - onPress={() => this.handlePress(this.openHelp)} + onPress={this.openHelp} showArrow={showArrow} theme={theme} /> @@ -238,7 +246,7 @@ class Settings extends PureComponent { i18nId='sidebar_right_menu.report' iconName='exclamation' iconType='fontawesome' - onPress={() => this.handlePress(this.openErrorEmail)} + onPress={this.openErrorEmail} showArrow={showArrow} theme={theme} /> @@ -247,7 +255,7 @@ class Settings extends PureComponent { i18nId='mobile.advanced_settings.title' iconName='ios-hammer' iconType='ion' - onPress={() => this.handlePress(this.goToAdvancedSettings)} + onPress={this.goToAdvancedSettings} showArrow={showArrow} theme={theme} /> @@ -257,7 +265,7 @@ class Settings extends PureComponent { i18nId='mobile.settings.modal.check_for_upgrade' iconName='update' iconType='material' - onPress={() => this.handlePress(this.goToClientUpgrade)} + onPress={this.goToClientUpgrade} showArrow={showArrow} theme={theme} /> @@ -267,7 +275,7 @@ class Settings extends PureComponent { i18nId='about.title' iconName='ios-information-circle' iconType='ion' - onPress={() => this.handlePress(this.goToAbout)} + onPress={this.goToAbout} separator={false} showArrow={showArrow} theme={theme} @@ -280,7 +288,7 @@ class Settings extends PureComponent { defaultMessage='Logout' i18nId='sidebar_right_menu.logout' isDestructor={true} - onPress={() => this.handlePress(this.logout)} + onPress={this.logout} separator={false} showArrow={false} theme={theme} diff --git a/app/screens/thread/thread.js b/app/screens/thread/thread.js index 4cb07a255..2c28ec0d1 100644 --- a/app/screens/thread/thread.js +++ b/app/screens/thread/thread.js @@ -11,6 +11,7 @@ import {General} from 'mattermost-redux/constants'; import KeyboardLayout from 'app/components/layout/keyboard_layout'; import PostList from 'app/components/post_list'; import PostTextbox from 'app/components/post_textbox'; +import SafeAreaView from 'app/components/safe_area_view'; import StatusBar from 'app/components/status_bar'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -88,25 +89,31 @@ class Thread extends PureComponent { const style = getStyle(theme); return ( - - - - + + + + + ); } } diff --git a/assets/base/config.json b/assets/base/config.json index 6bc89e57d..77d4d5096 100644 --- a/assets/base/config.json +++ b/assets/base/config.json @@ -33,9 +33,9 @@ "AutoSelectServerUrl": false, - "EnableMobileClientUpgrade": false, - "EnableMobileClientUpgradeUserSetting": false, - "EnableForceMobileClientUpgrade": false, - "MobileClientUpgradeAndroidApkLink": "", - "MobileClientUpgradeIosIpaLink": "" + "EnableMobileClientUpgrade": true, + "EnableMobileClientUpgradeUserSetting": true, + "EnableForceMobileClientUpgrade": true, + "MobileClientUpgradeAndroidApkLink": "https://about.mattermost.com/mattermost-android-app/", + "MobileClientUpgradeIosIpaLink": "https://about.mattermost.com/mattermost-ios-app/" } diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 1f4cdd103..d914c2b55 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; }; F23C99AA5FA10E457A76803A /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */; }; 3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */; }; + 8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9263CF9B16054263B13EA23B /* libRNSafeArea.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -638,6 +639,8 @@ FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = ""; }; 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */ = {isa = PBXFileReference; name = "RNReactNativeDocViewer.xcodeproj"; path = "../node_modules/react-native-doc-viewer/ios/RNReactNativeDocViewer.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; name = "libRNReactNativeDocViewer.a"; path = "libRNReactNativeDocViewer.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; + 849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; name = "RNSafeArea.xcodeproj"; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; name = "libRNSafeArea.a"; path = "libRNSafeArea.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -691,6 +694,7 @@ A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */, 5A0920184BD344979BCFCD5C /* libRCTVideo.a in Frameworks */, 3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */, + 8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1123,6 +1127,7 @@ A2968536BEC74A4ABBB73BD9 /* RCTYouTube.xcodeproj */, DF9DAAAA482343F3910A1A4C /* RCTVideo.xcodeproj */, 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */, + 849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -2044,6 +2049,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**", + "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea", ); INFOPLIST_FILE = MattermostTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -2053,6 +2059,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "$(inherited)", @@ -2075,6 +2082,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**", + "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea", ); INFOPLIST_FILE = MattermostTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -2084,6 +2092,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "$(inherited)", @@ -2127,6 +2136,7 @@ "$(SRCROOT)/../node_modules/react-native-youtube/**", "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**", + "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea", ); INFOPLIST_FILE = Mattermost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.3; @@ -2176,6 +2186,7 @@ "$(SRCROOT)/../node_modules/react-native-youtube/**", "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**", + "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea", ); INFOPLIST_FILE = Mattermost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.3; diff --git a/package.json b/package.json index 19aabaaa4..fb5722101 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-native-notifications": "enahum/react-native-notifications.git", "react-native-orientation": "enahum/react-native-orientation.git", "react-native-passcode-status": "1.1.0", + "react-native-safe-area": "0.2.1", "react-native-section-list-get-item-layout": "2.1.0", "react-native-sentry": "0.15.1", "react-native-slider": "0.11.0", diff --git a/yarn.lock b/yarn.lock index 90937c041..302b0014f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3961,7 +3961,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "1.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/4f36c293b136cdfb9f44b9536a5006084d0dc758" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/fb083f67b5560ff64fe251415460a1af5d690e5a" dependencies: deep-equal "1.0.1" form-data "2.3.1" @@ -5140,6 +5140,10 @@ react-native-passcode-status@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-native-passcode-status/-/react-native-passcode-status-1.1.0.tgz#e5922d5f4d79bc09849438d620406e5ccd31168a" +react-native-safe-area@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/react-native-safe-area/-/react-native-safe-area-0.2.1.tgz#9bf8eb8dd052b76fc4e830addcd63235dd29cf3c" + react-native-section-list-get-item-layout@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-native-section-list-get-item-layout/-/react-native-section-list-get-item-layout-2.1.0.tgz#ec5336fa769cfbe0c49ace50086a3cfdb2be2812"