From 459801d10bc761067f0acd9d75f31af9c9fe6ce8 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 25 Sep 2019 15:23:45 -0700 Subject: [PATCH] [MM-17560] Await dismissAllModals and popToRoot prior to calling showSearchModal (#3298) * Patch react-native-navigation * Make navigation actions regular functions * Fix unhandled promise rejection warning * Missing semicolons * Mock navigation actions * Add unit tests for navigation actions * Place all patches in patches directory * Fix channel_info snapshot test --- Makefile | 2 +- app/actions/navigation.js | 394 ------------- app/actions/navigation/index.js | 352 ++++++++++++ app/actions/navigation/index.test.js | 473 +++++++++++++++ app/actions/views/channel.js | 3 +- app/actions/views/channel.test.js | 17 + .../profile_picture_button.test.js.snap | 10 +- .../announcement_banner.js | 8 +- .../announcement_banner.test.js | 3 - app/components/announcement_banner/index.js | 12 +- app/components/at_mention/at_mention.js | 7 +- app/components/at_mention/index.js | 13 +- ...button.test.js.snap => index.test.js.snap} | 0 .../attachment_button/attachment_button.js | 538 ------------------ app/components/attachment_button/index.js | 534 ++++++++++++++++- ...ttachment_button.test.js => index.test.js} | 8 +- .../autocomplete_selector.js | 4 +- app/components/autocomplete_selector/index.js | 2 - app/components/channel_intro/channel_intro.js | 11 +- app/components/channel_intro/index.js | 12 +- app/components/channel_link/channel_link.js | 10 +- .../channel_link/channel_link.test.js | 16 +- app/components/channel_link/index.js | 3 - .../client_upgrade_listener.js | 8 +- .../client_upgrade_listener/index.js | 3 - .../edit_channel_info/edit_channel_info.js | 12 +- .../edit_channel_info.test.js | 4 - app/components/edit_channel_info/index.js | 16 +- .../file_attachment_document.js | 10 +- .../file_attachment_document/index.js | 13 +- .../file_attachment_list.js | 4 +- .../file_attachment_list.test.js | 2 - app/components/file_attachment_list/index.js | 2 - .../interactive_dialog_controller/index.js | 3 - .../interactive_dialog_controller.js | 5 +- .../interactive_dialog_controller.test.js | 1 - ...ashtag.test.js.snap => index.test.js.snap} | 0 app/components/markdown/hashtag/hashtag.js | 52 -- .../markdown/hashtag/hashtag.test.js | 57 -- app/components/markdown/hashtag/index.js | 58 +- app/components/markdown/hashtag/index.test.js | 61 ++ .../markdown/markdown_code_block/index.js | 13 +- .../markdown_code_block.js | 8 +- .../markdown/markdown_image/index.js | 13 +- .../markdown/markdown_image/markdown_image.js | 6 +- .../markdown/markdown_table/index.js | 13 +- .../markdown/markdown_table/markdown_table.js | 8 +- .../markdown/markdown_table_image/index.js | 13 +- .../markdown_table_image.js | 7 +- app/components/markdown/transform.js | 2 - app/components/markdown/transform.test.js | 16 +- ..._image.test.js.snap => index.test.js.snap} | 0 .../attachment_image/attachment_image.js | 178 ------ .../attachment_image/index.js | 176 +++++- ...attachment_image.test.js => index.test.js} | 5 +- app/components/post/index.js | 3 - app/components/post/post.js | 17 +- .../post_attachment_opengraph/index.js | 3 - .../post_attachment_opengraph.js | 4 +- .../post_attachment_opengraph.test.js | 1 - app/components/post_body/index.js | 13 +- app/components/post_body/post_body.js | 10 +- app/components/post_body/post_body.test.js | 3 - .../post_body_additional_content/index.js | 2 - .../post_body_additional_content.js | 4 +- app/components/post_list/index.js | 2 - app/components/post_list/post_list.js | 4 +- app/components/post_list/post_list.test.js | 9 +- .../__snapshots__/post_textbox.test.js.snap | 10 +- app/components/reactions/index.js | 3 - app/components/reactions/reactions.js | 12 +- app/components/root/index.js | 12 +- app/components/root/root.js | 8 +- .../sidebars/main/channels_list/list/index.js | 12 +- .../sidebars/main/channels_list/list/list.js | 19 +- .../sidebars/main/teams_list/index.js | 2 - .../sidebars/main/teams_list/teams_list.js | 6 +- app/components/sidebars/settings/index.js | 9 - .../sidebars/settings/settings_sidebar.js | 14 +- app/init/global_event_handler.js | 4 +- app/mattermost.js | 8 +- app/screens/add_reaction/add_reaction.js | 9 +- app/screens/add_reaction/index.js | 14 +- app/screens/channel/channel.ios.js | 4 +- app/screens/channel/channel_base.js | 26 +- .../channel_search_button.js | 4 +- .../channel_search_button/index.js | 3 - .../channel_post_list/channel_post_list.js | 4 +- .../channel/channel_post_list/index.js | 2 - app/screens/channel/index.js | 4 - .../channel_add_members.js | 12 +- .../channel_add_members.test.js | 17 +- app/screens/channel_add_members/index.js | 3 - .../__snapshots__/channel_info.test.js.snap | 1 - .../channel_info_header.test.js.snap | 5 + app/screens/channel_info/channel_info.js | 26 +- app/screens/channel_info/channel_info.test.js | 5 - .../channel_info/channel_info_header.js | 3 +- app/screens/channel_info/index.js | 12 - .../channel_members/channel_members.js | 12 +- .../channel_members/channel_members.test.js | 2 - app/screens/channel_members/index.js | 3 - app/screens/client_upgrade/client_upgrade.js | 10 +- app/screens/client_upgrade/index.js | 3 - app/screens/code/code.js | 6 +- app/screens/code/index.js | 13 +- app/screens/create_channel/create_channel.js | 17 +- app/screens/create_channel/index.js | 8 - app/screens/edit_channel/edit_channel.js | 15 +- app/screens/edit_channel/index.js | 3 - app/screens/edit_post/edit_post.js | 21 +- app/screens/edit_post/index.js | 4 - app/screens/edit_profile/edit_profile.js | 20 +- app/screens/edit_profile/edit_profile.test.js | 3 - app/screens/edit_profile/index.js | 4 - .../error_teams_list/error_teams_list.js | 4 +- .../error_teams_list/error_teams_list.test.js | 1 - app/screens/error_teams_list/index.js | 3 - .../expanded_announcement_banner.js | 5 +- .../expanded_announcement_banner/index.js | 2 - app/screens/flagged_posts/flagged_posts.js | 22 +- app/screens/flagged_posts/index.js | 10 - app/screens/image_preview/image_preview.js | 27 +- .../image_preview/image_preview.test.js | 22 +- app/screens/image_preview/index.js | 13 +- app/screens/interactive_dialog/index.js | 3 - .../interactive_dialog/interactive_dialog.js | 8 +- .../interactive_dialog.test.js | 1 - app/screens/login/index.js | 3 - app/screens/login/login.js | 14 +- app/screens/login/login.test.js | 21 +- app/screens/login_options/index.js | 12 +- app/screens/login_options/login_options.js | 14 +- app/screens/long_post/index.js | 3 - app/screens/long_post/long_post.js | 10 +- app/screens/mfa/index.js | 3 - app/screens/mfa/mfa.js | 4 +- app/screens/more_channels/index.js | 4 - app/screens/more_channels/more_channels.js | 14 +- .../more_channels/more_channels.test.js | 19 +- app/screens/more_dms/index.js | 4 - app/screens/more_dms/more_dms.js | 11 +- app/screens/notification/index.js | 5 - app/screens/notification/notification.js | 15 +- app/screens/options_modal/index.js | 13 +- app/screens/options_modal/options_modal.js | 8 +- app/screens/permalink/index.js | 10 - app/screens/permalink/permalink.js | 28 +- app/screens/permalink/permalink.test.js | 4 - app/screens/pinned_posts/index.js | 10 - app/screens/pinned_posts/pinned_posts.js | 24 +- app/screens/post_options/index.js | 3 - app/screens/post_options/post_options.js | 13 +- app/screens/post_options/post_options.test.js | 2 - app/screens/reaction_list/index.js | 2 - app/screens/reaction_list/reaction_list.js | 4 +- .../reaction_list/reaction_list.test.js | 1 - .../reaction_list/reaction_row/index.js | 12 +- .../reaction_row/reaction_row.js | 10 +- .../reaction_row/reaction_row.test.js | 3 - app/screens/recent_mentions/index.js | 10 - .../recent_mentions/recent_mentions.js | 22 +- app/screens/search/index.js | 4 - app/screens/search/search.js | 16 +- app/screens/select_server/index.js | 3 - app/screens/select_server/select_server.js | 9 +- app/screens/select_team/index.js | 3 - app/screens/select_team/select_team.js | 13 +- app/screens/select_team/select_team.test.js | 2 - app/screens/selector_screen/index.js | 3 - .../selector_screen/selector_screen.js | 4 +- .../selector_screen/selector_screen.test.js | 1 - .../advanced_settings/advanced_settings.js | 4 +- .../settings/advanced_settings/index.js | 2 - .../display_settings/display_settings.js | 17 +- .../display_settings/display_settings.test.js | 3 - .../settings/display_settings/index.js | 12 +- app/screens/settings/general/index.js | 3 - app/screens/settings/general/settings.js | 29 +- .../settings/notification_settings/index.js | 3 - .../notification_settings.js | 18 +- .../notification_settings_mentions/index.js | 12 +- .../notification_settings_mention_base.js | 3 - .../notification_settings_mentions.ios.js | 6 +- ...on_settings_mentions_keywords.test.js.snap | 12 +- .../index.js | 12 +- ...notification_settings_mentions_keywords.js | 7 +- ...ication_settings_mentions_keywords.test.js | 3 - app/screens/settings/sidebar/sidebar.test.js | 1 + app/screens/settings/theme/theme.test.js | 14 +- app/screens/settings/timezone/index.js | 2 - .../timezone/select_timezone/index.js | 12 +- .../select_timezone/select_timezone.js | 11 +- app/screens/settings/timezone/timezone.js | 4 +- app/screens/sso/index.js | 2 - app/screens/sso/sso.js | 4 +- app/screens/terms_of_service/index.js | 9 - .../terms_of_service/terms_of_service.js | 17 +- .../terms_of_service/terms_of_service.test.js | 19 +- app/screens/thread/index.js | 4 - app/screens/thread/thread.ios.test.js | 17 +- app/screens/thread/thread_base.js | 17 +- app/screens/user_profile/index.js | 13 - app/screens/user_profile/user_profile.js | 32 +- app/screens/user_profile/user_profile.test.js | 89 +-- app/store/index.js | 300 +--------- app/store/store.js | 301 ++++++++++ app/telemetry/telemetry.android.js | 2 +- .../bottom_sheet/bottom_sheet.android.js | 3 +- app/utils/images.js | 3 +- app/utils/push_notifications.js | 6 +- app/utils/theme.js | 10 +- package-lock.json | 51 +- .../react-native-haptic-feedback+1.8.2.patch | 0 patches/react-native-navigation+2.21.1.patch | 102 ++++ share_extension/android/index.js | 2 +- test/setup.js | 44 ++ 217 files changed, 2703 insertions(+), 2741 deletions(-) delete mode 100644 app/actions/navigation.js create mode 100644 app/actions/navigation/index.js create mode 100644 app/actions/navigation/index.test.js rename app/components/attachment_button/__snapshots__/{attachment_button.test.js.snap => index.test.js.snap} (100%) delete mode 100644 app/components/attachment_button/attachment_button.js rename app/components/attachment_button/{attachment_button.test.js => index.test.js} (95%) rename app/components/markdown/hashtag/__snapshots__/{hashtag.test.js.snap => index.test.js.snap} (100%) delete mode 100644 app/components/markdown/hashtag/hashtag.js delete mode 100644 app/components/markdown/hashtag/hashtag.test.js create mode 100644 app/components/markdown/hashtag/index.test.js rename app/components/message_attachments/attachment_image/__snapshots__/{attachment_image.test.js.snap => index.test.js.snap} (100%) delete mode 100644 app/components/message_attachments/attachment_image/attachment_image.js rename app/components/message_attachments/attachment_image/{attachment_image.test.js => index.test.js} (97%) create mode 100644 app/store/store.js rename {native_modules => patches}/react-native-haptic-feedback+1.8.2.patch (100%) create mode 100644 patches/react-native-navigation+2.21.1.patch diff --git a/Makefile b/Makefile index 956503b0e..009459e66 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ post-install: fi @sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js - @./node_modules/.bin/patch-package --patch-dir=native_modules + @./node_modules/.bin/patch-package start: | pre-run ## Starts the React Native packager server $(call start_packager) diff --git a/app/actions/navigation.js b/app/actions/navigation.js deleted file mode 100644 index 11a186b89..000000000 --- a/app/actions/navigation.js +++ /dev/null @@ -1,394 +0,0 @@ -// 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'; - -import EphemeralStore from 'app/store/ephemeral_store'; - -export function resetToChannel(passProps = {}) { - return (dispatch, getState) => { - const theme = getTheme(getState()); - - Navigation.setRoot({ - root: { - stack: { - children: [{ - component: { - name: 'Channel', - passProps, - options: { - layout: { - backgroundColor: 'transparent', - }, - statusBar: { - visible: true, - }, - topBar: { - visible: false, - height: 0, - backButton: { - color: theme.sidebarHeaderTextColor, - title: '', - }, - background: { - color: theme.sidebarHeaderBg, - }, - title: { - color: theme.sidebarHeaderTextColor, - }, - }, - }, - }, - }], - }, - }, - }); - }; -} - -export function resetToSelectServer(allowOtherServers) { - return (dispatch, getState) => { - const theme = getTheme(getState()); - - Navigation.setRoot({ - root: { - stack: { - children: [{ - component: { - name: 'SelectServer', - passProps: { - allowOtherServers, - }, - options: { - statusBar: { - visible: true, - }, - topBar: { - backButton: { - color: theme.sidebarHeaderTextColor, - title: '', - }, - background: { - color: theme.sidebarHeaderBg, - }, - visible: false, - height: 0, - }, - }, - }, - }], - }, - }, - }); - }; -} - -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(name, title, passProps = {}, options = {}) { - return (dispatch, getState) => { - const state = getState(); - const componentId = EphemeralStore.getNavigationTopComponentId(); - const theme = getTheme(state); - const defaultOptions = { - layout: { - backgroundColor: theme.centerChannelBg, - }, - topBar: { - animate: true, - visible: true, - backButton: { - color: theme.sidebarHeaderTextColor, - title: '', - }, - background: { - color: theme.sidebarHeaderBg, - }, - title: { - color: theme.sidebarHeaderTextColor, - text: title, - }, - }, - }; - - Navigation.push(componentId, { - component: { - name, - passProps, - options: merge(defaultOptions, options), - }, - }); - }; -} - -export function popTopScreen(screenId) { - return () => { - if (screenId) { - Navigation.pop(screenId); - } else { - const componentId = EphemeralStore.getNavigationTopComponentId(); - Navigation.pop(componentId); - } - }; -} - -export function popToRoot() { - return () => { - const componentId = EphemeralStore.getNavigationTopComponentId(); - - Navigation.popToRoot(componentId).catch(() => { - // RNN returns a promise rejection if there are no screens - // atop the root screen to pop. We'll do nothing in this - // case but we will catch the rejection here so that the - // caller doesn't have to. - }); - }; -} - -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, - }, - leftButtonColor: theme.sidebarHeaderTextColor, - rightButtonColor: theme.sidebarHeaderTextColor, - }, - }; - - 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 dismissModal(options = {}) { - return () => { - const componentId = EphemeralStore.getNavigationTopComponentId(); - - Navigation.dismissModal(componentId, options).catch(() => { - // RNN returns a promise rejection if there is no modal to - // dismiss. We'll do nothing in this case but we will catch - // the rejection here so that the caller doesn't have to. - }); - }; -} - -export function dismissAllModals(options = {}) { - return () => { - Navigation.dismissAllModals(options).catch(() => { - // RNN returns a promise rejection if there are no modals to - // dismiss. We'll do nothing in this case but we will catch - // the rejection here so that the caller doesn't have to. - }); - }; -} - -export function peek(name, passProps = {}, options = {}) { - return () => { - const componentId = EphemeralStore.getNavigationTopComponentId(); - const defaultOptions = { - preview: { - commit: false, - }, - }; - - Navigation.push(componentId, { - component: { - name, - passProps, - options: merge(defaultOptions, options), - }, - }); - }; -} - -export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) { - return () => { - Navigation.mergeOptions(componentId, { - topBar: { - ...buttons, - }, - }); - }; -} - -export function showOverlay(name, passProps, options = {}) { - return () => { - const defaultOptions = { - overlay: { - interceptTouchOutside: false, - }, - }; - - Navigation.showOverlay({ - component: { - name, - passProps, - options: merge(defaultOptions, options), - }, - }); - }; -} - -export function dismissOverlay(componentId) { - return () => { - return Navigation.dismissOverlay(componentId).catch(() => { - // RNN returns a promise rejection if there is no modal with - // this componentId to dismiss. We'll do nothing in this case - // but we will catch the rejection here so that the caller - // doesn't have to. - }); - }; -} - -export function applyTheme(componentId, skipBackButtonStyle = false) { - return (dispatch, getState) => { - const theme = getTheme(getState()); - - let backButton = { - color: theme.sidebarHeaderTextColor, - }; - - if (skipBackButtonStyle && Platform.OS === 'android') { - backButton = null; - } - - Navigation.mergeOptions(componentId, { - topBar: { - backButton, - background: { - color: theme.sidebarHeaderBg, - }, - title: { - color: theme.sidebarHeaderTextColor, - }, - }, - }); - }; -} diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js new file mode 100644 index 000000000..cfbacdb44 --- /dev/null +++ b/app/actions/navigation/index.js @@ -0,0 +1,352 @@ +// 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'; + +import store from 'app/store'; +import EphemeralStore from 'app/store/ephemeral_store'; + +function getThemeFromState() { + const state = store.getState(); + + return getTheme(state); +} + +export function resetToChannel(passProps = {}) { + const theme = getThemeFromState(); + + Navigation.setRoot({ + root: { + stack: { + children: [{ + component: { + name: 'Channel', + passProps, + options: { + layout: { + backgroundColor: 'transparent', + }, + statusBar: { + visible: true, + }, + topBar: { + visible: false, + height: 0, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + }, + }, + }, + }, + }], + }, + }, + }); +} + +export function resetToSelectServer(allowOtherServers) { + const theme = getThemeFromState(); + + Navigation.setRoot({ + root: { + stack: { + children: [{ + component: { + name: 'SelectServer', + passProps: { + allowOtherServers, + }, + options: { + statusBar: { + visible: true, + }, + topBar: { + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + visible: false, + height: 0, + }, + }, + }, + }], + }, + }, + }); +} + +export function resetToTeams(name, title, passProps = {}, options = {}) { + const theme = getThemeFromState(); + 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(name, title, passProps = {}, options = {}) { + const theme = getThemeFromState(); + const componentId = EphemeralStore.getNavigationTopComponentId(); + const defaultOptions = { + layout: { + backgroundColor: theme.centerChannelBg, + }, + topBar: { + animate: true, + visible: true, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + text: title, + }, + }, + }; + + Navigation.push(componentId, { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }); +} + +export function popTopScreen(screenId) { + if (screenId) { + Navigation.pop(screenId); + } else { + const componentId = EphemeralStore.getNavigationTopComponentId(); + Navigation.pop(componentId); + } +} + +export async function popToRoot() { + const componentId = EphemeralStore.getNavigationTopComponentId(); + + try { + await Navigation.popToRoot(componentId); + } catch (error) { + // RNN returns a promise rejection if there are no screens + // atop the root screen to pop. We'll do nothing in this case. + } +} + +export function showModal(name, title, passProps = {}, options = {}) { + const theme = getThemeFromState(); + 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, + }, + leftButtonColor: theme.sidebarHeaderTextColor, + rightButtonColor: theme.sidebarHeaderTextColor, + }, + }; + + Navigation.showModal({ + stack: { + children: [{ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }], + }, + }); +} + +export function showModalOverCurrentContext(name, passProps = {}, options = {}) { + 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); + + showModal(name, title, passProps, mergeOptions); +} + +export function showSearchModal(initialValue = '') { + const name = 'Search'; + const title = ''; + const passProps = {initialValue}; + const options = { + topBar: { + visible: false, + height: 0, + }, + }; + + showModal(name, title, passProps, options); +} + +export async function dismissModal(options = {}) { + const componentId = EphemeralStore.getNavigationTopComponentId(); + + try { + await Navigation.dismissModal(componentId, options); + } catch (error) { + // RNN returns a promise rejection if there is no modal to + // dismiss. We'll do nothing in this case. + } +} + +export async function dismissAllModals(options = {}) { + try { + await Navigation.dismissAllModals(options); + } catch (error) { + // RNN returns a promise rejection if there are no modals to + // dismiss. We'll do nothing in this case. + } +} + +export function peek(name, passProps = {}, options = {}) { + const componentId = EphemeralStore.getNavigationTopComponentId(); + const defaultOptions = { + preview: { + commit: false, + }, + }; + + Navigation.push(componentId, { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }); +} + +export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) { + const options = { + topBar: { + ...buttons, + }, + }; + + mergeNavigationOptions(componentId, options); +} + +export function mergeNavigationOptions(componentId, options) { + Navigation.mergeOptions(componentId, options); +} + +export function showOverlay(name, passProps, options = {}) { + const defaultOptions = { + overlay: { + interceptTouchOutside: false, + }, + }; + + Navigation.showOverlay({ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }); +} + +export async function dismissOverlay(componentId) { + try { + await Navigation.dismissOverlay(componentId); + } catch (error) { + // RNN returns a promise rejection if there is no modal with + // this componentId to dismiss. We'll do nothing in this case. + } +} diff --git a/app/actions/navigation/index.test.js b/app/actions/navigation/index.test.js new file mode 100644 index 000000000..cdb5d93cc --- /dev/null +++ b/app/actions/navigation/index.test.js @@ -0,0 +1,473 @@ +// 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 Preferences from 'mattermost-redux/constants/preferences'; + +import EphemeralStore from 'app/store/ephemeral_store'; +import * as NavigationActions from 'app/actions/navigation'; + +jest.unmock('app/actions/navigation'); +jest.mock('app/store/ephemeral_store', () => ({ + getNavigationTopComponentId: jest.fn(), +})); + +describe('app/actions/navigation', () => { + const topComponentId = 'top-component-id'; + const name = 'name'; + const title = 'title'; + const theme = Preferences.THEMES.default; + const passProps = { + testProp: 'prop', + }; + const options = { + testOption: 'test', + }; + EphemeralStore.getNavigationTopComponentId.mockReturnValue(topComponentId); + + test('resetToChannel should call Navigation.setRoot', () => { + const setRoot = jest.spyOn(Navigation, 'setRoot'); + + const expectedLayout = { + root: { + stack: { + children: [{ + component: { + name: 'Channel', + passProps, + options: { + layout: { + backgroundColor: 'transparent', + }, + statusBar: { + visible: true, + }, + topBar: { + visible: false, + height: 0, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + }, + }, + }, + }, + }], + }, + }, + }; + + NavigationActions.resetToChannel(passProps); + expect(setRoot).toHaveBeenCalledWith(expectedLayout); + }); + + test('resetToSelectServer should call Navigation.setRoot', () => { + const setRoot = jest.spyOn(Navigation, 'setRoot'); + + const allowOtherServers = false; + const expectedLayout = { + root: { + stack: { + children: [{ + component: { + name: 'SelectServer', + passProps: { + allowOtherServers, + }, + options: { + statusBar: { + visible: true, + }, + topBar: { + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + visible: false, + height: 0, + }, + }, + }, + }], + }, + }, + }; + + NavigationActions.resetToSelectServer(allowOtherServers); + expect(setRoot).toHaveBeenCalledWith(expectedLayout); + }); + + test('resetToTeams should call Navigation.setRoot', () => { + const setRoot = jest.spyOn(Navigation, 'setRoot'); + + 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, + }, + }, + }; + + const expectedLayout = { + root: { + stack: { + children: [{ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }], + }, + }, + }; + + NavigationActions.resetToTeams(name, title, passProps, options); + expect(setRoot).toHaveBeenCalledWith(expectedLayout); + }); + + test('goToScreen should call Navigation.push', () => { + const push = jest.spyOn(Navigation, 'push'); + + const defaultOptions = { + layout: { + backgroundColor: theme.centerChannelBg, + }, + topBar: { + animate: true, + visible: true, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarHeaderBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + text: title, + }, + }, + }; + + const expectedLayout = { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }; + + NavigationActions.goToScreen(name, title, passProps, options); + expect(push).toHaveBeenCalledWith(topComponentId, expectedLayout); + }); + + test('popTopScreen should call Navigation.pop', () => { + const pop = jest.spyOn(Navigation, 'pop'); + + NavigationActions.popTopScreen(); + expect(pop).toHaveBeenCalledWith(topComponentId); + + const otherComponentId = `other-${topComponentId}`; + NavigationActions.popTopScreen(otherComponentId); + expect(pop).toHaveBeenCalledWith(otherComponentId); + }); + + test('popToRoot should call Navigation.popToRoot', async () => { + const popToRoot = jest.spyOn(Navigation, 'popToRoot'); + + await NavigationActions.popToRoot(); + expect(popToRoot).toHaveBeenCalledWith(topComponentId); + }); + + test('showModal should call Navigation.showModal', () => { + const showModal = jest.spyOn(Navigation, 'showModal'); + + 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, + }, + leftButtonColor: theme.sidebarHeaderTextColor, + rightButtonColor: theme.sidebarHeaderTextColor, + }, + }; + + const expectedLayout = { + stack: { + children: [{ + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }], + }, + }; + + NavigationActions.showModal(name, title, passProps, options); + expect(showModal).toHaveBeenCalledWith(expectedLayout); + }); + + test('showModalOverCurrentContext should call Navigation.showModal', () => { + const showModal = jest.spyOn(Navigation, 'showModal'); + + const animationsEnabled = (Platform.OS === 'android').toString(); + const showModalOverCurrentContextTitle = ''; + const showModalOverCurrentContextOptions = { + 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 showModalOptions = { + 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: showModalOverCurrentContextTitle, + }, + leftButtonColor: theme.sidebarHeaderTextColor, + rightButtonColor: theme.sidebarHeaderTextColor, + }, + }; + const defaultOptions = merge(showModalOverCurrentContextOptions, options); + + const expectedLayout = { + stack: { + children: [{ + component: { + name, + passProps, + options: merge(showModalOptions, defaultOptions), + }, + }], + }, + }; + + NavigationActions.showModalOverCurrentContext(name, passProps, options); + expect(showModal).toHaveBeenCalledWith(expectedLayout); + }); + + test('showSearchModal should call Navigation.showModal', () => { + const showModal = jest.spyOn(Navigation, 'showModal'); + + const showSearchModalName = 'Search'; + const showSearchModalTitle = ''; + const initialValue = 'initial-value'; + const showSearchModalPassProps = {initialValue}; + const showSearchModalOptions = { + topBar: { + visible: false, + height: 0, + }, + }; + 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: showSearchModalTitle, + }, + leftButtonColor: theme.sidebarHeaderTextColor, + rightButtonColor: theme.sidebarHeaderTextColor, + }, + }; + + const expectedLayout = { + stack: { + children: [{ + component: { + name: showSearchModalName, + passProps: showSearchModalPassProps, + options: merge(defaultOptions, showSearchModalOptions), + }, + }], + }, + }; + + NavigationActions.showSearchModal(initialValue); + expect(showModal).toHaveBeenCalledWith(expectedLayout); + }); + + test('dismissModal should call Navigation.dismissModal', async () => { + const dismissModal = jest.spyOn(Navigation, 'dismissModal'); + + await NavigationActions.dismissModal(options); + expect(dismissModal).toHaveBeenCalledWith(topComponentId, options); + }); + + test('dismissAllModals should call Navigation.dismissAllModals', async () => { + const dismissAllModals = jest.spyOn(Navigation, 'dismissAllModals'); + + await NavigationActions.dismissAllModals(options); + expect(dismissAllModals).toHaveBeenCalledWith(options); + }); + + test('peek should call Navigation.push', async () => { + const push = jest.spyOn(Navigation, 'push'); + + const defaultOptions = { + preview: { + commit: false, + }, + }; + + const expectedLayout = { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }; + + await NavigationActions.peek(name, passProps, options); + expect(push).toHaveBeenCalledWith(topComponentId, expectedLayout); + }); + + test('mergeNavigationOptions should call Navigation.mergeOptions', () => { + const mergeOptions = jest.spyOn(Navigation, 'mergeOptions'); + + NavigationActions.mergeNavigationOptions(topComponentId, options); + expect(mergeOptions).toHaveBeenCalledWith(topComponentId, options); + }); + + test('setButtons should call Navigation.mergeOptions', () => { + const mergeOptions = jest.spyOn(Navigation, 'mergeOptions'); + + const buttons = { + leftButtons: ['left-button'], + rightButtons: ['right-button'], + }; + const setButtonsOptions = { + topBar: { + ...buttons, + }, + }; + + NavigationActions.setButtons(topComponentId, buttons); + expect(mergeOptions).toHaveBeenCalledWith(topComponentId, setButtonsOptions); + }); + + test('showOverlay should call Navigation.showOverlay', () => { + const showOverlay = jest.spyOn(Navigation, 'showOverlay'); + + const defaultOptions = { + overlay: { + interceptTouchOutside: false, + }, + }; + + const expectedLayout = { + component: { + name, + passProps, + options: merge(defaultOptions, options), + }, + }; + + NavigationActions.showOverlay(name, passProps, options); + expect(showOverlay).toHaveBeenCalledWith(expectedLayout); + }); + + test('dismissOverlay should call Navigation.dismissOverlay', async () => { + const dismissOverlay = jest.spyOn(Navigation, 'dismissOverlay'); + + await NavigationActions.dismissOverlay(topComponentId); + expect(dismissOverlay).toHaveBeenCalledWith(topComponentId); + }); +}); \ No newline at end of file diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 64b29a3c0..122901c02 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -10,7 +10,8 @@ import { fetchMyChannelsAndMembers, getChannelByNameAndTeamName, markChannelAsRead, - leaveChannel as serviceLeaveChannel, markChannelAsViewed, + markChannelAsViewed, + leaveChannel as serviceLeaveChannel, selectChannel, getChannelStats, } from 'mattermost-redux/actions/channels'; diff --git a/app/actions/views/channel.test.js b/app/actions/views/channel.test.js index 3775ad84b..3af66604b 100644 --- a/app/actions/views/channel.test.js +++ b/app/actions/views/channel.test.js @@ -20,6 +20,23 @@ jest.mock('mattermost-redux/selectors/entities/channels', () => ({ getMyChannelMember: () => ({data: {member: {}}}), })); +jest.mock('mattermost-redux/actions/channels', () => { + const channelActions = require.requireActual('mattermost-redux/actions/channels'); + return { + ...channelActions, + markChannelAsRead: jest.fn(), + markChannelAsViewed: jest.fn(), + }; +}); + +jest.mock('mattermost-redux/selectors/entities/teams', () => { + const teamSelectors = require.requireActual('mattermost-redux/selectors/entities/teams'); + return { + ...teamSelectors, + getTeamByName: jest.fn(() => ({name: 'current-team-name'})), + }; +}); + const mockStore = configureStore([thunk]); describe('Actions.Views.Channel', () => { diff --git a/app/components/__snapshots__/profile_picture_button.test.js.snap b/app/components/__snapshots__/profile_picture_button.test.js.snap index c4205e2cf..7e959089f 100644 --- a/app/components/__snapshots__/profile_picture_button.test.js.snap +++ b/app/components/__snapshots__/profile_picture_button.test.js.snap @@ -1,13 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`profile_picture_button should match snapshot 1`] = ` - `; diff --git a/app/components/announcement_banner/announcement_banner.js b/app/components/announcement_banner/announcement_banner.js index 2b239a8b2..2902f56ba 100644 --- a/app/components/announcement_banner/announcement_banner.js +++ b/app/components/announcement_banner/announcement_banner.js @@ -11,16 +11,15 @@ import { } from 'react-native'; import {intlShape} from 'react-intl'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; + import RemoveMarkdown from 'app/components/remove_markdown'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {goToScreen} from 'app/actions/navigation'; 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, @@ -55,7 +54,6 @@ export default class AnnouncementBanner extends PureComponent { } handlePress = () => { - const {actions} = this.props; const {intl} = this.context; const screen = 'ExpandedAnnouncementBanner'; @@ -64,7 +62,7 @@ export default class AnnouncementBanner extends PureComponent { defaultMessage: 'Announcement', }); - actions.goToScreen(screen, title); + 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 390e62dec..7dccb5ebe 100644 --- a/app/components/announcement_banner/announcement_banner.test.js +++ b/app/components/announcement_banner/announcement_banner.test.js @@ -12,9 +12,6 @@ jest.useFakeTimers(); describe('AnnouncementBanner', () => { const baseProps = { - actions: { - goToScreen: jest.fn(), - }, bannerColor: '#ddd', bannerDismissed: false, bannerEnabled: true, diff --git a/app/components/announcement_banner/index.js b/app/components/announcement_banner/index.js index 346f08acc..8033244d0 100644 --- a/app/components/announcement_banner/index.js +++ b/app/components/announcement_banner/index.js @@ -1,13 +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 {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import AnnouncementBanner from './announcement_banner'; @@ -28,12 +26,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner); +export default connect(mapStateToProps)(AnnouncementBanner); diff --git a/app/components/at_mention/at_mention.js b/app/components/at_mention/at_mention.js index d34bae8ff..d6a985048 100644 --- a/app/components/at_mention/at_mention.js +++ b/app/components/at_mention/at_mention.js @@ -11,12 +11,10 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils'; import CustomPropTypes from 'app/constants/custom_prop_types'; import mattermostManaged from 'app/mattermost_managed'; import BottomSheet from 'app/utils/bottom_sheet'; +import {goToScreen} from 'app/actions/navigation'; export default class AtMention extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, isSearchResult: PropTypes.bool, mentionName: PropTypes.string.isRequired, mentionStyle: CustomPropTypes.Style, @@ -50,7 +48,6 @@ export default class AtMention extends React.PureComponent { } goToUserProfile = () => { - const {actions} = this.props; const {intl} = this.context; const screen = 'UserProfile'; const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}); @@ -58,7 +55,7 @@ export default class AtMention extends React.PureComponent { userId: this.state.user.id, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; getUserDetailsFromMentionName(props) { diff --git a/app/components/at_mention/index.js b/app/components/at_mention/index.js index 857345ee5..07ac8c733 100644 --- a/app/components/at_mention/index.js +++ b/app/components/at_mention/index.js @@ -1,15 +1,12 @@ // 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 {getUsersByUsername} from 'mattermost-redux/selectors/entities/users'; import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; - import AtMention from './at_mention'; function mapStateToProps(state) { @@ -20,12 +17,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(AtMention); +export default connect(mapStateToProps)(AtMention); diff --git a/app/components/attachment_button/__snapshots__/attachment_button.test.js.snap b/app/components/attachment_button/__snapshots__/index.test.js.snap similarity index 100% rename from app/components/attachment_button/__snapshots__/attachment_button.test.js.snap rename to app/components/attachment_button/__snapshots__/index.test.js.snap diff --git a/app/components/attachment_button/attachment_button.js b/app/components/attachment_button/attachment_button.js deleted file mode 100644 index f6128a171..000000000 --- a/app/components/attachment_button/attachment_button.js +++ /dev/null @@ -1,538 +0,0 @@ -// 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 {intlShape} from 'react-intl'; -import { - Alert, - NativeModules, - Platform, - StyleSheet, -} from 'react-native'; -import RNFetchBlob from 'rn-fetch-blob'; -import DeviceInfo from 'react-native-device-info'; -import AndroidOpenSettings from 'react-native-android-open-settings'; - -import Icon from 'react-native-vector-icons/Ionicons'; -import {DocumentPicker} from 'react-native-document-picker'; -import ImagePicker from 'react-native-image-picker'; -import Permissions from 'react-native-permissions'; - -import {lookupMimeType} from 'mattermost-redux/utils/file_utils'; - -import TouchableWithFeedback from 'app/components/touchable_with_feedback'; -import {PermissionTypes} from 'app/constants'; -import {changeOpacity} from 'app/utils/theme'; -import {t} from 'app/utils/i18n'; - -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, - canBrowseFiles: PropTypes.bool, - canBrowsePhotoLibrary: PropTypes.bool, - canBrowseVideoLibrary: PropTypes.bool, - canTakePhoto: PropTypes.bool, - canTakeVideo: PropTypes.bool, - children: PropTypes.node, - fileCount: PropTypes.number, - maxFileCount: PropTypes.number.isRequired, - maxFileSize: PropTypes.number.isRequired, - onShowFileMaxWarning: PropTypes.func, - onShowFileSizeWarning: PropTypes.func, - onShowUnsupportedMimeTypeWarning: PropTypes.func, - theme: PropTypes.object.isRequired, - uploadFiles: PropTypes.func.isRequired, - wrapper: PropTypes.bool, - extraOptions: PropTypes.arrayOf(PropTypes.object), - }; - - static defaultProps = { - browseFileTypes: Platform.OS === 'ios' ? 'public.item' : '*/*', - validMimeTypes: [], - canBrowseFiles: true, - canBrowsePhotoLibrary: true, - canBrowseVideoLibrary: true, - canTakePhoto: true, - canTakeVideo: true, - maxFileCount: 5, - extraOptions: null, - }; - - static contextTypes = { - intl: intlShape.isRequired, - }; - - getPermissionDeniedMessage = (source, mediaType = '') => { - const {formatMessage} = this.context.intl; - const applicationName = DeviceInfo.getApplicationName(); - switch (source) { - case 'camera': { - if (mediaType === 'video') { - return { - title: formatMessage({ - id: 'mobile.camera_video_permission_denied_title', - defaultMessage: '{applicationName} would like to access your camera', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.camera_video_permission_denied_description', - defaultMessage: 'Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', - }), - }; - } - - return { - title: formatMessage({ - id: 'mobile.camera_photo_permission_denied_title', - defaultMessage: '{applicationName} would like to access your camera', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.camera_photo_permission_denied_description', - defaultMessage: 'Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', - }), - }; - } - case 'storage': - return { - title: formatMessage({ - id: 'mobile.storage_permission_denied_title', - defaultMessage: '{applicationName} would like to access your files', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.storage_permission_denied_description', - defaultMessage: 'Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.', - }), - }; - case 'video': - return { - title: formatMessage({ - id: 'mobile.android.videos_permission_denied_title', - defaultMessage: '{applicationName} would like to access your videos', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.android.videos_permission_denied_description', - defaultMessage: 'Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.', - }), - }; - case 'photo': - default: { - if (Platform.OS === 'android') { - return { - title: formatMessage({ - id: 'mobile.android.photos_permission_denied_title', - defaultMessage: '{applicationName} would like to access your photos', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.android.photos_permission_denied_description', - defaultMessage: 'Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.', - }), - }; - } - - return { - title: formatMessage({ - id: 'mobile.ios.photos_permission_denied_title', - defaultMessage: '{applicationName} would like to access your photos', - }, {applicationName}), - text: formatMessage({ - id: 'mobile.ios.photos_permission_denied_description', - defaultMessage: 'Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.', - }), - }; - } - } - } - - attachPhotoFromCamera = () => { - return this.attachFileFromCamera('camera', 'photo'); - }; - - attachFileFromCamera = async (source, mediaType) => { - const {formatMessage} = this.context.intl; - const {title, text} = this.getPermissionDeniedMessage('camera', mediaType); - const options = { - quality: 0.8, - videoQuality: 'high', - noData: true, - mediaType, - storageOptions: { - cameraRoll: true, - waitUntilSaved: true, - }, - permissionDenied: { - title, - text, - reTryTitle: formatMessage({ - id: 'mobile.permission_denied_retry', - defaultMessage: 'Settings', - }), - okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), - }, - }; - - const hasCameraPermission = await this.hasPhotoPermission(source, mediaType); - - if (hasCameraPermission) { - ImagePicker.launchCamera(options, (response) => { - if (response.error || response.didCancel) { - return; - } - - this.uploadFiles([response]); - }); - } - }; - - attachFileFromLibrary = async () => { - const {formatMessage} = this.context.intl; - const {title, text} = this.getPermissionDeniedMessage('photo'); - const options = { - quality: 0.8, - noData: true, - permissionDenied: { - title, - text, - reTryTitle: formatMessage({ - id: 'mobile.permission_denied_retry', - defaultMessage: 'Settings', - }), - okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), - }, - }; - - if (Platform.OS === 'ios') { - options.mediaType = 'mixed'; - } - - const hasPhotoPermission = await this.hasPhotoPermission('photo'); - - if (hasPhotoPermission) { - ImagePicker.launchImageLibrary(options, (response) => { - if (response.error || response.didCancel) { - return; - } - - this.uploadFiles([response]); - }); - } - }; - - attachVideoFromCamera = () => { - return this.attachFileFromCamera('camera', 'video'); - }; - - attachVideoFromLibraryAndroid = () => { - const {formatMessage} = this.context.intl; - const {title, text} = this.getPermissionDeniedMessage('video'); - const options = { - videoQuality: 'high', - mediaType: 'video', - noData: true, - permissionDenied: { - title, - text, - reTryTitle: formatMessage({ - id: 'mobile.permission_denied_retry', - defaultMessage: 'Settings', - }), - okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), - }, - }; - - ImagePicker.launchImageLibrary(options, (response) => { - if (response.error || response.didCancel) { - return; - } - - this.uploadFiles([response]); - }); - }; - - attachFileFromFiles = async () => { - const {browseFileTypes} = this.props; - const hasPermission = await this.hasStoragePermission(); - - if (hasPermission) { - DocumentPicker.show({ - filetype: [browseFileTypes], - }, async (error, res) => { - if (error) { - return; - } - - if (Platform.OS === 'android') { - // For android we need to retrieve the realPath in case the file being imported is from the cloud - const newUri = await ShareExtension.getFilePath(res.uri); - if (newUri.filePath) { - res.uri = newUri.filePath; - } else { - return; - } - } - - // Decode file uri to get the actual path - res.uri = decodeURIComponent(res.uri); - - this.uploadFiles([res]); - }); - } - }; - - hasPhotoPermission = async (source, mediaType = '') => { - if (Platform.OS === 'ios') { - const {formatMessage} = this.context.intl; - let permissionRequest; - const targetSource = source || 'photo'; - const hasPermissionToStorage = await Permissions.check(targetSource); - - switch (hasPermissionToStorage) { - case PermissionTypes.UNDETERMINED: - permissionRequest = await Permissions.request(targetSource); - if (permissionRequest !== PermissionTypes.AUTHORIZED) { - return false; - } - break; - case PermissionTypes.DENIED: { - const canOpenSettings = await Permissions.canOpenSettings(); - let grantOption = null; - if (canOpenSettings) { - grantOption = { - text: formatMessage({ - id: 'mobile.permission_denied_retry', - defaultMessage: 'Settings', - }), - onPress: () => Permissions.openSettings(), - }; - } - - const {title, text} = this.getPermissionDeniedMessage(source, mediaType); - - Alert.alert( - title, - text, - [ - grantOption, - { - text: formatMessage({ - id: 'mobile.permission_denied_dismiss', - defaultMessage: 'Don\'t Allow', - }), - }, - ] - ); - return false; - } - } - } - - return true; - }; - - hasStoragePermission = async () => { - if (Platform.OS === 'android') { - const {formatMessage} = this.context.intl; - let permissionRequest; - const hasPermissionToStorage = await Permissions.check('storage'); - - switch (hasPermissionToStorage) { - case PermissionTypes.UNDETERMINED: - permissionRequest = await Permissions.request('storage'); - if (permissionRequest !== PermissionTypes.AUTHORIZED) { - return false; - } - break; - case PermissionTypes.DENIED: { - const {title, text} = this.getPermissionDeniedMessage('storage'); - - Alert.alert( - title, - text, - [ - { - text: formatMessage({ - id: 'mobile.permission_denied_dismiss', - defaultMessage: 'Don\'t Allow', - }), - }, - { - text: formatMessage({ - id: 'mobile.permission_denied_retry', - defaultMessage: 'Settings', - }), - onPress: () => AndroidOpenSettings.appDetailsSettings(), - }, - ] - ); - return false; - } - } - } - - return true; - }; - - uploadFiles = async (files) => { - const file = files[0]; - if (!file.fileSize | !file.fileName) { - const path = (file.path || file.uri).replace('file://', ''); - const fileInfo = await RNFetchBlob.fs.stat(path); - file.fileSize = fileInfo.size; - file.fileName = fileInfo.filename; - } - - if (!file.type) { - file.type = lookupMimeType(file.fileName); - } - - const {validMimeTypes} = this.props; - if (validMimeTypes.length && !validMimeTypes.includes(file.type)) { - this.props.onShowUnsupportedMimeTypeWarning(); - } else if (file.fileSize > this.props.maxFileSize) { - this.props.onShowFileSizeWarning(file.fileName); - } else { - this.props.uploadFiles(files); - } - }; - - showFileAttachmentOptions = () => { - const { - canBrowseFiles, - canBrowsePhotoLibrary, - canBrowseVideoLibrary, - canTakePhoto, - canTakeVideo, - fileCount, - maxFileCount, - onShowFileMaxWarning, - extraOptions, - actions, - } = this.props; - - if (fileCount === maxFileCount) { - onShowFileMaxWarning(); - return; - } - - this.props.blurTextBox(); - const items = []; - - if (canTakePhoto) { - items.push({ - action: this.attachPhotoFromCamera, - text: { - id: t('mobile.file_upload.camera_photo'), - defaultMessage: 'Take Photo', - }, - icon: 'camera', - }); - } - - if (canTakeVideo) { - items.push({ - action: this.attachVideoFromCamera, - text: { - id: t('mobile.file_upload.camera_video'), - defaultMessage: 'Take Video', - }, - icon: 'video-camera', - }); - } - - if (canBrowsePhotoLibrary) { - items.push({ - action: this.attachFileFromLibrary, - text: { - id: t('mobile.file_upload.library'), - defaultMessage: 'Photo Library', - }, - icon: 'photo', - }); - } - - if (canBrowseVideoLibrary && Platform.OS === 'android') { - items.push({ - action: this.attachVideoFromLibraryAndroid, - text: { - id: t('mobile.file_upload.video'), - defaultMessage: 'Video Library', - }, - icon: 'file-video-o', - }); - } - - if (canBrowseFiles) { - items.push({ - action: this.attachFileFromFiles, - text: { - id: t('mobile.file_upload.browse'), - defaultMessage: 'Browse Files', - }, - icon: 'file', - }); - } - - if (extraOptions) { - extraOptions.forEach((option) => { - if (option !== null) { - items.push(option); - } - }); - } - - actions.showModalOverCurrentContext('OptionsModal', {items}); - }; - - render() { - const {theme, wrapper, children} = this.props; - - if (wrapper) { - return ( - - {children} - - ); - } - - return ( - - - - ); - } -} - -const style = StyleSheet.create({ - attachIcon: { - marginTop: Platform.select({ - ios: 2, - android: 0, - }), - }, - buttonContainer: { - height: Platform.select({ - ios: 34, - android: 36, - }), - width: 45, - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/app/components/attachment_button/index.js b/app/components/attachment_button/index.js index d48573136..3bae7eecd 100644 --- a/app/components/attachment_button/index.js +++ b/app/components/attachment_button/index.js @@ -1,19 +1,535 @@ // 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 {intlShape} from 'react-intl'; +import { + Alert, + NativeModules, + Platform, + StyleSheet, +} from 'react-native'; +import RNFetchBlob from 'rn-fetch-blob'; +import DeviceInfo from 'react-native-device-info'; +import AndroidOpenSettings from 'react-native-android-open-settings'; -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; +import Icon from 'react-native-vector-icons/Ionicons'; +import {DocumentPicker} from 'react-native-document-picker'; +import ImagePicker from 'react-native-image-picker'; +import Permissions from 'react-native-permissions'; +import {lookupMimeType} from 'mattermost-redux/utils/file_utils'; + +import TouchableWithFeedback from 'app/components/touchable_with_feedback'; +import {PermissionTypes} from 'app/constants'; +import {changeOpacity} from 'app/utils/theme'; +import {t} from 'app/utils/i18n'; import {showModalOverCurrentContext} from 'app/actions/navigation'; -import AttachmentButton from './attachment_button'; +const ShareExtension = NativeModules.MattermostShare; -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - showModalOverCurrentContext, - }, dispatch), +export default class AttachmentButton extends PureComponent { + static propTypes = { + blurTextBox: PropTypes.func.isRequired, + browseFileTypes: PropTypes.string, + validMimeTypes: PropTypes.array, + canBrowseFiles: PropTypes.bool, + canBrowsePhotoLibrary: PropTypes.bool, + canBrowseVideoLibrary: PropTypes.bool, + canTakePhoto: PropTypes.bool, + canTakeVideo: PropTypes.bool, + children: PropTypes.node, + fileCount: PropTypes.number, + maxFileCount: PropTypes.number.isRequired, + maxFileSize: PropTypes.number.isRequired, + onShowFileMaxWarning: PropTypes.func, + onShowFileSizeWarning: PropTypes.func, + onShowUnsupportedMimeTypeWarning: PropTypes.func, + theme: PropTypes.object.isRequired, + uploadFiles: PropTypes.func.isRequired, + wrapper: PropTypes.bool, + extraOptions: PropTypes.arrayOf(PropTypes.object), }; + + static defaultProps = { + browseFileTypes: Platform.OS === 'ios' ? 'public.item' : '*/*', + validMimeTypes: [], + canBrowseFiles: true, + canBrowsePhotoLibrary: true, + canBrowseVideoLibrary: true, + canTakePhoto: true, + canTakeVideo: true, + maxFileCount: 5, + extraOptions: null, + }; + + static contextTypes = { + intl: intlShape.isRequired, + }; + + getPermissionDeniedMessage = (source, mediaType = '') => { + const {formatMessage} = this.context.intl; + const applicationName = DeviceInfo.getApplicationName(); + switch (source) { + case 'camera': { + if (mediaType === 'video') { + return { + title: formatMessage({ + id: 'mobile.camera_video_permission_denied_title', + defaultMessage: '{applicationName} would like to access your camera', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.camera_video_permission_denied_description', + defaultMessage: 'Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', + }), + }; + } + + return { + title: formatMessage({ + id: 'mobile.camera_photo_permission_denied_title', + defaultMessage: '{applicationName} would like to access your camera', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.camera_photo_permission_denied_description', + defaultMessage: 'Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', + }), + }; + } + case 'storage': + return { + title: formatMessage({ + id: 'mobile.storage_permission_denied_title', + defaultMessage: '{applicationName} would like to access your files', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.storage_permission_denied_description', + defaultMessage: 'Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.', + }), + }; + case 'video': + return { + title: formatMessage({ + id: 'mobile.android.videos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your videos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.android.videos_permission_denied_description', + defaultMessage: 'Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.', + }), + }; + case 'photo': + default: { + if (Platform.OS === 'android') { + return { + title: formatMessage({ + id: 'mobile.android.photos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your photos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.android.photos_permission_denied_description', + defaultMessage: 'Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.', + }), + }; + } + + return { + title: formatMessage({ + id: 'mobile.ios.photos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your photos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.ios.photos_permission_denied_description', + defaultMessage: 'Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.', + }), + }; + } + } + } + + attachPhotoFromCamera = () => { + return this.attachFileFromCamera('camera', 'photo'); + }; + + attachFileFromCamera = async (source, mediaType) => { + const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('camera', mediaType); + const options = { + quality: 0.8, + videoQuality: 'high', + noData: true, + mediaType, + storageOptions: { + cameraRoll: true, + waitUntilSaved: true, + }, + permissionDenied: { + title, + text, + reTryTitle: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), + }, + }; + + const hasCameraPermission = await this.hasPhotoPermission(source, mediaType); + + if (hasCameraPermission) { + ImagePicker.launchCamera(options, (response) => { + if (response.error || response.didCancel) { + return; + } + + this.uploadFiles([response]); + }); + } + }; + + attachFileFromLibrary = async () => { + const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('photo'); + const options = { + quality: 0.8, + noData: true, + permissionDenied: { + title, + text, + reTryTitle: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), + }, + }; + + if (Platform.OS === 'ios') { + options.mediaType = 'mixed'; + } + + const hasPhotoPermission = await this.hasPhotoPermission('photo'); + + if (hasPhotoPermission) { + ImagePicker.launchImageLibrary(options, (response) => { + if (response.error || response.didCancel) { + return; + } + + this.uploadFiles([response]); + }); + } + }; + + attachVideoFromCamera = () => { + return this.attachFileFromCamera('camera', 'video'); + }; + + attachVideoFromLibraryAndroid = () => { + const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('video'); + const options = { + videoQuality: 'high', + mediaType: 'video', + noData: true, + permissionDenied: { + title, + text, + reTryTitle: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), + }, + }; + + ImagePicker.launchImageLibrary(options, (response) => { + if (response.error || response.didCancel) { + return; + } + + this.uploadFiles([response]); + }); + }; + + attachFileFromFiles = async () => { + const {browseFileTypes} = this.props; + const hasPermission = await this.hasStoragePermission(); + + if (hasPermission) { + DocumentPicker.show({ + filetype: [browseFileTypes], + }, async (error, res) => { + if (error) { + return; + } + + if (Platform.OS === 'android') { + // For android we need to retrieve the realPath in case the file being imported is from the cloud + const newUri = await ShareExtension.getFilePath(res.uri); + if (newUri.filePath) { + res.uri = newUri.filePath; + } else { + return; + } + } + + // Decode file uri to get the actual path + res.uri = decodeURIComponent(res.uri); + + this.uploadFiles([res]); + }); + } + }; + + hasPhotoPermission = async (source, mediaType = '') => { + if (Platform.OS === 'ios') { + const {formatMessage} = this.context.intl; + let permissionRequest; + const targetSource = source || 'photo'; + const hasPermissionToStorage = await Permissions.check(targetSource); + + switch (hasPermissionToStorage) { + case PermissionTypes.UNDETERMINED: + permissionRequest = await Permissions.request(targetSource); + if (permissionRequest !== PermissionTypes.AUTHORIZED) { + return false; + } + break; + case PermissionTypes.DENIED: { + const canOpenSettings = await Permissions.canOpenSettings(); + let grantOption = null; + if (canOpenSettings) { + grantOption = { + text: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + onPress: () => Permissions.openSettings(), + }; + } + + const {title, text} = this.getPermissionDeniedMessage(source, mediaType); + + Alert.alert( + title, + text, + [ + grantOption, + { + text: formatMessage({ + id: 'mobile.permission_denied_dismiss', + defaultMessage: 'Don\'t Allow', + }), + }, + ] + ); + return false; + } + } + } + + return true; + }; + + hasStoragePermission = async () => { + if (Platform.OS === 'android') { + const {formatMessage} = this.context.intl; + let permissionRequest; + const hasPermissionToStorage = await Permissions.check('storage'); + + switch (hasPermissionToStorage) { + case PermissionTypes.UNDETERMINED: + permissionRequest = await Permissions.request('storage'); + if (permissionRequest !== PermissionTypes.AUTHORIZED) { + return false; + } + break; + case PermissionTypes.DENIED: { + const {title, text} = this.getPermissionDeniedMessage('storage'); + + Alert.alert( + title, + text, + [ + { + text: formatMessage({ + id: 'mobile.permission_denied_dismiss', + defaultMessage: 'Don\'t Allow', + }), + }, + { + text: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + onPress: () => AndroidOpenSettings.appDetailsSettings(), + }, + ] + ); + return false; + } + } + } + + return true; + }; + + uploadFiles = async (files) => { + const file = files[0]; + if (!file.fileSize | !file.fileName) { + const path = (file.path || file.uri).replace('file://', ''); + const fileInfo = await RNFetchBlob.fs.stat(path); + file.fileSize = fileInfo.size; + file.fileName = fileInfo.filename; + } + + if (!file.type) { + file.type = lookupMimeType(file.fileName); + } + + const {validMimeTypes} = this.props; + if (validMimeTypes.length && !validMimeTypes.includes(file.type)) { + this.props.onShowUnsupportedMimeTypeWarning(); + } else if (file.fileSize > this.props.maxFileSize) { + this.props.onShowFileSizeWarning(file.fileName); + } else { + this.props.uploadFiles(files); + } + }; + + showFileAttachmentOptions = () => { + const { + canBrowseFiles, + canBrowsePhotoLibrary, + canBrowseVideoLibrary, + canTakePhoto, + canTakeVideo, + fileCount, + maxFileCount, + onShowFileMaxWarning, + extraOptions, + } = this.props; + + if (fileCount === maxFileCount) { + onShowFileMaxWarning(); + return; + } + + this.props.blurTextBox(); + const items = []; + + if (canTakePhoto) { + items.push({ + action: this.attachPhotoFromCamera, + text: { + id: t('mobile.file_upload.camera_photo'), + defaultMessage: 'Take Photo', + }, + icon: 'camera', + }); + } + + if (canTakeVideo) { + items.push({ + action: this.attachVideoFromCamera, + text: { + id: t('mobile.file_upload.camera_video'), + defaultMessage: 'Take Video', + }, + icon: 'video-camera', + }); + } + + if (canBrowsePhotoLibrary) { + items.push({ + action: this.attachFileFromLibrary, + text: { + id: t('mobile.file_upload.library'), + defaultMessage: 'Photo Library', + }, + icon: 'photo', + }); + } + + if (canBrowseVideoLibrary && Platform.OS === 'android') { + items.push({ + action: this.attachVideoFromLibraryAndroid, + text: { + id: t('mobile.file_upload.video'), + defaultMessage: 'Video Library', + }, + icon: 'file-video-o', + }); + } + + if (canBrowseFiles) { + items.push({ + action: this.attachFileFromFiles, + text: { + id: t('mobile.file_upload.browse'), + defaultMessage: 'Browse Files', + }, + icon: 'file', + }); + } + + if (extraOptions) { + extraOptions.forEach((option) => { + if (option !== null) { + items.push(option); + } + }); + } + + showModalOverCurrentContext('OptionsModal', {items}); + }; + + render() { + const {theme, wrapper, children} = this.props; + + if (wrapper) { + return ( + + {children} + + ); + } + + return ( + + + + ); + } } -export default connect(null, mapDispatchToProps)(AttachmentButton); +const style = StyleSheet.create({ + attachIcon: { + marginTop: Platform.select({ + ios: 2, + android: 0, + }), + }, + buttonContainer: { + height: Platform.select({ + ios: 34, + android: 36, + }), + width: 45, + alignItems: 'center', + justifyContent: 'center', + }, +}); \ No newline at end of file diff --git a/app/components/attachment_button/attachment_button.test.js b/app/components/attachment_button/index.test.js similarity index 95% rename from app/components/attachment_button/attachment_button.test.js rename to app/components/attachment_button/index.test.js index 1c0f52128..ceaabdd0f 100644 --- a/app/components/attachment_button/attachment_button.test.js +++ b/app/components/attachment_button/index.test.js @@ -9,9 +9,10 @@ import {Alert} from 'react-native'; import Preferences from 'mattermost-redux/constants/preferences'; import {VALID_MIME_TYPES} from 'app/screens/edit_profile/edit_profile'; -import AttachmentButton from './attachment_button'; import {PermissionTypes} from 'app/constants'; +import AttachmentButton from './index'; + jest.mock('react-intl'); jest.mock('Platform', () => { @@ -23,9 +24,6 @@ jest.mock('Platform', () => { describe('AttachmentButton', () => { const formatMessage = jest.fn(); const baseProps = { - actions: { - showModalOverCurrentContext: jest.fn(), - }, theme: Preferences.THEMES.default, blurTextBox: jest.fn(), maxFileSize: 10, @@ -99,4 +97,4 @@ describe('AttachmentButton', () => { await wrapper.instance().hasPhotoPermission('camera'); expect(Alert.alert).toBeCalled(); }); -}); +}); \ No newline at end of file diff --git a/app/components/autocomplete_selector/autocomplete_selector.js b/app/components/autocomplete_selector/autocomplete_selector.js index 9b8d24754..ce9b75459 100644 --- a/app/components/autocomplete_selector/autocomplete_selector.js +++ b/app/components/autocomplete_selector/autocomplete_selector.js @@ -14,12 +14,12 @@ import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import {preventDoubleTap} from 'app/utils/tap'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; import {ViewTypes} from 'app/constants'; +import {goToScreen} from 'app/actions/navigation'; export default class AutocompleteSelector extends PureComponent { static propTypes = { actions: PropTypes.shape({ setAutocompleteSelector: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, }).isRequired, label: PropTypes.string, placeholder: PropTypes.string.isRequired, @@ -102,7 +102,7 @@ export default class AutocompleteSelector extends PureComponent { const title = placeholder || formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'}); actions.setAutocompleteSelector(dataSource, this.handleSelect, options); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); render() { diff --git a/app/components/autocomplete_selector/index.js b/app/components/autocomplete_selector/index.js index afb4c445c..dc77afea9 100644 --- a/app/components/autocomplete_selector/index.js +++ b/app/components/autocomplete_selector/index.js @@ -6,7 +6,6 @@ import {connect} from 'react-redux'; import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; import {setAutocompleteSelector} from 'app/actions/views/post'; import AutocompleteSelector from './autocomplete_selector'; @@ -22,7 +21,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ setAutocompleteSelector, - goToScreen, }, dispatch), }; } diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js index 9efe499f2..a2ae4392f 100644 --- a/app/components/channel_intro/channel_intro.js +++ b/app/components/channel_intro/channel_intro.js @@ -7,9 +7,10 @@ import { Text, View, } from 'react-native'; +import {injectIntl, intlShape} from 'react-intl'; + import {getFullName} from 'mattermost-redux/utils/user_utils'; import {General} from 'mattermost-redux/constants'; -import {injectIntl, intlShape} from 'react-intl'; import BotTag from 'app/components/bot_tag'; import GuestTag from 'app/components/guest_tag'; @@ -20,12 +21,10 @@ import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; import {isGuest} from 'app/utils/users'; +import {goToScreen} from 'app/actions/navigation'; 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, @@ -39,14 +38,14 @@ class ChannelIntro extends PureComponent { }; goToUserProfile = (userId) => { - const {actions, intl} = this.props; + const {intl} = this.props; const screen = 'UserProfile'; const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}); const passProps = { userId, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; getDisplayName = (member) => { diff --git a/app/components/channel_intro/index.js b/app/components/channel_intro/index.js index aa10a1525..3a496f125 100644 --- a/app/components/channel_intro/index.js +++ b/app/components/channel_intro/index.js @@ -1,7 +1,6 @@ // 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'; @@ -11,7 +10,6 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isLandscape} from 'app/selectors/device'; -import {goToScreen} from 'app/actions/navigation'; import {getChannelMembersForDm} from 'app/selectors/channel'; import ChannelIntro from './channel_intro'; @@ -55,12 +53,4 @@ function makeMapStateToProps() { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelIntro); +export default connect(makeMapStateToProps)(ChannelIntro); diff --git a/app/components/channel_link/channel_link.js b/app/components/channel_link/channel_link.js index de35dac4e..ed1e7f70c 100644 --- a/app/components/channel_link/channel_link.js +++ b/app/components/channel_link/channel_link.js @@ -9,6 +9,7 @@ import {intlShape} from 'react-intl'; import CustomPropTypes from 'app/constants/custom_prop_types'; import {t} from 'app/utils/i18n'; import {alertErrorWithFallback} from 'app/utils/general'; +import {popToRoot, dismissAllModals} from 'app/actions/navigation'; import {getChannelFromChannelName} from './channel_link_utils'; @@ -23,10 +24,8 @@ export default class ChannelLink extends React.PureComponent { textStyle: CustomPropTypes.Style, channelsByName: PropTypes.object.isRequired, actions: PropTypes.shape({ - dismissAllModals: PropTypes.func.isRequired, handleSelectChannel: PropTypes.func.isRequired, joinChannel: PropTypes.func.isRequired, - popToRoot: PropTypes.func.isRequired, }).isRequired, }; @@ -75,10 +74,11 @@ export default class ChannelLink extends React.PureComponent { } if (channel.id) { - const {dismissAllModals, handleSelectChannel, popToRoot} = this.props.actions; + const {handleSelectChannel} = this.props.actions; handleSelectChannel(channel.id); - dismissAllModals(); - popToRoot(); + + await dismissAllModals(); + await popToRoot(); if (this.props.onChannelLinkPress) { this.props.onChannelLinkPress(channel); diff --git a/app/components/channel_link/channel_link.test.js b/app/components/channel_link/channel_link.test.js index a79abfad7..e8aab6b58 100644 --- a/app/components/channel_link/channel_link.test.js +++ b/app/components/channel_link/channel_link.test.js @@ -11,9 +11,13 @@ import ChannelLink from './channel_link'; jest.mock('react-intl'); -jest.mock('app/utils/general', () => ({ - alertErrorWithFallback: jest.fn(), -})); +jest.mock('app/utils/general', () => { + const general = require.requireActual('app/utils/general'); + return { + ...general, + alertErrorWithFallback: jest.fn(), + }; +}); describe('ChannelLink', () => { const formatMessage = jest.fn(); @@ -30,10 +34,8 @@ describe('ChannelLink', () => { textStyle: {color: '#3d3c40', fontSize: 15, lineHeight: 20}, channelsByName, actions: { - dismissAllModals: jest.fn(), handleSelectChannel: jest.fn(), joinChannel: jest.fn(), - popToRoot: jest.fn(), }, }; @@ -71,14 +73,14 @@ describe('ChannelLink', () => { expect(innerText.props().onPress).not.toBeDefined(); }); - test('should call props.actions and onChannelLinkPress on handlePress', () => { + test('should call props.actions and onChannelLinkPress on handlePress', async () => { const wrapper = shallow( , {context: {intl: {formatMessage}}}, ); const channel = channelsByName.firstChannel; - wrapper.instance().handlePress(); + await wrapper.instance().handlePress(); expect(baseProps.actions.handleSelectChannel).toHaveBeenCalledTimes(1); expect(baseProps.actions.handleSelectChannel).toBeCalledWith(channel.id); expect(baseProps.onChannelLinkPress).toHaveBeenCalledTimes(1); diff --git a/app/components/channel_link/index.js b/app/components/channel_link/index.js index 174c2b93f..6b2ed9edd 100644 --- a/app/components/channel_link/index.js +++ b/app/components/channel_link/index.js @@ -10,7 +10,6 @@ import {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entiti import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; -import {dismissAllModals, popToRoot} from 'app/actions/navigation'; import {handleSelectChannel} from 'app/actions/views/channel'; import ChannelLink from './channel_link'; @@ -44,10 +43,8 @@ function makeMapStateToProps() { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - dismissAllModals, handleSelectChannel, joinChannel, - popToRoot, }, dispatch), }; } diff --git a/app/components/client_upgrade_listener/client_upgrade_listener.js b/app/components/client_upgrade_listener/client_upgrade_listener.js index d7bb98eec..e633c4aab 100644 --- a/app/components/client_upgrade_listener/client_upgrade_listener.js +++ b/app/components/client_upgrade_listener/client_upgrade_listener.js @@ -17,6 +17,7 @@ import FormattedText from 'app/components/formatted_text'; import {DeviceTypes} from 'app/constants'; import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {showModal, dismissModal} from 'app/actions/navigation'; const {View: AnimatedView} = Animated; @@ -27,8 +28,6 @@ export default class ClientUpgradeListener extends PureComponent { actions: PropTypes.shape({ logError: PropTypes.func.isRequired, setLastUpgradeCheck: PropTypes.func.isRequired, - showModal: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, currentVersion: PropTypes.string, downloadLink: PropTypes.string, @@ -141,10 +140,9 @@ export default class ClientUpgradeListener extends PureComponent { }; handleLearnMore = () => { - const {actions} = this.props; const {intl} = this.context; - actions.dismissModal(); + dismissModal(); const screen = 'ClientUpgrade'; const title = intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}); @@ -160,7 +158,7 @@ export default class ClientUpgradeListener extends PureComponent { }, }; - actions.showModal(screen, title, passProps, options); + showModal(screen, title, passProps, options); this.toggleUpgradeMessage(false); }; diff --git a/app/components/client_upgrade_listener/index.js b/app/components/client_upgrade_listener/index.js index 173c14a50..8d4483fc8 100644 --- a/app/components/client_upgrade_listener/index.js +++ b/app/components/client_upgrade_listener/index.js @@ -6,7 +6,6 @@ import {connect} from 'react-redux'; import {logError} from 'mattermost-redux/actions/errors'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {showModal, dismissModal} from 'app/actions/navigation'; import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade'; import getClientUpgrade from 'app/selectors/client_upgrade'; import {isLandscape} from 'app/selectors/device'; @@ -33,8 +32,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ logError, setLastUpgradeCheck, - showModal, - dismissModal, }, dispatch), }; } diff --git a/app/components/edit_channel_info/edit_channel_info.js b/app/components/edit_channel_info/edit_channel_info.js index 605ac2213..13bafe9b7 100644 --- a/app/components/edit_channel_info/edit_channel_info.js +++ b/app/components/edit_channel_info/edit_channel_info.js @@ -19,6 +19,7 @@ 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 {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import { changeOpacity, @@ -27,14 +28,10 @@ import { } from 'app/utils/theme'; import {t} from 'app/utils/i18n'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {popTopScreen, dismissModal} from 'app/actions/navigation'; 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, @@ -97,11 +94,10 @@ export default class EditChannelInfo extends PureComponent { }; close = (goBack = false) => { - const {actions} = this.props; if (goBack) { - actions.popTopScreen(); + popTopScreen(); } else { - actions.dismissModal(); + dismissModal(); } }; diff --git a/app/components/edit_channel_info/edit_channel_info.test.js b/app/components/edit_channel_info/edit_channel_info.test.js index 3c395f4d1..a5d498f32 100644 --- a/app/components/edit_channel_info/edit_channel_info.test.js +++ b/app/components/edit_channel_info/edit_channel_info.test.js @@ -11,10 +11,6 @@ import EditChannelInfo from './edit_channel_info'; describe('EditChannelInfo', () => { const baseProps = { - actions: { - dismissModal: jest.fn(), - popTopScreen: jest.fn(), - }, theme: Preferences.THEMES.default, deviceWidth: 400, deviceHeight: 600, diff --git a/app/components/edit_channel_info/index.js b/app/components/edit_channel_info/index.js index 156b13db2..851b7a735 100644 --- a/app/components/edit_channel_info/index.js +++ b/app/components/edit_channel_info/index.js @@ -1,13 +1,8 @@ // 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 { - dismissModal, - popTopScreen, -} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import EditChannelInfo from './edit_channel_info'; @@ -17,13 +12,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - dismissModal, - popTopScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(EditChannelInfo); +export default connect(mapStateToProps)(EditChannelInfo); diff --git a/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js b/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js index d6e988cc3..eb6134f89 100644 --- a/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js +++ b/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js @@ -25,6 +25,7 @@ import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import {DeviceTypes} from 'app/constants/'; import mattermostBucket from 'app/mattermost_bucket'; import {changeOpacity} from 'app/utils/theme'; +import {goToScreen} from 'app/actions/navigation'; const {DOCUMENTS_PATH} = DeviceTypes; const DOWNLOADING_OFFSET = 28; @@ -37,9 +38,6 @@ const circularProgressWidth = 4; export default class FileAttachmentDocument extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, backgroundColor: PropTypes.string, canDownloadFiles: PropTypes.bool.isRequired, iconHeight: PropTypes.number, @@ -193,13 +191,14 @@ export default class FileAttachmentDocument extends PureComponent { }; previewTextFile = (file, delay = 2000) => { - const {actions} = this.props; const {data} = file; const prefix = Platform.OS === 'android' ? 'file:/' : ''; const path = `${DOCUMENTS_PATH}/${data.id}-${file.caption}`; const readFile = RNFetchBlob.fs.readFile(`${prefix}${path}`, 'utf8'); setTimeout(async () => { try { + this.setState({downloading: false, progress: 0}); + const content = await readFile; const screen = 'TextPreview'; const title = file.caption; @@ -207,8 +206,7 @@ export default class FileAttachmentDocument extends PureComponent { content, }; - actions.goToScreen(screen, title, passProps); - this.setState({downloading: false, progress: 0}); + goToScreen(screen, title, passProps); } catch (error) { RNFetchBlob.fs.unlink(path); } diff --git a/app/components/file_attachment_list/file_attachment_document/index.js b/app/components/file_attachment_list/file_attachment_document/index.js index 31fe7d916..ff81e105d 100644 --- a/app/components/file_attachment_list/file_attachment_document/index.js +++ b/app/components/file_attachment_list/file_attachment_document/index.js @@ -1,19 +1,8 @@ // 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 {goToScreen} from 'app/actions/navigation'; - import FileAttachmentDocument from './file_attachment_document'; -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(null, mapDispatchToProps, null, {forwardRef: true})(FileAttachmentDocument); +export default connect(null, null, null, {forwardRef: true})(FileAttachmentDocument); diff --git a/app/components/file_attachment_list/file_attachment_list.js b/app/components/file_attachment_list/file_attachment_list.js index c201a6ef6..fd8a15596 100644 --- a/app/components/file_attachment_list/file_attachment_list.js +++ b/app/components/file_attachment_list/file_attachment_list.js @@ -22,7 +22,6 @@ export default class FileAttachmentList extends Component { static propTypes = { actions: PropTypes.shape({ loadFilesForPostIfNecessary: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, canDownloadFiles: PropTypes.bool.isRequired, fileIds: PropTypes.array.isRequired, @@ -122,8 +121,7 @@ export default class FileAttachmentList extends Component { }; handlePreviewPress = preventDoubleTap((idx) => { - const {actions} = this.props; - previewImageAtIndex(this.items, idx, this.galleryFiles, actions.showModalOverCurrentContext); + previewImageAtIndex(this.items, idx, this.galleryFiles); }); renderItems = () => { diff --git a/app/components/file_attachment_list/file_attachment_list.test.js b/app/components/file_attachment_list/file_attachment_list.test.js index e2035d542..245e0bac1 100644 --- a/app/components/file_attachment_list/file_attachment_list.test.js +++ b/app/components/file_attachment_list/file_attachment_list.test.js @@ -16,7 +16,6 @@ describe('PostAttachmentOpenGraph', () => { const baseProps = { actions: { loadFilesForPostIfNecessary, - showModalOverCurrentContext: jest.fn(), }, canDownloadFiles: true, deviceHeight: 680, @@ -72,7 +71,6 @@ describe('PostAttachmentOpenGraph', () => { files: [], actions: { loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock, - showModalOverCurrentContext: jest.fn(), }, }; diff --git a/app/components/file_attachment_list/index.js b/app/components/file_attachment_list/index.js index 1cd0301af..26bc64c1e 100644 --- a/app/components/file_attachment_list/index.js +++ b/app/components/file_attachment_list/index.js @@ -8,7 +8,6 @@ import {canDownloadFilesOnMobile} from 'mattermost-redux/selectors/entities/gene import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; import {loadFilesForPostIfNecessary} from 'app/actions/views/channel'; import FileAttachmentList from './file_attachment_list'; @@ -28,7 +27,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ loadFilesForPostIfNecessary, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/components/interactive_dialog_controller/index.js b/app/components/interactive_dialog_controller/index.js index 5d0fb3b43..0c23b8d31 100644 --- a/app/components/interactive_dialog_controller/index.js +++ b/app/components/interactive_dialog_controller/index.js @@ -6,8 +6,6 @@ import {connect} from 'react-redux'; import {submitInteractiveDialog} from 'mattermost-redux/actions/integrations'; -import {showModal} from 'app/actions/navigation'; - import InteractiveDialogController from './interactive_dialog_controller'; function mapStateToProps(state) { @@ -20,7 +18,6 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - showModal, submitInteractiveDialog, }, dispatch), }; diff --git a/app/components/interactive_dialog_controller/interactive_dialog_controller.js b/app/components/interactive_dialog_controller/interactive_dialog_controller.js index e7565c9a7..b51baa5f0 100644 --- a/app/components/interactive_dialog_controller/interactive_dialog_controller.js +++ b/app/components/interactive_dialog_controller/interactive_dialog_controller.js @@ -7,10 +7,11 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Alert} from 'react-native'; import {intlShape} from 'react-intl'; +import {showModal} from 'app/actions/navigation'; + export default class InteractiveDialogController extends PureComponent { static propTypes = { actions: PropTypes.shape({ - showModal: PropTypes.func.isRequired, submitInteractiveDialog: PropTypes.func.isRequired, }).isRequired, triggerId: PropTypes.string, @@ -88,7 +89,7 @@ export default class InteractiveDialogController extends PureComponent { }, }; - this.props.actions.showModal('InteractiveDialog', dialog.title, null, options); + showModal('InteractiveDialog', dialog.title, null, options); } handleCancel = (dialog, url) => { diff --git a/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js b/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js index d048b5732..604225f11 100644 --- a/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js +++ b/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js @@ -73,7 +73,6 @@ function getBaseProps(triggerId, elements, introductionText) { return { actions: { - showModal: jest.fn(), submitInteractiveDialog: jest.fn(), }, triggerId, diff --git a/app/components/markdown/hashtag/__snapshots__/hashtag.test.js.snap b/app/components/markdown/hashtag/__snapshots__/index.test.js.snap similarity index 100% rename from app/components/markdown/hashtag/__snapshots__/hashtag.test.js.snap rename to app/components/markdown/hashtag/__snapshots__/index.test.js.snap diff --git a/app/components/markdown/hashtag/hashtag.js b/app/components/markdown/hashtag/hashtag.js deleted file mode 100644 index 540d49bff..000000000 --- a/app/components/markdown/hashtag/hashtag.js +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import PropTypes from 'prop-types'; -import React from 'react'; -import {Text} from 'react-native'; - -import CustomPropTypes from 'app/constants/custom_prop_types'; - -export default class Hashtag extends React.PureComponent { - static propTypes = { - hashtag: PropTypes.string.isRequired, - linkStyle: CustomPropTypes.Style.isRequired, - onHashtagPress: PropTypes.func, - actions: PropTypes.shape({ - popToRoot: PropTypes.func.isRequired, - showSearchModal: PropTypes.func.isRequired, - dismissAllModals: PropTypes.func.isRequired, - }).isRequired, - }; - - handlePress = () => { - const { - onHashtagPress, - hashtag, - actions, - } = this.props; - - if (onHashtagPress) { - onHashtagPress(hashtag); - - return; - } - - // Close thread view, permalink view, etc - actions.dismissAllModals(); - actions.popToRoot(); - - actions.showSearchModal('#' + this.props.hashtag); - }; - - render() { - return ( - - {`#${this.props.hashtag}`} - - ); - } -} diff --git a/app/components/markdown/hashtag/hashtag.test.js b/app/components/markdown/hashtag/hashtag.test.js deleted file mode 100644 index d75fc2a74..000000000 --- a/app/components/markdown/hashtag/hashtag.test.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {shallow} from 'enzyme'; -import React from 'react'; -import {Text} from 'react-native'; - -import Hashtag from './hashtag'; - -describe('Hashtag', () => { - const baseProps = { - hashtag: 'test', - linkStyle: {color: 'red'}, - actions: { - showSearchModal: jest.fn(), - dismissAllModals: jest.fn(), - popToRoot: jest.fn(), - }, - }; - - test('should match snapshot', () => { - const wrapper = shallow(); - - expect(wrapper.getElement()).toMatchSnapshot(); - }); - - test('should open hashtag search on click', () => { - const props = { - ...baseProps, - }; - - const wrapper = shallow(); - - wrapper.find(Text).simulate('press'); - - expect(props.actions.dismissAllModals).toHaveBeenCalled(); - expect(props.actions.popToRoot).toHaveBeenCalled(); - expect(props.actions.showSearchModal).toHaveBeenCalledWith('#test'); - }); - - test('should call onHashtagPress if provided', () => { - const props = { - ...baseProps, - onHashtagPress: jest.fn(), - }; - - const wrapper = shallow(); - - wrapper.find(Text).simulate('press'); - - expect(props.actions.dismissAllModals).not.toBeCalled(); - expect(props.actions.popToRoot).not.toBeCalled(); - expect(props.actions.showSearchModal).not.toBeCalled(); - - expect(props.onHashtagPress).toBeCalled(); - }); -}); diff --git a/app/components/markdown/hashtag/index.js b/app/components/markdown/hashtag/index.js index 9ffc10e5c..565763241 100644 --- a/app/components/markdown/hashtag/index.js +++ b/app/components/markdown/hashtag/index.js @@ -1,25 +1,47 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {Text} from 'react-native'; -import { - popToRoot, - showSearchModal, - dismissAllModals, -} from 'app/actions/navigation'; +import CustomPropTypes from 'app/constants/custom_prop_types'; +import {popToRoot, showSearchModal, dismissAllModals} from 'app/actions/navigation'; -import Hashtag from './hashtag'; - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - popToRoot, - showSearchModal, - dismissAllModals, - }, dispatch), +export default class Hashtag extends React.PureComponent { + static propTypes = { + hashtag: PropTypes.string.isRequired, + linkStyle: CustomPropTypes.Style.isRequired, + onHashtagPress: PropTypes.func, }; -} -export default connect(null, mapDispatchToProps)(Hashtag); + handlePress = async () => { + const { + onHashtagPress, + hashtag, + } = this.props; + + if (onHashtagPress) { + onHashtagPress(hashtag); + + return; + } + + // Close thread view, permalink view, etc + await dismissAllModals(); + await popToRoot(); + + showSearchModal('#' + this.props.hashtag); + }; + + render() { + return ( + + {`#${this.props.hashtag}`} + + ); + } +} diff --git a/app/components/markdown/hashtag/index.test.js b/app/components/markdown/hashtag/index.test.js new file mode 100644 index 000000000..6534ee263 --- /dev/null +++ b/app/components/markdown/hashtag/index.test.js @@ -0,0 +1,61 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {shallow} from 'enzyme'; +import React from 'react'; + +import * as NavigationActions from 'app/actions/navigation'; + +import Hashtag from './index'; + +describe('Hashtag', () => { + const baseProps = { + hashtag: 'test', + linkStyle: {color: 'red'}, + }; + + test('should match snapshot', () => { + const wrapper = shallow(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('handlePress should open hashtag search', async () => { + const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals'); + const popToRoot = jest.spyOn(NavigationActions, 'popToRoot'); + const showSearchModal = jest.spyOn(NavigationActions, 'showSearchModal'); + + const props = { + ...baseProps, + }; + + const wrapper = shallow(); + + await wrapper.instance().handlePress(); + + expect(dismissAllModals).toHaveBeenCalled(); + expect(popToRoot).toHaveBeenCalled(); + expect(showSearchModal).toHaveBeenCalledWith('#test'); + }); + + test('handlePress should call onHashtagPress if provided', async () => { + const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals'); + const popToRoot = jest.spyOn(NavigationActions, 'popToRoot'); + const showSearchModal = jest.spyOn(NavigationActions, 'showSearchModal'); + + const props = { + ...baseProps, + onHashtagPress: jest.fn(), + }; + + const wrapper = shallow(); + + await wrapper.instance().handlePress(); + + expect(dismissAllModals).not.toBeCalled(); + expect(popToRoot).not.toBeCalled(); + expect(showSearchModal).not.toBeCalled(); + + expect(props.onHashtagPress).toBeCalled(); + }); +}); diff --git a/app/components/markdown/markdown_code_block/index.js b/app/components/markdown/markdown_code_block/index.js index 76e8cbb51..37ccabce4 100644 --- a/app/components/markdown/markdown_code_block/index.js +++ b/app/components/markdown/markdown_code_block/index.js @@ -1,13 +1,10 @@ // 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; - import MarkdownCodeBlock from './markdown_code_block'; function mapStateToProps(state) { @@ -16,12 +13,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(MarkdownCodeBlock); +export default connect(mapStateToProps)(MarkdownCodeBlock); diff --git a/app/components/markdown/markdown_code_block/markdown_code_block.js b/app/components/markdown/markdown_code_block/markdown_code_block.js index 83020acea..692cf3a6c 100644 --- a/app/components/markdown/markdown_code_block/markdown_code_block.js +++ b/app/components/markdown/markdown_code_block/markdown_code_block.js @@ -19,14 +19,12 @@ import {getDisplayNameForLanguage} from 'app/utils/markdown'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import mattermostManaged from 'app/mattermost_managed'; +import {goToScreen} from 'app/actions/navigation'; const MAX_LINES = 4; export default class MarkdownCodeBlock extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, theme: PropTypes.object.isRequired, language: PropTypes.string, content: PropTypes.string.isRequired, @@ -42,7 +40,7 @@ export default class MarkdownCodeBlock extends React.PureComponent { }; handlePress = preventDoubleTap(() => { - const {actions, language, content} = this.props; + const {language, content} = this.props; const {intl} = this.context; const screen = 'Code'; const passProps = { @@ -68,7 +66,7 @@ export default class MarkdownCodeBlock extends React.PureComponent { }); } - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); handleLongPress = async () => { diff --git a/app/components/markdown/markdown_image/index.js b/app/components/markdown/markdown_image/index.js index f15c54ccc..19ef387fa 100644 --- a/app/components/markdown/markdown_image/index.js +++ b/app/components/markdown/markdown_image/index.js @@ -1,13 +1,10 @@ // 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 {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; - import {getDimensions} from 'app/selectors/device'; import MarkdownImage from './markdown_image'; @@ -19,12 +16,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - showModalOverCurrentContext, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(MarkdownImage); +export default connect(mapStateToProps)(MarkdownImage); diff --git a/app/components/markdown/markdown_image/markdown_image.js b/app/components/markdown/markdown_image/markdown_image.js index 779870206..98ed272ab 100644 --- a/app/components/markdown/markdown_image/markdown_image.js +++ b/app/components/markdown/markdown_image/markdown_image.js @@ -34,9 +34,6 @@ const VIEWPORT_IMAGE_REPLY_OFFSET = 13; export default class MarkdownImage extends React.Component { static propTypes = { - actions: PropTypes.shape({ - showModalOverCurrentContext: PropTypes.func.isRequired, - }).isRequired, children: PropTypes.node, deviceHeight: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, @@ -177,7 +174,6 @@ export default class MarkdownImage extends React.Component { originalWidth, uri, } = this.state; - const {actions} = this.props; const link = this.getSource(); let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?')); const extension = filename.split('.').pop(); @@ -199,7 +195,7 @@ export default class MarkdownImage extends React.Component { }, }]; - previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext); + previewImageAtIndex([this.refs.item], 0, files); }; loadImageSize = (source) => { diff --git a/app/components/markdown/markdown_table/index.js b/app/components/markdown/markdown_table/index.js index 4100a3fc4..e3e37c016 100644 --- a/app/components/markdown/markdown_table/index.js +++ b/app/components/markdown/markdown_table/index.js @@ -1,13 +1,10 @@ // 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; - import MarkdownTable from './markdown_table'; function mapStateToProps(state) { @@ -16,12 +13,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTable); +export default connect(mapStateToProps)(MarkdownTable); diff --git a/app/components/markdown/markdown_table/markdown_table.js b/app/components/markdown/markdown_table/markdown_table.js index de97017ba..f84573979 100644 --- a/app/components/markdown/markdown_table/markdown_table.js +++ b/app/components/markdown/markdown_table/markdown_table.js @@ -11,17 +11,16 @@ import { import LinearGradient from 'react-native-linear-gradient'; import {CELL_WIDTH} from 'app/components/markdown/markdown_table_cell/markdown_table_cell'; + import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {goToScreen} from 'app/actions/navigation'; const MAX_HEIGHT = 300; export default class MarkdownTable extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, children: PropTypes.node.isRequired, numColumns: PropTypes.number.isRequired, theme: PropTypes.object.isRequired, @@ -46,7 +45,6 @@ export default class MarkdownTable extends React.PureComponent { }; handlePress = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'Table'; const title = intl.formatMessage({ @@ -58,7 +56,7 @@ export default class MarkdownTable extends React.PureComponent { tableWidth: this.getTableWidth(), }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); handleContainerLayout = (e) => { diff --git a/app/components/markdown/markdown_table_image/index.js b/app/components/markdown/markdown_table_image/index.js index c1eea7804..96174a3a8 100644 --- a/app/components/markdown/markdown_table_image/index.js +++ b/app/components/markdown/markdown_table_image/index.js @@ -1,14 +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 {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen} from 'app/actions/navigation'; - import MarkdownTableImage from './markdown_table_image'; function mapStateToProps(state) { @@ -18,12 +15,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTableImage); +export default connect(mapStateToProps)(MarkdownTableImage); diff --git a/app/components/markdown/markdown_table_image/markdown_table_image.js b/app/components/markdown/markdown_table_image/markdown_table_image.js index 1a21d0e52..a13e3c717 100644 --- a/app/components/markdown/markdown_table_image/markdown_table_image.js +++ b/app/components/markdown/markdown_table_image/markdown_table_image.js @@ -9,12 +9,10 @@ import {Text} from 'react-native'; import CustomPropTypes from 'app/constants/custom_prop_types'; import {getCurrentServerUrl} from 'app/init/credentials'; import {preventDoubleTap} from 'app/utils/tap'; +import {goToScreen} from 'app/actions/navigation'; export default class MarkdownTableImage extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, children: PropTypes.node.isRequired, source: PropTypes.string.isRequired, textStyle: CustomPropTypes.Style.isRequired, @@ -27,7 +25,6 @@ export default class MarkdownTableImage extends React.PureComponent { }; handlePress = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'TableImage'; const title = intl.formatMessage({ @@ -38,7 +35,7 @@ export default class MarkdownTableImage extends React.PureComponent { imageSource: this.getImageSource(), }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); getImageSource = async () => { diff --git a/app/components/markdown/transform.js b/app/components/markdown/transform.js index bb1502118..48b0847a9 100644 --- a/app/components/markdown/transform.js +++ b/app/components/markdown/transform.js @@ -258,8 +258,6 @@ function getLastSibling(node) { export function highlightMentions(ast, mentionKeys) { const walker = ast.walker(); - // console.warn(mentionKeys); - let e; while ((e = walker.next())) { if (!e.entering) { diff --git a/app/components/markdown/transform.test.js b/app/components/markdown/transform.test.js index 88a2e1fb6..1e83e9ed6 100644 --- a/app/components/markdown/transform.test.js +++ b/app/components/markdown/transform.test.js @@ -2966,28 +2966,28 @@ describe('Components.Markdown.transform', () => { // Confirms that all parent, child, and sibling linkages are correct and go both ways. function verifyAst(node) { if (node.prev && node.prev.next !== node) { - console.error('node is not linked properly to prev'); + console.error('node is not linked properly to prev'); //eslint-disable-line no-console return false; } if (node.next && node.next.prev !== node) { - console.error('node is not linked properly to next'); + console.error('node is not linked properly to next'); //eslint-disable-line no-console return false; } if (!node.firstChild && node.lastChild) { - console.error('node has children, but is not linked to first child'); + console.error('node has children, but is not linked to first child'); //eslint-disable-line no-console return false; } if (node.firstChild && !node.lastChild) { - console.error('node has children, but is not linked to last child'); + console.error('node has children, but is not linked to last child'); //eslint-disable-line no-console return false; } for (let child = node.firstChild; child; child = child.next) { if (child.parent !== node) { - console.error('node is not linked properly to child'); + console.error('node is not linked properly to child'); //eslint-disable-line no-console return false; } @@ -2996,18 +2996,18 @@ function verifyAst(node) { } if (!child.next && child !== node.lastChild) { - console.error('node children are not linked correctly'); + console.error('node children are not linked correctly'); //eslint-disable-line no-console return false; } } if (node.firstChild && node.firstChild.prev) { - console.error('node\'s first child has previous sibling'); + console.error('node\'s first child has previous sibling'); //eslint-disable-line no-console return false; } if (node.lastChild && node.lastChild.next) { - console.error('node\'s last child has next sibling'); + console.error('node\'s last child has next sibling'); //eslint-disable-line no-console return false; } diff --git a/app/components/message_attachments/attachment_image/__snapshots__/attachment_image.test.js.snap b/app/components/message_attachments/attachment_image/__snapshots__/index.test.js.snap similarity index 100% rename from app/components/message_attachments/attachment_image/__snapshots__/attachment_image.test.js.snap rename to app/components/message_attachments/attachment_image/__snapshots__/index.test.js.snap diff --git a/app/components/message_attachments/attachment_image/attachment_image.js b/app/components/message_attachments/attachment_image/attachment_image.js deleted file mode 100644 index 45d75fe47..000000000 --- a/app/components/message_attachments/attachment_image/attachment_image.js +++ /dev/null @@ -1,178 +0,0 @@ -// 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 {Image, View} from 'react-native'; - -import ProgressiveImage from 'app/components/progressive_image'; -import TouchableWithFeedback from 'app/components/touchable_with_feedback'; -import {isGifTooLarge, previewImageAtIndex, calculateDimensions} from 'app/utils/images'; -import ImageCacheManager from 'app/utils/image_cache_manager'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -const VIEWPORT_IMAGE_OFFSET = 100; -const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10; - -export default class AttachmentImage extends PureComponent { - static propTypes = { - actions: PropTypes.shape({ - showModalOverCurrentContext: PropTypes.func.isRequired, - }).isRequired, - deviceHeight: PropTypes.number.isRequired, - deviceWidth: PropTypes.number.isRequired, - imageMetadata: PropTypes.object, - imageUrl: PropTypes.string, - theme: PropTypes.object.isRequired, - }; - - constructor(props) { - super(props); - - this.state = { - hasImage: Boolean(props.imageUrl), - imageUri: null, - }; - } - - componentDidMount() { - this.mounted = true; - const {imageUrl, imageMetadata} = this.props; - - this.setViewPortMaxWidth(); - if (imageMetadata) { - this.setImageDimensionsFromMeta(null, imageMetadata); - } - - if (imageUrl) { - ImageCacheManager.cache(null, imageUrl, this.setImageUrl); - } - } - - componentDidUpdate(prevProps) { - if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) { - ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl); - } - } - - handlePreviewImage = () => { - const {actions, imageUrl} = this.props; - const { - imageUri: uri, - originalHeight, - originalWidth, - } = this.state; - - let filename = imageUrl.substring(imageUrl.lastIndexOf('/') + 1, imageUrl.indexOf('?') === -1 ? imageUrl.length : imageUrl.indexOf('?')); - const extension = filename.split('.').pop(); - - if (extension === filename) { - const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.')); - filename = `${filename}${ext}`; - } - - const files = [{ - caption: filename, - dimensions: { - height: originalHeight, - width: originalWidth, - }, - source: {uri}, - data: { - localPath: uri, - }, - }]; - previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext); - }; - - setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => { - if (this.mounted) { - this.setState({ - ...dimensions, - originalWidth, - originalHeight, - imageUri, - }); - } - }; - - setImageDimensionsFromMeta = (imageUri, imageMetadata) => { - const dimensions = calculateDimensions(imageMetadata.height, imageMetadata.width, this.maxImageWidth); - this.setImageDimensions(imageUri, dimensions, imageMetadata.width, imageMetadata.height); - }; - - setImageUrl = (imageURL) => { - const {imageMetadata} = this.props; - - if (imageMetadata) { - this.setImageDimensionsFromMeta(imageURL, imageMetadata); - return; - } - - Image.getSize(imageURL, (width, height) => { - const dimensions = calculateDimensions(height, width, this.maxImageWidth); - this.setImageDimensions(imageURL, dimensions, width, height); - }, () => null); - }; - - setViewPortMaxWidth = () => { - const {deviceWidth, deviceHeight} = this.props; - const viewPortWidth = deviceWidth > deviceHeight ? deviceHeight : deviceWidth; - this.maxImageWidth = viewPortWidth - VIEWPORT_IMAGE_OFFSET; - }; - - render() { - const {imageMetadata, theme} = this.props; - const {hasImage, height, imageUri, width} = this.state; - - if (!hasImage || isGifTooLarge(imageMetadata)) { - return null; - } - - const style = getStyleSheet(theme); - - let progressiveImage; - if (imageUri) { - progressiveImage = ( - - ); - } else { - progressiveImage = (); - } - - return ( - - - {progressiveImage} - - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - container: { - marginTop: 5, - }, - imageContainer: { - borderColor: changeOpacity(theme.centerChannelColor, 0.1), - borderWidth: 1, - borderRadius: 2, - flex: 1, - padding: 5, - }, - }; -}); diff --git a/app/components/message_attachments/attachment_image/index.js b/app/components/message_attachments/attachment_image/index.js index 17bfedae3..6948c3206 100644 --- a/app/components/message_attachments/attachment_image/index.js +++ b/app/components/message_attachments/attachment_image/index.js @@ -1,19 +1,175 @@ // 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 React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import {Image, View} from 'react-native'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; +import ProgressiveImage from 'app/components/progressive_image'; +import TouchableWithFeedback from 'app/components/touchable_with_feedback'; +import {isGifTooLarge, previewImageAtIndex, calculateDimensions} from 'app/utils/images'; +import ImageCacheManager from 'app/utils/image_cache_manager'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import AttachmentImage from './attachment_image'; +const VIEWPORT_IMAGE_OFFSET = 100; +const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10; -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - showModalOverCurrentContext, - }, dispatch), +export default class AttachmentImage extends PureComponent { + static propTypes = { + deviceHeight: PropTypes.number.isRequired, + deviceWidth: PropTypes.number.isRequired, + imageMetadata: PropTypes.object, + imageUrl: PropTypes.string, + theme: PropTypes.object.isRequired, }; + + constructor(props) { + super(props); + + this.state = { + hasImage: Boolean(props.imageUrl), + imageUri: null, + }; + } + + componentDidMount() { + this.mounted = true; + const {imageUrl, imageMetadata} = this.props; + + this.setViewPortMaxWidth(); + if (imageMetadata) { + this.setImageDimensionsFromMeta(null, imageMetadata); + } + + if (imageUrl) { + ImageCacheManager.cache(null, imageUrl, this.setImageUrl); + } + } + + componentDidUpdate(prevProps) { + if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) { + ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl); + } + } + + handlePreviewImage = () => { + const {imageUrl} = this.props; + const { + imageUri: uri, + originalHeight, + originalWidth, + } = this.state; + + let filename = imageUrl.substring(imageUrl.lastIndexOf('/') + 1, imageUrl.indexOf('?') === -1 ? imageUrl.length : imageUrl.indexOf('?')); + const extension = filename.split('.').pop(); + + if (extension === filename) { + const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.')); + filename = `${filename}${ext}`; + } + + const files = [{ + caption: filename, + dimensions: { + height: originalHeight, + width: originalWidth, + }, + source: {uri}, + data: { + localPath: uri, + }, + }]; + previewImageAtIndex([this.refs.item], 0, files); + }; + + setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => { + if (this.mounted) { + this.setState({ + ...dimensions, + originalWidth, + originalHeight, + imageUri, + }); + } + }; + + setImageDimensionsFromMeta = (imageUri, imageMetadata) => { + const dimensions = calculateDimensions(imageMetadata.height, imageMetadata.width, this.maxImageWidth); + this.setImageDimensions(imageUri, dimensions, imageMetadata.width, imageMetadata.height); + }; + + setImageUrl = (imageURL) => { + const {imageMetadata} = this.props; + + if (imageMetadata) { + this.setImageDimensionsFromMeta(imageURL, imageMetadata); + return; + } + + Image.getSize(imageURL, (width, height) => { + const dimensions = calculateDimensions(height, width, this.maxImageWidth); + this.setImageDimensions(imageURL, dimensions, width, height); + }, () => null); + }; + + setViewPortMaxWidth = () => { + const {deviceWidth, deviceHeight} = this.props; + const viewPortWidth = deviceWidth > deviceHeight ? deviceHeight : deviceWidth; + this.maxImageWidth = viewPortWidth - VIEWPORT_IMAGE_OFFSET; + }; + + render() { + const {imageMetadata, theme} = this.props; + const {hasImage, height, imageUri, width} = this.state; + + if (!hasImage || isGifTooLarge(imageMetadata)) { + return null; + } + + const style = getStyleSheet(theme); + + let progressiveImage; + if (imageUri) { + progressiveImage = ( + + ); + } else { + progressiveImage = (); + } + + return ( + + + {progressiveImage} + + + ); + } } -export default connect(null, mapDispatchToProps)(AttachmentImage); +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + marginTop: 5, + }, + imageContainer: { + borderColor: changeOpacity(theme.centerChannelColor, 0.1), + borderWidth: 1, + borderRadius: 2, + flex: 1, + padding: 5, + }, + }; +}); diff --git a/app/components/message_attachments/attachment_image/attachment_image.test.js b/app/components/message_attachments/attachment_image/index.test.js similarity index 97% rename from app/components/message_attachments/attachment_image/attachment_image.test.js rename to app/components/message_attachments/attachment_image/index.test.js index 3661ec762..da1557e7f 100644 --- a/app/components/message_attachments/attachment_image/attachment_image.test.js +++ b/app/components/message_attachments/attachment_image/index.test.js @@ -11,13 +11,10 @@ const originalGetSizeFn = Image.getSize; import Preferences from 'mattermost-redux/constants/preferences'; -import AttachmentImage from './attachment_image'; +import AttachmentImage from './index'; describe('AttachmentImage', () => { const baseProps = { - actions: { - showModalOverCurrentContext: jest.fn(), - }, deviceHeight: 256, deviceWidth: 128, imageMetadata: {width: 32, height: 32}, diff --git a/app/components/post/index.js b/app/components/post/index.js index df48aeaa0..342fff76a 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -12,7 +12,6 @@ import {getUser, getCurrentUserId} from 'mattermost-redux/selectors/entities/use import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils'; -import {goToScreen, showModalOverCurrentContext} from 'app/actions/navigation'; import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel'; import {isLandscape} from 'app/selectors/device'; @@ -96,8 +95,6 @@ function mapDispatchToProps(dispatch) { removePost, setPostTooltipVisible, insertToDraft, - goToScreen, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/components/post/post.js b/app/components/post/post.js index 00b3be652..2ba71ef42 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -11,6 +11,10 @@ import { } from 'react-native'; import {intlShape} from 'react-intl'; +import {Posts} from 'mattermost-redux/constants'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; +import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils'; + import PostBody from 'app/components/post_body'; import PostHeader from 'app/components/post_header'; import PostPreHeader from 'app/components/post_header/post_pre_header'; @@ -22,10 +26,7 @@ import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; - -import {Posts} from 'mattermost-redux/constants'; -import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils'; +import {goToScreen, showModalOverCurrentContext} from 'app/actions/navigation'; import Config from 'assets/config'; @@ -35,8 +36,6 @@ export default class Post extends PureComponent { createPost: PropTypes.func.isRequired, insertToDraft: PropTypes.func.isRequired, removePost: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, channelIsReadOnly: PropTypes.bool, currentUserId: PropTypes.string.isRequired, @@ -91,7 +90,7 @@ export default class Post extends PureComponent { goToUserProfile = () => { const {intl} = this.context; - const {actions, post} = this.props; + const {post} = this.props; const screen = 'UserProfile'; const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}); const passProps = { @@ -100,7 +99,7 @@ export default class Post extends PureComponent { Keyboard.dismiss(); requestAnimationFrame(() => { - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); }; @@ -141,7 +140,7 @@ export default class Post extends PureComponent { }], }; - this.props.actions.showModalOverCurrentContext(screen, passProps); + showModalOverCurrentContext(screen, passProps); }; handlePress = preventDoubleTap(() => { diff --git a/app/components/post_attachment_opengraph/index.js b/app/components/post_attachment_opengraph/index.js index d18e1585b..f422ddf5e 100644 --- a/app/components/post_attachment_opengraph/index.js +++ b/app/components/post_attachment_opengraph/index.js @@ -6,8 +6,6 @@ import {bindActionCreators} from 'redux'; import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; - import {getDimensions} from 'app/selectors/device'; import PostAttachmentOpenGraph from './post_attachment_opengraph'; @@ -22,7 +20,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ getOpenGraphMetadata, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/components/post_attachment_opengraph/post_attachment_opengraph.js b/app/components/post_attachment_opengraph/post_attachment_opengraph.js index e80f1b732..ec66e3cc2 100644 --- a/app/components/post_attachment_opengraph/post_attachment_opengraph.js +++ b/app/components/post_attachment_opengraph/post_attachment_opengraph.js @@ -27,7 +27,6 @@ export default class PostAttachmentOpenGraph extends PureComponent { static propTypes = { actions: PropTypes.shape({ getOpenGraphMetadata: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, deviceHeight: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, @@ -194,7 +193,6 @@ export default class PostAttachmentOpenGraph extends PureComponent { originalWidth, originalHeight, } = this.state; - const {actions} = this.props; const filename = this.getFilename(link); const files = [{ @@ -209,7 +207,7 @@ export default class PostAttachmentOpenGraph extends PureComponent { }, }]; - previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext); + previewImageAtIndex([this.refs.item], 0, files); }; renderDescription = () => { diff --git a/app/components/post_attachment_opengraph/post_attachment_opengraph.test.js b/app/components/post_attachment_opengraph/post_attachment_opengraph.test.js index dca90cac2..b67bff4bf 100644 --- a/app/components/post_attachment_opengraph/post_attachment_opengraph.test.js +++ b/app/components/post_attachment_opengraph/post_attachment_opengraph.test.js @@ -22,7 +22,6 @@ describe('PostAttachmentOpenGraph', () => { const baseProps = { actions: { getOpenGraphMetadata: jest.fn(), - showModalOverCurrentContext: jest.fn(), }, deviceHeight: 600, deviceWidth: 400, diff --git a/app/components/post_body/index.js b/app/components/post_body/index.js index 5c6d21f27..7c73a1ca7 100644 --- a/app/components/post_body/index.js +++ b/app/components/post_body/index.js @@ -1,7 +1,6 @@ // 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, Posts} from 'mattermost-redux/constants'; @@ -22,8 +21,6 @@ import { } from 'mattermost-redux/utils/post_utils'; import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; - import {getDimensions} from 'app/selectors/device'; import {hasEmojisOnly} from 'app/utils/emoji_utils'; @@ -106,12 +103,4 @@ export function makeMapStateToProps() { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - showModalOverCurrentContext, - }, dispatch), - }; -} - -export default connect(makeMapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostBody); +export default connect(makeMapStateToProps, null, null, {forwardRef: true})(PostBody); diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js index d5c5c1d0f..618a313dd 100644 --- a/app/components/post_body/post_body.js +++ b/app/components/post_body/post_body.js @@ -22,6 +22,7 @@ import {emptyFunction} from 'app/utils/general'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {showModalOverCurrentContext} from 'app/actions/navigation'; import telemetry from 'app/telemetry'; @@ -34,9 +35,6 @@ const SHOW_MORE_HEIGHT = 60; export default class PostBody extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - showModalOverCurrentContext: PropTypes.func.isRequired, - }).isRequired, canDelete: PropTypes.bool, channelIsReadOnly: PropTypes.bool.isRequired, deviceHeight: PropTypes.number.isRequired, @@ -133,7 +131,6 @@ export default class PostBody extends PureComponent { onHashtagPress, onPermalinkPress, post, - actions, } = this.props; const screen = 'LongPost'; const passProps = { @@ -148,7 +145,7 @@ export default class PostBody extends PureComponent { }, }; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); }); showPostOptions = () => { @@ -165,7 +162,6 @@ export default class PostBody extends PureComponent { post, showAddReaction, location, - actions, } = this.props; if (isSystemMessage && (!canDelete || hasBeenDeleted)) { @@ -191,7 +187,7 @@ export default class PostBody extends PureComponent { Keyboard.dismiss(); requestAnimationFrame(() => { - actions.showModalOverCurrentContext(screen, passProps); + showModalOverCurrentContext(screen, passProps); }); }; diff --git a/app/components/post_body/post_body.test.js b/app/components/post_body/post_body.test.js index 94dcbc48c..3c299f9ec 100644 --- a/app/components/post_body/post_body.test.js +++ b/app/components/post_body/post_body.test.js @@ -12,9 +12,6 @@ import PostBody from './post_body.js'; describe('PostBody', () => { const baseProps = { - actions: { - showModalOverCurrentContext: jest.fn(), - }, canDelete: true, channelIsReadOnly: false, deviceHeight: 1920, diff --git a/app/components/post_body_additional_content/index.js b/app/components/post_body_additional_content/index.js index e4ace1dc0..115ecb98a 100644 --- a/app/components/post_body_additional_content/index.js +++ b/app/components/post_body_additional_content/index.js @@ -10,7 +10,6 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts'; import {getBool, getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {showModalOverCurrentContext} from 'app/actions/navigation'; import {ViewTypes} from 'app/constants'; import {getDimensions} from 'app/selectors/device'; import {extractFirstLink} from 'app/utils/url'; @@ -76,7 +75,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ getRedirectLocation, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js index 13c88735f..58d3f5b06 100644 --- a/app/components/post_body_additional_content/post_body_additional_content.js +++ b/app/components/post_body_additional_content/post_body_additional_content.js @@ -38,7 +38,6 @@ export default class PostBodyAdditionalContent extends PureComponent { static propTypes = { actions: PropTypes.shape({ getRedirectLocation: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, baseTextStyle: CustomPropTypes.Style, blockStyles: PropTypes.object, @@ -412,7 +411,6 @@ export default class PostBodyAdditionalContent extends PureComponent { handlePreviewImage = (imageRef) => { const {shortenedLink} = this.state; let {link} = this.props; - const {actions} = this.props; if (shortenedLink) { link = shortenedLink; } @@ -434,7 +432,7 @@ export default class PostBodyAdditionalContent extends PureComponent { }, }]; - previewImageAtIndex([imageRef], 0, files, actions.showModalOverCurrentContext); + previewImageAtIndex([imageRef], 0, files); }; playYouTubeVideo = () => { diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js index 1c8f430ef..92168b4ba 100644 --- a/app/components/post_list/index.js +++ b/app/components/post_list/index.js @@ -9,7 +9,6 @@ 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'; @@ -43,7 +42,6 @@ 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 fb5a9c7a6..2ec97afee 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -16,6 +16,7 @@ import {makeExtraData} from 'app/utils/list_view'; import {changeOpacity} from 'app/utils/theme'; import {matchDeepLink} from 'app/utils/url'; import telemetry from 'app/telemetry'; +import {showModalOverCurrentContext} from 'app/actions/navigation'; import DateHeader from './date_header'; import NewMessagesDivider from './new_messages_divider'; @@ -41,7 +42,6 @@ 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, @@ -353,7 +353,7 @@ export default class PostList extends PureComponent { }; this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + 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 fe7c684b7..6935a50bb 100644 --- a/app/components/post_list/post_list.test.js +++ b/app/components/post_list/post_list.test.js @@ -4,9 +4,11 @@ import React from 'react'; import {shallow} from 'enzyme'; -import PostList from './post_list'; import Preferences from 'mattermost-redux/constants/preferences'; +import * as NavigationActions from 'app/actions/navigation'; +import PostList from './post_list'; + jest.useFakeTimers(); describe('PostList', () => { @@ -18,7 +20,6 @@ describe('PostList', () => { refreshChannelWithRetry: jest.fn(), selectFocusedPostId: jest.fn(), setDeepLinkURL: jest.fn(), - showModalOverCurrentContext: jest.fn(), }, deepLinkURL: '', lastPostIndex: -1, @@ -42,10 +43,12 @@ describe('PostList', () => { }); test('setting permalink deep link', () => { + const showModalOverCurrentContext = jest.spyOn(NavigationActions, 'showModalOverCurrentContext'); + wrapper.setProps({deepLinkURL: deepLinks.permalink}); expect(baseProps.actions.setDeepLinkURL).toHaveBeenCalled(); expect(baseProps.actions.selectFocusedPostId).toHaveBeenCalled(); - expect(baseProps.actions.showModalOverCurrentContext).toHaveBeenCalled(); + expect(showModalOverCurrentContext).toHaveBeenCalled(); expect(wrapper.getElement()).toMatchSnapshot(); }); diff --git a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap index 4ce8b6f59..924bdf35f 100644 --- a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap +++ b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap @@ -19,8 +19,15 @@ exports[`PostTextBox should match, full snapshot 1`] = ` ] } > - { - const {actions, theme} = this.props; + const {theme} = this.props; const {formatMessage} = this.context.intl; const screen = 'AddReaction'; const title = formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}); @@ -62,7 +62,7 @@ export default class Reactions extends PureComponent { onEmojiPress: this.handleAddReactionToPost, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); }); @@ -86,7 +86,7 @@ export default class Reactions extends PureComponent { }; showReactionList = () => { - const {actions, postId} = this.props; + const {postId} = this.props; const screen = 'ReactionList'; const passProps = { @@ -94,7 +94,7 @@ export default class Reactions extends PureComponent { }; if (!this.onPressDetected) { - actions.showModalOverCurrentContext(screen, passProps); + showModalOverCurrentContext(screen, passProps); } }; diff --git a/app/components/root/index.js b/app/components/root/index.js index 7f4b28031..410e264bc 100644 --- a/app/components/root/index.js +++ b/app/components/root/index.js @@ -1,13 +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 {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; 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'; @@ -23,12 +21,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - resetToTeams, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(Root); +export default connect(mapStateToProps)(Root); diff --git a/app/components/root/root.js b/app/components/root/root.js index 95e46af44..d3f271886 100644 --- a/app/components/root/root.js +++ b/app/components/root/root.js @@ -9,14 +9,12 @@ import {Platform} from 'react-native'; import {Client4} from 'mattermost-redux/client'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; +import {resetToTeams} from 'app/actions/navigation'; import {NavigationTypes} from 'app/constants'; import {getTranslations} from 'app/i18n'; export default class Root extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - resetToTeams: PropTypes.func.isRequired, - }).isRequired, children: PropTypes.node, excludeEvents: PropTypes.bool, currentUrl: PropTypes.string, @@ -63,7 +61,7 @@ export default class Root extends PureComponent { } navigateToTeamsPage = (screen) => { - const {currentUrl, theme, actions} = this.props; + const {currentUrl, theme} = this.props; const {intl} = this.refs.provider.getChildContext(); let passProps = {theme}; @@ -93,7 +91,7 @@ export default class Root extends PureComponent { const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}); - actions.resetToTeams(screen, title, passProps, options); + resetToTeams(screen, title, passProps, options); } render() { diff --git a/app/components/sidebars/main/channels_list/list/index.js b/app/components/sidebars/main/channels_list/list/index.js index 82f450b12..fc7ab6c66 100644 --- a/app/components/sidebars/main/channels_list/list/index.js +++ b/app/components/sidebars/main/channels_list/list/index.js @@ -1,7 +1,6 @@ // 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'; @@ -20,7 +19,6 @@ import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selecto import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles'; import Permissions from 'mattermost-redux/constants/permissions'; -import {showModal} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import {DeviceTypes, ViewTypes} from 'app/constants'; @@ -77,14 +75,6 @@ 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; @@ -95,4 +85,4 @@ function areStatesEqual(next, prev) { return equalChannels && equalConfig && equalRoles && equalUsers && equalFav; } -export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(List); +export default connect(mapStateToProps, null, 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 9b78a9e56..da9af34b6 100644 --- a/app/components/sidebars/main/channels_list/list/list.js +++ b/app/components/sidebars/main/channels_list/list/list.js @@ -21,14 +21,14 @@ import {General} from 'mattermost-redux/constants'; import {debounce} from 'mattermost-redux/actions/helpers'; import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item'; +import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {DeviceTypes, ListTypes} from 'app/constants'; import {SidebarSectionTypes} from 'app/constants/view'; import BottomSheet from 'app/utils/bottom_sheet'; import {t} from 'app/utils/i18n'; import {preventDoubleTap} from 'app/utils/tap'; - -import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {showModal} from 'app/actions/navigation'; const VIEWABILITY_CONFIG = { ...ListTypes.VISIBILITY_CONFIG_DEFAULTS, @@ -50,9 +50,6 @@ export default class List extends PureComponent { orderedChannelIds: PropTypes.array.isRequired, previewChannel: PropTypes.func, isLandscape: PropTypes.bool.isRequired, - actions: PropTypes.shape({ - showModal: PropTypes.func.isRequired, - }).isRequired, }; static contextTypes = { @@ -224,7 +221,6 @@ export default class List extends PureComponent { }; goToCreatePublicChannel = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'CreateChannel'; const title = intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}); @@ -233,11 +229,10 @@ export default class List extends PureComponent { closeButton: this.closeButton, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); goToCreatePrivateChannel = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'CreateChannel'; const title = intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}); @@ -246,11 +241,10 @@ export default class List extends PureComponent { closeButton: this.closeButton, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); goToDirectMessages = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'MoreDirectMessages'; const title = intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'}); @@ -264,11 +258,10 @@ export default class List extends PureComponent { }, }; - actions.showModal(screen, title, passProps, options); + showModal(screen, title, passProps, options); }); goToMoreChannels = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'MoreChannels'; const title = intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'}); @@ -276,7 +269,7 @@ export default class List extends PureComponent { closeButton: this.closeButton, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); keyExtractor = (item) => item.id || item; diff --git a/app/components/sidebars/main/teams_list/index.js b/app/components/sidebars/main/teams_list/index.js index f72990601..8d595920b 100644 --- a/app/components/sidebars/main/teams_list/index.js +++ b/app/components/sidebars/main/teams_list/index.js @@ -7,7 +7,6 @@ import {connect} from 'react-redux'; import {getCurrentTeamId, getMySortedTeamIds, getJoinableTeamIds} from 'mattermost-redux/selectors/entities/teams'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {showModal} from 'app/actions/navigation'; import {handleTeamChange} from 'app/actions/views/select_team'; import {getCurrentLocale} from 'app/selectors/i18n'; @@ -28,7 +27,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ handleTeamChange, - showModal, }, dispatch), }; } diff --git a/app/components/sidebars/main/teams_list/teams_list.js b/app/components/sidebars/main/teams_list/teams_list.js index b139e7fa6..b882e9806 100644 --- a/app/components/sidebars/main/teams_list/teams_list.js +++ b/app/components/sidebars/main/teams_list/teams_list.js @@ -23,6 +23,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {removeProtocol} from 'app/utils/url'; import tracker from 'app/utils/time_tracker'; import telemetry from 'app/telemetry'; +import {showModal} from 'app/actions/navigation'; import TeamsListItem from './teams_list_item'; @@ -36,7 +37,6 @@ export default class TeamsList extends PureComponent { static propTypes = { actions: PropTypes.shape({ handleTeamChange: PropTypes.func.isRequired, - showModal: PropTypes.func.isRequired, }).isRequired, closeChannelDrawer: PropTypes.func.isRequired, currentTeamId: PropTypes.string.isRequired, @@ -86,7 +86,7 @@ export default class TeamsList extends PureComponent { goToSelectTeam = preventDoubleTap(async () => { const {intl} = this.context; - const {theme, actions} = this.props; + const {theme} = this.props; const {serverUrl} = this.state; const screen = 'SelectTeam'; const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}); @@ -103,7 +103,7 @@ export default class TeamsList extends PureComponent { }, }; - actions.showModal(screen, title, passProps, options); + showModal(screen, title, passProps, options); }); keyExtractor = (item) => { diff --git a/app/components/sidebars/settings/index.js b/app/components/sidebars/settings/index.js index 57c408b26..db0d83db5 100644 --- a/app/components/sidebars/settings/index.js +++ b/app/components/sidebars/settings/index.js @@ -8,12 +8,6 @@ import {logout, setStatus} from 'mattermost-redux/actions/users'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users'; -import { - showModal, - showModalOverCurrentContext, - dismissModal, -} from 'app/actions/navigation'; - import SettingsSidebar from './settings_sidebar'; function mapStateToProps(state) { @@ -32,9 +26,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ logout, setStatus, - showModal, - showModalOverCurrentContext, - dismissModal, }, dispatch), }; } diff --git a/app/components/sidebars/settings/settings_sidebar.js b/app/components/sidebars/settings/settings_sidebar.js index ea50c0678..f7d3b4531 100644 --- a/app/components/sidebars/settings/settings_sidebar.js +++ b/app/components/sidebars/settings/settings_sidebar.js @@ -25,6 +25,7 @@ import {confirmOutOfOfficeDisabled} from 'app/utils/status'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {showModal, showModalOverCurrentContext, dismissModal} from 'app/actions/navigation'; import DrawerItem from './drawer_item'; import UserInfo from './user_info'; @@ -35,9 +36,6 @@ export default class SettingsDrawer extends PureComponent { actions: PropTypes.shape({ logout: PropTypes.func.isRequired, setStatus: PropTypes.func.isRequired, - showModal: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, blurPostTextBox: PropTypes.func.isRequired, children: PropTypes.node, @@ -127,7 +125,6 @@ export default class SettingsDrawer extends PureComponent { }; handleSetStatus = preventDoubleTap(() => { - const {actions} = this.props; const items = [{ action: () => this.setStatus(General.ONLINE), text: { @@ -154,7 +151,7 @@ export default class SettingsDrawer extends PureComponent { }, }]; - actions.showModalOverCurrentContext('OptionsModal', {items}); + showModalOverCurrentContext('OptionsModal', {items}); }); goToEditProfile = preventDoubleTap(() => { @@ -216,7 +213,6 @@ export default class SettingsDrawer extends PureComponent { openModal = (screen, title, passProps) => { this.closeSettingsSidebar(); - const {actions} = this.props; const options = { topBar: { leftButtons: [{ @@ -227,7 +223,7 @@ export default class SettingsDrawer extends PureComponent { }; InteractionManager.runAfterInteractions(() => { - actions.showModal(screen, title, passProps, options); + showModal(screen, title, passProps, options); }); }; @@ -354,10 +350,10 @@ export default class SettingsDrawer extends PureComponent { }; setStatus = (status) => { - const {status: currentUserStatus, actions} = this.props; + const {status: currentUserStatus} = this.props; if (currentUserStatus === General.OUT_OF_OFFICE) { - actions.dismissModal(); + dismissModal(); this.closeSettingsSidebar(); this.confirmReset(status); return; diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js index 9f477ec6e..dc3c099d4 100644 --- a/app/init/global_event_handler.js +++ b/app/init/global_event_handler.js @@ -247,7 +247,7 @@ class GlobalEventHandler { handleInAppNotification = (notification) => { const {data} = notification; - const {dispatch, getState} = this.store; + const {getState} = this.store; const state = getState(); const currentChannelId = getCurrentChannelId(state); @@ -258,7 +258,7 @@ class GlobalEventHandler { }; EventEmitter.emit(NavigationTypes.NAVIGATION_SHOW_OVERLAY); - dispatch(showOverlay(screen, passProps)); + showOverlay(screen, passProps); } }; } diff --git a/app/mattermost.js b/app/mattermost.js index f1a35084b..6c9c9f5df 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -9,21 +9,19 @@ import {loadMe} from 'mattermost-redux/actions/users'; import {resetToChannel, resetToSelectServer} from 'app/actions/navigation'; import {setDeepLinkURL} from 'app/actions/views/root'; -import initialState from 'app/initial_state'; import {getAppCredentials} from 'app/init/credentials'; import emmProvider from 'app/init/emm_provider'; import 'app/init/device'; import 'app/init/fetch'; import globalEventHandler from 'app/init/global_event_handler'; import {registerScreens} from 'app/screens'; -import configureStore from 'app/store'; +import store from 'app/store'; import EphemeralStore from 'app/store/ephemeral_store'; import telemetry from 'app/telemetry'; import pushNotificationsUtils from 'app/utils/push_notifications'; const {MattermostShare} = NativeModules; const sharedExtensionStarted = Platform.OS === 'android' && MattermostShare.isOpened; -export const store = configureStore(initialState); const init = async () => { const credentials = await getAppCredentials(); @@ -57,9 +55,9 @@ const launchApp = async (credentials) => { if (credentials) { store.dispatch(loadMe()); - store.dispatch(resetToChannel({skipMetrics: true})); + resetToChannel({skipMetrics: true}); } else { - store.dispatch(resetToSelectServer(emmProvider.allowOtherServers)); + resetToSelectServer(emmProvider.allowOtherServers); } telemetry.startSinceLaunch(['start:splash_screen']); diff --git a/app/screens/add_reaction/add_reaction.js b/app/screens/add_reaction/add_reaction.js index e03ddbf85..86ab62b88 100644 --- a/app/screens/add_reaction/add_reaction.js +++ b/app/screens/add_reaction/add_reaction.js @@ -12,13 +12,10 @@ import {Navigation} from 'react-native-navigation'; import EmojiPicker from 'app/components/emoji_picker'; import {emptyFunction} from 'app/utils/general'; import {setNavigatorStyles} from 'app/utils/theme'; +import {dismissModal, setButtons} from 'app/actions/navigation'; export default class AddReaction extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - dismissModal: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - }).isRequired, componentId: PropTypes.string, closeButton: PropTypes.object, onEmojiPress: PropTypes.func, @@ -36,7 +33,7 @@ export default class AddReaction extends PureComponent { constructor(props) { super(props); - props.actions.setButtons(props.componentId, { + setButtons(props.componentId, { leftButtons: [{...this.leftButton, icon: props.closeButton}], }); } @@ -58,7 +55,7 @@ export default class AddReaction extends PureComponent { } close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; handleEmojiPress = (emoji) => { diff --git a/app/screens/add_reaction/index.js b/app/screens/add_reaction/index.js index eb24346b2..1c3d7556b 100644 --- a/app/screens/add_reaction/index.js +++ b/app/screens/add_reaction/index.js @@ -1,13 +1,10 @@ // 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {dismissModal, setButtons} from 'app/actions/navigation'; - import AddReaction from './add_reaction'; function mapStateToProps(state) { @@ -16,13 +13,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - dismissModal, - setButtons, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(AddReaction); +export default connect(mapStateToProps)(AddReaction); diff --git a/app/screens/channel/channel.ios.js b/app/screens/channel/channel.ios.js index 6c9c6c5f5..c9ee1a9ae 100644 --- a/app/screens/channel/channel.ios.js +++ b/app/screens/channel/channel.ios.js @@ -13,6 +13,7 @@ import PostTextbox from 'app/components/post_textbox'; import SafeAreaView from 'app/components/safe_area_view'; import StatusBar from 'app/components/status_bar'; import {DeviceTypes} from 'app/constants'; +import {peek} from 'app/actions/navigation'; import LocalConfig from 'assets/config'; @@ -26,10 +27,9 @@ const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange'; export default class ChannelIOS extends ChannelBase { previewChannel = (passProps, options) => { - const {actions} = this.props; const screen = 'ChannelPeek'; - actions.peek(screen, passProps, options); + peek(screen, passProps, options); }; optionalProps = {previewChannel: this.previewChannel}; diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index 076c7aea4..acaca1cf5 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -9,7 +9,6 @@ import { 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'; @@ -25,6 +24,11 @@ import PushNotifications from 'app/push_notifications'; import EphemeralStore from 'app/store/ephemeral_store'; import tracker from 'app/utils/time_tracker'; import telemetry from 'app/telemetry'; +import { + goToScreen, + showModalOverCurrentContext, + mergeNavigationOptions, +} from 'app/actions/navigation'; import LocalConfig from 'assets/config'; @@ -38,9 +42,6 @@ export default class ChannelBase extends PureComponent { selectDefaultTeam: PropTypes.func.isRequired, selectInitialChannel: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, - peek: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, getChannelStats: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string.isRequired, @@ -68,11 +69,12 @@ export default class ChannelBase extends PureComponent { this.postTextbox = React.createRef(); this.keyboardTracker = React.createRef(); - Navigation.mergeOptions(props.componentId, { + const options = { layout: { backgroundColor: props.theme.centerChannelBg, }, - }); + }; + mergeNavigationOptions(props.componentId, options); if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) { ClientUpgradeListener = require('app/components/client_upgrade_listener').default; @@ -113,11 +115,12 @@ export default class ChannelBase extends PureComponent { componentWillReceiveProps(nextProps) { if (this.props.theme !== nextProps.theme) { - Navigation.mergeOptions(this.props.componentId, { + const options = { layout: { backgroundColor: nextProps.theme.centerChannelBg, }, - }); + }; + mergeNavigationOptions(this.props.componentId, options); } if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) { @@ -177,7 +180,7 @@ export default class ChannelBase extends PureComponent { showTermsOfServiceModal = async () => { const {intl} = this.context; - const {actions, theme} = this.props; + const {theme} = this.props; const closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor); const screen = 'TermsOfService'; const passProps = {closeButton}; @@ -196,11 +199,10 @@ export default class ChannelBase extends PureComponent { }, }; - actions.showModalOverCurrentContext(screen, passProps, options); + 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'}); @@ -208,7 +210,7 @@ export default class ChannelBase extends PureComponent { Keyboard.dismiss(); requestAnimationFrame(() => { - actions.goToScreen(screen, title); + goToScreen(screen, title); }); }); 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 554d14f55..c2ac871e1 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 @@ -12,12 +12,12 @@ import AwesomeIcon from 'react-native-vector-icons/FontAwesome'; import {preventDoubleTap} from 'app/utils/tap'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; +import {showSearchModal} from 'app/actions/navigation'; export default class ChannelSearchButton extends PureComponent { static propTypes = { actions: PropTypes.shape({ clearSearch: PropTypes.func.isRequired, - showSearchModal: PropTypes.func.isRequired, }).isRequired, theme: PropTypes.object, }; @@ -27,7 +27,7 @@ export default class ChannelSearchButton extends PureComponent { Keyboard.dismiss(); await actions.clearSearch(); - await actions.showSearchModal(); + 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 8bbb62cfb..920322f7d 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,15 +6,12 @@ import {connect} from 'react-redux'; import {clearSearch} from 'mattermost-redux/actions/search'; -import {showSearchModal} from 'app/actions/navigation'; - import ChannelSearchButton from './channel_search_button'; function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ clearSearch, - showSearchModal, }, dispatch), }; } 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 82fab1e02..40b66c292 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -19,6 +19,7 @@ import {ViewTypes} from 'app/constants'; import tracker from 'app/utils/time_tracker'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import telemetry from 'app/telemetry'; +import {goToScreen} from 'app/actions/navigation'; let ChannelIntro = null; let LoadMorePosts = null; @@ -32,7 +33,6 @@ export default class ChannelPostList extends PureComponent { selectPost: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, refreshChannelWithRetry: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, }).isRequired, channelId: PropTypes.string.isRequired, channelRefreshingFailed: PropTypes.bool, @@ -119,7 +119,7 @@ export default class ChannelPostList extends PureComponent { }; requestAnimationFrame(() => { - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); }; diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js index 1f1d27ceb..b3689b36c 100644 --- a/app/screens/channel/channel_post_list/index.js +++ b/app/screens/channel/channel_post_list/index.js @@ -10,7 +10,6 @@ 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'; import {isLandscape} from 'app/selectors/device'; @@ -45,7 +44,6 @@ function mapDispatchToProps(dispatch) { selectPost, recordLoadTime, refreshChannelWithRetry, - goToScreen, }, dispatch), }; } diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js index cbfd5bc02..228353389 100644 --- a/app/screens/channel/index.js +++ b/app/screens/channel/index.js @@ -20,7 +20,6 @@ import { import {connection} from 'app/actions/device'; import {recordLoadTime} from 'app/actions/views/root'; import {selectDefaultTeam} from 'app/actions/views/select_team'; -import {peek, goToScreen, showModalOverCurrentContext} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import Channel from './channel'; @@ -51,9 +50,6 @@ function mapDispatchToProps(dispatch) { recordLoadTime, 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 d9918ad54..58ec88b2f 100644 --- a/app/screens/channel_add_members/channel_add_members.js +++ b/app/screens/channel_add_members/channel_add_members.js @@ -14,6 +14,7 @@ import {Navigation} from 'react-native-navigation'; import {debounce} from 'mattermost-redux/actions/helpers'; import {General} from 'mattermost-redux/constants'; import {filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils'; + import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import Loading from 'app/components/loading'; import CustomList, {FLATLIST, SECTIONLIST} from 'app/components/custom_list'; @@ -30,6 +31,7 @@ import { setNavigatorStyles, getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; +import {popTopScreen, setButtons} from 'app/actions/navigation'; export default class ChannelAddMembers extends PureComponent { static propTypes = { @@ -38,8 +40,6 @@ 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, @@ -82,7 +82,7 @@ export default class ChannelAddMembers extends PureComponent { showAsAction: 'always', }; - props.actions.setButtons(props.componentId, { + setButtons(props.componentId, { rightButtons: [this.addButton], }); } @@ -121,12 +121,12 @@ export default class ChannelAddMembers extends PureComponent { }; close = () => { - this.props.actions.popTopScreen(); + popTopScreen(); }; enableAddOption = (enabled) => { - const {actions, componentId} = this.props; - actions.setButtons(componentId, { + const {componentId} = this.props; + 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 c5df7b106..8d83487dd 100644 --- a/app/screens/channel_add_members/channel_add_members.test.js +++ b/app/screens/channel_add_members/channel_add_members.test.js @@ -5,6 +5,7 @@ import React from 'react'; import {Preferences} from 'mattermost-redux/constants'; +import * as NavigationActions from 'app/actions/navigation'; import {shallowWithIntl} from 'test/intl-test-helper'; import ChannelAddMembers from './channel_add_members'; @@ -16,8 +17,6 @@ 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', @@ -29,15 +28,17 @@ describe('ChannelAddMembers', () => { }; test('should render without error and call functions on mount', () => { + const setButtons = jest.spyOn(NavigationActions, 'setButtons'); + shallowWithIntl(); expect(baseProps.actions.getTeamStats).toBeCalledTimes(1); expect(baseProps.actions.getTeamStats).toBeCalledWith(baseProps.currentTeamId); 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]}); + expect(setButtons).toBeCalledTimes(2); + expect(setButtons.mock.calls[0][0]).toEqual(baseProps.componentId, {rightButtons: [button]}); + expect(setButtons.mock.calls[1][0]).toEqual(baseProps.componentId, {rightButtons: [button]}); }); test('should match state on clearSearch', () => { @@ -49,11 +50,13 @@ describe('ChannelAddMembers', () => { wrapper.setState({term: '', searchResults: []}); }); - test('should call props.popTopScreen on close', () => { + test('should call popTopScreen on close', () => { + const popTopScreen = jest.spyOn(NavigationActions, 'popTopScreen'); + const wrapper = shallowWithIntl(); wrapper.instance().close(); - expect(baseProps.actions.popTopScreen).toBeCalledTimes(1); + expect(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 431db3cea..8d4582477 100644 --- a/app/screens/channel_add_members/index.js +++ b/app/screens/channel_add_members/index.js @@ -11,7 +11,6 @@ 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 {isLandscape} from 'app/selectors/device'; import ChannelAddMembers from './channel_add_members'; @@ -37,8 +36,6 @@ function mapDispatchToProps(dispatch) { getProfilesNotInChannel, handleAddChannelMembers, searchProfiles, - setButtons, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap index ea562ae80..c2003eb01 100644 --- a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap @@ -29,7 +29,6 @@ exports[`channel_info should match snapshot 1`] = ` isGroupConstrained={false} memberCount={2} onPermalinkPress={[Function]} - popToRoot={[MockFunction]} purpose="Purpose" status="status" theme={ diff --git a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap index 3bb6582f1..1c745ee44 100644 --- a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap @@ -283,6 +283,7 @@ exports[`channel_info_header should match snapshot 1`] = ` }, } } + onChannelLinkPress={[MockFunction]} onPermalinkPress={[MockFunction]} textStyles={ Object { @@ -718,6 +719,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests 1`] = ` }, } } + onChannelLinkPress={[MockFunction]} onPermalinkPress={[MockFunction]} textStyles={ Object { @@ -1153,6 +1155,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = ` }, } } + onChannelLinkPress={[MockFunction]} onPermalinkPress={[MockFunction]} textStyles={ Object { @@ -1560,6 +1563,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`] }, } } + onChannelLinkPress={[MockFunction]} onPermalinkPress={[MockFunction]} textStyles={ Object { @@ -2016,6 +2020,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu }, } } + onChannelLinkPress={[MockFunction]} onPermalinkPress={[MockFunction]} textStyles={ Object { diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index ff9385a0b..f24d61c7f 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -17,6 +17,8 @@ import {preventDoubleTap} from 'app/utils/tap'; import {alertErrorWithFallback} from 'app/utils/general'; import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {goToScreen, popTopScreen, showModalOverCurrentContext} from 'app/actions/navigation'; + import pinIcon from 'assets/images/channel_info/pin.png'; import ChannelInfoHeader from './channel_info_header'; @@ -42,10 +44,6 @@ 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, @@ -122,31 +120,30 @@ export default class ChannelInfo extends PureComponent { actions.setChannelDisplayName(''); } - actions.popTopScreen(); + popTopScreen(); }; goToChannelAddMembers = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'ChannelAddMembers'; const title = intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToChannelMembers = preventDoubleTap(() => { - const {actions, canManageUsers} = this.props; + const {canManageUsers} = this.props; const {intl} = this.context; 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}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToPinnedPosts = preventDoubleTap(() => { - const {actions, currentChannel} = this.props; + const {currentChannel} = this.props; const {formatMessage} = this.context.intl; const id = t('channel_header.pinnedPosts'); const defaultMessage = 'Pinned Posts'; @@ -156,18 +153,17 @@ export default class ChannelInfo extends PureComponent { currentChannelId: currentChannel.id, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); handleChannelEdit = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const id = t('mobile.channel_info.edit'); const defaultMessage = 'Edit Channel'; const screen = 'EditChannel'; const title = intl.formatMessage({id, defaultMessage}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); handleLeave = () => { @@ -379,7 +375,7 @@ export default class ChannelInfo extends PureComponent { actions.selectFocusedPostId(postId); this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); }; renderViewOrManageMembersRow = () => { @@ -564,7 +560,6 @@ export default class ChannelInfo extends PureComponent { theme, isBot, isLandscape, - actions: {popToRoot}, } = this.props; const style = getStyleSheet(theme); @@ -605,7 +600,6 @@ export default class ChannelInfo extends PureComponent { isBot={isBot} hasGuests={currentChannelGuestCount > 0} isGroupConstrained={currentChannel.group_constrained} - popToRoot={popToRoot} /> } diff --git a/app/screens/channel_info/channel_info.test.js b/app/screens/channel_info/channel_info.test.js index 71d0ce1be..3aac0d3dc 100644 --- a/app/screens/channel_info/channel_info.test.js +++ b/app/screens/channel_info/channel_info.test.js @@ -79,11 +79,6 @@ describe('channel_info', () => { selectPenultimateChannel: jest.fn(), setChannelDisplayName: jest.fn(), handleSelectChannel: jest.fn(), - popTopScreen: jest.fn(), - goToScreen: jest.fn(), - popToRoot: jest.fn(), - dismissModal: jest.fn(), - showModalOverCurrentContext: jest.fn(), }, }; diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index cf607b4a6..9389822eb 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -24,6 +24,7 @@ import BottomSheet from 'app/utils/bottom_sheet'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {popToRoot} from 'app/actions/navigation'; export default class ChannelInfoHeader extends React.PureComponent { static propTypes = { @@ -42,7 +43,6 @@ export default class ChannelInfoHeader extends React.PureComponent { hasGuests: PropTypes.bool.isRequired, isGroupConstrained: PropTypes.bool, timeZone: PropTypes.string, - popToRoot: PropTypes.func, }; static contextTypes = { @@ -138,7 +138,6 @@ export default class ChannelInfoHeader extends React.PureComponent { isBot, isGroupConstrained, timeZone, - popToRoot, } = this.props; const style = getStyleSheet(theme); diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js index bbf55994a..03cfd499f 100644 --- a/app/screens/channel_info/index.js +++ b/app/screens/channel_info/index.js @@ -33,13 +33,6 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils'; -import { - popTopScreen, - goToScreen, - popToRoot, - dismissModal, - showModalOverCurrentContext, -} from 'app/actions/navigation'; import { closeDMChannel, closeGMChannel, @@ -148,11 +141,6 @@ function mapDispatchToProps(dispatch) { selectPenultimateChannel, setChannelDisplayName, handleSelectChannel, - popTopScreen, - goToScreen, - popToRoot, - dismissModal, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/channel_members/channel_members.js b/app/screens/channel_members/channel_members.js index 173ec925c..d1a8bad2e 100644 --- a/app/screens/channel_members/channel_members.js +++ b/app/screens/channel_members/channel_members.js @@ -14,6 +14,7 @@ import {Navigation} from 'react-native-navigation'; import {debounce} from 'mattermost-redux/actions/helpers'; import {General} from 'mattermost-redux/constants'; import {filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils'; + import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import Loading from 'app/components/loading'; import CustomList, {FLATLIST, SECTIONLIST} from 'app/components/custom_list'; @@ -30,6 +31,7 @@ import { setNavigatorStyles, getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; +import {popTopScreen, setButtons} from 'app/actions/navigation'; export default class ChannelMembers extends PureComponent { static propTypes = { @@ -37,8 +39,6 @@ export default class ChannelMembers extends PureComponent { getProfilesInChannel: PropTypes.func.isRequired, handleRemoveChannelMembers: PropTypes.func.isRequired, searchProfiles: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, canManageUsers: PropTypes.bool.isRequired, @@ -78,7 +78,7 @@ export default class ChannelMembers extends PureComponent { }; if (props.canManageUsers) { - props.actions.setButtons(props.componentId, { + setButtons(props.componentId, { rightButtons: [this.removeButton], }); } @@ -113,13 +113,13 @@ export default class ChannelMembers extends PureComponent { }; close = () => { - this.props.actions.popTopScreen(); + popTopScreen(); }; enableRemoveOption = (enabled) => { - const {actions, canManageUsers, componentId} = this.props; + const {canManageUsers, componentId} = this.props; if (canManageUsers) { - actions.setButtons(componentId, { + 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 1c4aa2e29..90cbdf641 100644 --- a/app/screens/channel_members/channel_members.test.js +++ b/app/screens/channel_members/channel_members.test.js @@ -19,8 +19,6 @@ describe('ChannelMembers', () => { getProfilesInChannel: jest.fn().mockImplementation(() => Promise.resolve()), handleRemoveChannelMembers: jest.fn(), searchProfiles: jest.fn(), - setButtons: jest.fn(), - popTopScreen: jest.fn(), }, componentId: 'component-id', isLandscape: false, diff --git a/app/screens/channel_members/index.js b/app/screens/channel_members/index.js index 06d383feb..96f6d0362 100644 --- a/app/screens/channel_members/index.js +++ b/app/screens/channel_members/index.js @@ -9,7 +9,6 @@ import {getCurrentChannel, canManageChannelMembers} from 'mattermost-redux/selec 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 {isLandscape} from 'app/selectors/device'; import ChannelMembers from './channel_members'; @@ -43,8 +42,6 @@ function mapDispatchToProps(dispatch) { getProfilesInChannel, handleRemoveChannelMembers, searchProfiles, - setButtons, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/client_upgrade/client_upgrade.js b/app/screens/client_upgrade/client_upgrade.js index c0768a3a5..c4de963f5 100644 --- a/app/screens/client_upgrade/client_upgrade.js +++ b/app/screens/client_upgrade/client_upgrade.js @@ -20,14 +20,13 @@ import {UpgradeTypes} from 'app/constants'; import logo from 'assets/images/logo.png'; import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade'; import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; +import {popTopScreen, dismissModal} from 'app/actions/navigation'; export default class ClientUpgrade extends PureComponent { static propTypes = { actions: PropTypes.shape({ logError: PropTypes.func.isRequired, setLastUpgradeCheck: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, currentVersion: PropTypes.string, @@ -68,7 +67,7 @@ export default class ClientUpgrade extends PureComponent { navigationButtonPressed({buttonId}) { if (buttonId === 'close-upgrade') { - this.props.actions.dismissModal(); + dismissModal(); } } @@ -97,15 +96,14 @@ export default class ClientUpgrade extends PureComponent { const { closeAction, userCheckedForUpgrade, - actions, } = this.props; if (closeAction) { closeAction(); } else if (userCheckedForUpgrade) { - actions.popTopScreen(); + popTopScreen(); } else { - actions.dismissModal(); + dismissModal(); } }; diff --git a/app/screens/client_upgrade/index.js b/app/screens/client_upgrade/index.js index 14f67f586..b0984934c 100644 --- a/app/screens/client_upgrade/index.js +++ b/app/screens/client_upgrade/index.js @@ -5,7 +5,6 @@ import {connect} from 'react-redux'; import {logError} from 'mattermost-redux/actions/errors'; -import {popTopScreen, dismissModal} from 'app/actions/navigation'; import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade'; import getClientUpgrade from 'app/selectors/client_upgrade'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -30,8 +29,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ logError, setLastUpgradeCheck, - popTopScreen, - dismissModal, }, dispatch), }; } diff --git a/app/screens/code/code.js b/app/screens/code/code.js index ef3e5e7b5..0e839adf7 100644 --- a/app/screens/code/code.js +++ b/app/screens/code/code.js @@ -20,12 +20,10 @@ import { setNavigatorStyles, getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; +import {popTopScreen} from 'app/actions/navigation'; export default class Code extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - popTopScreen: PropTypes.func.isRequired, - }).isRequired, componentId: PropTypes.string, theme: PropTypes.object.isRequired, content: PropTypes.string.isRequired, @@ -46,7 +44,7 @@ export default class Code extends React.PureComponent { } handleAndroidBack = () => { - this.props.actions.popTopScreen(); + popTopScreen(); return true; }; diff --git a/app/screens/code/index.js b/app/screens/code/index.js index c833020be..4b0abdcd9 100644 --- a/app/screens/code/index.js +++ b/app/screens/code/index.js @@ -1,13 +1,10 @@ // 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {popTopScreen} from 'app/actions/navigation'; - import Code from './code'; function mapStateToProps(state) { @@ -16,12 +13,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - popTopScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(Code); \ No newline at end of file +export default connect(mapStateToProps)(Code); \ No newline at end of file diff --git a/app/screens/create_channel/create_channel.js b/app/screens/create_channel/create_channel.js index e4aaf99d0..2a55c286d 100644 --- a/app/screens/create_channel/create_channel.js +++ b/app/screens/create_channel/create_channel.js @@ -15,6 +15,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import EditChannelInfo from 'app/components/edit_channel_info'; import {setNavigatorStyles} from 'app/utils/theme'; +import {popTopScreen, dismissModal, setButtons} from 'app/actions/navigation'; export default class CreateChannel extends PureComponent { static propTypes = { @@ -27,9 +28,6 @@ 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, }), }; @@ -116,16 +114,15 @@ export default class CreateChannel extends PureComponent { } close = (goBack = false) => { - const {actions} = this.props; if (goBack) { - actions.popTopScreen(); + popTopScreen(); } else { - actions.dismissModal(); + dismissModal(); } }; emitCanCreateChannel = (enabled) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { rightButtons: [{...this.rightButton, enabled}], }; @@ -134,11 +131,11 @@ export default class CreateChannel extends PureComponent { buttons.leftButtons = [this.left]; } - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; emitCreating = (loading) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { rightButtons: [{...this.rightButton, enabled: !loading}], }; @@ -147,7 +144,7 @@ export default class CreateChannel extends PureComponent { buttons.leftButtons = [this.left]; } - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; onCreateChannel = () => { diff --git a/app/screens/create_channel/index.js b/app/screens/create_channel/index.js index b08d294bf..e3111d7cb 100644 --- a/app/screens/create_channel/index.js +++ b/app/screens/create_channel/index.js @@ -6,11 +6,6 @@ import {connect} from 'react-redux'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import { - setButtons, - dismissModal, - popTopScreen, -} from 'app/actions/navigation'; import {handleCreateChannel} from 'app/actions/views/create_channel'; import {getDimensions} from 'app/selectors/device'; @@ -32,9 +27,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ handleCreateChannel, - setButtons, - dismissModal, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/edit_channel/edit_channel.js b/app/screens/edit_channel/edit_channel.js index 667e0ade1..22aa7f883 100644 --- a/app/screens/edit_channel/edit_channel.js +++ b/app/screens/edit_channel/edit_channel.js @@ -18,6 +18,7 @@ import {ViewTypes} from 'app/constants'; import {setNavigatorStyles} from 'app/utils/theme'; import {cleanUpUrlable} from 'app/utils/url'; import {t} from 'app/utils/i18n'; +import {popTopScreen, setButtons} from 'app/actions/navigation'; const messages = { display_name_required: { @@ -56,8 +57,6 @@ export default class EditChannel extends PureComponent { patchChannel: PropTypes.func.isRequired, getChannel: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, }), componentId: PropTypes.string, theme: PropTypes.object.isRequired, @@ -106,7 +105,7 @@ export default class EditChannel extends PureComponent { rightButtons: [this.rightButton], }; - props.actions.setButtons(props.componentId, buttons); + setButtons(props.componentId, buttons); } componentDidMount() { @@ -163,25 +162,25 @@ export default class EditChannel extends PureComponent { this.props.actions.setChannelDisplayName(this.state.displayName); } - this.props.actions.popTopScreen(); + popTopScreen(); }; emitCanUpdateChannel = (enabled) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { rightButtons: [{...this.rightButton, enabled}], }; - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; emitUpdating = (loading) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { rightButtons: [{...this.rightButton, enabled: !loading}], }; - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; validateDisplayName = (displayName) => { diff --git a/app/screens/edit_channel/index.js b/app/screens/edit_channel/index.js index b8a87fb6f..4a0eb2182 100644 --- a/app/screens/edit_channel/index.js +++ b/app/screens/edit_channel/index.js @@ -9,7 +9,6 @@ import {getCurrentTeamUrl} from 'mattermost-redux/selectors/entities/teams'; import {patchChannel, getChannel} from 'mattermost-redux/actions/channels'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {setButtons, popTopScreen} from 'app/actions/navigation'; import {setChannelDisplayName} from 'app/actions/views/channel'; import {getDimensions} from 'app/selectors/device'; @@ -36,8 +35,6 @@ function mapDispatchToProps(dispatch) { patchChannel, getChannel, setChannelDisplayName, - setButtons, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/edit_post/edit_post.js b/app/screens/edit_post/edit_post.js index acfd7a155..686fb8885 100644 --- a/app/screens/edit_post/edit_post.js +++ b/app/screens/edit_post/edit_post.js @@ -9,10 +9,13 @@ import { } from 'react-native'; import {Navigation} from 'react-native-navigation'; +import {RequestStatus} from 'mattermost-redux/constants'; + import ErrorText from 'app/components/error_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 {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import { changeOpacity, makeStyleSheetFromTheme, @@ -20,16 +23,12 @@ import { getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; import {t} from 'app/utils/i18n'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; - -import {RequestStatus} from 'mattermost-redux/constants'; +import {dismissModal, setButtons} from 'app/actions/navigation'; export default class EditPost extends PureComponent { static propTypes = { actions: PropTypes.shape({ editPost: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }), componentId: PropTypes.string, closeButton: PropTypes.object, @@ -61,7 +60,7 @@ export default class EditPost extends PureComponent { this.rightButton.color = props.theme.sidebarHeaderTextColor; this.rightButton.text = context.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'}); - props.actions.setButtons(props.componentId, { + setButtons(props.componentId, { leftButtons: [{...this.leftButton, icon: props.closeButton}], rightButtons: [this.rightButton], }); @@ -111,20 +110,20 @@ export default class EditPost extends PureComponent { } close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; emitCanEditPost = (enabled) => { - const {actions, componentId} = this.props; - actions.setButtons(componentId, { + const {componentId} = this.props; + setButtons(componentId, { leftButtons: [{...this.leftButton, icon: this.props.closeButton}], rightButtons: [{...this.rightButton, enabled}], }); }; emitEditing = (loading) => { - const {actions, componentId} = this.props; - actions.setButtons(componentId, { + const {componentId} = this.props; + setButtons(componentId, { leftButtons: [{...this.leftButton, icon: this.props.closeButton}], rightButtons: [{...this.rightButton, enabled: !loading}], }); diff --git a/app/screens/edit_post/index.js b/app/screens/edit_post/index.js index 7a14d69e0..877c089ba 100644 --- a/app/screens/edit_post/index.js +++ b/app/screens/edit_post/index.js @@ -7,8 +7,6 @@ import {connect} from 'react-redux'; import {editPost} from 'mattermost-redux/actions/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {setButtons, dismissModal} from 'app/actions/navigation'; - import {getDimensions, isLandscape} from 'app/selectors/device'; import EditPost from './edit_post'; @@ -29,8 +27,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ editPost, - setButtons, - dismissModal, }, dispatch), }; } diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index 056a59075..cfd96e528 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -9,7 +9,9 @@ import RNFetchBlob from 'rn-fetch-blob'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {DocumentPickerUtil} from 'react-native-document-picker'; import {Navigation} from 'react-native-navigation'; + import {Client4} from 'mattermost-redux/client'; +import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils'; import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; @@ -24,8 +26,7 @@ import StatusBar from 'app/components/status_bar/index'; import ProfilePictureButton from 'app/components/profile_picture_button'; import ProfilePicture from 'app/components/profile_picture'; import mattermostBucket from 'app/mattermost_bucket'; - -import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils'; +import {popTopScreen, dismissModal, setButtons} from 'app/actions/navigation'; const MAX_SIZE = 20 * 1024 * 1024; export const VALID_MIME_TYPES = [ @@ -87,9 +88,6 @@ export default class EditProfile extends PureComponent { setProfileImageUri: PropTypes.func.isRequired, removeProfileImage: PropTypes.func.isRequired, updateUser: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, currentUser: PropTypes.object.isRequired, @@ -122,7 +120,7 @@ export default class EditProfile extends PureComponent { this.rightButton.color = props.theme.sidebarHeaderTextColor; this.rightButton.text = context.intl.formatMessage({id: t('mobile.account.settings.save'), defaultMessage: 'Save'}); - props.actions.setButtons(props.componentId, buttons); + setButtons(props.componentId, buttons); this.state = { email, @@ -180,21 +178,21 @@ export default class EditProfile extends PureComponent { }; close = () => { - const {commandType, actions} = this.props; + const {commandType} = this.props; if (commandType === 'Push') { - actions.popTopScreen(); + popTopScreen(); } else { - actions.dismissModal(); + dismissModal(); } }; emitCanUpdateAccount = (enabled) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { rightButtons: [{...this.rightButton, enabled}], }; - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; handleRequestError = (error) => { diff --git a/app/screens/edit_profile/edit_profile.test.js b/app/screens/edit_profile/edit_profile.test.js index 681980db0..726cd2f61 100644 --- a/app/screens/edit_profile/edit_profile.test.js +++ b/app/screens/edit_profile/edit_profile.test.js @@ -21,9 +21,6 @@ describe('edit_profile', () => { updateUser: jest.fn(), setProfileImageUri: jest.fn(), removeProfileImage: jest.fn(), - popTopScreen: jest.fn(), - dismissModal: jest.fn(), - setButtons: jest.fn(), }; const baseProps = { diff --git a/app/screens/edit_profile/index.js b/app/screens/edit_profile/index.js index 70f3db182..420166ec5 100644 --- a/app/screens/edit_profile/index.js +++ b/app/screens/edit_profile/index.js @@ -8,7 +8,6 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; import {isLandscape} from 'app/selectors/device'; -import {popTopScreen, dismissModal, setButtons} from 'app/actions/navigation'; import {setProfileImageUri, removeProfileImage, updateUser} from 'app/actions/views/edit_profile'; import EditProfile from './edit_profile'; @@ -51,9 +50,6 @@ function mapDispatchToProps(dispatch) { setProfileImageUri, removeProfileImage, updateUser, - popTopScreen, - dismissModal, - setButtons, }, dispatch), }; } diff --git a/app/screens/error_teams_list/error_teams_list.js b/app/screens/error_teams_list/error_teams_list.js index defc11faa..43267140d 100644 --- a/app/screens/error_teams_list/error_teams_list.js +++ b/app/screens/error_teams_list/error_teams_list.js @@ -12,6 +12,7 @@ import {Navigation} from 'react-native-navigation'; import FailedNetworkAction from 'app/components/failed_network_action'; import Loading from 'app/components/loading'; import StatusBar from 'app/components/status_bar'; +import {resetToChannel} from 'app/actions/navigation'; import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; export default class ErrorTeamsList extends PureComponent { @@ -21,7 +22,6 @@ export default class ErrorTeamsList extends PureComponent { connection: PropTypes.func.isRequired, logout: PropTypes.func.isRequired, selectDefaultTeam: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, theme: PropTypes.object, @@ -56,7 +56,7 @@ export default class ErrorTeamsList extends PureComponent { const passProps = { disableTermsModal: true, }; - this.props.actions.resetToChannel(passProps); + resetToChannel(passProps); }; getUserInfo = async () => { diff --git a/app/screens/error_teams_list/error_teams_list.test.js b/app/screens/error_teams_list/error_teams_list.test.js index 229ec1f42..3f36a4736 100644 --- a/app/screens/error_teams_list/error_teams_list.test.js +++ b/app/screens/error_teams_list/error_teams_list.test.js @@ -22,7 +22,6 @@ describe('ErrorTeamsList', () => { connection: () => {}, // eslint-disable-line no-empty-function logout: () => {}, // eslint-disable-line no-empty-function selectDefaultTeam: () => {}, // eslint-disable-line no-empty-function - resetToChannel: jest.fn(), }, componentId: 'component-id', theme: Preferences.THEMES.default, diff --git a/app/screens/error_teams_list/index.js b/app/screens/error_teams_list/index.js index 981e62733..31424ed03 100644 --- a/app/screens/error_teams_list/index.js +++ b/app/screens/error_teams_list/index.js @@ -8,8 +8,6 @@ import {logout, loadMe} from 'mattermost-redux/actions/users'; import {connection} from 'app/actions/device'; import {selectDefaultTeam} from 'app/actions/views/select_team'; -import {resetToChannel} from 'app/actions/navigation'; - import ErrorTeamsList from './error_teams_list.js'; function mapDispatchToProps(dispatch) { @@ -19,7 +17,6 @@ function mapDispatchToProps(dispatch) { selectDefaultTeam, connection, loadMe, - resetToChannel, }, dispatch), }; } diff --git a/app/screens/expanded_announcement_banner/expanded_announcement_banner.js b/app/screens/expanded_announcement_banner/expanded_announcement_banner.js index 0b10ea863..54ea66cfc 100644 --- a/app/screens/expanded_announcement_banner/expanded_announcement_banner.js +++ b/app/screens/expanded_announcement_banner/expanded_announcement_banner.js @@ -5,17 +5,18 @@ import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView, View} from 'react-native'; import Button from 'react-native-button'; + import FormattedText from 'app/components/formatted_text'; import Markdown from 'app/components/markdown'; import SafeAreaView from 'app/components/safe_area_view'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {popTopScreen} from 'app/actions/navigation'; export default class ExpandedAnnouncementBanner extends React.PureComponent { static propTypes = { actions: PropTypes.shape({ dismissBanner: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, }).isRequired, allowDismissal: PropTypes.bool.isRequired, bannerText: PropTypes.string.isRequired, @@ -23,7 +24,7 @@ export default class ExpandedAnnouncementBanner extends React.PureComponent { } close = () => { - this.props.actions.popTopScreen(); + popTopScreen(); }; dismissBanner = () => { diff --git a/app/screens/expanded_announcement_banner/index.js b/app/screens/expanded_announcement_banner/index.js index 64c8b17d0..83837c9c3 100644 --- a/app/screens/expanded_announcement_banner/index.js +++ b/app/screens/expanded_announcement_banner/index.js @@ -7,7 +7,6 @@ import {connect} from 'react-redux'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {popTopScreen} from 'app/actions/navigation'; import {dismissBanner} from 'app/actions/views/announcement'; import ExpandedAnnouncementBanner from './expanded_announcement_banner'; @@ -26,7 +25,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ dismissBanner, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/flagged_posts/flagged_posts.js b/app/screens/flagged_posts/flagged_posts.js index e5d757006..b648ecb86 100644 --- a/app/screens/flagged_posts/flagged_posts.js +++ b/app/screens/flagged_posts/flagged_posts.js @@ -24,6 +24,12 @@ import mattermostManaged from 'app/mattermost_managed'; import SearchResultPost from 'app/screens/search/search_result_post'; import ChannelDisplayName from 'app/screens/search/channel_display_name'; import {changeOpacity} from 'app/utils/theme'; +import { + goToScreen, + showModalOverCurrentContext, + showSearchModal, + dismissModal, +} from 'app/actions/navigation'; export default class FlaggedPosts extends PureComponent { static propTypes = { @@ -34,9 +40,6 @@ export default class FlaggedPosts extends PureComponent { getFlaggedPosts: PropTypes.func.isRequired, selectFocusedPostId: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, - showSearchModal: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, didFail: PropTypes.bool, isLoading: PropTypes.bool, @@ -65,7 +68,7 @@ export default class FlaggedPosts extends PureComponent { navigationButtonPressed({buttonId}) { if (buttonId === 'close-settings') { - this.props.actions.dismissModal(); + dismissModal(); } } @@ -83,7 +86,7 @@ export default class FlaggedPosts extends PureComponent { Keyboard.dismiss(); actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; handleClosePermalink = () => { @@ -98,11 +101,8 @@ export default class FlaggedPosts extends PureComponent { }; handleHashtagPress = async (hashtag) => { - const {actions} = this.props; - - await actions.dismissModal(); - - actions.showSearchModal('#' + hashtag); + dismissModal(); + showSearchModal('#' + hashtag); }; keyExtractor = (item) => item; @@ -186,7 +186,7 @@ export default class FlaggedPosts extends PureComponent { }; this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); } }; diff --git a/app/screens/flagged_posts/index.js b/app/screens/flagged_posts/index.js index d48a92c54..57a9337f2 100644 --- a/app/screens/flagged_posts/index.js +++ b/app/screens/flagged_posts/index.js @@ -9,12 +9,6 @@ import {clearSearch, getFlaggedPosts} from 'mattermost-redux/actions/search'; import {RequestStatus} from 'mattermost-redux/constants'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import { - dismissModal, - goToScreen, - showSearchModal, - showModalOverCurrentContext, -} from 'app/actions/navigation'; import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel'; import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list'; @@ -46,10 +40,6 @@ function mapDispatchToProps(dispatch) { getFlaggedPosts, selectFocusedPostId, selectPost, - showSearchModal, - dismissModal, - goToScreen, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index 1d5d1a2e2..ba719c327 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -22,7 +22,6 @@ import {intlShape} from 'react-intl'; import Permissions from 'react-native-permissions'; import Gallery from 'react-native-image-gallery'; import DeviceInfo from 'react-native-device-info'; -import {Navigation} from 'react-native-navigation'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -33,6 +32,11 @@ import {getLocalFilePathFromFile, isDocument, isVideo} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; import {calculateDimensions} from 'app/utils/images'; import {t} from 'app/utils/i18n'; +import { + showModalOverCurrentContext, + dismissModal, + mergeNavigationOptions, +} from 'app/actions/navigation'; import Downloader from './downloader'; import VideoPreview from './video_preview'; @@ -45,10 +49,6 @@ const ANIM_CONFIG = {duration: 300}; export default class ImagePreview extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - dismissModal: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, - }).isRequired, componentId: PropTypes.string.isRequired, canDownloadFiles: PropTypes.bool.isRequired, deviceHeight: PropTypes.number.isRequired, @@ -72,11 +72,12 @@ export default class ImagePreview extends PureComponent { constructor(props) { super(props); - Navigation.mergeOptions(props.componentId, { + const options = { layout: { backgroundColor: '#000', }, - }); + }; + mergeNavigationOptions(props.componentId, options); this.openAnim = new Animated.Value(0); this.headerFooterAnim = new Animated.Value(1); @@ -111,15 +112,16 @@ export default class ImagePreview extends PureComponent { }; close = () => { - const {actions, getItemMeasures, componentId} = this.props; + const {getItemMeasures, componentId} = this.props; const {index} = this.state; this.setState({animating: true}); - Navigation.mergeOptions(componentId, { + const options = { layout: { backgroundColor: 'transparent', }, - }); + }; + mergeNavigationOptions(componentId, options); getItemMeasures(index, (origin) => { if (origin) { @@ -127,7 +129,7 @@ export default class ImagePreview extends PureComponent { } this.animateOpenAnimToValue(0, () => { - actions.dismissModal(); + dismissModal(); }); }); }; @@ -455,7 +457,6 @@ export default class ImagePreview extends PureComponent { }; showDownloadOptionsIOS = async () => { - const {actions} = this.props; const {formatMessage} = this.context.intl; const file = this.getCurrentFile(); const items = []; @@ -533,7 +534,7 @@ export default class ImagePreview extends PureComponent { onCancelPress: () => this.setHeaderAndFooterVisible(true), }; - actions.showModalOverCurrentContext(screen, passProps); + showModalOverCurrentContext(screen, passProps); } }; diff --git a/app/screens/image_preview/image_preview.test.js b/app/screens/image_preview/image_preview.test.js index 90ac21ef9..b02aa3352 100644 --- a/app/screens/image_preview/image_preview.test.js +++ b/app/screens/image_preview/image_preview.test.js @@ -6,10 +6,11 @@ import {shallow} from 'enzyme'; import { TouchableOpacity, } from 'react-native'; -import {Navigation} from 'react-native-navigation'; import Preferences from 'mattermost-redux/constants/preferences'; +import * as NavigationActions from 'app/actions/navigation'; + import ImagePreview from './image_preview'; jest.useFakeTimers(); @@ -19,13 +20,6 @@ jest.mock('react-native-doc-viewer', () => { OpenFile: jest.fn(), }; }); -jest.mock('react-native-navigation', () => ({ - Navigation: { - mergeOptions: jest.fn(), - }, -})); - -Navigation.mergeOptions = jest.fn(); describe('ImagePreview', () => { const baseProps = { @@ -42,10 +36,6 @@ describe('ImagePreview', () => { target: {}, theme: Preferences.THEMES.default, componentId: 'component-id', - actions: { - dismissModal: jest.fn(), - showModalOverCurrentContext: jest.fn(), - }, }; test('should match snapshot', () => { @@ -94,7 +84,9 @@ describe('ImagePreview', () => { expect(wrapper.state('index')).toEqual(1); }); - test('should match call getItemMeasures & Navigation.mergeOptions on close', () => { + test('should match call getItemMeasures & mergeNavigationOptions on close', () => { + const mergeNavigationOptions = jest.spyOn(NavigationActions, 'mergeNavigationOptions'); + const getItemMeasures = jest.fn(); const wrapper = shallow( { wrapper.instance().close(); - expect(Navigation.mergeOptions).toHaveBeenCalledTimes(2); - expect(Navigation.mergeOptions).toHaveBeenCalledWith( + expect(mergeNavigationOptions).toHaveBeenCalledTimes(2); + expect(mergeNavigationOptions).toHaveBeenCalledWith( baseProps.componentId, { layout: { diff --git a/app/screens/image_preview/index.js b/app/screens/image_preview/index.js index e72f70bff..c526f3475 100644 --- a/app/screens/image_preview/index.js +++ b/app/screens/image_preview/index.js @@ -1,13 +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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {canDownloadFilesOnMobile} from 'mattermost-redux/selectors/entities/general'; -import {dismissModal, showModalOverCurrentContext} from 'app/actions/navigation'; import {getDimensions} from 'app/selectors/device'; import ImagePreview from './image_preview'; @@ -20,13 +18,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - dismissModal, - showModalOverCurrentContext, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(ImagePreview); +export default connect(mapStateToProps)(ImagePreview); diff --git a/app/screens/interactive_dialog/index.js b/app/screens/interactive_dialog/index.js index 6b4fa1672..9c8bf17a7 100644 --- a/app/screens/interactive_dialog/index.js +++ b/app/screens/interactive_dialog/index.js @@ -7,8 +7,6 @@ import {connect} from 'react-redux'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {submitInteractiveDialog} from 'mattermost-redux/actions/integrations'; -import {dismissModal} from 'app/actions/navigation'; - import InteractiveDialog from './interactive_dialog'; function mapStateToProps(state) { @@ -32,7 +30,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ submitInteractiveDialog, - dismissModal, }, dispatch), }; } diff --git a/app/screens/interactive_dialog/interactive_dialog.js b/app/screens/interactive_dialog/interactive_dialog.js index 8ac992323..b00c23dda 100644 --- a/app/screens/interactive_dialog/interactive_dialog.js +++ b/app/screens/interactive_dialog/interactive_dialog.js @@ -8,11 +8,12 @@ import {Navigation} from 'react-native-navigation'; import {checkDialogElementForError, checkIfErrorsMatchElements} from 'mattermost-redux/utils/integration_utils'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - import StatusBar from 'app/components/status_bar'; import FormattedText from 'app/components/formatted_text'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {dismissModal} from 'app/actions/navigation'; + import DialogElement from './dialog_element.js'; import DialogIntroductionText from './dialog_introduction_text.js'; @@ -27,7 +28,6 @@ export default class InteractiveDialog extends PureComponent { theme: PropTypes.object, actions: PropTypes.shape({ submitInteractiveDialog: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, }; @@ -158,7 +158,7 @@ export default class InteractiveDialog extends PureComponent { } handleHide = () => { - this.props.actions.dismissModal(); + dismissModal(); } onChange = (name, value) => { diff --git a/app/screens/interactive_dialog/interactive_dialog.test.js b/app/screens/interactive_dialog/interactive_dialog.test.js index c4a6085c2..dafcd2ec3 100644 --- a/app/screens/interactive_dialog/interactive_dialog.test.js +++ b/app/screens/interactive_dialog/interactive_dialog.test.js @@ -19,7 +19,6 @@ describe('InteractiveDialog', () => { theme: Preferences.THEMES.default, actions: { submitInteractiveDialog: jest.fn(), - dismissModal: jest.fn(), }, componentId: 'component-id', }; diff --git a/app/screens/login/index.js b/app/screens/login/index.js index 077d7744b..8b1165225 100644 --- a/app/screens/login/index.js +++ b/app/screens/login/index.js @@ -8,7 +8,6 @@ import {login} from 'mattermost-redux/actions/users'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {isLandscape} from 'app/selectors/device'; -import {resetToChannel, goToScreen} from 'app/actions/navigation'; import LoginActions from 'app/actions/views/login'; import Login from './login.js'; @@ -32,8 +31,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ ...LoginActions, login, - resetToChannel, - goToScreen, }, dispatch), }; } diff --git a/app/screens/login/login.js b/app/screens/login/login.js index 0ab5e4386..2b009d6e0 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -20,16 +20,18 @@ import Button from 'react-native-button'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {RequestStatus} from 'mattermost-redux/constants'; + import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; -import {GlobalStyles} from 'app/styles'; +import {resetToChannel, goToScreen} from 'app/actions/navigation'; import {preventDoubleTap} from 'app/utils/tap'; import tracker from 'app/utils/time_tracker'; import {t} from 'app/utils/i18n'; import {setMfaPreflightDone, getMfaPreflightDone} from 'app/utils/security'; import {changeOpacity} from 'app/utils/theme'; +import {GlobalStyles} from 'app/styles'; import telemetry from 'app/telemetry'; @@ -43,8 +45,6 @@ export default class Login extends PureComponent { handleSuccessfulLogin: PropTypes.func.isRequired, scheduleExpiredNotification: PropTypes.func.isRequired, login: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, }).isRequired, theme: PropTypes.object, config: PropTypes.object.isRequired, @@ -92,16 +92,15 @@ export default class Login extends PureComponent { this.scheduleSessionExpiredNotification(); - this.props.actions.resetToChannel(); + resetToChannel(); }; goToMfa = () => { - const {actions} = this.props; const {intl} = this.context; const screen = 'MFA'; const title = intl.formatMessage({id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }; blur = () => { @@ -288,12 +287,11 @@ export default class Login extends PureComponent { }; forgotPassword = () => { - const {actions} = this.props; const {intl} = this.context; const screen = 'ForgotPassword'; const title = intl.formatMessage({id: 'password_form.title', defaultMessage: 'Password Reset'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); } render() { diff --git a/app/screens/login/login.test.js b/app/screens/login/login.test.js index fda355286..c99ac6b2f 100644 --- a/app/screens/login/login.test.js +++ b/app/screens/login/login.test.js @@ -9,6 +9,8 @@ import FormattedText from 'app/components/formatted_text'; import {shallowWithIntl} from 'test/intl-test-helper'; +import * as NavigationActions from 'app/actions/navigation'; + import {mfaExpectedErrors} from 'app/screens/login/login'; import Login from './login'; @@ -30,8 +32,6 @@ describe('Login', () => { handleSuccessfulLogin: jest.fn(), scheduleExpiredNotification: jest.fn(), login: jest.fn(), - resetToChannel: jest.fn(), - goToScreen: jest.fn(), }, isLandscape: false, }; @@ -80,6 +80,8 @@ describe('Login', () => { }); test('should send the user to the login screen after login', (done) => { + const resetToChannel = jest.spyOn(NavigationActions, 'resetToChannel').mockImplementation(() => done()); + let props = { ...baseProps, loginRequest: { @@ -88,13 +90,10 @@ describe('Login', () => { }; props.actions.handleSuccessfulLogin.mockImplementation(() => Promise.resolve()); - props.actions.resetToChannel.mockImplementation(() => { - done(); - }); const wrapper = shallowWithIntl(); - expect(props.actions.resetToChannel).not.toHaveBeenCalled(); + expect(resetToChannel).not.toHaveBeenCalled(); props = { ...props, @@ -104,7 +103,7 @@ describe('Login', () => { }; wrapper.setProps(props); - expect(props.actions.resetToChannel).not.toHaveBeenCalled(); + expect(resetToChannel).not.toHaveBeenCalled(); props = { ...props, @@ -118,6 +117,8 @@ describe('Login', () => { }); test('should go to MFA screen when login response returns MFA error', () => { + const goToScreen = jest.spyOn(NavigationActions, 'goToScreen'); + const mfaError = { error: { server_error_id: mfaExpectedErrors[0], @@ -127,7 +128,7 @@ describe('Login', () => { const wrapper = shallowWithIntl(); wrapper.instance().checkLoginResponse(mfaError); - expect(baseProps.actions.goToScreen). + expect(goToScreen). toHaveBeenCalledWith( 'MFA', 'Multi-factor Authentication', @@ -135,10 +136,12 @@ describe('Login', () => { }); test('should go to ForgotPassword screen when forgotPassword is called', () => { + const goToScreen = jest.spyOn(NavigationActions, 'goToScreen'); + const wrapper = shallowWithIntl(); wrapper.instance().forgotPassword(); - expect(baseProps.actions.goToScreen). + expect(goToScreen). toHaveBeenCalledWith( 'ForgotPassword', 'Password Reset', diff --git a/app/screens/login_options/index.js b/app/screens/login_options/index.js index d7ac6975b..f42ee03b5 100644 --- a/app/screens/login_options/index.js +++ b/app/screens/login_options/index.js @@ -1,13 +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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {goToScreen} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import LoginOptions from './login_options'; @@ -22,12 +20,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(LoginOptions); +export default connect(mapStateToProps)(LoginOptions); diff --git a/app/screens/login_options/login_options.js b/app/screens/login_options/login_options.js index ca078eb43..b76b849ac 100644 --- a/app/screens/login_options/login_options.js +++ b/app/screens/login_options/login_options.js @@ -13,22 +13,20 @@ import { } from 'react-native'; import Button from 'react-native-button'; -import {ViewTypes} from 'app/constants'; import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {GlobalStyles} from 'app/styles'; import {preventDoubleTap} from 'app/utils/tap'; +import {ViewTypes} from 'app/constants'; +import {goToScreen} from 'app/actions/navigation'; import LocalConfig from 'assets/config'; import gitlab from 'assets/images/gitlab.png'; import logo from 'assets/images/logo.png'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; export default class LoginOptions extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, config: PropTypes.object.isRequired, license: PropTypes.object.isRequired, isLandscape: PropTypes.bool.isRequired, @@ -47,21 +45,19 @@ export default class LoginOptions extends PureComponent { } goToLogin = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'Login'; const title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToSSO = (ssoType) => { - const {actions} = this.props; const {intl} = this.context; const screen = 'SSO'; const title = intl.formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'}); - actions.goToScreen(screen, title, {ssoType}); + goToScreen(screen, title, {ssoType}); }; orientationDidChange = () => { diff --git a/app/screens/long_post/index.js b/app/screens/long_post/index.js index 03ea3f150..6113c3e09 100644 --- a/app/screens/long_post/index.js +++ b/app/screens/long_post/index.js @@ -9,7 +9,6 @@ import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {getPost, makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {dismissModal, goToScreen} from 'app/actions/navigation'; import {loadThreadIfNecessary} from 'app/actions/views/channel'; import {isLandscape} from 'app/selectors/device'; import LongPost from './long_post'; @@ -39,8 +38,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ loadThreadIfNecessary, selectPost, - dismissModal, - goToScreen, }, dispatch), }; } diff --git a/app/screens/long_post/long_post.js b/app/screens/long_post/long_post.js index 9ef136f1c..ccc427a14 100644 --- a/app/screens/long_post/long_post.js +++ b/app/screens/long_post/long_post.js @@ -18,10 +18,11 @@ import FormattedText from 'app/components/formatted_text'; import Post from 'app/components/post'; import Reactions from 'app/components/reactions'; import SafeAreaView from 'app/components/safe_area_view'; +import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing'; import {emptyFunction} from 'app/utils/general'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing'; +import {goToScreen, dismissModal} from 'app/actions/navigation'; Animatable.initializeRegistryWithDefinitions({ growOut: { @@ -45,8 +46,6 @@ export default class LongPost extends PureComponent { actions: PropTypes.shape({ loadThreadIfNecessary: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, }).isRequired, channelName: PropTypes.string, fileIds: PropTypes.array, @@ -93,14 +92,13 @@ export default class LongPost extends PureComponent { actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); handleClose = () => { - const {actions} = this.props; if (this.refs.view) { this.refs.view.zoomOut().then(() => { - actions.dismissModal(); + dismissModal(); }); } }; diff --git a/app/screens/mfa/index.js b/app/screens/mfa/index.js index 4dea96eb2..4707c0bd2 100644 --- a/app/screens/mfa/index.js +++ b/app/screens/mfa/index.js @@ -6,8 +6,6 @@ import {connect} from 'react-redux'; import {login} from 'mattermost-redux/actions/users'; -import {popTopScreen} from 'app/actions/navigation'; - import Mfa from './mfa'; function mapStateToProps(state) { @@ -24,7 +22,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ login, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/mfa/mfa.js b/app/screens/mfa/mfa.js index f530f783b..f5a3cbdea 100644 --- a/app/screens/mfa/mfa.js +++ b/app/screens/mfa/mfa.js @@ -25,12 +25,12 @@ import {GlobalStyles} from 'app/styles'; import {preventDoubleTap} from 'app/utils/tap'; import {t} from 'app/utils/i18n'; import {setMfaPreflightDone} from 'app/utils/security'; +import {popTopScreen} from 'app/actions/navigation'; export default class Mfa extends PureComponent { static propTypes = { actions: PropTypes.shape({ login: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, }).isRequired, loginId: PropTypes.string.isRequired, password: PropTypes.string.isRequired, @@ -56,7 +56,7 @@ export default class Mfa extends PureComponent { // In case the login is successful the previous scene (login) will take care of the transition if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.FAILURE) { - this.props.actions.popTopScreen(); + popTopScreen(); } } diff --git a/app/screens/more_channels/index.js b/app/screens/more_channels/index.js index 2edac7278..9859af2ed 100644 --- a/app/screens/more_channels/index.js +++ b/app/screens/more_channels/index.js @@ -15,7 +15,6 @@ import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; 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'; @@ -55,9 +54,6 @@ 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 e8a0b4525..8574541cf 100644 --- a/app/screens/more_channels/more_channels.js +++ b/app/screens/more_channels/more_channels.js @@ -10,6 +10,7 @@ import {Navigation} from 'react-native-navigation'; import {debounce} from 'mattermost-redux/actions/helpers'; import {General} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; + import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import CustomList from 'app/components/custom_list'; import ChannelListRow from 'app/components/custom_list/channel_list_row'; @@ -19,6 +20,7 @@ import Loading from 'app/components/loading'; import SearchBar from 'app/components/search_bar'; import StatusBar from 'app/components/status_bar'; import {alertErrorWithFallback} from 'app/utils/general'; +import {goToScreen, dismissModal, setButtons} from 'app/actions/navigation'; import { changeOpacity, makeStyleSheetFromTheme, @@ -34,9 +36,6 @@ 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, @@ -136,7 +135,7 @@ export default class MoreChannels extends PureComponent { }; close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; doGetChannels = () => { @@ -164,7 +163,7 @@ export default class MoreChannels extends PureComponent { getChannels = debounce(this.doGetChannels, 100); setHeaderButtons = (createEnabled) => { - const {actions, canCreateChannels, componentId} = this.props; + const {canCreateChannels, componentId} = this.props; const buttons = { leftButtons: [this.leftButton], }; @@ -173,7 +172,7 @@ export default class MoreChannels extends PureComponent { buttons.rightButtons = [{...this.rightButton, enabled: createEnabled}]; } - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; loadedChannels = ({data}) => { @@ -228,7 +227,6 @@ export default class MoreChannels extends PureComponent { }; onCreateChannel = () => { - const {actions} = this.props; const {formatMessage} = this.context.intl; const screen = 'CreateChannel'; @@ -237,7 +235,7 @@ export default class MoreChannels extends PureComponent { channelType: General.OPEN_CHANNEL, }; - actions.goToScreen(screen, title, passProps); + 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 52086ada5..c2ebd9628 100644 --- a/app/screens/more_channels/more_channels.test.js +++ b/app/screens/more_channels/more_channels.test.js @@ -6,6 +6,8 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; +import * as NavigationActions from 'app/actions/navigation'; + import MoreChannels from './more_channels.js'; jest.mock('react-intl'); @@ -17,9 +19,6 @@ describe('MoreChannels', () => { 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 = { @@ -43,25 +42,29 @@ describe('MoreChannels', () => { expect(wrapper.getElement()).toMatchSnapshot(); }); - test('should call props.actions.dismissModal on close', () => { + test('should call dismissModal on close', () => { + const dismissModal = jest.spyOn(NavigationActions, 'dismissModal'); + const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, ); wrapper.instance().close(); - expect(baseProps.actions.dismissModal).toHaveBeenCalledTimes(1); + expect(dismissModal).toHaveBeenCalledTimes(1); }); - test('should call props.actions.setButtons on setHeaderButtons', () => { + test('should call setButtons on setHeaderButtons', () => { + const setButtons = jest.spyOn(NavigationActions, 'setButtons'); + const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, ); - expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(1); + expect(setButtons).toHaveBeenCalledTimes(1); wrapper.instance().setHeaderButtons(true); - expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(2); + expect(setButtons).toHaveBeenCalledTimes(2); }); test('should match return value of filterChannels', () => { diff --git a/app/screens/more_dms/index.js b/app/screens/more_dms/index.js index dc0f17866..d1a7760d9 100644 --- a/app/screens/more_dms/index.js +++ b/app/screens/more_dms/index.js @@ -14,8 +14,6 @@ import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selector import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUserId, getUsers} from 'mattermost-redux/selectors/entities/users'; -import {dismissModal, setButtons} from 'app/actions/navigation'; - import MoreDirectMessages from './more_dms'; function mapStateToProps(state) { @@ -43,8 +41,6 @@ function mapDispatchToProps(dispatch) { getProfilesInTeam, searchProfiles, setChannelDisplayName, - dismissModal, - setButtons, }, dispatch), }; } diff --git a/app/screens/more_dms/more_dms.js b/app/screens/more_dms/more_dms.js index 26c50f90a..c7489bfc1 100644 --- a/app/screens/more_dms/more_dms.js +++ b/app/screens/more_dms/more_dms.js @@ -12,8 +12,8 @@ import {General} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {getGroupDisplayNameFromUserIds} from 'mattermost-redux/utils/channel_utils'; import {displayUsername, filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import CustomList, {FLATLIST, SECTIONLIST} from 'app/components/custom_list'; import UserListRow from 'app/components/custom_list/user_list_row'; import FormattedText from 'app/components/formatted_text'; @@ -30,6 +30,7 @@ import { getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {dismissModal, setButtons} from 'app/actions/navigation'; import SelectedUsers from './selected_users'; @@ -45,8 +46,6 @@ export default class MoreDirectMessages extends PureComponent { getProfilesInTeam: PropTypes.func.isRequired, searchProfiles: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, allProfiles: PropTypes.object.isRequired, @@ -115,7 +114,7 @@ export default class MoreDirectMessages extends PureComponent { } close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; clearSearch = () => { @@ -332,9 +331,9 @@ export default class MoreDirectMessages extends PureComponent { }; updateNavigationButtons = (startEnabled, context = this.context) => { - const {actions, componentId, theme} = this.props; + const {componentId, theme} = this.props; const {formatMessage} = context.intl; - actions.setButtons(componentId, { + setButtons(componentId, { rightButtons: [{ color: theme.sidebarHeaderTextColor, id: START_BUTTON, diff --git a/app/screens/notification/index.js b/app/screens/notification/index.js index 47d22b173..7674cf74a 100644 --- a/app/screens/notification/index.js +++ b/app/screens/notification/index.js @@ -12,8 +12,6 @@ import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selector import {getUser} from 'mattermost-redux/selectors/entities/users'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import {dismissOverlay, dismissAllModals, popToRoot} from 'app/actions/navigation'; - import Notification from './notification'; function mapStateToProps(state, ownProps) { @@ -44,9 +42,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ loadFromPushNotification, - dismissOverlay, - dismissAllModals, - popToRoot, }, dispatch), }; } diff --git a/app/screens/notification/notification.js b/app/screens/notification/notification.js index 4ae79b031..00c504b64 100644 --- a/app/screens/notification/notification.js +++ b/app/screens/notification/notification.js @@ -25,6 +25,7 @@ import FormattedText from 'app/components/formatted_text'; import ProfilePicture from 'app/components/profile_picture'; import {changeOpacity} from 'app/utils/theme'; import {NavigationTypes} from 'app/constants'; +import {popToRoot, dismissAllModals, dismissOverlay} from 'app/actions/navigation'; import logo from 'assets/images/icon.png'; import webhookIcon from 'assets/images/icons/webhook.jpg'; @@ -36,9 +37,6 @@ export default class Notification extends PureComponent { static propTypes = { actions: PropTypes.shape({ loadFromPushNotification: PropTypes.func.isRequired, - dismissOverlay: PropTypes.func.isRequired, - dismissAllModals: PropTypes.func.isRequired, - popToRoot: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string.isRequired, channel: PropTypes.object, @@ -91,11 +89,10 @@ export default class Notification extends PureComponent { } setDidDisappearListener = () => { - this.didDismissListener = Navigation.events().registerComponentDidDisappearListener(({componentId}) => { + this.didDismissListener = Navigation.events().registerComponentDidDisappearListener(async ({componentId}) => { if (componentId === this.props.componentId && this.tapped) { - const {actions} = this.props; - actions.dismissAllModals(); - actions.popToRoot(); + await dismissAllModals(); + await popToRoot(); } }); } @@ -120,8 +117,8 @@ export default class Notification extends PureComponent { dismissOverlay = () => { this.clearDismissTimer(); - const {actions, componentId} = this.props; - actions.dismissOverlay(componentId); + const {componentId} = this.props; + dismissOverlay(componentId); } animateDismissOverlay = () => { diff --git a/app/screens/options_modal/index.js b/app/screens/options_modal/index.js index 5ba6a8b79..acecbd45a 100644 --- a/app/screens/options_modal/index.js +++ b/app/screens/options_modal/index.js @@ -1,11 +1,8 @@ // 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 {dismissModal} from 'app/actions/navigation'; - import {getDimensions, isLandscape} from 'app/selectors/device'; import OptionsModal from './options_modal'; @@ -17,12 +14,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - dismissModal, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(OptionsModal); +export default connect(mapStateToProps)(OptionsModal); diff --git a/app/screens/options_modal/options_modal.js b/app/screens/options_modal/options_modal.js index b1935b768..22880bf4d 100644 --- a/app/screens/options_modal/options_modal.js +++ b/app/screens/options_modal/options_modal.js @@ -15,6 +15,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {NavigationTypes} from 'app/constants'; import {emptyFunction} from 'app/utils/general'; +import {dismissModal} from 'app/actions/navigation'; import OptionsModalList from './options_modal_list'; @@ -23,9 +24,6 @@ const DURATION = 200; export default class OptionsModal extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - dismissModal: PropTypes.func.isRequired, - }).isRequired, items: PropTypes.array.isRequired, deviceHeight: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, @@ -71,7 +69,7 @@ export default class OptionsModal extends PureComponent { toValue: this.props.deviceHeight, duration: DURATION, }).start(() => { - this.props.actions.dismissModal(); + dismissModal(); }); }; @@ -79,7 +77,7 @@ export default class OptionsModal extends PureComponent { if (Platform.OS === 'android') { this.close(); } else { - this.props.actions.dismissModal(); + dismissModal(); } }; diff --git a/app/screens/permalink/index.js b/app/screens/permalink/index.js index df9453faf..d97525ec9 100644 --- a/app/screens/permalink/index.js +++ b/app/screens/permalink/index.js @@ -17,12 +17,6 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {isLandscape} from 'app/selectors/device'; -import { - goToScreen, - dismissModal, - dismissAllModals, - resetToChannel, -} from 'app/actions/navigation'; import { handleSelectChannel, loadThreadIfNecessary, @@ -81,10 +75,6 @@ function mapDispatchToProps(dispatch) { selectPost, setChannelDisplayName, setChannelLoading, - goToScreen, - dismissModal, - dismissAllModals, - resetToChannel, }, dispatch), }; } diff --git a/app/screens/permalink/permalink.js b/app/screens/permalink/permalink.js index 3520f67eb..7d6bb3aeb 100644 --- a/app/screens/permalink/permalink.js +++ b/app/screens/permalink/permalink.js @@ -24,9 +24,18 @@ import Loading from 'app/components/loading'; import PostList from 'app/components/post_list'; import PostListRetry from 'app/components/post_list_retry'; import SafeAreaView from 'app/components/safe_area_view'; +import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing'; + import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing'; + +import { + resetToChannel, + goToScreen, + dismissModal, + dismissAllModals, + popToRoot, +} from 'app/actions/navigation'; Animatable.initializeRegistryWithDefinitions({ growOut: { @@ -58,10 +67,6 @@ export default class Permalink extends PureComponent { selectPost: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, setChannelLoading: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - dismissAllModals: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, }).isRequired, channelId: PropTypes.string, channelIsArchived: PropTypes.bool, @@ -189,7 +194,7 @@ export default class Permalink extends PureComponent { actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); handleClose = () => { @@ -198,7 +203,7 @@ export default class Permalink extends PureComponent { this.mounted = false; this.refs.view.zoomOut().then(() => { actions.selectPost(''); - actions.dismissModal(); + dismissModal(); if (onClose) { onClose(); @@ -225,7 +230,7 @@ export default class Permalink extends PureComponent { } }; - jumpToChannel = (channelId, channelDisplayName) => { + jumpToChannel = async (channelId, channelDisplayName) => { if (channelId) { const {actions, channelTeamId, currentTeamId, onClose} = this.props; const currentChannelId = this.props.channelId; @@ -238,17 +243,18 @@ export default class Permalink extends PureComponent { actions.selectPost(''); + await dismissAllModals(); + await popToRoot(); + if (channelId === currentChannelId) { EventEmitter.emit('reset_channel'); } else { const passProps = { disableTermsModal: true, }; - actions.resetToChannel(passProps); + resetToChannel(passProps); } - actions.dismissAllModals(); - if (onClose) { onClose(); } diff --git a/app/screens/permalink/permalink.test.js b/app/screens/permalink/permalink.test.js index ff7654cbc..0d8edbe6b 100644 --- a/app/screens/permalink/permalink.test.js +++ b/app/screens/permalink/permalink.test.js @@ -22,10 +22,6 @@ describe('Permalink', () => { selectPost: jest.fn(), setChannelDisplayName: jest.fn(), setChannelLoading: jest.fn(), - goToScreen: jest.fn(), - dismissModal: jest.fn(), - dismissAllModals: jest.fn(), - resetToChannel: jest.fn(), }; const baseProps = { diff --git a/app/screens/pinned_posts/index.js b/app/screens/pinned_posts/index.js index 30f18d440..4cfb165c6 100644 --- a/app/screens/pinned_posts/index.js +++ b/app/screens/pinned_posts/index.js @@ -9,12 +9,6 @@ import {clearSearch, getPinnedPosts} from 'mattermost-redux/actions/search'; import {RequestStatus} from 'mattermost-redux/constants'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import { - dismissModal, - goToScreen, - showSearchModal, - showModalOverCurrentContext, -} from 'app/actions/navigation'; import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel'; import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list'; @@ -48,10 +42,6 @@ function mapDispatchToProps(dispatch) { getPinnedPosts, selectFocusedPostId, selectPost, - dismissModal, - goToScreen, - showSearchModal, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/pinned_posts/pinned_posts.js b/app/screens/pinned_posts/pinned_posts.js index 8197e1a6e..7aa59937e 100644 --- a/app/screens/pinned_posts/pinned_posts.js +++ b/app/screens/pinned_posts/pinned_posts.js @@ -23,6 +23,13 @@ import StatusBar from 'app/components/status_bar'; import mattermostManaged from 'app/mattermost_managed'; import SearchResultPost from 'app/screens/search/search_result_post'; import {changeOpacity} from 'app/utils/theme'; +import { + goToScreen, + showModalOverCurrentContext, + showSearchModal, + dismissModal, +} from 'app/actions/navigation'; + import noResultsImage from 'assets/images/no_results/pin.png'; export default class PinnedPosts extends PureComponent { @@ -34,10 +41,6 @@ export default class PinnedPosts extends PureComponent { getPinnedPosts: PropTypes.func.isRequired, selectFocusedPostId: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - showSearchModal: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, currentChannelId: PropTypes.string.isRequired, didFail: PropTypes.bool, @@ -64,7 +67,7 @@ export default class PinnedPosts extends PureComponent { navigationButtonPressed({buttonId}) { if (buttonId === 'close-settings') { - this.props.actions.dismissModal(); + dismissModal(); } } @@ -81,7 +84,7 @@ export default class PinnedPosts extends PureComponent { Keyboard.dismiss(); actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; handleClosePermalink = () => { @@ -96,11 +99,8 @@ export default class PinnedPosts extends PureComponent { }; handleHashtagPress = async (hashtag) => { - const {actions} = this.props; - - await actions.dismissModal(); - - actions.showSearchModal('#' + hashtag); + dismissModal(); + showSearchModal('#' + hashtag); }; keyExtractor = (item) => item; @@ -182,7 +182,7 @@ export default class PinnedPosts extends PureComponent { }; this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); } }; diff --git a/app/screens/post_options/index.js b/app/screens/post_options/index.js index cbfa74ec5..f326b45e5 100644 --- a/app/screens/post_options/index.js +++ b/app/screens/post_options/index.js @@ -22,7 +22,6 @@ import {getCurrentTeamId, getCurrentTeamUrl} from 'mattermost-redux/selectors/en import {canEditPost} from 'mattermost-redux/utils/post_utils'; import {THREAD} from 'app/constants/screen'; -import {dismissModal, showModal} from 'app/actions/navigation'; import {addReaction} from 'app/actions/views/emoji'; import {getDimensions, isLandscape} from 'app/selectors/device'; @@ -123,8 +122,6 @@ function mapDispatchToProps(dispatch) { removePost, unflagPost, unpinPost, - dismissModal, - showModal, }, dispatch), }; } diff --git a/app/screens/post_options/post_options.js b/app/screens/post_options/post_options.js index 3d623bc32..0a62175bf 100644 --- a/app/screens/post_options/post_options.js +++ b/app/screens/post_options/post_options.js @@ -12,6 +12,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import SlideUpPanel from 'app/components/slide_up_panel'; import {BOTTOM_MARGIN} from 'app/components/slide_up_panel/slide_up_panel'; import {t} from 'app/utils/i18n'; +import {showModal, dismissModal} from 'app/actions/navigation'; import {OPTION_HEIGHT, getInitialPosition} from './post_options_utils'; import PostOption from './post_option'; @@ -26,8 +27,6 @@ export default class PostOptions extends PureComponent { removePost: PropTypes.func.isRequired, unflagPost: PropTypes.func.isRequired, unpinPost: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - showModal: PropTypes.func.isRequired, }).isRequired, canAddReaction: PropTypes.bool, canReply: PropTypes.bool, @@ -52,7 +51,7 @@ export default class PostOptions extends PureComponent { }; close = async (cb) => { - await this.props.actions.dismissModal(); + dismissModal(); if (typeof cb === 'function') { setTimeout(cb, 300); @@ -262,7 +261,7 @@ export default class PostOptions extends PureComponent { }; handleAddReaction = () => { - const {actions, theme} = this.props; + const {theme} = this.props; const {formatMessage} = this.context.intl; this.close(() => { @@ -274,7 +273,7 @@ export default class PostOptions extends PureComponent { onEmojiPress: this.handleAddReactionToPost, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); }); }; @@ -352,7 +351,7 @@ export default class PostOptions extends PureComponent { }; handlePostEdit = () => { - const {actions, theme, post} = this.props; + const {theme, post} = this.props; const {intl} = this.context; this.close(() => { @@ -364,7 +363,7 @@ export default class PostOptions extends PureComponent { closeButton: source, }; - actions.showModal(screen, title, passProps); + showModal(screen, title, passProps); }); }); }; diff --git a/app/screens/post_options/post_options.test.js b/app/screens/post_options/post_options.test.js index 56c380d7c..6be5ab8e1 100644 --- a/app/screens/post_options/post_options.test.js +++ b/app/screens/post_options/post_options.test.js @@ -26,8 +26,6 @@ describe('PostOptions', () => { removePost: jest.fn(), unflagPost: jest.fn(), unpinPost: jest.fn(), - dismissModal: jest.fn(), - showModal: jest.fn(), }; const post = { diff --git a/app/screens/reaction_list/index.js b/app/screens/reaction_list/index.js index 70fc1b742..337daf32f 100644 --- a/app/screens/reaction_list/index.js +++ b/app/screens/reaction_list/index.js @@ -9,7 +9,6 @@ import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts import {getCurrentUserId, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users'; import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {dismissModal} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import {getUniqueUserIds} from 'app/utils/reaction'; @@ -38,7 +37,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ getMissingProfilesByIds, - dismissModal, }, dispatch), }; } diff --git a/app/screens/reaction_list/reaction_list.js b/app/screens/reaction_list/reaction_list.js index 0be0a36d0..726b86e34 100644 --- a/app/screens/reaction_list/reaction_list.js +++ b/app/screens/reaction_list/reaction_list.js @@ -18,6 +18,7 @@ import { getUniqueUserIds, sortReactions, } from 'app/utils/reaction'; +import {dismissModal} from 'app/actions/navigation'; import ReactionHeader from './reaction_header'; import ReactionRow from './reaction_row'; @@ -28,7 +29,6 @@ export default class ReactionList extends PureComponent { static propTypes = { actions: PropTypes.shape({ getMissingProfilesByIds: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, reactions: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, @@ -108,7 +108,7 @@ export default class ReactionList extends PureComponent { } close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; getMissingProfiles = () => { diff --git a/app/screens/reaction_list/reaction_list.test.js b/app/screens/reaction_list/reaction_list.test.js index 12626a479..d9c297302 100644 --- a/app/screens/reaction_list/reaction_list.test.js +++ b/app/screens/reaction_list/reaction_list.test.js @@ -15,7 +15,6 @@ describe('ReactionList', () => { const baseProps = { actions: { getMissingProfilesByIds: jest.fn(), - dismissModal: jest.fn(), }, allUserIds: ['user_id_1', 'user_id_2'], reactions: {'user_id_1-smile': {emoji_name: 'smile', user_id: 'user_id_1'}, 'user_id_2-+1': {emoji_name: '+1', user_id: 'user_id_2'}}, diff --git a/app/screens/reaction_list/reaction_row/index.js b/app/screens/reaction_list/reaction_row/index.js index 9da44288b..e07be3c2c 100644 --- a/app/screens/reaction_list/reaction_row/index.js +++ b/app/screens/reaction_list/reaction_row/index.js @@ -1,10 +1,8 @@ // 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 {goToScreen} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import ReactionRow from './reaction_row'; @@ -14,12 +12,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(ReactionRow); +export default connect(mapStateToProps)(ReactionRow); diff --git a/app/screens/reaction_list/reaction_row/reaction_row.js b/app/screens/reaction_list/reaction_row/reaction_row.js index 812f2be03..458fa418c 100644 --- a/app/screens/reaction_list/reaction_row/reaction_row.js +++ b/app/screens/reaction_list/reaction_row/reaction_row.js @@ -13,17 +13,15 @@ import { import {displayUsername} from 'mattermost-redux/utils/user_utils'; import ProfilePicture from 'app/components/profile_picture'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {goToScreen} from 'app/actions/navigation'; import Emoji from 'app/components/emoji'; export default class ReactionRow extends React.PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, emojiName: PropTypes.string.isRequired, teammateNameDisplay: PropTypes.string.isRequired, theme: PropTypes.object.isRequired, @@ -40,7 +38,7 @@ export default class ReactionRow extends React.PureComponent { }; goToUserProfile = () => { - const {actions, user} = this.props; + const {user} = this.props; const {formatMessage} = this.context.intl; const screen = 'UserProfile'; const title = formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}); @@ -48,7 +46,7 @@ export default class ReactionRow extends React.PureComponent { userId: user.id, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; render() { diff --git a/app/screens/reaction_list/reaction_row/reaction_row.test.js b/app/screens/reaction_list/reaction_row/reaction_row.test.js index 9f65bf3bb..78d9d19f0 100644 --- a/app/screens/reaction_list/reaction_row/reaction_row.test.js +++ b/app/screens/reaction_list/reaction_row/reaction_row.test.js @@ -9,9 +9,6 @@ import ReactionRow from './reaction_row'; describe('ReactionRow', () => { const baseProps = { - actions: { - goToScreen: jest.fn(), - }, emojiName: 'smile', teammateNameDisplay: 'username', theme: Preferences.THEMES.default, diff --git a/app/screens/recent_mentions/index.js b/app/screens/recent_mentions/index.js index 7ee56b2ef..83198213c 100644 --- a/app/screens/recent_mentions/index.js +++ b/app/screens/recent_mentions/index.js @@ -9,12 +9,6 @@ import {clearSearch, getRecentMentions} from 'mattermost-redux/actions/search'; import {RequestStatus} from 'mattermost-redux/constants'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import { - dismissModal, - goToScreen, - showSearchModal, - showModalOverCurrentContext, -} from 'app/actions/navigation'; import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel'; import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list'; @@ -46,10 +40,6 @@ function mapDispatchToProps(dispatch) { getRecentMentions, selectFocusedPostId, selectPost, - showSearchModal, - dismissModal, - goToScreen, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/recent_mentions/recent_mentions.js b/app/screens/recent_mentions/recent_mentions.js index 226d95c64..b146861d2 100644 --- a/app/screens/recent_mentions/recent_mentions.js +++ b/app/screens/recent_mentions/recent_mentions.js @@ -24,6 +24,12 @@ import mattermostManaged from 'app/mattermost_managed'; import SearchResultPost from 'app/screens/search/search_result_post'; import ChannelDisplayName from 'app/screens/search/channel_display_name'; import {changeOpacity} from 'app/utils/theme'; +import { + goToScreen, + showModalOverCurrentContext, + showSearchModal, + dismissModal, +} from 'app/actions/navigation'; export default class RecentMentions extends PureComponent { static propTypes = { @@ -34,9 +40,6 @@ export default class RecentMentions extends PureComponent { getRecentMentions: PropTypes.func.isRequired, selectFocusedPostId: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, - showSearchModal: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, didFail: PropTypes.bool, isLoading: PropTypes.bool, @@ -77,7 +80,7 @@ export default class RecentMentions extends PureComponent { Keyboard.dismiss(); actions.loadThreadIfNecessary(rootId); actions.selectPost(rootId); - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; handleClosePermalink = () => { @@ -92,18 +95,15 @@ export default class RecentMentions extends PureComponent { }; handleHashtagPress = async (hashtag) => { - const {actions} = this.props; - - await actions.dismissModal(); - - actions.showSearchModal('#' + hashtag); + dismissModal(); + showSearchModal('#' + hashtag); }; keyExtractor = (item) => item; navigationButtonPressed({buttonId}) { if (buttonId === 'close-settings') { - this.props.actions.dismissModal(); + dismissModal(); } } @@ -184,7 +184,7 @@ export default class RecentMentions extends PureComponent { }; this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); } }; diff --git a/app/screens/search/index.js b/app/screens/search/index.js index 7f6918eae..b727f547b 100644 --- a/app/screens/search/index.js +++ b/app/screens/search/index.js @@ -15,7 +15,6 @@ import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; -import {dismissModal, goToScreen, showModalOverCurrentContext} from 'app/actions/navigation'; import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel'; import {handleSearchDraftChanged} from 'app/actions/views/search'; import {isLandscape} from 'app/selectors/device'; @@ -85,9 +84,6 @@ function mapDispatchToProps(dispatch) { searchPostsWithParams, getMorePostsForSearch, selectPost, - dismissModal, - goToScreen, - showModalOverCurrentContext, }, dispatch), }; } diff --git a/app/screens/search/search.js b/app/screens/search/search.js index e9b0983c7..91b566181 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -28,15 +28,16 @@ import PostSeparator from 'app/components/post_separator'; import SafeAreaView from 'app/components/safe_area_view'; import SearchBar from 'app/components/search_bar'; import StatusBar from 'app/components/status_bar'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {DeviceTypes, ListTypes} from 'app/constants'; import mattermostManaged from 'app/mattermost_managed'; import {preventDoubleTap} from 'app/utils/tap'; +import {goToScreen, showModalOverCurrentContext, dismissModal} from 'app/actions/navigation'; import { changeOpacity, makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import ChannelDisplayName from './channel_display_name'; import Modifier, {MODIFIER_LABEL_HEIGHT} from './modifier'; @@ -61,9 +62,6 @@ export default class Search extends PureComponent { getMorePostsForSearch: PropTypes.func.isRequired, selectFocusedPostId: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - showModalOverCurrentContext: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string.isRequired, currentTeamId: PropTypes.string.isRequired, @@ -154,7 +152,7 @@ export default class Search extends PureComponent { if (this.state.preview) { this.refs.preview.handleClose(); } else { - this.props.actions.dismissModal(); + dismissModal(); } } } @@ -185,7 +183,7 @@ export default class Search extends PureComponent { cancelSearch = preventDoubleTap(() => { this.handleTextChanged('', true); - this.props.actions.dismissModal(); + dismissModal(); }); goToThread = (post) => { @@ -204,12 +202,12 @@ export default class Search extends PureComponent { rootId, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; handleHashtagPress = (hashtag) => { if (this.showingPermalink) { - this.props.actions.dismissModal(); + dismissModal(); this.handleClosePermalink(); } @@ -464,7 +462,7 @@ export default class Search extends PureComponent { }; this.showingPermalink = true; - actions.showModalOverCurrentContext(screen, passProps, options); + showModalOverCurrentContext(screen, passProps, options); } }; diff --git a/app/screens/select_server/index.js b/app/screens/select_server/index.js index 8bc27e0cf..1f9195c4f 100644 --- a/app/screens/select_server/index.js +++ b/app/screens/select_server/index.js @@ -8,7 +8,6 @@ import {getPing, resetPing, setServerVersion} from 'mattermost-redux/actions/gen import {login} from 'mattermost-redux/actions/users'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {resetToChannel, goToScreen} from 'app/actions/navigation'; import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade'; import {handleSuccessfulLogin, scheduleExpiredNotification} from 'app/actions/views/login'; import {loadConfigAndLicense} from 'app/actions/views/root'; @@ -45,8 +44,6 @@ function mapDispatchToProps(dispatch) { resetPing, setLastUpgradeCheck, setServerVersion, - resetToChannel, - goToScreen, }, dispatch), }; } diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index 528b8b0e6..dd63b80cf 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -21,7 +21,6 @@ import { } from 'react-native'; import Button from 'react-native-button'; import RNFetchBlob from 'rn-fetch-blob'; -import SafeAreaView from 'app/components/safe_area_view'; import merge from 'deepmerge'; @@ -29,6 +28,7 @@ import {Client4} from 'mattermost-redux/client'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; +import SafeAreaView from 'app/components/safe_area_view'; import fetchConfig from 'app/init/fetch'; import mattermostBucket from 'app/mattermost_bucket'; import {GlobalStyles} from 'app/styles'; @@ -38,6 +38,7 @@ import {preventDoubleTap} from 'app/utils/tap'; import tracker from 'app/utils/time_tracker'; import {t} from 'app/utils/i18n'; import {changeOpacity} from 'app/utils/theme'; +import {resetToChannel, goToScreen} from 'app/actions/navigation'; import telemetry from 'app/telemetry'; @@ -53,7 +54,6 @@ export default class SelectServer extends PureComponent { loadConfigAndLicense: PropTypes.func.isRequired, login: PropTypes.func.isRequired, resetPing: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, setLastUpgradeCheck: PropTypes.func.isRequired, setServerVersion: PropTypes.func.isRequired, }).isRequired, @@ -159,7 +159,6 @@ export default class SelectServer extends PureComponent { }; goToNextScreen = (screen, title, passProps = {}, navOptions = {}) => { - const {actions} = this.props; const defaultOptions = { popGesture: !LocalConfig.AutoSelectServerUrl, topBar: { @@ -169,7 +168,7 @@ export default class SelectServer extends PureComponent { }; const options = merge(defaultOptions, navOptions); - actions.goToScreen(screen, title, passProps, options); + goToScreen(screen, title, passProps, options); }; handleAndroidKeyboard = () => { @@ -287,7 +286,7 @@ export default class SelectServer extends PureComponent { await this.props.actions.handleSuccessfulLogin(); this.scheduleSessionExpiredNotification(); - this.props.actions.resetToChannel(); + resetToChannel(); }; pingServer = (url, retryWithHttp = true) => { diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js index beb5b8c9f..944e483c7 100644 --- a/app/screens/select_team/index.js +++ b/app/screens/select_team/index.js @@ -9,7 +9,6 @@ import {logout} from 'mattermost-redux/actions/users'; import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; -import {resetToChannel, dismissModal} from 'app/actions/navigation'; import {handleTeamChange} from 'app/actions/views/select_team'; import {isLandscape} from 'app/selectors/device'; import {isGuest} from 'app/utils/users'; @@ -34,8 +33,6 @@ function mapDispatchToProps(dispatch) { handleTeamChange, joinTeam, logout, - resetToChannel, - dismissModal, }, dispatch), }; } diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index b4b1be8e9..7d2c29703 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -18,12 +18,13 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import FormattedText from 'app/components/formatted_text'; import Loading from 'app/components/loading'; import StatusBar from 'app/components/status_bar'; -import {preventDoubleTap} from 'app/utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; -import {t} from 'app/utils/i18n'; import CustomList from 'app/components/custom_list'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import TeamIcon from 'app/components/team_icon'; +import {resetToChannel, dismissModal} from 'app/actions/navigation'; +import {preventDoubleTap} from 'app/utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; +import {t} from 'app/utils/i18n'; const TEAMS_PER_PAGE = 50; @@ -34,8 +35,6 @@ export default class SelectTeam extends PureComponent { handleTeamChange: PropTypes.func.isRequired, joinTeam: PropTypes.func.isRequired, logout: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string.isRequired, currentUrl: PropTypes.string.isRequired, @@ -116,7 +115,7 @@ export default class SelectTeam extends PureComponent { }; close = () => { - this.props.actions.dismissModal(); + dismissModal(); }; goToChannelView = () => { @@ -124,7 +123,7 @@ export default class SelectTeam extends PureComponent { disableTermsModal: true, }; - this.props.actions.resetToChannel(passProps); + 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 550093919..587bed05a 100644 --- a/app/screens/select_team/select_team.test.js +++ b/app/screens/select_team/select_team.test.js @@ -29,8 +29,6 @@ describe('SelectTeam', () => { handleTeamChange: jest.fn(), joinTeam: jest.fn(), logout: jest.fn(), - resetToChannel: jest.fn(), - dismissModal: jest.fn(), }; const baseProps = { diff --git a/app/screens/selector_screen/index.js b/app/screens/selector_screen/index.js index 1bd00b4ac..e5f69d1fd 100644 --- a/app/screens/selector_screen/index.js +++ b/app/screens/selector_screen/index.js @@ -9,8 +9,6 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getProfiles, searchProfiles} from 'mattermost-redux/actions/users'; import {getChannels, searchChannels} from 'mattermost-redux/actions/channels'; -import {popTopScreen} from 'app/actions/navigation'; - import SelectorScreen from './selector_screen'; function mapStateToProps(state) { @@ -34,7 +32,6 @@ function mapDispatchToProps(dispatch) { getChannels, searchProfiles, searchChannels, - popTopScreen, }, dispatch), }; } diff --git a/app/screens/selector_screen/selector_screen.js b/app/screens/selector_screen/selector_screen.js index c89483faf..fc7be8b56 100644 --- a/app/screens/selector_screen/selector_screen.js +++ b/app/screens/selector_screen/selector_screen.js @@ -31,6 +31,7 @@ import { getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {popTopScreen} from 'app/actions/navigation'; export default class SelectorScreen extends PureComponent { static propTypes = { @@ -39,7 +40,6 @@ export default class SelectorScreen extends PureComponent { getChannels: PropTypes.func.isRequired, searchProfiles: PropTypes.func.isRequired, searchChannels: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, }), componentId: PropTypes.string, currentTeamId: PropTypes.string.isRequired, @@ -98,7 +98,7 @@ export default class SelectorScreen extends PureComponent { }; close = () => { - this.props.actions.popTopScreen(); + popTopScreen(); }; handleSelectItem = (id, item) => { diff --git a/app/screens/selector_screen/selector_screen.test.js b/app/screens/selector_screen/selector_screen.test.js index eb6d311cb..b017809f9 100644 --- a/app/screens/selector_screen/selector_screen.test.js +++ b/app/screens/selector_screen/selector_screen.test.js @@ -67,7 +67,6 @@ describe('SelectorScreen', () => { getChannels, searchProfiles, searchChannels, - popTopScreen: jest.fn(), }; const baseProps = { diff --git a/app/screens/settings/advanced_settings/advanced_settings.js b/app/screens/settings/advanced_settings/advanced_settings.js index 06d881331..9faf22d5a 100644 --- a/app/screens/settings/advanced_settings/advanced_settings.js +++ b/app/screens/settings/advanced_settings/advanced_settings.js @@ -22,13 +22,13 @@ import {t} from 'app/utils/i18n'; import {deleteFileCache, getFileCacheSize} from 'app/utils/file'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {dismissAllModals} from 'app/actions/navigation'; import Config from 'assets/config'; class AdvancedSettings extends Component { static propTypes = { actions: PropTypes.shape({ - dismissAllModals: PropTypes.func.isRequired, purgeOfflineStore: PropTypes.func.isRequired, }).isRequired, intl: intlShape.isRequired, @@ -81,7 +81,7 @@ class AdvancedSettings extends Component { actions.purgeOfflineStore(); if (Platform.OS === 'android') { - actions.dismissAllModals(); + dismissAllModals(); } }); diff --git a/app/screens/settings/advanced_settings/index.js b/app/screens/settings/advanced_settings/index.js index 1109a77a1..0655ea903 100644 --- a/app/screens/settings/advanced_settings/index.js +++ b/app/screens/settings/advanced_settings/index.js @@ -4,7 +4,6 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {dismissAllModals} from 'app/actions/navigation'; import {purgeOfflineStore} from 'app/actions/views/root'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isLandscape} from 'app/selectors/device'; @@ -20,7 +19,6 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - dismissAllModals, purgeOfflineStore, }, dispatch), }; diff --git a/app/screens/settings/display_settings/display_settings.js b/app/screens/settings/display_settings/display_settings.js index dbf9420d1..2734a5971 100644 --- a/app/screens/settings/display_settings/display_settings.js +++ b/app/screens/settings/display_settings/display_settings.js @@ -15,12 +15,10 @@ import ClockDisplay from 'app/screens/settings/clock_display'; import SettingsItem from 'app/screens/settings/settings_item'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {goToScreen} from 'app/actions/navigation'; export default class DisplaySettings extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, componentId: PropTypes.string, theme: PropTypes.object.isRequired, enableTheme: PropTypes.bool.isRequired, @@ -41,13 +39,12 @@ export default class DisplaySettings extends PureComponent { }; goToClockDisplaySettings = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; if (Platform.OS === 'ios') { const screen = 'ClockDisplaySettings'; const title = intl.formatMessage({id: 'user.settings.display.clockDisplay', defaultMessage: 'Clock Display'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); return; } @@ -55,30 +52,28 @@ export default class DisplaySettings extends PureComponent { }); goToSidebarSettings = preventDoubleTap(() => { - const {actions, theme} = this.props; + const {theme} = this.props; const {intl} = this.context; const screen = 'SidebarSettings'; const title = intl.formatMessage({id: 'mobile.display_settings.sidebar', defaultMessage: 'Sidebar'}); - actions.goToScreen(screen, title, {theme}); + goToScreen(screen, title, {theme}); }); goToTimezoneSettings = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'TimezoneSettings'; const title = intl.formatMessage({id: 'mobile.advanced_settings.timezone', defaultMessage: 'Timezone'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToThemeSettings = preventDoubleTap(() => { - const {actions} = this.props; const {intl} = this.context; const screen = 'ThemeSettings'; const title = intl.formatMessage({id: 'mobile.display_settings.theme', defaultMessage: 'Theme'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); render() { diff --git a/app/screens/settings/display_settings/display_settings.test.js b/app/screens/settings/display_settings/display_settings.test.js index eb28389f2..a76dfbb59 100644 --- a/app/screens/settings/display_settings/display_settings.test.js +++ b/app/screens/settings/display_settings/display_settings.test.js @@ -15,9 +15,6 @@ jest.mock('react-intl'); describe('DisplaySettings', () => { const baseProps = { - actions: { - goToScreen: jest.fn(), - }, theme: Preferences.THEMES.default, enableTheme: false, enableTimezone: false, diff --git a/app/screens/settings/display_settings/index.js b/app/screens/settings/display_settings/index.js index 038533b84..4db4e9d1a 100644 --- a/app/screens/settings/display_settings/index.js +++ b/app/screens/settings/display_settings/index.js @@ -1,13 +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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; -import {goToScreen} from 'app/actions/navigation'; import {getAllowedThemes} from 'app/selectors/theme'; import {isThemeSwitchingEnabled} from 'app/utils/theme'; import {isLandscape} from 'app/selectors/device'; @@ -25,12 +23,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(DisplaySettings); +export default connect(mapStateToProps)(DisplaySettings); diff --git a/app/screens/settings/general/index.js b/app/screens/settings/general/index.js index 01e2aff21..38efbe620 100644 --- a/app/screens/settings/general/index.js +++ b/app/screens/settings/general/index.js @@ -9,7 +9,6 @@ import {getCurrentUrl, getConfig} from 'mattermost-redux/selectors/entities/gene import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {goToScreen, dismissModal} from 'app/actions/navigation'; import {purgeOfflineStore} from 'app/actions/views/root'; import {isLandscape} from 'app/selectors/device'; import {removeProtocol} from 'app/utils/url'; @@ -36,8 +35,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ clearErrors, purgeOfflineStore, - goToScreen, - dismissModal, }, dispatch), }; } diff --git a/app/screens/settings/general/settings.js b/app/screens/settings/general/settings.js index e0e522d18..d3d9c4336 100644 --- a/app/screens/settings/general/settings.js +++ b/app/screens/settings/general/settings.js @@ -19,6 +19,7 @@ import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {isValidUrl} from 'app/utils/url'; import {t} from 'app/utils/i18n'; +import {goToScreen, dismissModal} from 'app/actions/navigation'; import LocalConfig from 'assets/config'; @@ -27,8 +28,6 @@ class Settings extends PureComponent { actions: PropTypes.shape({ clearErrors: PropTypes.func.isRequired, purgeOfflineStore: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, config: PropTypes.object.isRequired, @@ -53,7 +52,7 @@ class Settings extends PureComponent { navigationButtonPressed({buttonId}) { if (buttonId === 'close-settings') { - this.props.actions.dismissModal(); + dismissModal(); } } @@ -84,39 +83,39 @@ class Settings extends PureComponent { }; goToAbout = preventDoubleTap(() => { - const {actions, intl, config} = this.props; + const {intl, config} = this.props; const screen = 'About'; const title = intl.formatMessage({id: 'about.title', defaultMessage: 'About {appTitle}'}, {appTitle: config.SiteName || 'Mattermost'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToNotifications = preventDoubleTap(() => { - const {actions, intl} = this.props; + const {intl} = this.props; const screen = 'NotificationSettings'; const title = intl.formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToDisplaySettings = preventDoubleTap(() => { - const {actions, intl} = this.props; + const {intl} = this.props; const screen = 'DisplaySettings'; const title = intl.formatMessage({id: 'user.settings.modal.display', defaultMessage: 'Display'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToAdvancedSettings = preventDoubleTap(() => { - const {actions, intl} = this.props; + const {intl} = this.props; const screen = 'AdvancedSettings'; const title = intl.formatMessage({id: 'mobile.advanced_settings.title', defaultMessage: 'Advanced Settings'}); - actions.goToScreen(screen, title); + goToScreen(screen, title); }); goToSelectTeam = preventDoubleTap(() => { - const {actions, currentUrl, intl, theme} = this.props; + const {currentUrl, intl, theme} = this.props; const screen = 'SelectTeam'; const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}); const passProps = { @@ -124,18 +123,18 @@ class Settings extends PureComponent { theme, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); goToClientUpgrade = preventDoubleTap(() => { - const {actions, intl} = this.props; + const {intl} = this.props; const screen = 'ClientUpgrade'; const title = intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}); const passProps = { userCheckedForUpgrade: true, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); openErrorEmail = preventDoubleTap(() => { diff --git a/app/screens/settings/notification_settings/index.js b/app/screens/settings/notification_settings/index.js index 4c98954eb..7104f03b2 100644 --- a/app/screens/settings/notification_settings/index.js +++ b/app/screens/settings/notification_settings/index.js @@ -11,8 +11,6 @@ import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; import {isLandscape} from 'app/selectors/device'; import {updateMe} from 'mattermost-redux/actions/users'; -import {goToScreen} from 'app/actions/navigation'; - import NotificationSettings from './notification_settings'; function mapStateToProps(state) { @@ -38,7 +36,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ updateMe, - goToScreen, }, dispatch), }; } diff --git a/app/screens/settings/notification_settings/notification_settings.js b/app/screens/settings/notification_settings/notification_settings.js index c15a1bef7..a8679a8f5 100644 --- a/app/screens/settings/notification_settings/notification_settings.js +++ b/app/screens/settings/notification_settings/notification_settings.js @@ -21,12 +21,12 @@ import {getNotificationProps} from 'app/utils/notify_props'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {goToScreen} from 'app/actions/navigation'; class NotificationSettings extends PureComponent { static propTypes = { actions: PropTypes.shape({ updateMe: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, }), componentId: PropTypes.string, currentUser: PropTypes.object.isRequired, @@ -63,7 +63,7 @@ class NotificationSettings extends PureComponent { }); goToNotificationSettingsAutoResponder = () => { - const {actions, currentUser, intl} = this.props; + const {currentUser, intl} = this.props; const screen = 'NotificationSettingsAutoResponder'; const title = intl.formatMessage({ id: 'mobile.notification_settings.auto_responder_short', @@ -74,11 +74,11 @@ class NotificationSettings extends PureComponent { onBack: this.saveAutoResponder, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; goToNotificationSettingsEmail = () => { - const {actions, currentUser, intl} = this.props; + const {currentUser, intl} = this.props; const screen = 'NotificationSettingsEmail'; const title = intl.formatMessage({ id: 'mobile.notification_settings.email_title', @@ -88,11 +88,11 @@ class NotificationSettings extends PureComponent { currentUser, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; goToNotificationSettingsMentions = () => { - const {actions, currentUser, intl} = this.props; + const {currentUser, intl} = this.props; const screen = 'NotificationSettingsMentions'; const title = intl.formatMessage({ id: 'mobile.notification_settings.mentions_replies', @@ -103,11 +103,11 @@ class NotificationSettings extends PureComponent { onBack: this.saveNotificationProps, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; goToNotificationSettingsMobile = () => { - const {actions, currentUser, intl} = this.props; + const {currentUser, intl} = this.props; const screen = 'NotificationSettingsMobile'; const title = intl.formatMessage({ id: 'mobile.notification_settings.mobile_title', @@ -121,7 +121,7 @@ class NotificationSettings extends PureComponent { notificationPreferences, }; requestAnimationFrame(() => { - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); }).catch((e) => { Alert.alert('There was a problem getting the device preferences', e.message); diff --git a/app/screens/settings/notification_settings_mentions/index.js b/app/screens/settings/notification_settings_mentions/index.js index c0fbfb402..ca04f8c97 100644 --- a/app/screens/settings/notification_settings_mentions/index.js +++ b/app/screens/settings/notification_settings_mentions/index.js @@ -1,12 +1,10 @@ // 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {isLandscape} from 'app/selectors/device'; -import {goToScreen} from 'app/actions/navigation'; import NotificationSettingsMentions from './notification_settings_mentions'; @@ -17,12 +15,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - goToScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(NotificationSettingsMentions); +export default connect(mapStateToProps)(NotificationSettingsMentions); diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js b/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js index d2fced3eb..d10a0fe1e 100644 --- a/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js +++ b/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js @@ -11,9 +11,6 @@ import {setNavigatorStyles} from 'app/utils/theme'; export default class NotificationSettingsMentionsBase extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - goToScreen: PropTypes.func.isRequired, - }).isRequired, componentId: PropTypes.string, currentUser: PropTypes.object.isRequired, intl: intlShape.isRequired, diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js index 39a8cd97a..776cc89ca 100644 --- a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js +++ b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js @@ -8,18 +8,20 @@ import { View, } from 'react-native'; import {injectIntl} from 'react-intl'; + import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; import Section from 'app/screens/settings/section'; import SectionItem from 'app/screens/settings/section_item'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import {goToScreen} from 'app/actions/navigation'; import NotificationSettingsMentionsBase from './notification_settings_mention_base'; class NotificationSettingsMentionsIos extends NotificationSettingsMentionsBase { goToNotificationSettingsMentionKeywords = () => { - const {actions, intl} = this.props; + const {intl} = this.props; this.goingBack = false; const screen = 'NotificationSettingsMentionsKeywords'; @@ -29,7 +31,7 @@ class NotificationSettingsMentionsIos extends NotificationSettingsMentionsBase { onBack: this.updateMentionKeys, }; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; renderMentionSection(style) { diff --git a/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap b/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap index 3c43dd499..defd9275c 100644 --- a/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap +++ b/app/screens/settings/notification_settings_mentions_keywords/__snapshots__/notification_settings_mentions_keywords.test.js.snap @@ -5,14 +5,9 @@ NotificationSettingsMentionsKeywords { "context": Object {}, "handleSubmit": [Function], "keywordsRef": [Function], - "navigationEventListener": Object { - "remove": [Function], - }, + "navigationEventListener": undefined, "onKeywordsChangeText": [Function], "props": Object { - "actions": Object { - "popTopScreen": [MockFunction], - }, "componentId": "component-id", "intl": Object { "defaultFormats": Object {}, @@ -93,11 +88,6 @@ NotificationSettingsMentionsKeywords { "useState": [Function], }, "_element": { - this.props.actions.popTopScreen(); + popTopScreen(); }; keywordsRef = (ref) => { diff --git a/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.test.js b/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.test.js index 3f1c11e41..e90a16f11 100644 --- a/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.test.js +++ b/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.test.js @@ -11,9 +11,6 @@ import NotificationSettingsMentionsKeywords from './notification_settings_mentio describe('NotificationSettingsMentionsKeywords', () => { const baseProps = { - actions: { - popTopScreen: jest.fn(), - }, componentId: 'component-id', keywords: '', isLandscape: false, diff --git a/app/screens/settings/sidebar/sidebar.test.js b/app/screens/settings/sidebar/sidebar.test.js index 2acf53742..78d9c1452 100644 --- a/app/screens/settings/sidebar/sidebar.test.js +++ b/app/screens/settings/sidebar/sidebar.test.js @@ -13,6 +13,7 @@ import SidebarSettings from './index'; jest.mock('react-intl'); jest.mock('app/mattermost_managed', () => ({ isRunningInSplitView: jest.fn().mockResolvedValue(false), + addEventListener: jest.fn(), })); describe('SidebarSettings', () => { diff --git a/app/screens/settings/theme/theme.test.js b/app/screens/settings/theme/theme.test.js index 53318bc99..cbc658296 100644 --- a/app/screens/settings/theme/theme.test.js +++ b/app/screens/settings/theme/theme.test.js @@ -3,23 +3,17 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {Navigation} from 'react-native-navigation'; import Preferences from 'mattermost-redux/constants/preferences'; import EphemeralStore from 'app/store/ephemeral_store'; +import * as NavigationActions from 'app/actions/navigation'; import Theme from './theme'; import ThemeTile from './theme_tile'; jest.mock('react-intl'); -jest.mock('react-native-navigation', () => ({ - Navigation: { - mergeOptions: jest.fn(), - }, -})); - const allowedThemes = [ { type: 'Mattermost', @@ -153,7 +147,9 @@ describe('Theme', () => { expect(wrapper.find(ThemeTile)).toHaveLength(4); }); - test('should call Navigation.mergeOptions on all navigation components when theme changes', () => { + test('should call mergeNavigationOptions on all navigation components when theme changes', () => { + const mergeNavigationOptions = jest.spyOn(NavigationActions, 'mergeNavigationOptions'); + const componentIds = ['component-1', 'component-2', 'component-3']; componentIds.forEach((componentId) => { EphemeralStore.addNavigationComponentId(componentId); @@ -185,7 +181,7 @@ describe('Theme', () => { }, }; - expect(Navigation.mergeOptions.mock.calls).toEqual([ + expect(mergeNavigationOptions.mock.calls).toEqual([ [componentIds[2], options], [componentIds[1], options], [componentIds[0], options], diff --git a/app/screens/settings/timezone/index.js b/app/screens/settings/timezone/index.js index a27919eda..fa3799dd7 100644 --- a/app/screens/settings/timezone/index.js +++ b/app/screens/settings/timezone/index.js @@ -10,7 +10,6 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getUserTimezone} from 'mattermost-redux/selectors/entities/timezone'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import {isLandscape} from 'app/selectors/device'; -import {goToScreen} from 'app/actions/navigation'; import {updateUser} from 'app/actions/views/edit_profile'; import Timezone from './timezone'; @@ -34,7 +33,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ getSupportedTimezones, updateUser, - goToScreen, }, dispatch), }; } diff --git a/app/screens/settings/timezone/select_timezone/index.js b/app/screens/settings/timezone/select_timezone/index.js index 681f264c4..632b30363 100644 --- a/app/screens/settings/timezone/select_timezone/index.js +++ b/app/screens/settings/timezone/select_timezone/index.js @@ -1,13 +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 {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getSupportedTimezones} from 'mattermost-redux/selectors/entities/general'; -import {popTopScreen} from 'app/actions/navigation'; import {isLandscape} from 'app/selectors/device'; import SelectTimezone from './select_timezone'; @@ -30,12 +28,4 @@ function mapStateToProps(state, props) { }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - popTopScreen, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(SelectTimezone); +export default connect(mapStateToProps)(SelectTimezone); diff --git a/app/screens/settings/timezone/select_timezone/select_timezone.js b/app/screens/settings/timezone/select_timezone/select_timezone.js index 8928125fd..a16f810e1 100644 --- a/app/screens/settings/timezone/select_timezone/select_timezone.js +++ b/app/screens/settings/timezone/select_timezone/select_timezone.js @@ -13,7 +13,7 @@ import {intlShape} from 'react-intl'; import SearchBar from 'app/components/search_bar'; import StatusBar from 'app/components/status_bar'; -import SelectTimezoneRow from './select_timezone_row'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {ListTypes} from 'app/constants'; import { @@ -21,16 +21,15 @@ import { makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme, } from 'app/utils/theme'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; +import {popTopScreen} from 'app/actions/navigation'; + +import SelectTimezoneRow from './select_timezone_row'; const ITEM_HEIGHT = 45; const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS; export default class Timezone extends PureComponent { static propTypes = { - actions: PropTypes.shape({ - popTopScreen: PropTypes.func.isRequired, - }).isRequired, selectedTimezone: PropTypes.string.isRequired, initialScrollIndex: PropTypes.number.isRequired, timezones: PropTypes.array.isRequired, @@ -67,7 +66,7 @@ export default class Timezone extends PureComponent { timezoneSelected = (timezone) => { this.props.onBack(timezone); - this.props.actions.popTopScreen(); + popTopScreen(); }; handleTextChanged = (value) => { diff --git a/app/screens/settings/timezone/timezone.js b/app/screens/settings/timezone/timezone.js index e04d35bbb..ffebb05d5 100644 --- a/app/screens/settings/timezone/timezone.js +++ b/app/screens/settings/timezone/timezone.js @@ -18,6 +18,7 @@ import Section from 'app/screens/settings/section'; import SectionItem from 'app/screens/settings/section_item'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {getDeviceTimezone} from 'app/utils/timezone'; +import {goToScreen} from 'app/actions/navigation'; export default class Timezone extends PureComponent { static propTypes = { @@ -116,7 +117,6 @@ export default class Timezone extends PureComponent { goToSelectTimezone = () => { const { - actions, userTimezone: {manualTimezone}, } = this.props; const {intl} = this.context; @@ -129,7 +129,7 @@ export default class Timezone extends PureComponent { this.goingBack = false; - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }; render() { diff --git a/app/screens/sso/index.js b/app/screens/sso/index.js index 64553978a..f17246b5b 100644 --- a/app/screens/sso/index.js +++ b/app/screens/sso/index.js @@ -4,7 +4,6 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {resetToChannel} from 'app/actions/navigation'; import {handleSuccessfulLogin, scheduleExpiredNotification} from 'app/actions/views/login'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -25,7 +24,6 @@ function mapDispatchToProps(dispatch) { scheduleExpiredNotification, handleSuccessfulLogin, setStoreFromLocalData, - resetToChannel, }, dispatch), }; } diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index 20ed882b8..788a37196 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -19,6 +19,7 @@ import {Client4} from 'mattermost-redux/client'; import {ViewTypes} from 'app/constants'; import Loading from 'app/components/loading'; import StatusBar from 'app/components/status_bar'; +import {resetToChannel} from 'app/actions/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import tracker from 'app/utils/time_tracker'; @@ -68,7 +69,6 @@ class SSO extends PureComponent { scheduleExpiredNotification: PropTypes.func.isRequired, handleSuccessfulLogin: PropTypes.func.isRequired, setStoreFromLocalData: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, }).isRequired, }; @@ -119,7 +119,7 @@ class SSO extends PureComponent { this.scheduleSessionExpiredNotification(); - this.props.actions.resetToChannel(); + resetToChannel(); }; onMessage = (event) => { diff --git a/app/screens/terms_of_service/index.js b/app/screens/terms_of_service/index.js index 51519d733..a9c843bac 100644 --- a/app/screens/terms_of_service/index.js +++ b/app/screens/terms_of_service/index.js @@ -8,12 +8,6 @@ import {getTermsOfService, logout, updateMyTermsOfServiceStatus} from 'mattermos import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import { - setButtons, - dismissModal, - dismissAllModals, -} from 'app/actions/navigation'; - import TermsOfService from './terms_of_service.js'; function mapStateToProps(state) { @@ -31,9 +25,6 @@ function mapDispatchToProps(dispatch) { getTermsOfService, logout, updateMyTermsOfServiceStatus, - setButtons, - dismissModal, - dismissAllModals, }, dispatch), }; } diff --git a/app/screens/terms_of_service/terms_of_service.js b/app/screens/terms_of_service/terms_of_service.js index ceebcb7b7..db72b2192 100644 --- a/app/screens/terms_of_service/terms_of_service.js +++ b/app/screens/terms_of_service/terms_of_service.js @@ -19,15 +19,14 @@ import StatusBar from 'app/components/status_bar'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; +import {dismissModal, dismissAllModals, setButtons} from 'app/actions/navigation'; + export default class TermsOfService extends PureComponent { static propTypes = { actions: PropTypes.shape({ logout: PropTypes.func.isRequired, getTermsOfService: PropTypes.func.isRequired, updateMyTermsOfServiceStatus: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - dismissAllModals: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, closeButton: PropTypes.object, @@ -98,13 +97,13 @@ export default class TermsOfService extends PureComponent { } setNavigatorButtons = (enabled = true) => { - const {actions, componentId} = this.props; + const {componentId} = this.props; const buttons = { leftButtons: [{...this.leftButton, enabled}], rightButtons: [{...this.rightButton, enabled}], }; - actions.setButtons(componentId, buttons); + setButtons(componentId, buttons); }; enableNavigatorLogout = () => { @@ -113,13 +112,13 @@ export default class TermsOfService extends PureComponent { rightButtons: [{...this.rightButton, enabled: false}], }; - this.props.actions.setButtons(buttons); + setButtons(buttons); }; - closeTermsAndLogout = () => { + closeTermsAndLogout = async () => { const {actions} = this.props; - actions.dismissAllModals(); + await dismissAllModals(); actions.logout(); }; @@ -156,7 +155,7 @@ export default class TermsOfService extends PureComponent { this.registerUserAction( true, () => { - this.props.actions.dismissModal(); + dismissModal(); }, this.handleAcceptTerms ); diff --git a/app/screens/terms_of_service/terms_of_service.test.js b/app/screens/terms_of_service/terms_of_service.test.js index 5bfa16711..85a21ae01 100644 --- a/app/screens/terms_of_service/terms_of_service.test.js +++ b/app/screens/terms_of_service/terms_of_service.test.js @@ -6,6 +6,8 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; +import * as NavigationActions from 'app/actions/navigation'; + import TermsOfService from './terms_of_service.js'; jest.mock('react-intl'); @@ -23,9 +25,6 @@ describe('TermsOfService', () => { getTermsOfService: jest.fn(), updateMyTermsOfServiceStatus: jest.fn(), logout: jest.fn(), - setButtons: jest.fn(), - dismissModal: jest.fn(), - dismissAllModals: jest.fn(), }; const baseProps = { @@ -81,18 +80,20 @@ describe('TermsOfService', () => { expect(wrapper.getElement()).toMatchSnapshot(); }); - test('should call props.actions.setButtons on setNavigatorButtons', async () => { + test('should call setButtons on setNavigatorButtons', async () => { + const setButtons = jest.spyOn(NavigationActions, 'setButtons'); + const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, ); wrapper.setState({loading: false, termsId: 1, termsText: 'Terms Text'}); - expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(2); + expect(setButtons).toHaveBeenCalledTimes(2); wrapper.instance().setNavigatorButtons(true); - expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(3); + expect(setButtons).toHaveBeenCalledTimes(3); wrapper.instance().setNavigatorButtons(false); - expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(4); + expect(setButtons).toHaveBeenCalledTimes(4); }); test('should enable/disable navigator buttons on setNavigatorButtons true/false', () => { @@ -120,6 +121,8 @@ describe('TermsOfService', () => { }); test('should call dismissAllModals on closeTermsAndLogout', () => { + const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals'); + const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, @@ -127,6 +130,6 @@ describe('TermsOfService', () => { wrapper.setState({loading: false, termsId: 1, termsText: 'Terms Text'}); wrapper.instance().closeTermsAndLogout(); - expect(baseProps.actions.dismissAllModals).toHaveBeenCalledTimes(1); + expect(dismissAllModals).toHaveBeenCalledTimes(1); }); }); diff --git a/app/screens/thread/index.js b/app/screens/thread/index.js index a089d7755..7f1573ab0 100644 --- a/app/screens/thread/index.js +++ b/app/screens/thread/index.js @@ -10,8 +10,6 @@ import {selectPost} from 'mattermost-redux/actions/posts'; import {makeGetChannel, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels'; import {makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts'; -import {popTopScreen, resetToChannel} from 'app/actions/navigation'; - import Thread from './thread'; function makeMapStateToProps() { @@ -39,8 +37,6 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ selectPost, - popTopScreen, - resetToChannel, }, dispatch), }; } diff --git a/app/screens/thread/thread.ios.test.js b/app/screens/thread/thread.ios.test.js index 0478ed22d..bfb819f43 100644 --- a/app/screens/thread/thread.ios.test.js +++ b/app/screens/thread/thread.ios.test.js @@ -6,23 +6,18 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; import {General, RequestStatus} from 'mattermost-redux/constants'; + import PostList from 'app/components/post_list'; +import * as NavigationActions from 'app/actions/navigation'; import ThreadIOS from './thread.ios'; jest.mock('react-intl'); -jest.mock('react-native-navigation', () => ({ - Navigation: { - mergeOptions: jest.fn(), - }, -})); describe('thread', () => { const baseProps = { actions: { selectPost: jest.fn(), - popTopScreen: jest.fn(), - resetToChannel: jest.fn(), }, channelId: 'channel_id', channelType: General.OPEN_CHANNEL, @@ -55,7 +50,9 @@ describe('thread', () => { expect(wrapper.getElement()).toMatchSnapshot(); }); - test('should call props.actions.resetToChannel on onCloseChannel', () => { + test('should call resetToChannel on onCloseChannel', () => { + const resetToChannel = jest.spyOn(NavigationActions, 'resetToChannel'); + const passProps = { disableTermsModal: true, }; @@ -66,8 +63,8 @@ describe('thread', () => { {context: {intl: {formatMessage: jest.fn()}}}, ); wrapper.instance().onCloseChannel(); - expect(baseProps.actions.resetToChannel).toHaveBeenCalledTimes(1); - expect(baseProps.actions.resetToChannel).toBeCalledWith(passProps); + expect(resetToChannel).toHaveBeenCalledTimes(1); + expect(resetToChannel).toBeCalledWith(passProps); }); test('should match snapshot, render footer', () => { diff --git a/app/screens/thread/thread_base.js b/app/screens/thread/thread_base.js index 50c733642..0aa3c357d 100644 --- a/app/screens/thread/thread_base.js +++ b/app/screens/thread/thread_base.js @@ -5,20 +5,18 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Keyboard} from 'react-native'; import {intlShape} from 'react-intl'; -import {Navigation} from 'react-native-navigation'; import {General, RequestStatus} from 'mattermost-redux/constants'; import Loading from 'app/components/loading'; -import {setNavigatorStyles} from 'app/utils/theme'; import DeletedPost from 'app/components/deleted_post'; +import {resetToChannel, popTopScreen, mergeNavigationOptions} from 'app/actions/navigation'; +import {setNavigatorStyles} from 'app/utils/theme'; export default class ThreadBase extends PureComponent { static propTypes = { actions: PropTypes.shape({ selectPost: PropTypes.func.isRequired, - popTopScreen: PropTypes.func.isRequired, - resetToChannel: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, channelId: PropTypes.string.isRequired, @@ -55,13 +53,14 @@ export default class ThreadBase extends PureComponent { this.postTextbox = React.createRef(); - Navigation.mergeOptions(props.componentId, { + const options = { topBar: { title: { text: title, }, }, - }); + }; + mergeNavigationOptions(props.componentId, options); this.state = { lastViewedAt: props.myMember && props.myMember.last_viewed_at, @@ -88,8 +87,8 @@ export default class ThreadBase extends PureComponent { } close = () => { - const {actions, componentId} = this.props; - actions.popTopScreen(componentId); + const {componentId} = this.props; + popTopScreen(componentId); }; handleAutoComplete = (value) => { @@ -124,6 +123,6 @@ export default class ThreadBase extends PureComponent { const passProps = { disableTermsModal: true, }; - this.props.actions.resetToChannel(passProps); + resetToChannel(passProps); }; } diff --git a/app/screens/user_profile/index.js b/app/screens/user_profile/index.js index 4010b31fb..e6383f723 100644 --- a/app/screens/user_profile/index.js +++ b/app/screens/user_profile/index.js @@ -15,14 +15,6 @@ import {loadBot} from 'mattermost-redux/actions/bots'; import {getBotAccounts} from 'mattermost-redux/selectors/entities/bots'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; -import { - setButtons, - dismissModal, - goToScreen, - dismissAllModals, - popToRoot, -} from 'app/actions/navigation'; - import UserProfile from './user_profile'; function mapStateToProps(state, ownProps) { @@ -52,11 +44,6 @@ function mapDispatchToProps(dispatch) { makeDirectChannel, setChannelDisplayName, loadBot, - setButtons, - dismissModal, - goToScreen, - dismissAllModals, - popToRoot, }, dispatch), }; } diff --git a/app/screens/user_profile/user_profile.js b/app/screens/user_profile/user_profile.js index 1e1f7d1fb..e7675eaf0 100644 --- a/app/screens/user_profile/user_profile.js +++ b/app/screens/user_profile/user_profile.js @@ -11,8 +11,10 @@ import { } from 'react-native'; import {intlShape} from 'react-intl'; import {Navigation} from 'react-native-navigation'; + import {displayUsername} from 'mattermost-redux/utils/user_utils'; import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils'; + import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import ProfilePicture from 'app/components/profile_picture'; import FormattedText from 'app/components/formatted_text'; @@ -20,11 +22,20 @@ import FormattedTime from 'app/components/formatted_time'; import StatusBar from 'app/components/status_bar'; import BotTag from 'app/components/bot_tag'; import GuestTag from 'app/components/guest_tag'; + import {alertErrorWithFallback} from 'app/utils/general'; import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; import {isGuest} from 'app/utils/users'; +import { + goToScreen, + popToRoot, + dismissModal, + dismissAllModals, + setButtons, +} from 'app/actions/navigation'; + import UserProfileRow from './user_profile_row'; import Config from 'assets/config'; @@ -34,11 +45,6 @@ export default class UserProfile extends PureComponent { makeDirectChannel: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, loadBot: PropTypes.func.isRequired, - setButtons: PropTypes.func.isRequired, - dismissModal: PropTypes.func.isRequired, - goToScreen: PropTypes.func.isRequired, - dismissAllModals: PropTypes.func.isRequired, - popToRoot: PropTypes.func.isRequired, }).isRequired, componentId: PropTypes.string, config: PropTypes.object.isRequired, @@ -74,7 +80,7 @@ export default class UserProfile extends PureComponent { rightButtons: [this.rightButton], }; - props.actions.setButtons(props.componentId, buttons); + setButtons(props.componentId, buttons); } } @@ -103,16 +109,16 @@ export default class UserProfile extends PureComponent { } } - close = () => { - const {actions, fromSettings, componentId} = this.props; + close = async () => { + const {fromSettings} = this.props; if (fromSettings) { - actions.dismissModal(); + dismissModal(); return; } - actions.dismissAllModals(); - actions.popToRoot(componentId); + await dismissAllModals(); + await popToRoot(); }; getDisplayName = () => { @@ -227,7 +233,7 @@ export default class UserProfile extends PureComponent { }; goToEditProfile = () => { - const {actions, user: currentUser} = this.props; + const {user: currentUser} = this.props; const {formatMessage} = this.context.intl; const commandType = 'Push'; const screen = 'EditProfile'; @@ -235,7 +241,7 @@ export default class UserProfile extends PureComponent { const passProps = {currentUser, commandType}; requestAnimationFrame(() => { - actions.goToScreen(screen, title, passProps); + goToScreen(screen, title, passProps); }); }; diff --git a/app/screens/user_profile/user_profile.test.js b/app/screens/user_profile/user_profile.test.js index a3524e65a..79d36b16c 100644 --- a/app/screens/user_profile/user_profile.test.js +++ b/app/screens/user_profile/user_profile.test.js @@ -5,6 +5,8 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; +import * as NavigationActions from 'app/actions/navigation'; + import UserProfile from './user_profile.js'; import BotTag from 'app/components/bot_tag'; import GuestTag from 'app/components/guest_tag'; @@ -23,11 +25,6 @@ describe('user_profile', () => { setChannelDisplayName: jest.fn(), makeDirectChannel: jest.fn(), loadBot: jest.fn(), - setButtons: jest.fn(), - dismissModal: jest.fn(), - goToScreen: jest.fn(), - dismissAllModals: jest.fn(), - popToRoot: jest.fn(), }; const baseProps = { actions, @@ -54,7 +51,7 @@ describe('user_profile', () => { is_bot: false, }; - test('should match snapshot', async () => { + test('should match snapshot', () => { const wrapper = shallow( { expect(wrapper.getElement()).toMatchSnapshot(); }); - test('should contain bot tag', async () => { + test('should contain bot tag', () => { const botUser = { email: 'test@test.com', first_name: 'test', @@ -91,7 +88,7 @@ describe('user_profile', () => { )).toEqual(true); }); - test('should contain guest tag', async () => { + test('should contain guest tag', () => { const guestUser = { email: 'test@test.com', first_name: 'test', @@ -117,7 +114,10 @@ describe('user_profile', () => { )).toEqual(true); }); - test('should push EditProfile', async () => { + test('should push EditProfile', () => { + jest.spyOn(global, 'requestAnimationFrame').mockImplementation((cb) => cb()); + const goToScreen = jest.spyOn(NavigationActions, 'goToScreen'); + const wrapper = shallow( { ); wrapper.instance().goToEditProfile(); - setTimeout(() => { - expect(baseProps.actions.goToScreen).toHaveBeenCalledTimes(1); - }, 16); + expect(goToScreen).toHaveBeenCalledTimes(1); }); test('should call goToEditProfile', () => { - const props = { - ...baseProps, - actions: { - ...baseProps.actions, - goToScreen: jest.fn(), - }, - }; + const goToScreen = jest.spyOn(NavigationActions, 'goToScreen'); + const wrapper = shallow( , {context: {intl: {formatMessage: jest.fn()}}}, @@ -150,12 +143,14 @@ describe('user_profile', () => { const event = {buttonId: wrapper.instance().rightButton.id}; wrapper.instance().navigationButtonPressed(event); - setTimeout(() => { - expect(baseProps.actions.goToScreen).toHaveBeenCalledTimes(1); - }, 0); + expect(goToScreen).toHaveBeenCalledTimes(1); }); - test('should close', async () => { + test('close should dismiss modal when fromSettings is true', async () => { + const dismissModal = jest.spyOn(NavigationActions, 'dismissModal'); + const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals'); + const popToRoot = jest.spyOn(NavigationActions, 'popToRoot'); + const props = {...baseProps, fromSettings: true}; const wrapper = shallow( @@ -166,15 +161,45 @@ describe('user_profile', () => { {context: {intl: {formatMessage: jest.fn()}}}, ); + await wrapper.instance().close(); + expect(dismissModal).toHaveBeenCalledTimes(1); + expect(dismissAllModals).toHaveBeenCalledTimes(0); + expect(popToRoot).toHaveBeenCalledTimes(0); + }); + + test('close should dismiss all modals and pop to root when fromSettings is false', async () => { + const dismissModal = jest.spyOn(NavigationActions, 'dismissModal'); + const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals'); + const popToRoot = jest.spyOn(NavigationActions, 'popToRoot'); + + const props = {...baseProps, fromSettings: false}; + + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + await wrapper.instance().close(); + expect(dismissModal).toHaveBeenCalledTimes(0); + expect(dismissAllModals).toHaveBeenCalledTimes(1); + expect(popToRoot).toHaveBeenCalledTimes(1); + }); + + test('should call close', () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + const close = jest.spyOn(wrapper.instance(), 'close'); const event = {buttonId: 'close-settings'}; wrapper.instance().navigationButtonPressed(event); - expect(props.actions.dismissModal).toHaveBeenCalledTimes(1); - - props.fromSettings = false; - wrapper.setProps({...props}); - wrapper.instance().navigationButtonPressed(event); - expect(props.actions.dismissAllModals).toHaveBeenCalledTimes(1); - expect(props.actions.popToRoot).toHaveBeenCalledTimes(1); - expect(props.actions.popToRoot).toHaveBeenCalledWith(props.componentId); + expect(close).toHaveBeenCalledTimes(1); }); }); diff --git a/app/store/index.js b/app/store/index.js index be1155865..ec5b0c483 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -1,301 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {batchActions} from 'redux-batched-actions'; -import {Platform} from 'react-native'; -import AsyncStorage from '@react-native-community/async-storage'; -import {createBlacklistFilter} from 'redux-persist-transform-filter'; -import {createTransform, persistStore} from 'redux-persist'; +import initialState from 'app/initial_state'; +import configureAppStore from './store'; -import {ErrorTypes, GeneralTypes} from 'mattermost-redux/action_types'; -import {General, RequestStatus} from 'mattermost-redux/constants'; -import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import configureStore from 'mattermost-redux/store'; -import EventEmitter from 'mattermost-redux/utils/event_emitter'; +const store = configureAppStore(initialState); -import {NavigationTypes, ViewTypes} from 'app/constants'; -import appReducer from 'app/reducers'; -import {throttle} from 'app/utils/general'; -import {getSiteUrl, setSiteUrl} from 'app/utils/image_cache_manager'; -import {createSentryMiddleware} from 'app/utils/sentry/middleware'; - -import mattermostBucket from 'app/mattermost_bucket'; - -import {messageRetention} from './middleware'; -import {createThunkMiddleware} from './thunk'; -import {transformSet} from './utils'; - -function getAppReducer() { - return require('../../app/reducers'); // eslint-disable-line global-require -} - -const usersSetTransform = [ - 'profilesInChannel', - 'profilesNotInChannel', - 'profilesInTeam', - 'profilesNotInTeam', -]; - -const channelSetTransform = [ - 'channelsInTeam', -]; - -const rolesSetTransform = [ - 'pending', -]; - -const setTransforms = [ - ...usersSetTransform, - ...channelSetTransform, - ...rolesSetTransform, -]; - -export default function configureAppStore(initialState) { - const viewsBlackListFilter = createBlacklistFilter( - 'views', - ['extension', 'login', 'root'] - ); - - const typingBlackListFilter = createBlacklistFilter( - 'entities', - ['typing'] - ); - - const channelViewBlackList = {loading: true, refreshing: true, loadingPosts: true, postVisibility: true, retryFailed: true, loadMorePostsVisible: true}; - const channelViewBlackListFilter = createTransform( - (inboundState) => { - const channel = {}; - - for (const channelKey of Object.keys(inboundState.channel)) { - if (!channelViewBlackList[channelKey]) { - channel[channelKey] = inboundState.channel[channelKey]; - } - } - - return { - ...inboundState, - channel, - }; - }, - null, - {whitelist: ['views']} // Only run this filter on the views state (or any other entry that ends up being named views) - ); - - const emojiBlackList = {nonExistentEmoji: true}; - const emojiBlackListFilter = createTransform( - (inboundState) => { - const emojis = {}; - - for (const emojiKey of Object.keys(inboundState.emojis)) { - if (!emojiBlackList[emojiKey]) { - emojis[emojiKey] = inboundState.emojis[emojiKey]; - } - } - - return { - ...inboundState, - emojis, - }; - }, - null, - {whitelist: ['entities']} // Only run this filter on the entities state (or any other entry that ends up being named entities) - ); - - const setTransformer = createTransform( - (inboundState, key) => { - if (key === 'entities') { - const state = {...inboundState}; - for (const prop in state) { - if (state.hasOwnProperty(prop)) { - state[prop] = transformSet(state[prop], setTransforms); - } - } - - return state; - } - - return inboundState; - }, - (outboundState, key) => { - if (key === 'entities') { - const state = {...outboundState}; - for (const prop in state) { - if (state.hasOwnProperty(prop)) { - state[prop] = transformSet(state[prop], setTransforms, false); - } - } - - return state; - } - - return outboundState; - } - ); - - const offlineOptions = { - effect: (effect, action) => { - if (typeof effect !== 'function') { - throw new Error('Offline Action: effect must be a function.'); - } else if (!action.meta.offline.commit) { - throw new Error('Offline Action: commit action must be present.'); - } - - return effect(); - }, - persist: (store, options) => { - const persistor = persistStore(store, {storage: AsyncStorage, ...options}, () => { - store.dispatch({ - type: General.STORE_REHYDRATION_COMPLETE, - }); - }); - - let purging = false; - - // for iOS write the entities to a shared file - if (Platform.OS === 'ios') { - store.subscribe(throttle(() => { - const state = store.getState(); - if (state.entities) { - const channelsInTeam = {...state.entities.channels.channelsInTeam}; - Object.keys(channelsInTeam).forEach((teamId) => { - channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]); - }); - - const profilesInChannel = {...state.entities.users.profilesInChannel}; - Object.keys(profilesInChannel).forEach((channelId) => { - profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]); - }); - - const entities = { - ...state.entities, - channels: { - ...state.entities.channels, - channelsInTeam, - }, - users: { - ...state.entities.users, - profilesInChannel, - profilesNotInTeam: [], - profilesWithoutTeam: [], - profilesNotInChannel: [], - }, - }; - mattermostBucket.writeToFile('entities', JSON.stringify(entities)); - } - }, 1000)); - } - - // check to see if the logout request was successful - store.subscribe(async () => { - const state = store.getState(); - const config = getConfig(state); - - if (getSiteUrl() !== config?.SiteURL) { - setSiteUrl(config.SiteURL); - } - - if ((state.requests.users.logout.status === RequestStatus.SUCCESS || state.requests.users.logout.status === RequestStatus.FAILURE) && !purging) { - purging = true; - - await persistor.purge(); - - store.dispatch(batchActions([ - { - type: General.OFFLINE_STORE_RESET, - data: initialState, - }, - { - type: General.STORE_REHYDRATION_COMPLETE, - }, - { - type: ViewTypes.SERVER_URL_CHANGED, - serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl, - }, - { - type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, - data: state.entities.general.deviceToken, - }, - ])); - - // When logging out remove the data stored in the bucket - mattermostBucket.removePreference('cert'); - mattermostBucket.removePreference('emm'); - mattermostBucket.removeFile('entities'); - setSiteUrl(null); - - setTimeout(() => { - purging = false; - }, 500); - } else if (state.views.root.purge && !purging) { - purging = true; - - await persistor.purge(); - - store.dispatch(batchActions([ - { - type: General.OFFLINE_STORE_RESET, - data: initialState, - }, - { - type: ErrorTypes.RESTORE_ERRORS, - data: [...state.errors], - }, - { - type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, - data: state.entities.general.deviceToken, - }, - { - type: GeneralTypes.RECEIVED_APP_CREDENTIALS, - data: { - url: state.entities.general.credentials.url, - }, - }, - { - type: ViewTypes.SERVER_URL_CHANGED, - serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl, - }, - { - type: GeneralTypes.RECEIVED_SERVER_VERSION, - data: state.entities.general.serverVersion, - }, - { - type: General.STORE_REHYDRATION_COMPLETE, - }, - ], 'BATCH_FOR_RESTART')); - - setTimeout(() => { - purging = false; - EventEmitter.emit(NavigationTypes.RESTART_APP); - }, 500); - } - }); - - return persistor; - }, - persistOptions: { - autoRehydrate: { - log: false, - }, - blacklist: ['device', 'navigation', 'offline', 'requests'], - debounce: 500, - transforms: [ - setTransformer, - viewsBlackListFilter, - typingBlackListFilter, - channelViewBlackListFilter, - emojiBlackListFilter, - ], - }, - }; - - const clientOptions = { - additionalMiddleware: [ - createThunkMiddleware(), - createSentryMiddleware(), - messageRetention, - ], - enableThunk: false, // We override the default thunk middleware - }; - - return configureStore(initialState, appReducer, offlineOptions, getAppReducer, clientOptions); -} +export default store; diff --git a/app/store/store.js b/app/store/store.js new file mode 100644 index 000000000..be1155865 --- /dev/null +++ b/app/store/store.js @@ -0,0 +1,301 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {batchActions} from 'redux-batched-actions'; +import {Platform} from 'react-native'; +import AsyncStorage from '@react-native-community/async-storage'; +import {createBlacklistFilter} from 'redux-persist-transform-filter'; +import {createTransform, persistStore} from 'redux-persist'; + +import {ErrorTypes, GeneralTypes} from 'mattermost-redux/action_types'; +import {General, RequestStatus} from 'mattermost-redux/constants'; +import {getConfig} from 'mattermost-redux/selectors/entities/general'; +import configureStore from 'mattermost-redux/store'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + +import {NavigationTypes, ViewTypes} from 'app/constants'; +import appReducer from 'app/reducers'; +import {throttle} from 'app/utils/general'; +import {getSiteUrl, setSiteUrl} from 'app/utils/image_cache_manager'; +import {createSentryMiddleware} from 'app/utils/sentry/middleware'; + +import mattermostBucket from 'app/mattermost_bucket'; + +import {messageRetention} from './middleware'; +import {createThunkMiddleware} from './thunk'; +import {transformSet} from './utils'; + +function getAppReducer() { + return require('../../app/reducers'); // eslint-disable-line global-require +} + +const usersSetTransform = [ + 'profilesInChannel', + 'profilesNotInChannel', + 'profilesInTeam', + 'profilesNotInTeam', +]; + +const channelSetTransform = [ + 'channelsInTeam', +]; + +const rolesSetTransform = [ + 'pending', +]; + +const setTransforms = [ + ...usersSetTransform, + ...channelSetTransform, + ...rolesSetTransform, +]; + +export default function configureAppStore(initialState) { + const viewsBlackListFilter = createBlacklistFilter( + 'views', + ['extension', 'login', 'root'] + ); + + const typingBlackListFilter = createBlacklistFilter( + 'entities', + ['typing'] + ); + + const channelViewBlackList = {loading: true, refreshing: true, loadingPosts: true, postVisibility: true, retryFailed: true, loadMorePostsVisible: true}; + const channelViewBlackListFilter = createTransform( + (inboundState) => { + const channel = {}; + + for (const channelKey of Object.keys(inboundState.channel)) { + if (!channelViewBlackList[channelKey]) { + channel[channelKey] = inboundState.channel[channelKey]; + } + } + + return { + ...inboundState, + channel, + }; + }, + null, + {whitelist: ['views']} // Only run this filter on the views state (or any other entry that ends up being named views) + ); + + const emojiBlackList = {nonExistentEmoji: true}; + const emojiBlackListFilter = createTransform( + (inboundState) => { + const emojis = {}; + + for (const emojiKey of Object.keys(inboundState.emojis)) { + if (!emojiBlackList[emojiKey]) { + emojis[emojiKey] = inboundState.emojis[emojiKey]; + } + } + + return { + ...inboundState, + emojis, + }; + }, + null, + {whitelist: ['entities']} // Only run this filter on the entities state (or any other entry that ends up being named entities) + ); + + const setTransformer = createTransform( + (inboundState, key) => { + if (key === 'entities') { + const state = {...inboundState}; + for (const prop in state) { + if (state.hasOwnProperty(prop)) { + state[prop] = transformSet(state[prop], setTransforms); + } + } + + return state; + } + + return inboundState; + }, + (outboundState, key) => { + if (key === 'entities') { + const state = {...outboundState}; + for (const prop in state) { + if (state.hasOwnProperty(prop)) { + state[prop] = transformSet(state[prop], setTransforms, false); + } + } + + return state; + } + + return outboundState; + } + ); + + const offlineOptions = { + effect: (effect, action) => { + if (typeof effect !== 'function') { + throw new Error('Offline Action: effect must be a function.'); + } else if (!action.meta.offline.commit) { + throw new Error('Offline Action: commit action must be present.'); + } + + return effect(); + }, + persist: (store, options) => { + const persistor = persistStore(store, {storage: AsyncStorage, ...options}, () => { + store.dispatch({ + type: General.STORE_REHYDRATION_COMPLETE, + }); + }); + + let purging = false; + + // for iOS write the entities to a shared file + if (Platform.OS === 'ios') { + store.subscribe(throttle(() => { + const state = store.getState(); + if (state.entities) { + const channelsInTeam = {...state.entities.channels.channelsInTeam}; + Object.keys(channelsInTeam).forEach((teamId) => { + channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]); + }); + + const profilesInChannel = {...state.entities.users.profilesInChannel}; + Object.keys(profilesInChannel).forEach((channelId) => { + profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]); + }); + + const entities = { + ...state.entities, + channels: { + ...state.entities.channels, + channelsInTeam, + }, + users: { + ...state.entities.users, + profilesInChannel, + profilesNotInTeam: [], + profilesWithoutTeam: [], + profilesNotInChannel: [], + }, + }; + mattermostBucket.writeToFile('entities', JSON.stringify(entities)); + } + }, 1000)); + } + + // check to see if the logout request was successful + store.subscribe(async () => { + const state = store.getState(); + const config = getConfig(state); + + if (getSiteUrl() !== config?.SiteURL) { + setSiteUrl(config.SiteURL); + } + + if ((state.requests.users.logout.status === RequestStatus.SUCCESS || state.requests.users.logout.status === RequestStatus.FAILURE) && !purging) { + purging = true; + + await persistor.purge(); + + store.dispatch(batchActions([ + { + type: General.OFFLINE_STORE_RESET, + data: initialState, + }, + { + type: General.STORE_REHYDRATION_COMPLETE, + }, + { + type: ViewTypes.SERVER_URL_CHANGED, + serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl, + }, + { + type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, + data: state.entities.general.deviceToken, + }, + ])); + + // When logging out remove the data stored in the bucket + mattermostBucket.removePreference('cert'); + mattermostBucket.removePreference('emm'); + mattermostBucket.removeFile('entities'); + setSiteUrl(null); + + setTimeout(() => { + purging = false; + }, 500); + } else if (state.views.root.purge && !purging) { + purging = true; + + await persistor.purge(); + + store.dispatch(batchActions([ + { + type: General.OFFLINE_STORE_RESET, + data: initialState, + }, + { + type: ErrorTypes.RESTORE_ERRORS, + data: [...state.errors], + }, + { + type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, + data: state.entities.general.deviceToken, + }, + { + type: GeneralTypes.RECEIVED_APP_CREDENTIALS, + data: { + url: state.entities.general.credentials.url, + }, + }, + { + type: ViewTypes.SERVER_URL_CHANGED, + serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl, + }, + { + type: GeneralTypes.RECEIVED_SERVER_VERSION, + data: state.entities.general.serverVersion, + }, + { + type: General.STORE_REHYDRATION_COMPLETE, + }, + ], 'BATCH_FOR_RESTART')); + + setTimeout(() => { + purging = false; + EventEmitter.emit(NavigationTypes.RESTART_APP); + }, 500); + } + }); + + return persistor; + }, + persistOptions: { + autoRehydrate: { + log: false, + }, + blacklist: ['device', 'navigation', 'offline', 'requests'], + debounce: 500, + transforms: [ + setTransformer, + viewsBlackListFilter, + typingBlackListFilter, + channelViewBlackListFilter, + emojiBlackListFilter, + ], + }, + }; + + const clientOptions = { + additionalMiddleware: [ + createThunkMiddleware(), + createSentryMiddleware(), + messageRetention, + ], + enableThunk: false, // We override the default thunk middleware + }; + + return configureStore(initialState, appReducer, offlineOptions, getAppReducer, clientOptions); +} diff --git a/app/telemetry/telemetry.android.js b/app/telemetry/telemetry.android.js index cd6dd4fc4..903d6543b 100644 --- a/app/telemetry/telemetry.android.js +++ b/app/telemetry/telemetry.android.js @@ -3,7 +3,7 @@ import LocalConfig from 'assets/config'; // eslint-disable-line -import {store} from 'app/mattermost'; +import store from 'app/store'; import { saveToTelemetryServer, diff --git a/app/utils/bottom_sheet/bottom_sheet.android.js b/app/utils/bottom_sheet/bottom_sheet.android.js index e45100b26..2a1548527 100644 --- a/app/utils/bottom_sheet/bottom_sheet.android.js +++ b/app/utils/bottom_sheet/bottom_sheet.android.js @@ -1,7 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {store} from 'app/mattermost'; import {showModalOverCurrentContext} from 'app/actions/navigation'; export default { @@ -15,6 +14,6 @@ export default { text: o, })); - store.dispatch(showModalOverCurrentContext('OptionsModal', {title: '', items})); + showModalOverCurrentContext('OptionsModal', {title: '', items}); }, }; diff --git a/app/utils/images.js b/app/utils/images.js index c231e2ca2..104779914 100644 --- a/app/utils/images.js +++ b/app/utils/images.js @@ -7,6 +7,7 @@ import { IMAGE_MAX_HEIGHT, IMAGE_MIN_DIMENSION, } from 'app/constants/image'; +import {showModalOverCurrentContext} from 'app/actions/navigation'; let previewComponents; @@ -57,7 +58,7 @@ export const calculateDimensions = (height, width, viewPortWidth = 0, viewPortHe }; }; -export function previewImageAtIndex(components, index, files, showModalOverCurrentContext) { +export function previewImageAtIndex(components, index, files) { previewComponents = components; const component = components[index]; if (component) { diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index 3a7529959..84e726294 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -16,6 +16,7 @@ import { loadFromPushNotification, } from 'app/actions/views/root'; import {dismissAllModals, popToRoot} from 'app/actions/navigation'; + import {ViewTypes} from 'app/constants'; import {getLocalizedMessage} from 'app/i18n'; import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials'; @@ -54,9 +55,8 @@ class PushNotificationUtils { EventEmitter.emit('close_channel_drawer'); EventEmitter.emit('close_settings_sidebar'); - const {dispatch} = this.store; - dispatch(dismissAllModals()); - dispatch(popToRoot()); + await dismissAllModals(); + await popToRoot(); PushNotifications.resetNotification(); } diff --git a/app/utils/theme.js b/app/utils/theme.js index 8d21d9aef..ee4ea9cba 100644 --- a/app/utils/theme.js +++ b/app/utils/theme.js @@ -2,12 +2,12 @@ // See LICENSE.txt for license information. import {StyleSheet} from 'react-native'; -import {Navigation} from 'react-native-navigation'; - import tinyColor from 'tinycolor2'; import * as ThemeUtils from 'mattermost-redux/utils/theme_utils'; +import {mergeNavigationOptions} from 'app/actions/navigation'; + export function makeStyleSheetFromTheme(getStyleFromTheme) { return ThemeUtils.makeStyleFromTheme((theme) => { return StyleSheet.create(getStyleFromTheme(theme)); @@ -23,7 +23,7 @@ export function concatStyles(...styles) { } export function setNavigatorStyles(componentId, theme) { - Navigation.mergeOptions(componentId, { + const options = { topBar: { title: { color: theme.sidebarHeaderTextColor, @@ -40,7 +40,9 @@ export function setNavigatorStyles(componentId, theme) { layout: { backgroundColor: theme.centerChannelBg, }, - }); + }; + + mergeNavigationOptions(componentId, options); } export function isThemeSwitchingEnabled(state) { diff --git a/package-lock.json b/package-lock.json index 7ff0e53b7..7f058c820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5612,7 +5612,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -5657,8 +5656,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "4.0.1", @@ -8446,8 +8444,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -8468,14 +8465,12 @@ "balanced-match": { "version": "1.0.0", "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8490,20 +8485,17 @@ "code-point-at": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -8620,8 +8612,7 @@ "inherits": { "version": "2.0.3", "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -8633,7 +8624,6 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8648,7 +8638,6 @@ "version": "3.0.4", "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8656,14 +8645,12 @@ "minimist": { "version": "0.0.8", "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8682,7 +8669,6 @@ "version": "0.5.1", "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8763,8 +8749,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -8776,7 +8761,6 @@ "version": "1.4.0", "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -8862,8 +8846,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -8899,7 +8882,6 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8919,7 +8901,6 @@ "version": "3.0.1", "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8963,14 +8944,12 @@ "wrappy": { "version": "1.0.2", "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, @@ -17099,8 +17078,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true + "dev": true }, "braces": { "version": "2.3.2", @@ -17363,8 +17341,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true, - "optional": true + "dev": true }, "micromatch": { "version": "3.1.10", diff --git a/native_modules/react-native-haptic-feedback+1.8.2.patch b/patches/react-native-haptic-feedback+1.8.2.patch similarity index 100% rename from native_modules/react-native-haptic-feedback+1.8.2.patch rename to patches/react-native-haptic-feedback+1.8.2.patch diff --git a/patches/react-native-navigation+2.21.1.patch b/patches/react-native-navigation+2.21.1.patch new file mode 100644 index 000000000..842f41e72 --- /dev/null +++ b/patches/react-native-navigation+2.21.1.patch @@ -0,0 +1,102 @@ +diff --git a/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m b/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m +index 91b878d..9cd61d5 100644 +--- a/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m ++++ b/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m +@@ -72,7 +72,7 @@ - (void)setRoot:(NSDictionary*)layout commandId:(NSString*)commandId completion: + } + } + +- [_modalManager dismissAllModalsAnimated:NO]; ++ [_modalManager dismissAllModalsAnimated:NO completion:nil]; + + UIViewController *vc = [_controllerFactory createLayout:layout[@"root"]]; + +@@ -286,10 +286,9 @@ - (void)dismissAllModals:(NSDictionary *)mergeOptions commandId:(NSString*)comma + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + [_eventEmitter sendOnNavigationCommandCompletion:dismissAllModals commandId:commandId params:@{}]; +- completion(); + }]; + RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:mergeOptions]; +- [_modalManager dismissAllModalsAnimated:[options.animations.dismissModal.enable getWithDefaultValue:YES]]; ++ [_modalManager dismissAllModalsAnimated:[options.animations.dismissModal.enable getWithDefaultValue:YES] completion:completion]; + + [CATransaction commit]; + } +diff --git a/node_modules/react-native-navigation/lib/ios/RNNModalManager.h b/node_modules/react-native-navigation/lib/ios/RNNModalManager.h +index 9809db0..a602c92 100644 +--- a/node_modules/react-native-navigation/lib/ios/RNNModalManager.h ++++ b/node_modules/react-native-navigation/lib/ios/RNNModalManager.h +@@ -19,6 +19,6 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, N + - (void)showModal:(UIViewController *)viewController animated:(BOOL)animated completion:(RNNTransitionWithComponentIdCompletionBlock)completion; + - (void)showModal:(UIViewController *)viewController animated:(BOOL)animated hasCustomAnimation:(BOOL)hasCustomAnimation completion:(RNNTransitionWithComponentIdCompletionBlock)completion; + - (void)dismissModal:(UIViewController *)viewController completion:(RNNTransitionCompletionBlock)completion; +-- (void)dismissAllModalsAnimated:(BOOL)animated; ++- (void)dismissAllModalsAnimated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion; + + @end +diff --git a/node_modules/react-native-navigation/lib/ios/RNNModalManager.m b/node_modules/react-native-navigation/lib/ios/RNNModalManager.m +index 18c0eb8..a404da7 100644 +--- a/node_modules/react-native-navigation/lib/ios/RNNModalManager.m ++++ b/node_modules/react-native-navigation/lib/ios/RNNModalManager.m +@@ -50,9 +50,16 @@ - (void)dismissModal:(UIViewController *)viewController completion:(RNNTransitio + } + } + +--(void)dismissAllModalsAnimated:(BOOL)animated { ++-(void)dismissAllModalsAnimated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion { ++ if (!_presentedModals || !_presentedModals.count) { ++ if (completion) { ++ completion(); ++ } ++ return; ++ } ++ + UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController; +- [root dismissViewControllerAnimated:animated completion:nil]; ++ [root dismissViewControllerAnimated:animated completion:completion]; + [_delegate dismissedMultipleModals:_presentedModals]; + [_pendingModalIdsToDismiss removeAllObjects]; + [_presentedModals removeAllObjects]; +diff --git a/node_modules/react-native-navigation/lib/ios/ReactNativeNavigation.xcodeproj/xcuserdata/miguel.xcuserdatad/xcschemes/xcschememanagement.plist b/node_modules/react-native-navigation/lib/ios/ReactNativeNavigation.xcodeproj/xcuserdata/miguel.xcuserdatad/xcschemes/xcschememanagement.plist +new file mode 100644 +index 0000000..a2f2607 +--- /dev/null ++++ b/node_modules/react-native-navigation/lib/ios/ReactNativeNavigation.xcodeproj/xcuserdata/miguel.xcuserdatad/xcschemes/xcschememanagement.plist +@@ -0,0 +1,14 @@ ++ ++ ++ ++ ++ SchemeUserState ++ ++ ReactNativeNavigation.xcscheme_^#shared#^_ ++ ++ orderHint ++ 6 ++ ++ ++ ++ +diff --git a/node_modules/react-native-navigation/lib/ios/ReactNativeNavigationTests/RNNModalManagerTest.m b/node_modules/react-native-navigation/lib/ios/ReactNativeNavigationTests/RNNModalManagerTest.m +index 407051a..8f02aae 100644 +--- a/node_modules/react-native-navigation/lib/ios/ReactNativeNavigationTests/RNNModalManagerTest.m ++++ b/node_modules/react-native-navigation/lib/ios/ReactNativeNavigationTests/RNNModalManagerTest.m +@@ -51,7 +51,7 @@ - (void)testDismissMultipleModalsInvokeDelegateWithCorrectParameters { + [_modalManager showModal:_vc3 animated:NO completion:nil]; + + _modalManager.delegate = self; +- [_modalManager dismissAllModalsAnimated:NO]; ++ [_modalManager dismissAllModalsAnimated:NO completion:nil]; + + XCTAssertTrue(_modalDismissedCount == 3); + } +@@ -87,7 +87,7 @@ - (void)testDismissAllModals_AfterDismissingPreviousModal_InvokeDelegateWithCorr + [_modalManager dismissModal:_vc2 completion:nil]; + + XCTAssertTrue(_modalDismissedCount == 1); +- [_modalManager dismissAllModalsAnimated:NO]; ++ [_modalManager dismissAllModalsAnimated:NO completion:nil]; + XCTAssertTrue(_modalDismissedCount == 2); + } + diff --git a/share_extension/android/index.js b/share_extension/android/index.js index 305a28627..f865125ce 100644 --- a/share_extension/android/index.js +++ b/share_extension/android/index.js @@ -7,7 +7,7 @@ import {IntlProvider} from 'react-intl'; import {getTranslations} from 'app/i18n'; import {getCurrentLocale} from 'app/selectors/i18n'; -import {store} from 'app/mattermost'; +import store from 'app/store'; import {extensionSelectTeamId} from './actions'; import Extension from './extension'; diff --git a/test/setup.js b/test/setup.js index 48a6ea4e4..8dfb3aa00 100644 --- a/test/setup.js +++ b/test/setup.js @@ -9,6 +9,7 @@ configure({adapter: new Adapter()}); const mockImpl = new MockAsyncStorage(); jest.mock('@react-native-community/async-storage', () => mockImpl); global.window = {}; +global.fetch = jest.fn(() => Promise.resolve()); /* eslint-disable no-console */ @@ -72,6 +73,49 @@ jest.mock('react-native-cookies', () => ({ getInitialURL: jest.fn(), })); +jest.mock('react-native-navigation', () => { + const RNN = require.requireActual('react-native-navigation'); + return { + ...RNN, + Navigation: { + ...RNN.Navigation, + events: () => ({ + registerAppLaunchedListener: jest.fn(), + bindComponent: jest.fn(), + }), + setRoot: jest.fn(), + pop: jest.fn(), + push: jest.fn(), + showModal: jest.fn(), + dismissModal: jest.fn(), + dismissAllModals: jest.fn(), + popToRoot: jest.fn(), + mergeOptions: jest.fn(), + showOverlay: jest.fn(), + dismissOverlay: jest.fn(), + }, + }; +}); + +jest.mock('app/actions/navigation', () => ({ + resetToChannel: jest.fn(), + resetToSelectServer: jest.fn(), + resetToTeams: jest.fn(), + goToScreen: jest.fn(), + popTopScreen: jest.fn(), + showModal: jest.fn(), + showModalOverCurrentContext: jest.fn(), + showSearchModal: jest.fn(), + peek: jest.fn(), + setButtons: jest.fn(), + showOverlay: jest.fn(), + mergeNavigationOptions: jest.fn(), + popToRoot: jest.fn(() => Promise.resolve()), + dismissModal: jest.fn(() => Promise.resolve()), + dismissAllModals: jest.fn(() => Promise.resolve()), + dismissOverlay: jest.fn(() => Promise.resolve()), +})); + let logs; let warns; let errors;