diff --git a/app/actions/navigation.js b/app/actions/navigation.js index 56eb887af..ca261c905 100644 --- a/app/actions/navigation.js +++ b/app/actions/navigation.js @@ -312,10 +312,8 @@ export function peek(name, passProps = {}, options = {}) { }; } -export function setButtons(buttons = {leftButtons: [], rightButtons: []}) { +export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) { return () => { - const componentId = EphemeralStore.getTopComponentId(); - Navigation.mergeOptions(componentId, { topBar: { ...buttons, diff --git a/app/components/announcement_banner/announcement_banner.js b/app/components/announcement_banner/announcement_banner.js index 3da97744b..67b4b6d91 100644 --- a/app/components/announcement_banner/announcement_banner.js +++ b/app/components/announcement_banner/announcement_banner.js @@ -18,12 +18,14 @@ const {View: AnimatedView} = Animated; export default class AnnouncementBanner extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + goToScreen: PropTypes.func.isRequired, + }).isRequired, bannerColor: PropTypes.string, bannerDismissed: PropTypes.bool, bannerEnabled: PropTypes.bool, bannerText: PropTypes.string, bannerTextColor: PropTypes.string, - navigator: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, }; @@ -52,23 +54,16 @@ export default class AnnouncementBanner extends PureComponent { } handlePress = () => { - const {navigator, theme} = this.props; + const {actions} = this.props; + const {intl} = this.context; - navigator.push({ - screen: 'ExpandedAnnouncementBanner', - title: this.context.intl.formatMessage({ - id: 'mobile.announcement_banner.title', - defaultMessage: 'Announcement', - }), - animated: true, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, + const screen = 'ExpandedAnnouncementBanner'; + const title = intl.formatMessage({ + id: 'mobile.announcement_banner.title', + defaultMessage: 'Announcement', }); + + actions.goToScreen(screen, title); }; toggleBanner = (show = true) => { diff --git a/app/components/announcement_banner/announcement_banner.test.js b/app/components/announcement_banner/announcement_banner.test.js index 5312962ba..73085ebf1 100644 --- a/app/components/announcement_banner/announcement_banner.test.js +++ b/app/components/announcement_banner/announcement_banner.test.js @@ -12,12 +12,14 @@ jest.useFakeTimers(); describe('AnnouncementBanner', () => { const baseProps = { + actions: { + goToScreen: jest.fn(), + }, bannerColor: '#ddd', bannerDismissed: false, bannerEnabled: true, bannerText: 'Banner Text', bannerTextColor: '#fff', - navigator: {}, theme: Preferences.THEMES.default, }; diff --git a/app/components/announcement_banner/index.js b/app/components/announcement_banner/index.js index f560c461d..f06d6eea1 100644 --- a/app/components/announcement_banner/index.js +++ b/app/components/announcement_banner/index.js @@ -1,11 +1,14 @@ // 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 {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import {goToScreen} from 'app/actions/navigation'; + import AnnouncementBanner from './announcement_banner'; function mapStateToProps(state) { @@ -23,4 +26,12 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(AnnouncementBanner); +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + goToScreen, + }, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner); diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js index d50e434d2..b9d0200ea 100644 --- a/app/components/channel_intro/channel_intro.js +++ b/app/components/channel_intro/channel_intro.js @@ -4,7 +4,6 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Platform, Text, TouchableOpacity, View, @@ -21,11 +20,13 @@ import {t} from 'app/utils/i18n'; class ChannelIntro extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + goToScreen: PropTypes.func.isRequired, + }).isRequired, creator: PropTypes.object, currentChannel: PropTypes.object.isRequired, currentChannelMembers: PropTypes.array.isRequired, intl: intlShape.isRequired, - navigator: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, }; @@ -34,28 +35,14 @@ class ChannelIntro extends PureComponent { }; goToUserProfile = (userId) => { - const {intl, navigator, theme} = this.props; - const options = { - screen: 'UserProfile', - title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}), - animated: true, - backButtonTitle: '', - passProps: { - userId, - }, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, + const {actions, intl} = this.props; + const screen = 'UserProfile'; + const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}); + const passProps = { + userId, }; - if (Platform.OS === 'ios') { - navigator.push(options); - } else { - navigator.showModal(options); - } + actions.goToScreen(screen, title, passProps); }; getDisplayName = (member) => { diff --git a/app/components/channel_intro/index.js b/app/components/channel_intro/index.js index 5495a4d01..8d6ccf49e 100644 --- a/app/components/channel_intro/index.js +++ b/app/components/channel_intro/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 {createSelector} from 'reselect'; @@ -10,6 +11,7 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import {goToScreen} from 'app/actions/navigation'; import {getChannelMembersForDm} from 'app/selectors/channel'; import ChannelIntro from './channel_intro'; @@ -52,4 +54,12 @@ function makeMapStateToProps() { }; } -export default connect(makeMapStateToProps)(ChannelIntro); +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + goToScreen, + }, dispatch), + }; +} + +export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelIntro); diff --git a/app/components/edit_channel_info/edit_channel_info.js b/app/components/edit_channel_info/edit_channel_info.js new file mode 100644 index 000000000..028240bdc --- /dev/null +++ b/app/components/edit_channel_info/edit_channel_info.js @@ -0,0 +1,415 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import { + Platform, + TouchableWithoutFeedback, + View, + Text, + findNodeHandle, +} from 'react-native'; +import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; + +import ErrorText from 'app/components/error_text'; +import FormattedText from 'app/components/formatted_text'; +import Loading from 'app/components/loading'; +import StatusBar from 'app/components/status_bar'; +import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder'; + +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {General} from 'mattermost-redux/constants'; +import {getShortenedURL} from 'app/utils/url'; +import {t} from 'app/utils/i18n'; + +export default class EditChannelInfo extends PureComponent { + static propTypes = { + actions: PropTypes.shape({ + dismissModal: PropTypes.func.isRequired, + popTopScreen: PropTypes.func.isRequired, + }), + theme: PropTypes.object.isRequired, + deviceWidth: PropTypes.number.isRequired, + deviceHeight: PropTypes.number.isRequired, + channelType: PropTypes.string, + enableRightButton: PropTypes.func, + saving: PropTypes.bool.isRequired, + editing: PropTypes.bool, + error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + displayName: PropTypes.string, + currentTeamUrl: PropTypes.string, + channelURL: PropTypes.string, + purpose: PropTypes.string, + header: PropTypes.string, + onDisplayNameChange: PropTypes.func, + onChannelURLChange: PropTypes.func, + onPurposeChange: PropTypes.func, + onHeaderChange: PropTypes.func, + oldDisplayName: PropTypes.string, + oldChannelURL: PropTypes.string, + oldHeader: PropTypes.string, + oldPurpose: PropTypes.string, + }; + + static defaultProps = { + editing: false, + }; + + constructor(props) { + super(props); + + this.nameInput = React.createRef(); + this.urlInput = React.createRef(); + this.purposeInput = React.createRef(); + this.headerInput = React.createRef(); + this.lastText = React.createRef(); + this.scroll = React.createRef(); + } + + blur = () => { + if (this.nameInput?.current) { + this.nameInput.current.blur(); + } + + // TODO: uncomment below once the channel URL field is added + // if (this.urlInput?.current) { + // this.urlInput.current.blur(); + // } + + if (this.purposeInput?.current) { + this.purposeInput.current.blur(); + } + if (this.headerInput?.current) { + this.headerInput.current.blur(); + } + + if (this.scroll?.current) { + this.scroll.current.scrollToPosition(0, 0, true); + } + }; + + close = (goBack = false) => { + const {actions} = this.props; + if (goBack) { + actions.popTopScreen(); + } else { + actions.dismissModal(); + } + }; + + canUpdate = (displayName, channelURL, purpose, header) => { + const { + oldDisplayName, + oldChannelURL, + oldPurpose, + oldHeader, + } = this.props; + + return displayName !== oldDisplayName || channelURL !== oldChannelURL || + purpose !== oldPurpose || header !== oldHeader; + }; + + enableRightButton = (enable = false) => { + this.props.enableRightButton(enable); + }; + + onDisplayNameChangeText = (displayName) => { + const {editing, onDisplayNameChange} = this.props; + onDisplayNameChange(displayName); + + if (editing) { + const {channelURL, purpose, header} = this.props; + const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); + this.enableRightButton(canUpdate); + return; + } + + const displayNameExists = displayName && displayName.length >= 2; + this.props.enableRightButton(displayNameExists); + }; + + onDisplayURLChangeText = (channelURL) => { + const {editing, onChannelURLChange} = this.props; + onChannelURLChange(channelURL); + + if (editing) { + const {displayName, purpose, header} = this.props; + const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); + this.enableRightButton(canUpdate); + } + }; + + onPurposeChangeText = (purpose) => { + const {editing, onPurposeChange} = this.props; + onPurposeChange(purpose); + + if (editing) { + const {displayName, channelURL, header} = this.props; + const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); + this.enableRightButton(canUpdate); + } + }; + + onHeaderChangeText = (header) => { + const {editing, onHeaderChange} = this.props; + onHeaderChange(header); + + if (editing) { + const {displayName, channelURL, purpose} = this.props; + const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); + this.enableRightButton(canUpdate); + } + }; + + scrollToEnd = () => { + if (this.scroll?.current && this.lastText?.current) { + this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current)); + } + }; + + render() { + const { + theme, + editing, + channelType, + currentTeamUrl, + deviceWidth, + deviceHeight, + displayName, + channelURL, + header, + purpose, + } = this.props; + const {error, saving} = this.props; + const fullUrl = currentTeamUrl + '/channels'; + const shortUrl = getShortenedURL(fullUrl, 35); + + const style = getStyleSheet(theme); + + const displayHeaderOnly = channelType === General.DM_CHANNEL || + channelType === General.GM_CHANNEL; + + if (saving) { + return ( + + + + + ); + } + + let displayError; + if (error) { + displayError = ( + + + + + + ); + } + + return ( + + + + {displayError} + + + {!displayHeaderOnly && ( + + + + + + + + + )} + {/*TODO: Hide channel url field until it's added to CreateChannel */} + {false && editing && !displayHeaderOnly && ( + + + + + {shortUrl} + + + + + + + )} + {!displayHeaderOnly && ( + + + + + + + + + + + + + )} + + + + + + + + + + + + + + + ); + } +} + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flex: 1, + backgroundColor: theme.centerChannelBg, + }, + scrollView: { + flex: 1, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), + paddingTop: 10, + }, + errorContainer: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), + }, + errorWrapper: { + justifyContent: 'center', + alignItems: 'center', + }, + inputContainer: { + marginTop: 10, + backgroundColor: '#fff', + }, + input: { + color: '#333', + fontSize: 14, + height: 40, + paddingHorizontal: 15, + }, + titleContainer30: { + flexDirection: 'row', + marginTop: 30, + }, + titleContainer15: { + flexDirection: 'row', + marginTop: 15, + }, + title: { + fontSize: 14, + color: theme.centerChannelColor, + marginLeft: 15, + }, + optional: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 14, + marginLeft: 5, + }, + helpText: { + fontSize: 14, + color: changeOpacity(theme.centerChannelColor, 0.5), + marginTop: 10, + marginHorizontal: 15, + }, + }; +}); diff --git a/app/components/edit_channel_info/index.js b/app/components/edit_channel_info/index.js index 66927a3f1..a523704f5 100644 --- a/app/components/edit_channel_info/index.js +++ b/app/components/edit_channel_info/index.js @@ -1,414 +1,23 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + import { - Platform, - TouchableWithoutFeedback, - View, - Text, - findNodeHandle, -} from 'react-native'; -import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; + dismissModal, + popTopScreen, +} from 'app/actions/navigation'; -import ErrorText from 'app/components/error_text'; -import FormattedText from 'app/components/formatted_text'; -import Loading from 'app/components/loading'; -import StatusBar from 'app/components/status_bar'; -import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder'; +import EditChannelInfo from './edit_channel_info'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import {General} from 'mattermost-redux/constants'; -import {getShortenedURL} from 'app/utils/url'; -import {t} from 'app/utils/i18n'; - -export default class EditChannelInfo extends PureComponent { - static propTypes = { - navigator: PropTypes.object.isRequired, - theme: PropTypes.object.isRequired, - deviceWidth: PropTypes.number.isRequired, - deviceHeight: PropTypes.number.isRequired, - channelType: PropTypes.string, - enableRightButton: PropTypes.func, - saving: PropTypes.bool.isRequired, - editing: PropTypes.bool, - error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - displayName: PropTypes.string, - currentTeamUrl: PropTypes.string, - channelURL: PropTypes.string, - purpose: PropTypes.string, - header: PropTypes.string, - onDisplayNameChange: PropTypes.func, - onChannelURLChange: PropTypes.func, - onPurposeChange: PropTypes.func, - onHeaderChange: PropTypes.func, - oldDisplayName: PropTypes.string, - oldChannelURL: PropTypes.string, - oldHeader: PropTypes.string, - oldPurpose: PropTypes.string, +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + dismissModal, + popTopScreen, + }, dispatch), }; - - static defaultProps = { - editing: false, - }; - - constructor(props) { - super(props); - - this.nameInput = React.createRef(); - this.urlInput = React.createRef(); - this.purposeInput = React.createRef(); - this.headerInput = React.createRef(); - this.lastText = React.createRef(); - this.scroll = React.createRef(); - } - - blur = () => { - if (this.nameInput?.current) { - this.nameInput.current.blur(); - } - - // TODO: uncomment below once the channel URL field is added - // if (this.urlInput?.current) { - // this.urlInput.current.blur(); - // } - - if (this.purposeInput?.current) { - this.purposeInput.current.blur(); - } - if (this.headerInput?.current) { - this.headerInput.current.blur(); - } - - if (this.scroll?.current) { - this.scroll.current.scrollToPosition(0, 0, true); - } - }; - - close = (goBack = false) => { - if (goBack) { - this.props.navigator.pop({animated: true}); - } else { - this.props.navigator.dismissModal({ - animationType: 'slide-down', - }); - } - }; - - canUpdate = (displayName, channelURL, purpose, header) => { - const { - oldDisplayName, - oldChannelURL, - oldPurpose, - oldHeader, - } = this.props; - - return displayName !== oldDisplayName || channelURL !== oldChannelURL || - purpose !== oldPurpose || header !== oldHeader; - }; - - enableRightButton = (enable = false) => { - this.props.enableRightButton(enable); - }; - - onDisplayNameChangeText = (displayName) => { - const {editing, onDisplayNameChange} = this.props; - onDisplayNameChange(displayName); - - if (editing) { - const {channelURL, purpose, header} = this.props; - const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); - this.enableRightButton(canUpdate); - return; - } - - const displayNameExists = displayName && displayName.length >= 2; - this.props.enableRightButton(displayNameExists); - }; - - onDisplayURLChangeText = (channelURL) => { - const {editing, onChannelURLChange} = this.props; - onChannelURLChange(channelURL); - - if (editing) { - const {displayName, purpose, header} = this.props; - const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); - this.enableRightButton(canUpdate); - } - }; - - onPurposeChangeText = (purpose) => { - const {editing, onPurposeChange} = this.props; - onPurposeChange(purpose); - - if (editing) { - const {displayName, channelURL, header} = this.props; - const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); - this.enableRightButton(canUpdate); - } - }; - - onHeaderChangeText = (header) => { - const {editing, onHeaderChange} = this.props; - onHeaderChange(header); - - if (editing) { - const {displayName, channelURL, purpose} = this.props; - const canUpdate = this.canUpdate(displayName, channelURL, purpose, header); - this.enableRightButton(canUpdate); - } - }; - - scrollToEnd = () => { - if (this.scroll?.current && this.lastText?.current) { - this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current)); - } - }; - - render() { - const { - theme, - editing, - channelType, - currentTeamUrl, - deviceWidth, - deviceHeight, - displayName, - channelURL, - header, - purpose, - } = this.props; - const {error, saving} = this.props; - const fullUrl = currentTeamUrl + '/channels'; - const shortUrl = getShortenedURL(fullUrl, 35); - - const style = getStyleSheet(theme); - - const displayHeaderOnly = channelType === General.DM_CHANNEL || - channelType === General.GM_CHANNEL; - - if (saving) { - return ( - - - - - ); - } - - let displayError; - if (error) { - displayError = ( - - - - - - ); - } - - return ( - - - - {displayError} - - - {!displayHeaderOnly && ( - - - - - - - - - )} - {/*TODO: Hide channel url field until it's added to CreateChannel */} - {false && editing && !displayHeaderOnly && ( - - - - - {shortUrl} - - - - - - - )} - {!displayHeaderOnly && ( - - - - - - - - - - - - - )} - - - - - - - - - - - - - - - ); - } } -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - container: { - flex: 1, - backgroundColor: theme.centerChannelBg, - }, - scrollView: { - flex: 1, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), - paddingTop: 10, - }, - errorContainer: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), - }, - errorWrapper: { - justifyContent: 'center', - alignItems: 'center', - }, - inputContainer: { - marginTop: 10, - backgroundColor: '#fff', - }, - input: { - color: '#333', - fontSize: 14, - height: 40, - paddingHorizontal: 15, - }, - titleContainer30: { - flexDirection: 'row', - marginTop: 30, - }, - titleContainer15: { - flexDirection: 'row', - marginTop: 15, - }, - title: { - fontSize: 14, - color: theme.centerChannelColor, - marginLeft: 15, - }, - optional: { - color: changeOpacity(theme.centerChannelColor, 0.5), - fontSize: 14, - marginLeft: 5, - }, - helpText: { - fontSize: 14, - color: changeOpacity(theme.centerChannelColor, 0.5), - marginTop: 10, - marginHorizontal: 15, - }, - }; -}); - +export default connect(null, mapDispatchToProps)(EditChannelInfo); diff --git a/app/components/interactive_dialog_controller/index.js b/app/components/interactive_dialog_controller/index.js index 10d93d094..a2577835a 100644 --- a/app/components/interactive_dialog_controller/index.js +++ b/app/components/interactive_dialog_controller/index.js @@ -1,8 +1,11 @@ // 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 {showModal} from 'app/actions/navigation'; + import InteractiveDialogController from './interactive_dialog_controller'; function mapStateToProps(state) { @@ -12,4 +15,12 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(InteractiveDialogController); +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + showModal, + }, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(InteractiveDialogController); diff --git a/app/components/interactive_dialog_controller/interactive_dialog_controller.js b/app/components/interactive_dialog_controller/interactive_dialog_controller.js index fcba9b347..7ec4e0177 100644 --- a/app/components/interactive_dialog_controller/interactive_dialog_controller.js +++ b/app/components/interactive_dialog_controller/interactive_dialog_controller.js @@ -7,9 +7,11 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; export default class InteractiveDialogController extends PureComponent { static propTypes = { + actions: PropTypes.shape({ + showModal: PropTypes.func.isRequired, + }).isRequired, triggerId: PropTypes.string, dialog: PropTypes.object, - navigator: PropTypes.object, theme: PropTypes.object, }; @@ -22,7 +24,7 @@ export default class InteractiveDialogController extends PureComponent { } componentDidUpdate(prevProps) { - const triggerId = this.props.triggerId; + const {actions, triggerId} = this.props; if (!triggerId) { return; } @@ -41,33 +43,24 @@ export default class InteractiveDialogController extends PureComponent { return; } - const theme = this.props.theme; - - this.props.navigator.showModal({ - backButtonTitle: '', - screen: 'InteractiveDialog', - title: dialogData.dialog.title, - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - navigatorButtons: { + const screen = 'InteractiveDialog'; + const title = dialogData.dialog.title; + const passProps = {}; + const options = { + topBar: { leftButtons: [{ id: 'close-dialog', icon: this.closeButton, }], - rightButtons: [ - { - id: 'submit-dialog', - showAsAction: 'always', - title: dialogData.dialog.submit_label, - }, - ], + rightButtons: [{ + id: 'submit-dialog', + showAsAction: 'always', + text: dialogData.dialog.submit_label, + }], }, - }); + }; + + actions.showModal(screen, title, passProps, options); } render() { diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js index 92168b4ba..1c8f430ef 100644 --- a/app/components/post_list/index.js +++ b/app/components/post_list/index.js @@ -9,6 +9,7 @@ import {getConfig, getCurrentUrl} from 'mattermost-redux/selectors/entities/gene import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {makePreparePostIdsForPostList, START_OF_NEW_MESSAGES} from 'mattermost-redux/utils/post_list'; +import {showModalOverCurrentContext} from 'app/actions/navigation'; import {handleSelectChannelByName, loadChannelsByTeamName, refreshChannelWithRetry} from 'app/actions/views/channel'; import {setDeepLinkURL} from 'app/actions/views/root'; @@ -42,6 +43,7 @@ function mapDispatchToProps(dispatch) { refreshChannelWithRetry, selectFocusedPostId, setDeepLinkURL, + showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index 3cc733f04..39e16dcac 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -41,6 +41,7 @@ export default class PostList extends PureComponent { refreshChannelWithRetry: PropTypes.func.isRequired, selectFocusedPostId: PropTypes.func.isRequired, setDeepLinkURL: PropTypes.func.isRequired, + showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, channelId: PropTypes.string, deepLinkURL: PropTypes.string, @@ -51,7 +52,6 @@ export default class PostList extends PureComponent { isSearchResult: PropTypes.bool, lastPostIndex: PropTypes.number.isRequired, lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types - navigator: PropTypes.object, onLoadMoreUp: PropTypes.func, onHashtagPress: PropTypes.func, onPermalinkPress: PropTypes.func, @@ -250,7 +250,6 @@ export default class PostList extends PureComponent { isSearchResult: this.props.isSearchResult, location: this.props.location, managedConfig: mattermostManaged.getCachedConfig(), - navigator: this.props.navigator, onHashtagPress: this.props.onHashtagPress, onPermalinkPress: this.handlePermalinkPress, onPress: this.props.onPostPress, @@ -303,29 +302,24 @@ export default class PostList extends PureComponent { }; showPermalinkView = (postId) => { - const {actions, navigator} = this.props; + const {actions} = this.props; actions.selectFocusedPostId(postId); if (!this.showingPermalink) { + const screen = 'Permalink'; + const passProps = { + isPermalink: true, + onClose: this.handleClosePermalink, + }; const options = { - screen: 'Permalink', - animationType: 'none', - backButtonTitle: '', - overrideBackPress: true, - navigatorStyle: { - navBarHidden: true, - screenBackgroundColor: changeOpacity('#000', 0.2), - modalPresentationStyle: 'overCurrentContext', - }, - passProps: { - isPermalink: true, - onClose: this.handleClosePermalink, + layout: { + backgroundColor: changeOpacity('#000', 0.2), }, }; this.showingPermalink = true; - navigator.showModal(options); + actions.showModalOverCurrentContext(screen, passProps, options); } }; diff --git a/app/components/post_list/post_list.test.js b/app/components/post_list/post_list.test.js index 0c6455843..a4219bf89 100644 --- a/app/components/post_list/post_list.test.js +++ b/app/components/post_list/post_list.test.js @@ -18,11 +18,9 @@ describe('PostList', () => { refreshChannelWithRetry: jest.fn(), selectFocusedPostId: jest.fn(), setDeepLinkURL: jest.fn(), + showModalOverCurrentContext: jest.fn(), }, deepLinkURL: '', - navigator: { - showModal: jest.fn(), - }, lastPostIndex: -1, postIds: ['post-id-1', 'post-id-2'], serverURL, @@ -47,7 +45,7 @@ describe('PostList', () => { wrapper.setProps({deepLinkURL: deepLinks.permalink}); expect(baseProps.actions.setDeepLinkURL).toHaveBeenCalled(); expect(baseProps.actions.selectFocusedPostId).toHaveBeenCalled(); - expect(baseProps.navigator.showModal).toHaveBeenCalled(); + expect(baseProps.actions.showModalOverCurrentContext).toHaveBeenCalled(); expect(wrapper.getElement()).toMatchSnapshot(); }); diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index 53e40b4ef..0a96f4250 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -6,12 +6,12 @@ import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; import { Keyboard, - Platform, StyleSheet, View, } from 'react-native'; - +import {Navigation} from 'react-native-navigation'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; + import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {app} from 'app/mattermost'; @@ -40,12 +40,14 @@ export default class ChannelBase extends PureComponent { selectInitialChannel: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, peek: PropTypes.func.isRequired, + goToScreen: PropTypes.func.isRequired, + showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, + componentId: PropTypes.string.isRequired, currentChannelId: PropTypes.string, channelsRequestFailed: PropTypes.bool, currentTeamId: PropTypes.string, isLandscape: PropTypes.bool, - navigator: PropTypes.object, theme: PropTypes.object.isRequired, showTermsOfService: PropTypes.bool, disableTermsModal: PropTypes.bool, @@ -65,8 +67,10 @@ export default class ChannelBase extends PureComponent { this.postTextbox = React.createRef(); this.keyboardTracker = React.createRef(); - props.navigator.setStyle({ - screenBackgroundColor: props.theme.centerChannelBg, + Navigation.mergeOptions(props.componentId, { + layout: { + backgroundColor: props.theme.centerChannelBg, + }, }); if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) { @@ -104,8 +108,10 @@ export default class ChannelBase extends PureComponent { componentWillReceiveProps(nextProps) { if (this.props.theme !== nextProps.theme) { - this.props.navigator.setStyle({ - screenBackgroundColor: nextProps.theme.centerChannelBg, + Navigation.mergeOptions(this.props.componentId, { + layout: { + backgroundColor: nextProps.theme.centerChannelBg, + }, }); } @@ -161,53 +167,32 @@ export default class ChannelBase extends PureComponent { }; showTermsOfServiceModal = async () => { - const {navigator, theme} = this.props; + const {actions, theme} = this.props; const closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor); - navigator.showModal({ - screen: 'TermsOfService', - animationType: 'slide-up', - title: '', - backButtonTitle: '', - animated: true, - navigatorStyle: { - navBarTextColor: theme.centerChannelColor, - navBarBackgroundColor: theme.centerChannelBg, - navBarButtonColor: theme.buttonBg, - screenBackgroundColor: theme.centerChannelBg, - modalPresentationStyle: 'overCurrentContext', - }, - overrideBackPress: true, - passProps: { - closeButton, - }, - }); - }; - - goToChannelInfo = preventDoubleTap(() => { - const {intl} = this.context; - const {navigator, theme} = this.props; + const screen = 'TermsOfService'; + const passProps = { + closeButton, + }; const options = { - screen: 'ChannelInfo', - title: intl.formatMessage({id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}), - animated: true, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, + layout: { + backgroundColor: theme.centerChannelBg, }, }; + actions.showModalOverCurrentContext(screen, passProps, options); + }; + + goToChannelInfo = preventDoubleTap(() => { + const {actions} = this.props; + const {intl} = this.context; + const screen = 'ChannelInfo'; + const title = intl.formatMessage({id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}); + Keyboard.dismiss(); - if (Platform.OS === 'android') { - navigator.showModal(options); - } else { - requestAnimationFrame(() => { - navigator.push(options); - }); - } + requestAnimationFrame(() => { + actions.goToScreen(screen, title); + }); }); handleAutoComplete = (value) => { @@ -265,7 +250,6 @@ export default class ChannelBase extends PureComponent { channelsRequestFailed, currentChannelId, isLandscape, - navigator, theme, } = this.props; @@ -282,7 +266,7 @@ export default class ChannelBase extends PureComponent { const Loading = require('app/components/channel_loader').default; return ( - + {drawerContent} diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js index d84a2cd6f..9e203e057 100644 --- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js +++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js @@ -24,7 +24,6 @@ const { export default class ChannelNavBar extends PureComponent { static propTypes = { isLandscape: PropTypes.bool.isRequired, - navigator: PropTypes.object.isRequired, openChannelDrawer: PropTypes.func.isRequired, openSettingsDrawer: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired, @@ -32,7 +31,7 @@ export default class ChannelNavBar extends PureComponent { }; render() { - const {isLandscape, navigator, onPress, theme} = this.props; + const {isLandscape, onPress, theme} = this.props; const {openChannelDrawer, openSettingsDrawer} = this.props; const style = getStyleFromTheme(theme); const padding = {paddingHorizontal: 0}; @@ -72,7 +71,6 @@ export default class ChannelNavBar extends PureComponent { /> diff --git a/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js b/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js index c40a87411..554d14f55 100644 --- a/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js +++ b/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js @@ -19,7 +19,6 @@ export default class ChannelSearchButton extends PureComponent { clearSearch: PropTypes.func.isRequired, showSearchModal: PropTypes.func.isRequired, }).isRequired, - navigator: PropTypes.object, theme: PropTypes.object, }; diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js index 26e2383a6..dc8ca68bc 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -33,13 +33,14 @@ export default class ChannelPostList extends PureComponent { selectPost: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, refreshChannelWithRetry: PropTypes.func.isRequired, + showModal: PropTypes.func.isRequired, + goToScreen: PropTypes.func.isRequired, }).isRequired, channelId: PropTypes.string.isRequired, channelRefreshingFailed: PropTypes.bool, currentUserId: PropTypes.string, lastViewedAt: PropTypes.number, loadMorePostsVisible: PropTypes.bool.isRequired, - navigator: PropTypes.object, postIds: PropTypes.array, postVisibility: PropTypes.number, refreshing: PropTypes.bool.isRequired, @@ -105,36 +106,23 @@ export default class ChannelPostList extends PureComponent { goToThread = (post) => { telemetry.start(['post_list:thread']); - const {actions, channelId, navigator, theme} = this.props; + const {actions, channelId} = this.props; const rootId = (post.root_id || post.id); Keyboard.dismiss(); actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - const options = { - screen: 'Thread', - animated: true, - backButtonTitle: '', - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - channelId, - rootId, - }, + const screen = 'Thread'; + const title = ''; + const passProps = { + channelId, + rootId, }; - if (Platform.OS === 'android') { - navigator.showModal(options); - } else { - requestAnimationFrame(() => { - navigator.push(options); - }); - } + requestAnimationFrame(() => { + actions.goToScreen(screen, title, passProps); + }); }; loadMorePostsTop = () => { @@ -181,7 +169,6 @@ export default class ChannelPostList extends PureComponent { return ( ); }; @@ -194,7 +181,6 @@ export default class ChannelPostList extends PureComponent { currentUserId, lastViewedAt, loadMorePostsVisible, - navigator, refreshing, theme, } = this.props; @@ -223,7 +209,6 @@ export default class ChannelPostList extends PureComponent { currentUserId={currentUserId} lastViewedAt={lastViewedAt} channelId={channelId} - navigator={navigator} renderFooter={this.renderFooter} refreshing={refreshing} scrollViewNativeID={channelId} @@ -234,7 +219,7 @@ export default class ChannelPostList extends PureComponent { return ( {component} - + ); diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js index 2e34bdea1..dd5cd8368 100644 --- a/app/screens/channel/channel_post_list/index.js +++ b/app/screens/channel/channel_post_list/index.js @@ -10,6 +10,7 @@ import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels' import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import {goToScreen} from 'app/actions/navigation'; import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel'; import {recordLoadTime} from 'app/actions/views/root'; @@ -42,6 +43,7 @@ function mapDispatchToProps(dispatch) { selectPost, recordLoadTime, refreshChannelWithRetry, + goToScreen, }, dispatch), }; } diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js index 1c9fc4c26..e26fc7e13 100644 --- a/app/screens/channel/index.js +++ b/app/screens/channel/index.js @@ -19,7 +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 {peek, goToScreen, showModalOverCurrentContext} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import Channel from './channel'; @@ -50,6 +50,8 @@ function mapDispatchToProps(dispatch) { startPeriodicStatusUpdates, stopPeriodicStatusUpdates, peek, + goToScreen, + showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/channel_add_members/channel_add_members.js b/app/screens/channel_add_members/channel_add_members.js index 11e42ea88..95eb6010a 100644 --- a/app/screens/channel_add_members/channel_add_members.js +++ b/app/screens/channel_add_members/channel_add_members.js @@ -33,6 +33,8 @@ export default class ChannelAddMembers extends PureComponent { getProfilesNotInChannel: PropTypes.func.isRequired, handleAddChannelMembers: PropTypes.func.isRequired, searchProfiles: PropTypes.func.isRequired, + setButtons: PropTypes.func.isRequired, + popTopScreen: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, currentChannelId: PropTypes.string.isRequired, @@ -40,7 +42,6 @@ export default class ChannelAddMembers extends PureComponent { currentTeamId: PropTypes.string.isRequired, currentUserId: PropTypes.string.isRequired, profilesNotInChannel: PropTypes.array.isRequired, - navigator: PropTypes.object, theme: PropTypes.object.isRequired, }; @@ -68,13 +69,13 @@ export default class ChannelAddMembers extends PureComponent { }; this.addButton = { - disabled: true, + enalbed: false, id: 'add-members', - title: context.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'}), + text: context.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'}), showAsAction: 'always', }; - props.navigator.setButtons({ + props.actions.setButtons(props.componentId, { rightButtons: [this.addButton], }); } @@ -113,12 +114,13 @@ export default class ChannelAddMembers extends PureComponent { }; close = () => { - this.props.navigator.pop({animated: true}); + this.props.actions.popTopScreen(); }; enableAddOption = (enabled) => { - this.props.navigator.setButtons({ - rightButtons: [{...this.addButton, disabled: !enabled}], + const {actions, componentId} = this.props; + actions.setButtons(componentId, { + rightButtons: [{...this.addButton, enabled}], }); }; diff --git a/app/screens/channel_add_members/channel_add_members.test.js b/app/screens/channel_add_members/channel_add_members.test.js index 3d5eaed74..0684d5685 100644 --- a/app/screens/channel_add_members/channel_add_members.test.js +++ b/app/screens/channel_add_members/channel_add_members.test.js @@ -16,15 +16,13 @@ describe('ChannelAddMembers', () => { getProfilesNotInChannel: jest.fn().mockResolvedValue({}), handleAddChannelMembers: jest.fn().mockResolvedValue({}), searchProfiles: jest.fn().mockResolvedValue({data: []}), + setButtons: jest.fn(), + popTopScreen: jest.fn(), }, currentChannelId: 'current_channel_id', currentTeamId: 'current_team_id', currentUserId: 'current_user_id', profilesNotInChannel: [], - navigator: { - pop: jest.fn(), - setButtons: jest.fn(), - }, theme: Preferences.THEMES.default, componentId: 'component-id', }; @@ -35,10 +33,10 @@ describe('ChannelAddMembers', () => { expect(baseProps.actions.getTeamStats).toBeCalledTimes(1); expect(baseProps.actions.getTeamStats).toBeCalledWith(baseProps.currentTeamId); - const button = {disabled: true, id: 'add-members', title: 'Add', showAsAction: 'always'}; - expect(baseProps.navigator.setButtons).toBeCalledTimes(2); - expect(baseProps.navigator.setButtons.mock.calls[0][0]).toEqual({rightButtons: [button]}); - expect(baseProps.navigator.setButtons.mock.calls[1][0]).toEqual({rightButtons: [button]}); + const button = {enabled: false, id: 'add-members', text: 'Add', showAsAction: 'always'}; + expect(baseProps.actions.setButtons).toBeCalledTimes(2); + expect(baseProps.actions.setButtons.mock.calls[0][0]).toEqual(baseProps.componentId, {rightButtons: [button]}); + expect(baseProps.actions.setButtons.mock.calls[1][0]).toEqual(baseProps.componentId, {rightButtons: [button]}); }); test('should match state on clearSearch', () => { @@ -50,12 +48,11 @@ describe('ChannelAddMembers', () => { wrapper.setState({term: '', searchResults: []}); }); - test('should call props.navigator on close', () => { + test('should call props.popTopScreen on close', () => { const wrapper = shallowWithIntl(); wrapper.instance().close(); - expect(baseProps.navigator.pop).toBeCalledTimes(1); - expect(baseProps.navigator.pop).toBeCalledWith({animated: true}); + expect(baseProps.actions.popTopScreen).toBeCalledTimes(1); }); test('should match state on onProfilesLoaded', () => { diff --git a/app/screens/channel_add_members/index.js b/app/screens/channel_add_members/index.js index f44704853..c2efa8625 100644 --- a/app/screens/channel_add_members/index.js +++ b/app/screens/channel_add_members/index.js @@ -11,6 +11,7 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUserId, getProfilesNotInCurrentChannel} from 'mattermost-redux/selectors/entities/users'; +import {setButtons, popTopScreen} from 'app/actions/navigation'; import {handleAddChannelMembers} from 'app/actions/views/channel_add_members'; import ChannelAddMembers from './channel_add_members'; @@ -35,6 +36,8 @@ function mapDispatchToProps(dispatch) { getProfilesNotInChannel, handleAddChannelMembers, searchProfiles, + setButtons, + popTopScreen, }, dispatch), }; } diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index cb2e3b08e..c2a07a787 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -42,6 +42,10 @@ export default class ChannelInfo extends PureComponent { selectPenultimateChannel: PropTypes.func.isRequired, handleSelectChannel: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, + popTopScreen: PropTypes.func.isRequired, + goToScreen: PropTypes.func.isRequired, + dismissModal: PropTypes.func.isRequired, + showModalOverCurrentContext: PropTypes.func.isRequired, }), componentId: PropTypes.string, viewArchivedChannels: PropTypes.bool.isRequired, @@ -50,7 +54,6 @@ export default class ChannelInfo extends PureComponent { currentChannelCreatorName: PropTypes.string, currentChannelMemberCount: PropTypes.number, currentUserId: PropTypes.string, - navigator: PropTypes.object, status: PropTypes.string, theme: PropTypes.object.isRequired, isChannelMuted: PropTypes.bool.isRequired, @@ -105,95 +108,61 @@ export default class ChannelInfo extends PureComponent { } close = (redirect = true) => { + const {actions} = this.props; + if (redirect) { - this.props.actions.setChannelDisplayName(''); + actions.setChannelDisplayName(''); } if (Platform.OS === 'android') { - this.props.navigator.dismissModal({animated: true}); + actions.dismissModal(); } else { - this.props.navigator.pop({animated: true}); + actions.popTopScreen(); } }; goToChannelAddMembers = preventDoubleTap(() => { + const {actions} = this.props; const {intl} = this.context; - const {navigator, theme} = this.props; - navigator.push({ - backButtonTitle: '', - screen: 'ChannelAddMembers', - title: intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - }); + const screen = 'ChannelAddMembers'; + const title = intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'}); + + actions.goToScreen(screen, title); }); goToChannelMembers = preventDoubleTap(() => { + const {actions, canManageUsers} = this.props; const {intl} = this.context; - const {canManageUsers, navigator, theme} = this.props; const id = canManageUsers ? t('channel_header.manageMembers') : t('channel_header.viewMembers'); const defaultMessage = canManageUsers ? 'Manage Members' : 'View Members'; + const screen = 'ChannelMembers'; + const title = intl.formatMessage({id, defaultMessage}); - navigator.push({ - backButtonTitle: '', - screen: 'ChannelMembers', - title: intl.formatMessage({id, defaultMessage}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - }); + actions.goToScreen(screen, title); }); goToPinnedPosts = preventDoubleTap(() => { + const {actions, currentChannel} = this.props; const {formatMessage} = this.context.intl; - const {actions, currentChannel, navigator, theme} = this.props; const id = t('channel_header.pinnedPosts'); const defaultMessage = 'Pinned Posts'; + const screen = 'PinnedPosts'; + const title = formatMessage({id, defaultMessage}); + const passProps = { + currentChannelId: currentChannel.id, + }; - actions.clearPinnedPosts(currentChannel.id); - navigator.push({ - backButtonTitle: '', - screen: 'PinnedPosts', - title: formatMessage({id, defaultMessage}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - currentChannelId: currentChannel.id, - }, - }); + actions.goToScreen(screen, title, passProps); }); handleChannelEdit = preventDoubleTap(() => { + const {actions} = this.props; const {intl} = this.context; - const {navigator, theme} = this.props; const id = t('mobile.channel_info.edit'); const defaultMessage = 'Edit Channel'; + const screen = 'EditChannel'; + const title = intl.formatMessage({id, defaultMessage}); - navigator.push({ - backButtonTitle: '', - screen: 'EditChannel', - title: intl.formatMessage({id, defaultMessage}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - }); + actions.goToScreen(screen, title); }); handleLeave = () => { @@ -337,30 +306,22 @@ export default class ChannelInfo extends PureComponent { }); showPermalinkView = (postId) => { - const {actions, navigator} = this.props; + const {actions} = this.props; + const screen = 'Permalink'; + const passProps = { + isPermalink: true, + onClose: this.handleClosePermalink, + }; + const options = { + layout: { + backgroundColor: changeOpacity('#000', 0.2), + }, + }; actions.selectFocusedPostId(postId); - if (!this.showingPermalink) { - const options = { - screen: 'Permalink', - animationType: 'none', - backButtonTitle: '', - overrideBackPress: true, - navigatorStyle: { - navBarHidden: true, - screenBackgroundColor: changeOpacity('#000', 0.2), - modalPresentationStyle: 'overCurrentContext', - }, - passProps: { - isPermalink: true, - onClose: this.handleClosePermalink, - }, - }; - - this.showingPermalink = true; - navigator.showModal(options); - } + this.showingPermalink = true; + actions.showModalOverCurrentContext(screen, passProps, options); }; renderViewOrManageMembersRow = () => { @@ -510,7 +471,6 @@ export default class ChannelInfo extends PureComponent { currentChannel, currentChannelCreatorName, currentChannelMemberCount, - navigator, status, theme, isBot, @@ -545,7 +505,6 @@ export default class ChannelInfo extends PureComponent { displayName={currentChannel.display_name} header={currentChannel.header} memberCount={currentChannelMemberCount} - navigator={navigator} onPermalinkPress={this.handlePermalinkPress} purpose={currentChannel.purpose} status={status} diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index 0f10ef56e..fad941d7a 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -23,7 +23,6 @@ export default class ChannelInfoHeader extends React.PureComponent { memberCount: PropTypes.number, displayName: PropTypes.string.isRequired, header: PropTypes.string, - navigator: PropTypes.object.isRequired, onPermalinkPress: PropTypes.func, purpose: PropTypes.string, status: PropTypes.string, @@ -41,7 +40,6 @@ export default class ChannelInfoHeader extends React.PureComponent { displayName, header, memberCount, - navigator, onPermalinkPress, purpose, status, @@ -88,7 +86,6 @@ export default class ChannelInfoHeader extends React.PureComponent { defaultMessage='Purpose' /> { - this.props.navigator.pop({animated: true}); + this.props.actions.popTopScreen(); }; enableRemoveOption = (enabled) => { - if (this.props.canManageUsers) { - this.props.navigator.setButtons({ - rightButtons: [{...this.removeButton, disabled: !enabled}], + const {actions, canManageUsers, componentId} = this.props; + if (canManageUsers) { + actions.setButtons(componentId, { + rightButtons: [{...this.removeButton, enabled}], }); } }; diff --git a/app/screens/channel_members/channel_members.test.js b/app/screens/channel_members/channel_members.test.js index efb4e750e..4ad691310 100644 --- a/app/screens/channel_members/channel_members.test.js +++ b/app/screens/channel_members/channel_members.test.js @@ -10,11 +10,6 @@ import CustomList from 'app/components/custom_list'; import ChannelMembers from './channel_members'; describe('ChannelMembers', () => { - const navigator = { - setOnNavigatorEvent: jest.fn(), - setButtons: jest.fn(), - }; - const baseProps = { theme: Preferences.THEMES.default, currentUserId: 'current-user-id', @@ -24,8 +19,9 @@ describe('ChannelMembers', () => { getProfilesInChannel: jest.fn().mockImplementation(() => Promise.resolve()), handleRemoveChannelMembers: jest.fn(), searchProfiles: jest.fn(), + setButtons: jest.fn(), + popTopScreen: jest.fn(), }, - navigator, componentId: 'component-id', }; diff --git a/app/screens/channel_members/index.js b/app/screens/channel_members/index.js index b37975c3f..5c6b54f50 100644 --- a/app/screens/channel_members/index.js +++ b/app/screens/channel_members/index.js @@ -4,12 +4,14 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {handleRemoveChannelMembers} from 'app/actions/views/channel_members'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentChannel, canManageChannelMembers} from 'mattermost-redux/selectors/entities/channels'; import {makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users'; import {getProfilesInChannel, searchProfiles} from 'mattermost-redux/actions/users'; +import {setButtons, popTopScreen} from 'app/actions/navigation'; +import {handleRemoveChannelMembers} from 'app/actions/views/channel_members'; + import ChannelMembers from './channel_members'; function makeMapStateToProps() { @@ -40,6 +42,8 @@ function mapDispatchToProps(dispatch) { getProfilesInChannel, handleRemoveChannelMembers, searchProfiles, + setButtons, + popTopScreen, }, dispatch), }; } diff --git a/app/screens/channel_peek/channel_peek.js b/app/screens/channel_peek/channel_peek.js index 162cdd85b..f517ba967 100644 --- a/app/screens/channel_peek/channel_peek.js +++ b/app/screens/channel_peek/channel_peek.js @@ -20,7 +20,6 @@ export default class ChannelPeek extends PureComponent { channelId: PropTypes.string.isRequired, currentUserId: PropTypes.string, lastViewedAt: PropTypes.number, - navigator: PropTypes.object, postIds: PropTypes.array, theme: PropTypes.object.isRequired, }; @@ -72,7 +71,6 @@ export default class ChannelPeek extends PureComponent { channelId, currentUserId, lastViewedAt, - navigator, theme, } = this.props; @@ -89,7 +87,6 @@ export default class ChannelPeek extends PureComponent { currentUserId={currentUserId} lastViewedAt={lastViewedAt} channelId={channelId} - navigator={navigator} /> ); diff --git a/app/screens/create_channel/create_channel.js b/app/screens/create_channel/create_channel.js index 18c379e73..1e5994e7c 100644 --- a/app/screens/create_channel/create_channel.js +++ b/app/screens/create_channel/create_channel.js @@ -19,7 +19,6 @@ import {setNavigatorStyles} from 'app/utils/theme'; export default class CreateChannel extends PureComponent { static propTypes = { componentId: PropTypes.string, - navigator: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, deviceWidth: PropTypes.number.isRequired, deviceHeight: PropTypes.number.isRequired, @@ -28,6 +27,9 @@ export default class CreateChannel extends PureComponent { closeButton: PropTypes.object, actions: PropTypes.shape({ handleCreateChannel: PropTypes.func.isRequired, + setButtons: PropTypes.func.isRequired, + dismissModal: PropTypes.func.isRequired, + popTopScreen: PropTypes.func.isRequired, }), }; @@ -45,7 +47,7 @@ export default class CreateChannel extends PureComponent { rightButton = { id: 'create-channel', - disabled: true, + enabled: false, showAsAction: 'always', }; @@ -60,7 +62,7 @@ export default class CreateChannel extends PureComponent { header: '', }; - this.rightButton.title = context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}); + this.rightButton.text = context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}); if (props.closeButton) { this.left = {...this.leftButton, icon: props.closeButton}; @@ -74,7 +76,7 @@ export default class CreateChannel extends PureComponent { buttons.leftButtons = [this.left]; } - props.navigator.setButtons(buttons); + props.actions.setButtons(props.componentId, buttons); } componentDidMount() { @@ -123,37 +125,38 @@ export default class CreateChannel extends PureComponent { } close = (goBack = false) => { + const {actions} = this.props; if (goBack) { - this.props.navigator.pop({animated: true}); + actions.popTopScreen(); } else { - this.props.navigator.dismissModal({ - animationType: 'slide-down', - }); + actions.dismissModal(); } }; emitCanCreateChannel = (enabled) => { + const {actions, componentId} = this.props; const buttons = { - rightButtons: [{...this.rightButton, disabled: !enabled}], + rightButtons: [{...this.rightButton, enabled}], }; if (this.left) { buttons.leftButtons = [this.left]; } - this.props.navigator.setButtons(buttons); + actions.setButtons(componentId, buttons); }; emitCreating = (loading) => { + const {actions, componentId} = this.props; const buttons = { - rightButtons: [{...this.rightButton, disabled: loading}], + rightButtons: [{...this.rightButton, enabled: !loading}], }; if (this.left) { buttons.leftButtons = [this.left]; } - this.props.navigator.setButtons(buttons); + actions.setButtons(componentId, buttons); }; onCreateChannel = () => { @@ -176,7 +179,6 @@ export default class CreateChannel extends PureComponent { render() { const { - navigator, theme, deviceWidth, deviceHeight, @@ -191,7 +193,6 @@ export default class CreateChannel extends PureComponent { return ( { + const {actions, componentId} = this.props; const buttons = { - rightButtons: [{...this.rightButton, disabled: !enabled}], + rightButtons: [{...this.rightButton, enabled}], }; - this.props.navigator.setButtons(buttons); + actions.setButtons(componentId, buttons); }; emitUpdating = (loading) => { + const {actions, componentId} = this.props; const buttons = { - rightButtons: [{...this.rightButton, disabled: loading}], + rightButtons: [{...this.rightButton, enabled: !loading}], }; - this.props.navigator.setButtons(buttons); + actions.setButtons(componentId, buttons); }; validateDisplayName = (displayName) => { @@ -277,7 +280,6 @@ export default class EditChannel extends PureComponent { purpose: oldPurpose, type, }, - navigator, theme, currentTeamUrl, deviceWidth, @@ -294,7 +296,6 @@ export default class EditChannel extends PureComponent { return ( { + const {actions, componentId} = this.props; const buttons = { - rightButtons: [{...this.rightButton, disabled: !enabled}], + rightButtons: [{...this.rightButton, enabled}], }; - this.props.actions.setButtons(buttons); + actions.setButtons(componentId, buttons); }; handleRequestError = (error) => { diff --git a/app/screens/more_channels/index.js b/app/screens/more_channels/index.js index 679744979..fa8617a0b 100644 --- a/app/screens/more_channels/index.js +++ b/app/screens/more_channels/index.js @@ -12,11 +12,12 @@ import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {showCreateOption} from 'mattermost-redux/utils/channel_utils'; import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; - -import {handleSelectChannel, setChannelDisplayName} from 'app/actions/views/channel'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; +import {setButtons, dismissModal, goToScreen} from 'app/actions/navigation'; +import {handleSelectChannel, setChannelDisplayName} from 'app/actions/views/channel'; + import MoreChannels from './more_channels'; const joinableChannels = createSelector( @@ -53,6 +54,9 @@ function mapDispatchToProps(dispatch) { getChannels, searchChannels, setChannelDisplayName, + setButtons, + dismissModal, + goToScreen, }, dispatch), }; } diff --git a/app/screens/more_channels/more_channels.js b/app/screens/more_channels/more_channels.js index ac118b36f..416a0bf53 100644 --- a/app/screens/more_channels/more_channels.js +++ b/app/screens/more_channels/more_channels.js @@ -29,6 +29,9 @@ export default class MoreChannels extends PureComponent { getChannels: PropTypes.func.isRequired, searchChannels: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, + setButtons: PropTypes.func.isRequired, + dismissModal: PropTypes.func.isRequired, + goToScreen: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, canCreateChannels: PropTypes.bool.isRequired, @@ -36,7 +39,6 @@ export default class MoreChannels extends PureComponent { closeButton: PropTypes.object, currentUserId: PropTypes.string.isRequired, currentTeamId: PropTypes.string.isRequired, - navigator: PropTypes.object, theme: PropTypes.object.isRequired, }; @@ -65,7 +67,7 @@ export default class MoreChannels extends PureComponent { this.rightButton = { id: 'create-pub-channel', - title: context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}), + text: context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}), showAsAction: 'always', }; @@ -82,7 +84,7 @@ export default class MoreChannels extends PureComponent { buttons.rightButtons = [this.rightButton]; } - props.navigator.setButtons(buttons); + props.actions.setButtons(props.componentId, buttons); } componentDidMount() { @@ -136,7 +138,7 @@ export default class MoreChannels extends PureComponent { }; close = () => { - this.props.navigator.dismissModal({animationType: 'slide-down'}); + this.props.actions.dismissModal(); }; doGetChannels = () => { @@ -164,16 +166,16 @@ export default class MoreChannels extends PureComponent { getChannels = debounce(this.doGetChannels, 100); headerButtons = (createEnabled) => { - const {canCreateChannels} = this.props; + const {actions, canCreateChannels, componentId} = this.props; const buttons = { leftButtons: [this.leftButton], }; if (canCreateChannels) { - buttons.rightButtons = [{...this.rightButton, disabled: !createEnabled}]; + buttons.rightButtons = [{...this.rightButton, enabled: createEnabled}]; } - this.props.navigator.setButtons(buttons); + actions.setButtons(componentId, buttons); }; loadedChannels = ({data}) => { @@ -228,25 +230,16 @@ export default class MoreChannels extends PureComponent { }; onCreateChannel = () => { + const {actions} = this.props; const {formatMessage} = this.context.intl; - const {navigator, theme} = this.props; - navigator.push({ - screen: 'CreateChannel', - animationType: 'slide-up', - title: 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, - }, - }); + const screen = 'CreateChannel'; + const title = formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}); + const passProps = { + channelType: General.OPEN_CHANNEL, + }; + + actions.goToScreen(screen, title, passProps); }; renderLoading = () => { diff --git a/app/screens/more_channels/more_channels.test.js b/app/screens/more_channels/more_channels.test.js index 32eaee752..7fe062b9f 100644 --- a/app/screens/more_channels/more_channels.test.js +++ b/app/screens/more_channels/more_channels.test.js @@ -11,18 +11,15 @@ import MoreChannels from './more_channels.js'; jest.mock('react-intl'); describe('MoreChannels', () => { - const navigator = { - setButtons: jest.fn(), - dismissModal: jest.fn(), - push: jest.fn(), - }; - const actions = { handleSelectChannel: jest.fn(), joinChannel: jest.fn(), getChannels: jest.fn().mockResolvedValue({data: [{id: 'id2', name: 'name2', display_name: 'display_name2'}]}), searchChannels: jest.fn(), setChannelDisplayName: jest.fn(), + setButtons: jest.fn(), + dismissModal: jest.fn(), + goToScreen: jest.fn(), }; const baseProps = { @@ -32,7 +29,6 @@ describe('MoreChannels', () => { closeButton: {}, currentUserId: 'current_user_id', currentTeamId: 'current_team_id', - navigator, theme: Preferences.THEMES.default, componentId: 'component-id', }; @@ -46,27 +42,25 @@ describe('MoreChannels', () => { expect(wrapper.getElement()).toMatchSnapshot(); }); - test('should call props.navigator.dismissModal on close', () => { + test('should call props.actions.dismissModal on close', () => { const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, ); wrapper.instance().close(); - expect(baseProps.navigator.dismissModal).toHaveBeenCalledTimes(1); - expect(baseProps.navigator.dismissModal).toHaveBeenCalledWith({animationType: 'slide-down'}); + expect(baseProps.actions.dismissModal).toHaveBeenCalledTimes(1); }); - test('should call props.navigator.setButtons on headerButtons', () => { - const props = {...baseProps, navigator: {...navigator, setButtons: jest.fn()}}; + test('should call props.actions.setButtons on headerButtons', () => { const wrapper = shallow( - , + , {context: {intl: {formatMessage: jest.fn()}}}, ); - expect(props.navigator.setButtons).toHaveBeenCalledTimes(1); + expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(1); wrapper.instance().headerButtons(true); - expect(props.navigator.setButtons).toHaveBeenCalledTimes(2); + expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(2); }); test('should match return value of filterChannels', () => {