diff --git a/app/actions/navigation.js b/app/actions/navigation.js index 3cb3e7dc0..88880e01b 100644 --- a/app/actions/navigation.js +++ b/app/actions/navigation.js @@ -1,13 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {Platform} from 'react-native'; import {Navigation} from 'react-native-navigation'; import merge from 'deepmerge'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -export function resetToChannel() { +export function resetToChannel(passProps = {}) { return (dispatch, getState) => { const theme = getTheme(getState()); @@ -17,6 +18,7 @@ export function resetToChannel() { children: [{ component: { name: 'Channel', + passProps, options: { layout: { backgroundColor: 'transparent', @@ -25,6 +27,8 @@ export function resetToChannel() { visible: true, }, topBar: { + visible: false, + height: 0, backButton: { color: theme.sidebarHeaderTextColor, title: '', @@ -35,8 +39,6 @@ export function resetToChannel() { title: { color: theme.sidebarHeaderTextColor, }, - visible: false, - height: 0, }, }, }, @@ -84,6 +86,48 @@ export function resetToSelectServer(allowOtherServers) { }; } +export function resetToTeams(name, title, passProps = {}, options = {}) { + return (dispatch, getState) => { + const theme = getTheme(getState()); + const defaultOptions = { + layout: { + backgroundColor: theme.centerChannelBg, + }, + statusBar: { + visible: true, + }, + topBar: { + visible: true, + title: { + color: theme.sidebarHeaderTextColor, + text: title, + }, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + }, + }; + + Navigation.setRoot({ + root: { + stack: { + children: [{ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }], + }, + }, + }); + }; +} + export function goToScreen(componentId, name, title, passProps = {}, options = {}) { return (dispatch, getState) => { const theme = getTheme(getState()); @@ -108,6 +152,119 @@ export function goToScreen(componentId, name, title, passProps = {}, options = { }, }; + Navigation.push(componentId, { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }); + }; +} + +export function showModal(name, title, passProps = {}, options = {}) { + return (dispatch, getState) => { + const theme = getTheme(getState()); + const defaultOptions = { + layout: { + backgroundColor: theme.centerChannelBg, + }, + statusBar: { + visible: true, + }, + topBar: { + animate: true, + visible: true, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + text: title, + }, + }, + }; + + Navigation.showModal({ + stack: { + children: [{ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }], + }, + }); + }; +} + +export function showModalOverCurrentContext(name, passProps = {}, options = {}) { + return (dispatch) => { + const title = ''; + const animationsEnabled = (Platform.OS === 'android').toString(); + const defaultOptions = { + modalPresentationStyle: 'overCurrentContext', + layout: { + backgroundColor: 'transparent', + }, + topBar: { + visible: false, + height: 0, + }, + animations: { + showModal: { + enabled: animationsEnabled, + alpha: { + from: 0, + to: 1, + duration: 250, + }, + }, + dismissModal: { + enabled: animationsEnabled, + alpha: { + from: 1, + to: 0, + duration: 250, + }, + }, + }, + }; + const mergeOptions = merge(defaultOptions, options); + + dispatch(showModal(name, title, passProps, mergeOptions)); + }; +} + +export function showSearchModal(initialValue = '') { + return (dispatch) => { + const name = 'Search'; + const title = ''; + const passProps = {initialValue}; + const options = { + topBar: { + visible: false, + height: 0, + }, + }; + + dispatch(showModal(name, title, passProps, options)); + }; +} + +export function peek(componentId, name, passProps = {}, options = {}) { + return () => { + const defaultOptions = { + preview: { + commit: false, + }, + }; + Navigation.push(componentId, { component: { name, diff --git a/app/actions/views/search.js b/app/actions/views/search.js index 2726e6635..0e6f0c3a2 100644 --- a/app/actions/views/search.js +++ b/app/actions/views/search.js @@ -14,6 +14,8 @@ export function handleSearchDraftChanged(text) { }; } +// TODO: Remove this once all screens/components call +// app/actions/navigation's showSearchModal export function showSearchModal(navigator, initialValue = '') { return (dispatch, getState) => { const theme = getTheme(getState()); diff --git a/app/components/__snapshots__/profile_picture_button.test.js.snap b/app/components/__snapshots__/profile_picture_button.test.js.snap index c93aadecd..97f0ea52d 100644 --- a/app/components/__snapshots__/profile_picture_button.test.js.snap +++ b/app/components/__snapshots__/profile_picture_button.test.js.snap @@ -1,20 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`profile_picture_button should match snapshot 1`] = ` - `; diff --git a/app/components/__snapshots__/attachment_button.test.js.snap b/app/components/attachment_button/__snapshots__/attachment_button.test.js.snap similarity index 100% rename from app/components/__snapshots__/attachment_button.test.js.snap rename to app/components/attachment_button/__snapshots__/attachment_button.test.js.snap diff --git a/app/components/attachment_button.js b/app/components/attachment_button/attachment_button.js similarity index 97% rename from app/components/attachment_button.js rename to app/components/attachment_button/attachment_button.js index 29bd3d0d0..5c746c5d8 100644 --- a/app/components/attachment_button.js +++ b/app/components/attachment_button/attachment_button.js @@ -27,6 +27,9 @@ const ShareExtension = NativeModules.MattermostShare; export default class AttachmentButton extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + showModalOverCurrentContext: PropTypes.func.isRequired, + }).isRequired, blurTextBox: PropTypes.func.isRequired, browseFileTypes: PropTypes.string, validMimeTypes: PropTypes.array, @@ -366,6 +369,7 @@ export default class AttachmentButton extends PureComponent { maxFileCount, onShowFileMaxWarning, extraOptions, + actions, } = this.props; if (fileCount === maxFileCount) { @@ -439,21 +443,7 @@ export default class AttachmentButton extends PureComponent { }); } - this.props.navigator.showModal({ - screen: 'OptionsModal', - title: '', - animationType: 'none', - passProps: { - items, - }, - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: 'transparent', - modalPresentationStyle: 'overCurrentContext', - }, - }); + actions.showModalOverCurrentContext('OptionsModal', {items}); }; render() { diff --git a/app/components/attachment_button.test.js b/app/components/attachment_button/attachment_button.test.js similarity index 96% rename from app/components/attachment_button.test.js rename to app/components/attachment_button/attachment_button.test.js index ae789c5c6..b98df3716 100644 --- a/app/components/attachment_button.test.js +++ b/app/components/attachment_button/attachment_button.test.js @@ -13,6 +13,9 @@ jest.mock('react-intl'); describe('AttachmentButton', () => { const baseProps = { + actions: { + showModalOverCurrentContext: jest.fn(), + }, theme: Preferences.THEMES.default, navigator: {}, blurTextBox: jest.fn(), diff --git a/app/components/attachment_button/index.js b/app/components/attachment_button/index.js new file mode 100644 index 000000000..d48573136 --- /dev/null +++ b/app/components/attachment_button/index.js @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {showModalOverCurrentContext} from 'app/actions/navigation'; + +import AttachmentButton from './attachment_button'; + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + showModalOverCurrentContext, + }, dispatch), + }; +} + +export default connect(null, mapDispatchToProps)(AttachmentButton); diff --git a/app/components/root/index.js b/app/components/root/index.js index c2234531c..747bfdb9c 100644 --- a/app/components/root/index.js +++ b/app/components/root/index.js @@ -1,13 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; - -import {getCurrentLocale} from 'app/selectors/i18n'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; + +import {resetToTeams} from 'app/actions/navigation'; +import {getCurrentLocale} from 'app/selectors/i18n'; import {removeProtocol} from 'app/utils/url'; import Root from './root'; @@ -23,4 +25,12 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(Root); +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + resetToTeams, + }, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(Root); diff --git a/app/components/root/root.js b/app/components/root/root.js index 5566e7335..051c4c715 100644 --- a/app/components/root/root.js +++ b/app/components/root/root.js @@ -14,6 +14,9 @@ import {getTranslations} from 'app/i18n'; export default class Root extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + resetToTeams: PropTypes.func.isRequired, + }).isRequired, children: PropTypes.node, navigator: PropTypes.object, excludeEvents: PropTypes.bool, @@ -83,28 +86,24 @@ export default class Root extends PureComponent { } navigateToTeamsPage = (screen) => { - const {currentUrl, navigator, theme} = this.props; + const {currentUrl, theme, actions} = this.props; const {intl} = this.refs.provider.getChildContext(); - let navigatorButtons; let passProps = {theme}; + const options = {topBar: {}}; if (Platform.OS === 'android') { - navigatorButtons = { - rightButtons: [{ - title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), - id: 'logout', - buttonColor: theme.sidebarHeaderTextColor, - showAsAction: 'always', - }], - }; + options.topBar.rightButtons = [{ + id: 'logout', + text: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), + color: theme.sidebarHeaderTextColor, + showAsAction: 'always', + }]; } else { - navigatorButtons = { - leftButtons: [{ - title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), - id: 'logout', - buttonColor: theme.sidebarHeaderTextColor, - }], - }; + options.topBar.leftButtons = [{ + id: 'logout', + text: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}), + color: theme.sidebarHeaderTextColor, + }]; } if (screen === 'SelectTeam') { @@ -115,20 +114,9 @@ export default class Root extends PureComponent { }; } - navigator.resetTo({ - screen, - title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}), - animated: false, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - navigatorButtons, - passProps, - }); + const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}); + + actions.resetToTeams(screen, title, passProps, options); } handleNotificationTapped = async () => { diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js index b69b77ec9..f1bd342cd 100644 --- a/app/components/safe_area_view/safe_area_view.ios.js +++ b/app/components/safe_area_view/safe_area_view.ios.js @@ -5,7 +5,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Dimensions, Keyboard, NativeModules, View} from 'react-native'; import SafeArea from 'react-native-safe-area'; -import {Navigation} from 'react-native-navigation'; import {DeviceTypes} from 'app/constants'; import mattermostManaged from 'app/mattermost_managed'; @@ -53,8 +52,6 @@ export default class SafeAreaIos extends PureComponent { } componentDidMount() { - this.navigationEventListener = Navigation.events().bindComponent(this); - Dimensions.addEventListener('change', this.getSafeAreaInsets); this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); @@ -69,14 +66,6 @@ export default class SafeAreaIos extends PureComponent { this.mounted = false; } - componentDidAppear() { - this.getSafeAreaInsets(); - } - - componentDidDisappear() { - this.getSafeAreaInsets(); - } - getStatusBarHeight = () => { try { StatusBarManager.getHeight( diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index 5940ff56f..e69b5ce57 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -2,11 +2,10 @@ exports[`ChannelItem should match snapshot 1`] = ` - - + `; exports[`ChannelItem should match snapshot for current user i.e currentUser (you) 1`] = ` - - + `; exports[`ChannelItem should match snapshot for current user i.e currentUser (you) when isSearchResult 1`] = ` - - + `; exports[`ChannelItem should match snapshot for deactivated user and is currentChannel 1`] = ` - - + `; exports[`ChannelItem should match snapshot for deactivated user and is searchResult 1`] = ` - - + `; @@ -561,11 +556,10 @@ exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`; exports[`ChannelItem should match snapshot with draft 1`] = ` - - + `; exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` - - + `; diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index 4e5301985..a5d01ffce 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -2,17 +2,17 @@ // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; -import {General} from 'mattermost-redux/constants'; - import PropTypes from 'prop-types'; import { Animated, - Platform, TouchableHighlight, Text, View, } from 'react-native'; import {intlShape} from 'react-intl'; +import {Navigation} from 'react-native-navigation'; + +import {General} from 'mattermost-redux/constants'; import Badge from 'app/components/badge'; import ChannelIcon from 'app/components/channel_icon'; @@ -32,7 +32,6 @@ export default class ChannelItem extends PureComponent { isUnread: PropTypes.bool, hasDraft: PropTypes.bool, mentions: PropTypes.number.isRequired, - navigator: PropTypes.object, onSelectChannel: PropTypes.func.isRequired, shouldHideChannel: PropTypes.bool, showUnreadForMsgs: PropTypes.bool.isRequired, @@ -40,6 +39,7 @@ export default class ChannelItem extends PureComponent { unreadMsgs: PropTypes.number.isRequired, isSearchResult: PropTypes.bool, isBot: PropTypes.bool.isRequired, + previewChannel: PropTypes.func, }; static defaultProps = { @@ -58,28 +58,25 @@ export default class ChannelItem extends PureComponent { }); }); - onPreview = () => { - const {channelId, navigator} = this.props; - if (Platform.OS === 'ios' && navigator && this.previewRef) { + onPreview = ({reactTag}) => { + const {channelId, previewChannel} = this.props; + if (previewChannel) { const {intl} = this.context; - - navigator.push({ - screen: 'ChannelPeek', - previewCommit: false, - previewView: this.previewRef, - previewActions: [{ - id: 'action-mark-as-read', - title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}), - }], - passProps: { - channelId, + const passProps = { + channelId, + }; + const options = { + preview: { + reactTag, + actions: [{ + id: 'action-mark-as-read', + title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}), + }], }, - }); - } - }; + }; - setPreviewRef = (ref) => { - this.previewRef = ref; + previewChannel(passProps, options); + } }; showChannelAsUnread = () => { @@ -190,11 +187,12 @@ export default class ChannelItem extends PureComponent { ); return ( - - + {extraBorder} @@ -210,7 +208,7 @@ export default class ChannelItem extends PureComponent { {badge} - + ); } diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js index 8774c4614..38334340d 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js @@ -3,7 +3,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {TouchableHighlight} from 'react-native'; +import {Navigation} from 'react-native-navigation'; import Preferences from 'mattermost-redux/constants/preferences'; @@ -216,7 +216,7 @@ describe('ChannelItem', () => { {context: {intl: {formatMessage: jest.fn()}}}, ); - wrapper.find(TouchableHighlight).simulate('press'); + wrapper.find(Navigation.TouchablePreview).simulate('press'); jest.runAllTimers(); const expectedChannelParams = {id: baseProps.channelId, display_name: baseProps.displayName, fake: channel.fake, type: channel.type}; diff --git a/app/components/sidebars/main/channels_list/channels_list.js b/app/components/sidebars/main/channels_list/channels_list.js index 4d5ec4d15..1a26c82f0 100644 --- a/app/components/sidebars/main/channels_list/channels_list.js +++ b/app/components/sidebars/main/channels_list/channels_list.js @@ -22,7 +22,6 @@ let FilteredList = null; export default class ChannelsList extends PureComponent { static propTypes = { - navigator: PropTypes.object, onJoinChannel: PropTypes.func.isRequired, onSearchEnds: PropTypes.func.isRequired, onSearchStart: PropTypes.func.isRequired, @@ -30,6 +29,7 @@ export default class ChannelsList extends PureComponent { onShowTeams: PropTypes.func.isRequired, theme: PropTypes.object.isRequired, drawerOpened: PropTypes.bool, + previewChannel: PropTypes.func, }; static contextTypes = { @@ -86,9 +86,9 @@ export default class ChannelsList extends PureComponent { render() { const {intl} = this.context; const { - navigator, onShowTeams, theme, + previewChannel, } = this.props; const {searching, term} = this.state; @@ -101,14 +101,15 @@ export default class ChannelsList extends PureComponent { onSelectChannel={this.onSelectChannel} styles={styles} term={term} + previewChannel={previewChannel} /> ); } else { list = ( ); } diff --git a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js index 7546f3630..d37d56c42 100644 --- a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js +++ b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js @@ -54,6 +54,7 @@ class FilteredList extends Component { styles: PropTypes.object.isRequired, term: PropTypes.string, theme: PropTypes.object.isRequired, + previewChannel: PropTypes.func, }; static defaultProps = { @@ -126,6 +127,7 @@ class FilteredList extends Component { isUnread={channel.isUnread} mentions={0} onSelectChannel={this.onSelectChannel} + previewChannel={this.props.previewChannel} /> ); }; diff --git a/app/components/sidebars/main/channels_list/list/index.js b/app/components/sidebars/main/channels_list/list/index.js index adb6f31a7..eaca23964 100644 --- a/app/components/sidebars/main/channels_list/list/index.js +++ b/app/components/sidebars/main/channels_list/list/index.js @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {General} from 'mattermost-redux/constants'; @@ -17,6 +18,8 @@ import {memoizeResult} from 'mattermost-redux/utils/helpers'; import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; +import {showModal} from 'app/actions/navigation'; + import {DeviceTypes, ViewTypes} from 'app/constants'; import List from './list'; @@ -67,6 +70,14 @@ function mapStateToProps(state) { }; } +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + showModal, + }, dispatch), + }; +} + function areStatesEqual(next, prev) { const equalRoles = getCurrentUserRoles(prev) === getCurrentUserRoles(next); const equalChannels = next.entities.channels === prev.entities.channels; @@ -77,4 +88,4 @@ function areStatesEqual(next, prev) { return equalChannels && equalConfig && equalRoles && equalUsers && equalFav; } -export default connect(mapStateToProps, null, null, {pure: true, areStatesEqual})(List); +export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(List); diff --git a/app/components/sidebars/main/channels_list/list/list.js b/app/components/sidebars/main/channels_list/list/list.js index 27ce989e1..9e0e3101f 100644 --- a/app/components/sidebars/main/channels_list/list/list.js +++ b/app/components/sidebars/main/channels_list/list/list.js @@ -34,14 +34,17 @@ let UnreadIndicator = null; export default class List extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + showModal: PropTypes.func.isRequired, + }).isRequired, canCreatePrivateChannels: PropTypes.bool.isRequired, favoriteChannelIds: PropTypes.array.isRequired, - navigator: PropTypes.object, onSelectChannel: PropTypes.func.isRequired, unreadChannelIds: PropTypes.array.isRequired, styles: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, orderedChannelIds: PropTypes.array.isRequired, + previewChannel: PropTypes.func, }; static contextTypes = { @@ -159,7 +162,10 @@ export default class List extends PureComponent { }; showCreateChannelOptions = () => { - const {canCreatePrivateChannels, navigator} = this.props; + const { + canCreatePrivateChannels, + actions, + } = this.props; const items = []; const moreChannels = { @@ -197,117 +203,87 @@ export default class List extends PureComponent { } items.push(newConversation); - navigator.showModal({ - screen: 'OptionsModal', - title: '', - animationType: 'none', - passProps: { - items, - onItemPress: () => navigator.dismissModal({ - animationType: 'none', - }), + const screen = 'OptionsModal'; + const title = ''; + const passProps = { + items, + }; + const options = { + modalPresentationStyle: 'overCurrentContext', + layout: { + backgroundColor: 'transparent', }, - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: 'transparent', - modalPresentationStyle: 'overCurrentContext', + topBar: { + visible: false, + height: 0, }, - }); + animations: { + showModal: { + enable: false, + }, + dismissModal: { + enable: false, + }, + }, + }; + + actions.showModal(screen, title, passProps, options); }; goToCreatePublicChannel = preventDoubleTap(() => { - const {navigator, theme} = this.props; + const {actions} = this.props; const {intl} = this.context; + const screen = 'CreateChannel'; + const title = intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}); + const passProps = { + channelType: General.OPEN_CHANNEL, + closeButton: this.closeButton, + }; - navigator.showModal({ - screen: 'CreateChannel', - animationType: 'slide-up', - title: intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}), - backButtonTitle: '', - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - channelType: General.OPEN_CHANNEL, - closeButton: this.closeButton, - }, - }); + actions.showModal(screen, title, passProps); }); goToCreatePrivateChannel = preventDoubleTap(() => { - const {navigator, theme} = this.props; + const {actions} = this.props; const {intl} = this.context; + const screen = 'CreateChannel'; + const title = intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}); + const passProps = { + channelType: General.PRIVATE_CHANNEL, + closeButton: this.closeButton, + }; - navigator.showModal({ - screen: 'CreateChannel', - animationType: 'slide-up', - title: intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}), - backButtonTitle: '', - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - channelType: General.PRIVATE_CHANNEL, - closeButton: this.closeButton, - }, - }); + actions.showModal(screen, title, passProps); }); goToDirectMessages = preventDoubleTap(() => { - const {navigator, theme} = this.props; + const {actions} = this.props; const {intl} = this.context; - - navigator.showModal({ - screen: 'MoreDirectMessages', - title: intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'}), - animationType: 'slide-up', - animated: true, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - navigatorButtons: { + const screen = 'MoreDirectMessages'; + const title = intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'}); + const passProps = {}; + const options = { + topBar: { leftButtons: [{ id: 'close-dms', icon: this.closeButton, }], }, - }); + }; + + actions.showModal(screen, title, passProps, options); }); goToMoreChannels = preventDoubleTap(() => { - const {navigator, theme} = this.props; + const {actions} = this.props; const {intl} = this.context; + const screen = 'MoreChannels'; + const title = intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'}); + const passProps = { + closeButton: this.closeButton, + }; - navigator.showModal({ - screen: 'MoreChannels', - animationType: 'slide-up', - title: intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'}), - backButtonTitle: '', - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - closeButton: this.closeButton, - }, - }); + actions.showModal(screen, title, passProps); }); keyExtractor = (item) => item.id || item; @@ -349,15 +325,15 @@ export default class List extends PureComponent { }; renderItem = ({item}) => { - const {favoriteChannelIds, unreadChannelIds} = this.props; + const {favoriteChannelIds, unreadChannelIds, previewChannel} = this.props; return ( ); }; diff --git a/app/components/sidebars/main/main_sidebar.js b/app/components/sidebars/main/main_sidebar.js index c4f89b70a..1b048cc00 100644 --- a/app/components/sidebars/main/main_sidebar.js +++ b/app/components/sidebars/main/main_sidebar.js @@ -44,9 +44,9 @@ export default class ChannelSidebar extends Component { currentUserId: PropTypes.string.isRequired, deviceWidth: PropTypes.number.isRequired, isLandscape: PropTypes.bool.isRequired, - navigator: PropTypes.object, teamsCount: PropTypes.number.isRequired, theme: PropTypes.object.isRequired, + previewChannel: PropTypes.func, }; static contextTypes = { @@ -297,9 +297,9 @@ export default class ChannelSidebar extends Component { renderNavigationView = (drawerWidth) => { const { - navigator, teamsCount, theme, + previewChannel, } = this.props; const { @@ -331,7 +331,6 @@ export default class ChannelSidebar extends Component { > ); @@ -345,7 +344,6 @@ export default class ChannelSidebar extends Component { > ); @@ -362,7 +361,6 @@ export default class ChannelSidebar extends Component { navBarBackgroundColor={theme.sidebarBg} backgroundColor={theme.sidebarHeaderBg} footerColor={theme.sidebarHeaderBg} - navigator={navigator} > { const {intl} = this.context; - const {currentUrl, navigator, theme} = this.props; - - navigator.showModal({ - screen: 'SelectTeam', - title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}), - animationType: 'slide-up', - animated: true, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - navigatorButtons: { + const {currentUrl, theme, actions} = this.props; + const screen = 'SelectTeam'; + const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}); + const passProps = { + currentUrl, + theme, + }; + const options = { + topBar: { leftButtons: [{ id: 'close-teams', icon: this.closeButton, }], }, - passProps: { - currentUrl, - theme, - }, - }); + }; + + actions.showModal(screen, title, passProps, options); }); keyExtractor = (item) => { diff --git a/app/screens/channel/channel.ios.js b/app/screens/channel/channel.ios.js index d53d567de..05d67f9ca 100644 --- a/app/screens/channel/channel.ios.js +++ b/app/screens/channel/channel.ios.js @@ -25,6 +25,15 @@ const CHANNEL_POST_TEXTBOX_CURSOR_CHANGE = 'onChannelTextBoxCursorChange'; const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange'; export default class ChannelIOS extends ChannelBase { + previewChannel = (passProps, options) => { + const {actions, componentId} = this.props; + const screen = 'ChannelPeek'; + + actions.peek(componentId, screen, passProps, options); + }; + + optionalProps = {previewChannel: this.previewChannel}; + render() { const {height} = Dimensions.get('window'); const { @@ -82,6 +91,6 @@ export default class ChannelIOS extends ChannelBase { ); - return this.renderChannel(drawerContent); + return this.renderChannel(drawerContent, this.optionalProps); } } diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index da40eacda..53e40b4ef 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -39,6 +39,7 @@ export default class ChannelBase extends PureComponent { selectDefaultTeam: PropTypes.func.isRequired, selectInitialChannel: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, + peek: PropTypes.func.isRequired, }).isRequired, currentChannelId: PropTypes.string, channelsRequestFailed: PropTypes.bool, @@ -259,7 +260,7 @@ export default class ChannelBase extends PureComponent { } }; - renderChannel(drawerContent) { + renderChannel(drawerContent, optionalProps = {}) { const { channelsRequestFailed, currentChannelId, @@ -298,6 +299,7 @@ export default class ChannelBase extends PureComponent { ref={this.channelSidebarRef} blurPostTextBox={this.blurPostTextBox} navigator={navigator} + {...optionalProps} > { - const {actions, navigator} = this.props; + const {actions} = this.props; Keyboard.dismiss(); await actions.clearSearch(); - await actions.showSearchModal(navigator); + await actions.showSearchModal(); }); render() { diff --git a/app/screens/channel/channel_nav_bar/channel_search_button/index.js b/app/screens/channel/channel_nav_bar/channel_search_button/index.js index 12e7f3d09..8bbb62cfb 100644 --- a/app/screens/channel/channel_nav_bar/channel_search_button/index.js +++ b/app/screens/channel/channel_nav_bar/channel_search_button/index.js @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {clearSearch} from 'mattermost-redux/actions/search'; -import {showSearchModal} from 'app/actions/views/search'; +import {showSearchModal} from 'app/actions/navigation'; import ChannelSearchButton from './channel_search_button'; diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js index 40b00d318..1c9fc4c26 100644 --- a/app/screens/channel/index.js +++ b/app/screens/channel/index.js @@ -19,6 +19,7 @@ import { import {connection} from 'app/actions/device'; import {recordLoadTime} from 'app/actions/views/root'; import {selectDefaultTeam} from 'app/actions/views/select_team'; +import {peek} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import Channel from './channel'; @@ -48,6 +49,7 @@ function mapDispatchToProps(dispatch) { recordLoadTime, startPeriodicStatusUpdates, stopPeriodicStatusUpdates, + peek, }, dispatch), }; } diff --git a/app/screens/channel_peek/channel_peek.js b/app/screens/channel_peek/channel_peek.js index 081952558..162cdd85b 100644 --- a/app/screens/channel_peek/channel_peek.js +++ b/app/screens/channel_peek/channel_peek.js @@ -21,12 +21,11 @@ export default class ChannelPeek extends PureComponent { currentUserId: PropTypes.string, lastViewedAt: PropTypes.number, navigator: PropTypes.object, - postIds: PropTypes.array.isRequired, + postIds: PropTypes.array, theme: PropTypes.object.isRequired, }; static defaultProps = { - postIds: [], postVisibility: 15, }; @@ -65,7 +64,7 @@ export default class ChannelPeek extends PureComponent { } getVisiblePostIds = (props) => { - return props.postIds.slice(0, 15); + return props.postIds?.slice(0, 15) || []; }; render() { diff --git a/app/screens/index.js b/app/screens/index.js index bd19a2f6d..e5ab8f5af 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -15,6 +15,7 @@ const navigator = { push: () => {}, // eslint-disable-line no-empty-function setOnNavigatorEvent: () => {}, // eslint-disable-line no-empty-function setStyle: () => {}, // eslint-disable-line no-empty-function + setButtons: () => {}, // eslint-disable-line no-empty-function }; export function registerScreens(store, Provider) { diff --git a/app/screens/more_channels/more_channels.js b/app/screens/more_channels/more_channels.js index 0330b8c59..ac118b36f 100644 --- a/app/screens/more_channels/more_channels.js +++ b/app/screens/more_channels/more_channels.js @@ -54,6 +54,7 @@ export default class MoreChannels extends PureComponent { this.searchTimeoutId = 0; this.page = -1; this.next = true; + this.mounted = false; this.state = { channels: props.channels.slice(0, General.CHANNELS_CHUNK_SIZE), @@ -86,10 +87,14 @@ export default class MoreChannels extends PureComponent { componentDidMount() { this.navigationEventListener = Navigation.events().bindComponent(this); - + this.mounted = true; this.doGetChannels(); } + componentWillUnmount() { + this.mounted = false; + } + componentWillReceiveProps(nextProps) { const {term} = this.state; let channels; @@ -138,7 +143,7 @@ export default class MoreChannels extends PureComponent { const {actions, currentTeamId} = this.props; const {loading, term} = this.state; - if (this.next && !loading && !term) { + if (this.next && !loading && !term && this.mounted) { this.setState({loading: true}, () => { actions.getChannels( currentTeamId, @@ -172,12 +177,14 @@ export default class MoreChannels extends PureComponent { }; loadedChannels = ({data}) => { - if (data && !data.length) { - this.next = false; - } + if (this.mounted) { + if (data && !data.length) { + this.next = false; + } - this.page += 1; - this.setState({loading: false}); + this.page += 1; + this.setState({loading: false}); + } }; onSelectChannel = async (id) => { diff --git a/app/screens/more_dms/more_dms.js b/app/screens/more_dms/more_dms.js index 1af96f318..0d8723af3 100644 --- a/app/screens/more_dms/more_dms.js +++ b/app/screens/more_dms/more_dms.js @@ -61,6 +61,7 @@ export default class MoreDirectMessages extends PureComponent { this.searchTimeoutId = 0; this.next = true; this.page = -1; + this.mounted = false; this.state = { profiles: [], @@ -77,10 +78,15 @@ export default class MoreDirectMessages extends PureComponent { componentDidMount() { this.navigationEventListener = Navigation.events().bindComponent(this); + this.mounted = true; this.getProfiles(); } + componentWillUnmount() { + this.mounted = false; + } + componentDidUpdate(prevProps) { const {componentId, theme} = this.props; const {selectedCount, startingConversation} = this.state; @@ -111,7 +117,7 @@ export default class MoreDirectMessages extends PureComponent { getProfiles = debounce(() => { const {loading, term} = this.state; - if (this.next && !loading && !term) { + if (this.next && !loading && !term && this.mounted) { this.setState({loading: true}, () => { const {actions, currentTeamId, restrictDirectMessage} = this.props; @@ -182,13 +188,15 @@ export default class MoreDirectMessages extends PureComponent { }; loadedProfiles = ({data}) => { - const {profiles} = this.state; - if (data && !data.length) { - this.next = false; - } + if (this.mounted) { + const {profiles} = this.state; + if (data && !data.length) { + this.next = false; + } - this.page += 1; - this.setState({loading: false, profiles: [...profiles, ...data]}); + this.page += 1; + this.setState({loading: false, profiles: [...profiles, ...data]}); + } }; makeDirectChannel = async (id) => { diff --git a/app/screens/options_modal/options_modal.js b/app/screens/options_modal/options_modal.js index 55835939d..062e8dac9 100644 --- a/app/screens/options_modal/options_modal.js +++ b/app/screens/options_modal/options_modal.js @@ -9,6 +9,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; +import {Navigation} from 'react-native-navigation'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -22,12 +23,11 @@ const DURATION = 200; export default class OptionsModal extends PureComponent { static propTypes = { + componentId: PropTypes.string.isRequired, items: PropTypes.array.isRequired, deviceHeight: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, - navigator: PropTypes.object, onCancelPress: PropTypes.func, - onItemPress: PropTypes.func, title: PropTypes.oneOfType([ PropTypes.string, PropTypes.object, @@ -36,7 +36,6 @@ export default class OptionsModal extends PureComponent { static defaultProps = { onCancelPress: emptyFunction, - onItemPress: emptyFunction, }; constructor(props) { @@ -69,16 +68,17 @@ export default class OptionsModal extends PureComponent { toValue: this.props.deviceHeight, duration: DURATION, }).start(() => { - this.props.navigator.dismissModal({ - animationType: 'none', - }); + Navigation.dismissModal(this.props.componentId); }); }; + onItemPress = () => { + Navigation.dismissModal(this.props.componentId); + } + render() { const { items, - onItemPress, title, } = this.props; @@ -89,7 +89,7 @@ export default class OptionsModal extends PureComponent { diff --git a/app/screens/search/search.js b/app/screens/search/search.js index 23ef8a56b..4bd53c82c 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -57,6 +57,7 @@ export default class Search extends PureComponent { selectFocusedPostId: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, }).isRequired, + componentId: PropTypes.string.isRequired, currentTeamId: PropTypes.string.isRequired, initialValue: PropTypes.string, isLandscape: PropTypes.bool.isRequired, @@ -146,7 +147,7 @@ export default class Search extends PureComponent { if (this.state.preview) { this.refs.preview.handleClose(); } else { - this.props.navigator.dismissModal(); + Navigation.dismissModal(this.props.componentId); } } } @@ -176,9 +177,8 @@ export default class Search extends PureComponent { }; cancelSearch = preventDoubleTap(() => { - const {navigator} = this.props; this.handleTextChanged('', true); - navigator.dismissModal({animationType: 'slide-down'}); + Navigation.dismissModal(this.props.componentId); }); goToThread = (post) => { @@ -211,7 +211,7 @@ export default class Search extends PureComponent { handleHashtagPress = (hashtag) => { if (this.showingPermalink) { - this.props.navigator.dismissModal(); + Navigation.dismissModal(this.props.componentId); this.handleClosePermalink(); } diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js index edc3c6f2a..3cfe25e34 100644 --- a/app/screens/select_team/index.js +++ b/app/screens/select_team/index.js @@ -4,12 +4,13 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {handleTeamChange} from 'app/actions/views/select_team'; - import {getTeams, joinTeam} from 'mattermost-redux/actions/teams'; import {logout} from 'mattermost-redux/actions/users'; import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams'; +import {resetToChannel} from 'app/actions/navigation'; +import {handleTeamChange} from 'app/actions/views/select_team'; + import SelectTeam from './select_team.js'; function mapStateToProps(state) { @@ -26,6 +27,7 @@ function mapDispatchToProps(dispatch) { handleTeamChange, joinTeam, logout, + resetToChannel, }, dispatch), }; } diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index 54661ee00..5b66520fb 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -45,10 +45,10 @@ export default class SelectTeam extends PureComponent { handleTeamChange: PropTypes.func.isRequired, joinTeam: PropTypes.func.isRequired, logout: PropTypes.func.isRequired, + resetToChannel: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, currentUrl: PropTypes.string.isRequired, - navigator: PropTypes.object, userWithoutTeams: PropTypes.bool, teams: PropTypes.array.isRequired, theme: PropTypes.object, @@ -124,27 +124,15 @@ export default class SelectTeam extends PureComponent { }; close = () => { - this.props.navigator.dismissModal({ - animationType: 'slide-down', - }); + Navigation.dismissModal(this.props.componentId); }; goToChannelView = () => { - const {navigator, theme} = this.props; + const passProps = { + disableTermsModal: true, + }; - navigator.resetTo({ - screen: 'Channel', - animated: false, - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - disableTermsModal: true, - }, - }); + this.props.actions.resetToChannel(passProps); }; onSelectTeam = async (team) => { diff --git a/app/screens/select_team/select_team.test.js b/app/screens/select_team/select_team.test.js index 19e3d6440..9d6adc9df 100644 --- a/app/screens/select_team/select_team.test.js +++ b/app/screens/select_team/select_team.test.js @@ -29,6 +29,7 @@ describe('SelectTeam', () => { handleTeamChange: jest.fn(), joinTeam: jest.fn(), logout: jest.fn(), + resetToChannel: jest.fn(), }; const baseProps = { diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index f261b1423..33cce9067 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -56,8 +56,6 @@ NSString* const NotificationClearAction = @"clear"; NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; [ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions]; - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.window.backgroundColor = [UIColor whiteColor]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: nil]; os_log(OS_LOG_DEFAULT, "Mattermost started!!"); diff --git a/package-lock.json b/package-lock.json index c2c6bbba2..f2058a1c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15534,9 +15534,9 @@ "from": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab" }, "react-native-navigation": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-2.20.2.tgz", - "integrity": "sha512-Ok1x2bBFLcIGzYF5k9SFikkUSDr4MWp1zLYy6KernL4g20DE9flgPphOrllbOEDSA87NwDUv+QsUPbpOPeW1iw==", + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-2.21.1.tgz", + "integrity": "sha512-CcICsn02NhfUZtTEg5QnXn7MiAlewc+uA3Q+UBO3SZw2rN94j8FBmEhvbvhFSWecBzqUTRvsXueMFVo4ruTAcQ==", "requires": { "hoist-non-react-statics": "3.x.x", "lodash": "4.17.x", diff --git a/package.json b/package.json index 36a79f431..45878152d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "react-native-keychain": "3.1.3", "react-native-linear-gradient": "2.5.4", "react-native-local-auth": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab", - "react-native-navigation": "2.20.2", + "react-native-navigation": "2.21.1", "react-native-notifications": "github:mattermost/react-native-notifications#f6d32b55ecda9f6a6765437c21b075e13ac64141", "react-native-passcode-status": "1.1.1", "react-native-permissions": "1.1.1",