[MM-16139] [MM-16140] [MM-16141] Update the remaining screens/components (#2920)

* Update screens

* Update login tests

* Remove done

* Fix failing tests

* Update screens and components

* Check styles fix

* Update tests

* Prevent setState call after component unmounts

* Add empty setButtons func to dummy navigator

* Remove platform check

* Remove Platform import

* Update react-native-navigation version

* Add separate showModalOverCurrentContext function

* check-style fixes

* Remove overriding of AppDelegate's window

* Fix modal over current context animation

* Add showSearchModal navigation action

* Check-style fix

* Address review comments

* Update SettingsSidebar and children

* Update EditProfile screen

* Update SettingsSidebar

* Keep track of latest componentId to appear

* Track componentId in state to use in navigation actions

* Update FlaggedPosts and children

* Update RecentMentions

* Update Settings

* Store componentIds in ephemeral store

* Update AttachmentButton

* Remove unnecessary dismissModal

* Fix typo

* Upate NotificationSettings

* Update NotificationSettingsAutoResponder

* Update NotificationSettingsMentions

* Update NotificationSettingsMobile

* Update CreateChannel and children

* Update MoreChannels

* Update ChannelPeek and children

* Update ChannelNavBar and children

* Update ChannelPostList and children

* Update ChannelInfo and children

* Update ChannelAddMembers

* Update ChannelMembers

* Update EditChannel

* Fix setting of top bar buttons

* Update Channel screen and children

* Update PinnedPosts

* Update LongPost

* Update PostOptions

* Update PostTextboxBase

* Update EditPost

* Check-styles fix

* Update DisplaySettings

* Update Timezone

* Update SelectTimezone

* Update IntlWrapper and Notification

* Update InteractiveDialog and children

* Update ExpandedAnnouncementBanner

* Update ClientUpgradeListener

* Update MoreDirectMessages

* Update SelectorScreen

* Update UserProfile

* Update Thread

* Update About

* Update ImagePreview

* Update TextPreview

* Update AddReaction

* Update ReactionList and children

* Update Code

* Update TermsOfService

* Update Permalink

* Update Permalink

* Update ErrorTeamsList

* Update Search

* Remove navigator prop

* Fix setButtons calls

* Remove navigationComponentId

* Fix test

* Handle in-app notifs in global event handler

* Fix in-app notification handling

* Fix typo

* Fix tests

* Auto dismiss in-app notif after 5 seconds

* Fix typo

* Update overlay dismissal

* Animate in-app notif dismissal and allow gesture dismissal
This commit is contained in:
Miguel Alatzar 2019-07-08 10:03:31 -07:00 committed by GitHub
parent 1b9b1e3f39
commit 5eee2b7652
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 598 additions and 899 deletions

View file

@ -178,7 +178,12 @@ export function popToRoot() {
return () => {
const componentId = EphemeralStore.getTopComponentId();
Navigation.popToRoot(componentId);
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.
});
};
}
@ -289,7 +294,11 @@ export function dismissModal(options = {}) {
export function dismissAllModals(options = {}) {
return () => {
Navigation.dismissAllModals(options);
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.
});
};
}
@ -321,3 +330,32 @@ export function setButtons(componentId, buttons = {leftButtons: [], rightButtons
});
};
}
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.
});
};
}

View file

@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {ViewTypes} from 'app/constants';
export function handleSearchDraftChanged(text) {
@ -13,27 +11,3 @@ export function handleSearchDraftChanged(text) {
}, getState);
};
}
// TODO: Remove this once all screens/components call
// app/actions/navigation's showSearchModal
export function showSearchModal(navigator, initialValue = '') {
return (dispatch, getState) => {
const theme = getTheme(getState());
const options = {
screen: 'Search',
animated: true,
backButtonTitle: '',
overrideBackPress: true,
passProps: {
initialValue,
},
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: theme.centerChannelBg,
},
};
navigator.showModal(options);
};
}

View file

@ -9,13 +9,6 @@ exports[`profile_picture_button should match snapshot 1`] = `
]
}
maxFileSize={20971520}
navigator={
Object {
"dismissModal": [MockFunction],
"push": [MockFunction],
"setButtons": [MockFunction],
}
}
theme={
Object {
"awayIndicator": "#ffbc42",

View file

@ -27,6 +27,8 @@ 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,
@ -35,7 +37,6 @@ export default class ClientUpgradeListener extends PureComponent {
lastUpgradeCheck: PropTypes.number,
latestVersion: PropTypes.string,
minVersion: PropTypes.string,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired,
};
@ -140,27 +141,26 @@ export default class ClientUpgradeListener extends PureComponent {
};
handleLearnMore = () => {
const {actions} = this.props;
const {intl} = this.context;
this.props.navigator.dismissModal({animationType: 'none'});
this.props.navigator.showModal({
screen: 'ClientUpgrade',
title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}),
navigatorStyle: {
navBarHidden: false,
statusBarHidden: false,
statusBarHideWithNavBar: false,
},
navigatorButtons: {
actions.dismissModal();
const screen = 'ClientUpgrade';
const title = intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'});
const passProps = {
upgradeType: this.state.upgradeType,
};
const options = {
topBar: {
leftButtons: [{
id: 'close-upgrade',
icon: this.closeButton,
}],
},
passProps: {
upgradeType: this.state.upgradeType,
},
});
};
actions.showModal(screen, title, passProps, options);
this.toggleUpgradeMessage(false);
};

View file

@ -4,11 +4,12 @@ import {bindActionCreators} from 'redux';
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';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import ClientUpgradeListener from './client_upgrade_listener';
@ -32,6 +33,8 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
logError,
setLastUpgradeCheck,
showModal,
dismissModal,
}, dispatch),
};
}

View file

@ -18,7 +18,6 @@ export default class MessageAttachments extends PureComponent {
deviceWidth: PropTypes.number.isRequired,
postId: PropTypes.string.isRequired,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
theme: PropTypes.object,
@ -33,7 +32,6 @@ export default class MessageAttachments extends PureComponent {
deviceHeight,
deviceWidth,
metadata,
navigator,
onHashtagPress,
onPermalinkPress,
postId,
@ -52,7 +50,6 @@ export default class MessageAttachments extends PureComponent {
deviceWidth={deviceWidth}
key={'att_' + i}
metadata={metadata}
navigator={navigator}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}
postId={postId}

View file

@ -37,7 +37,6 @@ describe('PostAttachmentOpenGraph', () => {
},
isReplyPost: false,
link: 'https://mattermost.com/',
navigator: {},
theme: Preferences.THEMES.default,
};

View file

@ -16,23 +16,11 @@ exports[`PostTextBox should match, full snapshot 1`] = `
}
}
>
<AttachmentButton
<Connect(AttachmentButton)
blurTextBox={[Function]}
browseFileTypes="public.item"
canBrowseFiles={true}
canBrowsePhotoLibrary={true}
canBrowseVideoLibrary={true}
canTakePhoto={true}
canTakeVideo={true}
extraOptions={null}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
navigator={
Object {
"showModal": [MockFunction],
}
}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
@ -64,7 +52,6 @@ exports[`PostTextBox should match, full snapshot 1`] = `
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
<View
style={

View file

@ -38,9 +38,6 @@ describe('PostTextBox', () => {
files: [],
maxFileSize: 1024,
maxMessageLength: 4000,
navigator: {
showModal: jest.fn(),
},
rootId: '',
theme: Preferences.THEMES.default,
uploadFileRequestStatus: 'NOT_STARTED',

View file

@ -10,15 +10,8 @@ import ProfilePictureButton from './profile_picture_button.js';
import {Client4} from 'mattermost-redux/client';
describe('profile_picture_button', () => {
const navigator = {
setButtons: jest.fn(),
dismissModal: jest.fn(),
push: jest.fn(),
};
const baseProps = {
theme: Preferences.THEMES.default,
navigator,
currentUser: {
first_name: 'Dwight',
last_name: 'Schrute',

View file

@ -4,7 +4,6 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@ -19,7 +18,6 @@ function mapStateToProps(state) {
return {
theme: getTheme(state),
currentChannelId: getCurrentChannelId(state),
currentUrl: removeProtocol(getCurrentUrl(state)),
locale,
};

View file

@ -9,7 +9,7 @@ import {Platform} from 'react-native';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {NavigationTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
export default class Root extends PureComponent {
@ -18,9 +18,7 @@ export default class Root extends PureComponent {
resetToTeams: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.node,
navigator: PropTypes.object,
excludeEvents: PropTypes.bool,
currentChannelId: PropTypes.string,
currentUrl: PropTypes.string,
locale: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
@ -30,8 +28,6 @@ export default class Root extends PureComponent {
Client4.setAcceptLanguage(this.props.locale);
if (!this.props.excludeEvents) {
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
EventEmitter.on(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
EventEmitter.on(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
EventEmitter.on(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
}
@ -45,30 +41,11 @@ export default class Root extends PureComponent {
componentWillUnmount() {
if (!this.props.excludeEvents) {
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
EventEmitter.off(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
EventEmitter.off(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
EventEmitter.off(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
}
}
handleInAppNotification = (notification) => {
const {data} = notification;
const {currentChannelId, navigator} = this.props;
if (data && data.channel_id !== currentChannelId) {
navigator.showInAppNotification({
screen: 'Notification',
position: 'top',
autoDismissTimerSec: 5,
dismissWithSwipe: true,
passProps: {
notification,
},
});
}
};
handleNoTeams = () => {
if (!this.refs.provider) {
setTimeout(this.handleNoTeams, 200);
@ -119,18 +96,6 @@ export default class Root extends PureComponent {
actions.resetToTeams(screen, title, passProps, options);
}
handleNotificationTapped = async () => {
const {navigator} = this.props;
if (Platform.OS === 'android') {
navigator.dismissModal({animation: 'none'});
}
navigator.popToRoot({
animated: false,
});
};
render() {
const locale = this.props.locale;

View file

@ -31,7 +31,6 @@ describe('ChannelItem', () => {
isUnread: true,
hasDraft: false,
mentions: 0,
navigator: {push: () => {}}, // eslint-disable-line no-empty-function
onSelectChannel: () => {}, // eslint-disable-line no-empty-function
shouldHideChannel: false,
showUnreadForMsgs: true,

View file

@ -68,10 +68,12 @@ export default class SettingsDrawer extends PureComponent {
}
componentDidMount() {
EventEmitter.on('close_settings_sidebar', this.closeSettingsSidebar);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
}
componentWillUnmount() {
EventEmitter.off('close_settings_sidebar', this.closeSettingsSidebar);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
}

View file

@ -9,6 +9,7 @@ const NavigationTypes = keyMirror({
NAVIGATION_NO_TEAMS: null,
RESTART_APP: null,
NAVIGATION_ERROR_TEAMS: null,
NAVIGATION_SHOW_OVERLAY: null,
});
export default NavigationTypes;

View file

@ -42,7 +42,6 @@ const ViewTypes = keyMirror({
COMMENT_DRAFT_SELECTION_CHANGED: null,
NOTIFICATION_IN_APP: null,
NOTIFICATION_TAPPED: null,
SET_POST_DRAFT: null,
SET_COMMENT_DRAFT: null,

View file

@ -11,11 +11,13 @@ import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
import {selectDefaultChannel} from 'app/actions/views/channel';
import {showOverlay} from 'app/actions/navigation';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
import {NavigationTypes} from 'app/constants';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
import mattermostManaged from 'app/mattermost_managed';
import PushNotifications from 'app/push_notifications';
@ -38,12 +40,15 @@ class GlobalEventHandler {
EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged);
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel);
this.turnOnInAppNotificationHandling();
Dimensions.addEventListener('change', this.onOrientationChange);
AppState.addEventListener('change', this.onAppStateChange);
Linking.addEventListener('url', this.onDeepLink);
}
appActive = async () => {
this.turnOnInAppNotificationHandling();
// if the app is being controlled by an EMM provider
if (emmProvider.enabled && emmProvider.inAppPinCode) {
const authExpired = (Date.now() - emmProvider.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;
@ -57,6 +62,8 @@ class GlobalEventHandler {
};
appInactive = () => {
this.turnOffInAppNotificationHandling();
const {dispatch} = this.store;
// When the app is sent to the background we set the time when that happens
@ -227,6 +234,31 @@ class GlobalEventHandler {
dispatch(logout());
}
};
turnOnInAppNotificationHandling = () => {
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
}
turnOffInAppNotificationHandling = () => {
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
}
handleInAppNotification = (notification) => {
const {data} = notification;
const {dispatch, getState} = this.store;
const state = getState();
const currentChannelId = getCurrentChannelId(state);
if (data && data.channel_id !== currentChannelId) {
const screen = 'Notification';
const passProps = {
notification,
};
EventEmitter.emit(NavigationTypes.NAVIGATION_SHOW_OVERLAY);
dispatch(showOverlay(screen, passProps));
}
};
}
export default new GlobalEventHandler();

View file

@ -26,7 +26,6 @@ export default class About extends PureComponent {
componentId: PropTypes.string,
config: PropTypes.object.isRequired,
license: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};

View file

@ -15,9 +15,12 @@ import {setNavigatorStyles} from 'app/utils/theme';
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,
navigator: PropTypes.object.isRequired,
onEmojiPress: PropTypes.func,
theme: PropTypes.object.isRequired,
};
@ -33,7 +36,7 @@ export default class AddReaction extends PureComponent {
constructor(props) {
super(props);
props.navigator.setButtons({
props.actions.setButtons(props.componentId, {
leftButtons: [{...this.leftButton, icon: props.closeButton}],
});
}
@ -55,9 +58,7 @@ export default class AddReaction extends PureComponent {
}
close = () => {
this.props.navigator.dismissModal({
animationType: 'slide-down',
});
this.props.actions.dismissModal();
};
handleEmojiPress = (emoji) => {

View file

@ -1,10 +1,13 @@
// 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) {
@ -13,4 +16,13 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(AddReaction);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissModal,
setButtons,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AddReaction);

View file

@ -20,35 +20,30 @@ import ChannelBase, {ClientUpgradeListener, style} from './channel_base';
export default class ChannelAndroid extends ChannelBase {
render() {
const {height} = Dimensions.get('window');
const {
navigator,
} = this.props;
const channelLoaderStyle = [style.channelLoader, {height}];
const drawerContent = (
<SafeAreaView navigator={navigator}>
<SafeAreaView>
<StatusBar/>
<NetworkIndicator/>
<ChannelNavBar
navigator={navigator}
openChannelDrawer={this.openChannelSidebar}
openSettingsDrawer={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<KeyboardLayout>
<View style={style.flex}>
<ChannelPostList navigator={navigator}/>
<ChannelPostList/>
</View>
<PostTextbox
ref={this.postTextbox}
navigator={navigator}
/>
</KeyboardLayout>
<ChannelLoader
height={height}
style={channelLoaderStyle}
/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
</SafeAreaView>
);

View file

@ -38,7 +38,6 @@ export default class ChannelIOS extends ChannelBase {
const {height} = Dimensions.get('window');
const {
currentChannelId,
navigator,
} = this.props;
const channelLoaderStyle = [style.channelLoader, {height}];
@ -48,17 +47,15 @@ export default class ChannelIOS extends ChannelBase {
const drawerContent = (
<React.Fragment>
<SafeAreaView navigator={navigator}>
<SafeAreaView>
<StatusBar/>
<NetworkIndicator/>
<ChannelNavBar
navigator={navigator}
openChannelDrawer={this.openChannelSidebar}
openSettingsDrawer={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<ChannelPostList
navigator={navigator}
updateNativeScrollView={this.updateNativeScrollView}
/>
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
@ -74,7 +71,7 @@ export default class ChannelIOS extends ChannelBase {
height={height}
style={channelLoaderStyle}
/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
</SafeAreaView>
<KeyboardTrackingView
ref={this.keyboardTracker}
@ -85,7 +82,6 @@ export default class ChannelIOS extends ChannelBase {
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
ref={this.postTextbox}
navigator={navigator}
/>
</KeyboardTrackingView>
</React.Fragment>

View file

@ -18,8 +18,10 @@ import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/ut
export default class Code extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
popTopScreen: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
content: PropTypes.string.isRequired,
};
@ -39,7 +41,7 @@ export default class Code extends React.PureComponent {
}
handleAndroidBack = () => {
this.props.navigator.pop();
this.props.actions.popTopScreen();
return true;
};

View file

@ -1,10 +1,13 @@
// 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) {
@ -13,4 +16,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(Code);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
popTopScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Code);

View file

@ -32,9 +32,9 @@ 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,
navigator: PropTypes.object,
theme: PropTypes.object,
};
@ -64,21 +64,10 @@ export default class ErrorTeamsList extends PureComponent {
}
goToChannelView = () => {
const {navigator, theme} = this.props;
navigator.resetTo({
screen: 'Channel',
animated: false,
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
disableTermsModal: true,
},
});
const passProps = {
disableTermsModal: true,
};
this.props.actions.resetToChannel(passProps);
};
getUserInfo = async () => {

View file

@ -22,6 +22,7 @@ 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,
@ -39,6 +40,7 @@ describe('ErrorTeamsList', () => {
const selectDefaultTeam = jest.fn();
const logout = jest.fn();
const actions = {
...baseProps.actions,
loadMe,
logout,
selectDefaultTeam,

View file

@ -8,6 +8,8 @@ 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) {
@ -17,6 +19,7 @@ function mapDispatchToProps(dispatch) {
selectDefaultTeam,
connection,
loadMe,
resetToChannel,
}, dispatch),
};
}

View file

@ -16,15 +16,15 @@ 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,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
}
close = () => {
this.props.navigator.pop();
this.props.actions.popTopScreen();
};
dismissBanner = () => {
@ -67,7 +67,6 @@ export default class ExpandedAnnouncementBanner extends React.PureComponent {
<Markdown
baseTextStyle={style.baseTextStyle}
blockStyles={getMarkdownBlockStyles(this.props.theme)}
navigator={this.props.navigator}
onChannelLinkPress={this.handleChannelLinkPress}
textStyles={getMarkdownTextStyles(this.props.theme)}
value={this.props.bannerText}

View file

@ -7,6 +7,7 @@ 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';
@ -25,6 +26,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissBanner,
popTopScreen,
}, dispatch),
};
}

View file

@ -21,6 +21,7 @@ import LinearGradient from 'react-native-linear-gradient';
import {intlShape} from 'react-intl';
import Permissions from 'react-native-permissions';
import Gallery from 'react-native-image-gallery';
import {Navigation} from 'react-native-navigation';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
@ -43,13 +44,17 @@ 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,
deviceWidth: PropTypes.number.isRequired,
files: PropTypes.array,
getItemMeasures: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
navigator: PropTypes.object,
origin: PropTypes.object,
target: PropTypes.object,
theme: PropTypes.object.isRequired,
@ -66,8 +71,10 @@ export default class ImagePreview extends PureComponent {
constructor(props) {
super(props);
props.navigator.setStyle({
screenBackgroundColor: '#000',
Navigation.mergeOptions(props.componentId, {
layout: {
backgroundColor: '#000',
},
});
this.openAnim = new Animated.Value(0);
@ -103,12 +110,14 @@ export default class ImagePreview extends PureComponent {
};
close = () => {
const {getItemMeasures, navigator} = this.props;
const {actions, getItemMeasures, componentId} = this.props;
const {index} = this.state;
this.setState({animating: true});
navigator.setStyle({
screenBackgroundColor: 'transparent',
Navigation.mergeOptions(componentId, {
layout: {
backgroundColor: 'transparent',
},
});
getItemMeasures(index, (origin) => {
@ -117,7 +126,7 @@ export default class ImagePreview extends PureComponent {
}
this.animateOpenAnimToValue(0, () => {
navigator.dismissModal({animationType: 'none'});
actions.dismissModal();
});
});
};
@ -177,7 +186,7 @@ export default class ImagePreview extends PureComponent {
};
renderAttachmentDocument = (file) => {
const {canDownloadFiles, theme, navigator} = this.props;
const {canDownloadFiles, theme} = this.props;
return (
<View style={[style.flex, style.center]}>
@ -190,7 +199,6 @@ export default class ImagePreview extends PureComponent {
file={file}
iconHeight={100}
iconWidth={100}
navigator={navigator}
theme={theme}
wrapperHeight={200}
wrapperWidth={200}
@ -441,6 +449,7 @@ export default class ImagePreview extends PureComponent {
};
showDownloadOptionsIOS = async () => {
const {actions} = this.props;
const {formatMessage} = this.context.intl;
const file = this.getCurrentFile();
const items = [];
@ -504,30 +513,17 @@ export default class ImagePreview extends PureComponent {
});
}
const options = {
title: file.caption,
items,
onCancelPress: () => this.setHeaderAndFooterVisible(true),
};
if (items.length) {
this.setHeaderAndFooterVisible(false);
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
...options,
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
});
const screen = 'OptionsModal';
const passProps = {
title: file.caption,
items,
onCancelPress: () => this.setHeaderAndFooterVisible(true),
};
actions.showModalOverCurrentContext(screen, passProps);
}
};

View file

@ -6,6 +6,7 @@ import {shallow} from 'enzyme';
import {
TouchableOpacity,
} from 'react-native';
import {Navigation} from 'react-native-navigation';
import Preferences from 'mattermost-redux/constants/preferences';
@ -18,6 +19,13 @@ 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 = {
@ -30,10 +38,14 @@ describe('ImagePreview', () => {
],
getItemMeasures: jest.fn(),
index: 0,
navigator: {setStyle: jest.fn()},
origin: {},
target: {},
theme: Preferences.THEMES.default,
componentId: 'component-id',
actions: {
dismissModal: jest.fn(),
showModalOverCurrentContext: jest.fn(),
},
};
test('should match snapshot', () => {
@ -67,21 +79,26 @@ describe('ImagePreview', () => {
expect(wrapper.state('index')).toEqual(1);
});
test('should match call getItemMeasures & navigator.setStyle on close', () => {
test('should match call getItemMeasures & Navigation.mergeOptions on close', () => {
const getItemMeasures = jest.fn();
const navigator = {setStyle: jest.fn()};
const wrapper = shallow(
<ImagePreview
{...baseProps}
getItemMeasures={getItemMeasures}
navigator={navigator}
/>,
{context: {intl: {formatMessage: jest.fn()}}},
);
wrapper.instance().close();
expect(navigator.setStyle).toHaveBeenCalledTimes(2);
expect(navigator.setStyle).toBeCalledWith({screenBackgroundColor: 'transparent'});
expect(Navigation.mergeOptions).toHaveBeenCalledTimes(2);
expect(Navigation.mergeOptions).toHaveBeenCalledWith(
baseProps.componentId,
{
layout: {
backgroundColor: 'transparent',
},
},
);
});
});

View file

@ -1,12 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getDimensions} from 'app/selectors/device';
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';
function mapStateToProps(state) {
@ -17,4 +20,13 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(ImagePreview);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissModal,
showModalOverCurrentContext,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ImagePreview);

View file

@ -9,23 +9,12 @@ import Channel from 'app/screens/channel';
import Root from 'app/components/root';
import SelectServer from 'app/screens/select_server';
// TODO remove dummy navigator object
const navigator = {
push: () => {}, // eslint-disable-line no-empty-function
setOnNavigatorEvent: () => {}, // eslint-disable-line no-empty-function
setStyle: () => {}, // eslint-disable-line no-empty-function
setButtons: () => {}, // eslint-disable-line no-empty-function
};
export function registerScreens(store, Provider) {
// TODO consolidate this with app/utils/wrap_context_provider
const wrapper = (Comp) => (props) => ( // eslint-disable-line react/display-name
<Provider store={store}>
<Root navigator={navigator}>
<Comp
{...props}
navigator={navigator}
/>
<Root>
<Comp {...props}/>
</Root>
</Provider>
);

View file

@ -25,7 +25,6 @@ export default class DialogElement extends PureComponent {
options: PropTypes.arrayOf(PropTypes.object),
value: PropTypes.any,
onChange: PropTypes.func,
navigator: PropTypes.object,
theme: PropTypes.object,
};
@ -71,7 +70,6 @@ export default class DialogElement extends PureComponent {
theme,
dataSource,
options,
navigator,
} = this.props;
let {maxLength} = this.props;
@ -128,7 +126,6 @@ export default class DialogElement extends PureComponent {
placeholder={placeholder}
showRequiredAsterisk={true}
selected={this.state.selected}
navigator={navigator}
roundedBorders={false}
/>
);

View file

@ -7,6 +7,8 @@ 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) {
@ -29,6 +31,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
submitInteractiveDialog,
dismissModal,
}, dispatch),
};
}

View file

@ -22,10 +22,10 @@ export default class InteractiveDialog extends PureComponent {
elements: PropTypes.arrayOf(PropTypes.object).isRequired,
notifyOnCancel: PropTypes.bool,
state: PropTypes.string,
navigator: PropTypes.object,
theme: PropTypes.object,
actions: PropTypes.shape({
submitInteractiveDialog: PropTypes.func.isRequired,
dismissModal: PropTypes.func.isRequired,
}).isRequired,
};
@ -136,9 +136,7 @@ export default class InteractiveDialog extends PureComponent {
}
handleHide = () => {
this.props.navigator.dismissModal({
animationType: 'slide-down',
});
this.props.actions.dismissModal();
}
onChange = (name, value) => {
@ -147,7 +145,7 @@ export default class InteractiveDialog extends PureComponent {
}
render() {
const {elements, theme, navigator} = this.props;
const {elements, theme} = this.props;
const style = getStyleFromTheme(theme);
return (
@ -172,7 +170,6 @@ export default class InteractiveDialog extends PureComponent {
options={e.options}
value={this.state.values[e.name]}
onChange={this.onChange}
navigator={navigator}
theme={theme}
/>
);

View file

@ -19,8 +19,6 @@ describe('InteractiveDialog', () => {
theme: Preferences.THEMES.default,
actions: {
submitInteractiveDialog: jest.fn(),
},
navigator: {
dismissModal: jest.fn(),
},
componentId: 'component-id',
@ -37,11 +35,9 @@ describe('InteractiveDialog', () => {
});
test('should submit dialog', async () => {
const submitInteractiveDialog = jest.fn();
const wrapper = shallow(
<InteractiveDialog
{...baseProps}
actions={{submitInteractiveDialog}}
/>,
);
@ -53,16 +49,14 @@ describe('InteractiveDialog', () => {
};
wrapper.instance().handleSubmit();
expect(submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(submitInteractiveDialog).toHaveBeenCalledWith(dialog);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
});
test('should submit dialog on cancel', async () => {
const submitInteractiveDialog = jest.fn();
const wrapper = shallow(
<InteractiveDialog
{...baseProps}
actions={{submitInteractiveDialog}}
notifyOnCancel={true}
/>,
);
@ -75,21 +69,19 @@ describe('InteractiveDialog', () => {
};
wrapper.instance().navigationButtonPressed({buttonId: 'close-dialog'});
expect(submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(submitInteractiveDialog).toHaveBeenCalledWith(dialog);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
});
test('should not submit dialog on cancel', async () => {
const submitInteractiveDialog = jest.fn();
const wrapper = shallow(
<InteractiveDialog
{...baseProps}
actions={{submitInteractiveDialog}}
notifyOnCancel={false}
/>,
);
wrapper.instance().navigationButtonPressed({buttonId: 'close-dialog'});
expect(submitInteractiveDialog).not.toHaveBeenCalled();
expect(baseProps.actions.submitInteractiveDialog).not.toHaveBeenCalled();
});
});

View file

@ -14,6 +14,8 @@ 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) {
@ -40,6 +42,8 @@ function mapDispatchToProps(dispatch) {
getProfilesInTeam,
searchProfiles,
setChannelDisplayName,
dismissModal,
setButtons,
}, dispatch),
};
}

View file

@ -39,13 +39,14 @@ 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,
currentDisplayName: PropTypes.string,
currentTeamId: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
navigator: PropTypes.object,
restrictDirectMessage: PropTypes.bool.isRequired,
teammateNameDisplay: PropTypes.string,
theme: PropTypes.object.isRequired,
@ -108,7 +109,7 @@ export default class MoreDirectMessages extends PureComponent {
}
close = () => {
this.props.navigator.dismissModal({animationType: 'slide-down'});
this.props.actions.dismissModal();
};
clearSearch = () => {
@ -325,13 +326,14 @@ export default class MoreDirectMessages extends PureComponent {
};
updateNavigationButtons = (startEnabled, context = this.context) => {
const {actions, componentId} = this.props;
const {formatMessage} = context.intl;
this.props.navigator.setButtons({
actions.setButtons(componentId, {
rightButtons: [{
id: START_BUTTON,
title: formatMessage({id: 'mobile.more_dms.start', defaultMessage: 'Start'}),
text: formatMessage({id: 'mobile.more_dms.start', defaultMessage: 'Start'}),
showAsAction: 'always',
disabled: !startEnabled,
enabled: startEnabled,
}],
});
};

View file

@ -12,6 +12,8 @@ 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) {
@ -42,6 +44,9 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
loadFromPushNotification,
dismissOverlay,
dismissAllModals,
popToRoot,
}, dispatch),
};
}

View file

@ -8,55 +8,149 @@ import {
InteractionManager,
Platform,
StyleSheet,
Text,
TouchableOpacity,
Text,
View,
} from 'react-native';
import FormattedText from 'app/components/formatted_text';
import ProfilePicture from 'app/components/profile_picture';
import {changeOpacity} from 'app/utils/theme';
import {Navigation} from 'react-native-navigation';
import * as Animatable from 'react-native-animatable';
import {PanGestureHandler} from 'react-native-gesture-handler';
import {isDirectChannel} from 'mattermost-redux/utils/channel_utils';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
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 logo from 'assets/images/icon.png';
import webhookIcon from 'assets/images/icons/webhook.jpg';
const IMAGE_SIZE = 33;
const AUTO_DISMISS_TIME_MILLIS = 5000;
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,
config: PropTypes.object,
deviceWidth: PropTypes.number.isRequired,
notification: PropTypes.object.isRequired,
teammateNameDisplay: PropTypes.string,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired,
user: PropTypes.object,
};
state = {
keyFrames: {
from: {
translateY: -100,
},
to: {
translateY: 0,
},
},
}
tapped = false;
componentDidMount() {
this.setDismissTimer();
this.setDidDisappearListener();
this.setShowOverlayListener();
}
componentWillUnmount() {
this.clearDismissTimer();
this.clearDidDisappearListener();
this.clearShowOverlayListener();
}
setDismissTimer = () => {
this.dismissTimer = setTimeout(() => {
if (!this.tapped) {
this.animateDismissOverlay();
}
}, AUTO_DISMISS_TIME_MILLIS);
}
clearDismissTimer = () => {
if (this.dismissTimer) {
clearTimeout(this.dismissTimer);
this.dismissTimer = null;
}
}
setDidDisappearListener = () => {
this.didDismissListener = Navigation.events().registerComponentDidDisappearListener(({componentId}) => {
if (componentId === this.props.componentId && this.tapped) {
const {actions} = this.props;
actions.dismissAllModals();
actions.popToRoot();
}
});
}
clearDidDisappearListener = () => {
this.didDismissListener.remove();
}
setShowOverlayListener = () => {
EventEmitter.on(NavigationTypes.NAVIGATION_SHOW_OVERLAY, this.onNewOverlay);
}
clearShowOverlayListener = () => {
EventEmitter.off(NavigationTypes.NAVIGATION_SHOW_OVERLAY, this.onNewOverlay);
}
onNewOverlay = () => {
// Dismiss this overlay so that there is only ever one.
this.dismissOverlay();
}
dismissOverlay = () => {
this.clearDismissTimer();
const {actions, componentId} = this.props;
actions.dismissOverlay(componentId);
}
animateDismissOverlay = () => {
this.clearDismissTimer();
this.setState({
keyFrames: {
from: {
translateY: 0,
},
to: {
translateY: -100,
},
},
});
setTimeout(() => this.dismissOverlay(), 1000);
}
notificationTapped = () => {
const {actions, navigator, notification} = this.props;
this.tapped = true;
this.clearDismissTimer();
const {actions, notification} = this.props;
EventEmitter.emit('close_channel_drawer');
EventEmitter.emit('close_settings_sidebar');
InteractionManager.runAfterInteractions(() => {
navigator.dismissInAppNotification();
this.dismissOverlay();
if (!notification.localNotification) {
actions.loadFromPushNotification(notification);
if (Platform.OS === 'android') {
navigator.dismissModal({animation: 'none'});
}
navigator.popToRoot({
animated: false,
});
}
});
};
@ -202,28 +296,39 @@ export default class Notification extends PureComponent {
const icon = this.getNotificationIcon();
return (
<View style={[style.container, {width: deviceWidth}]}>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this.notificationTapped}
<PanGestureHandler
onGestureEvent={this.animateDismissOverlay}
minOffsetY={-20}
>
<Animatable.View
duration={250}
useNativeDriver={true}
animation={this.state.keyFrames}
>
<View style={style.iconContainer}>
{icon}
<View style={[style.container, {width: deviceWidth}]}>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this.notificationTapped}
>
<View style={style.iconContainer}>
{icon}
</View>
<View style={style.textContainer}>
{title}
<View style={{flex: 1}}>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={style.message}
>
{messageText}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
<View style={style.textContainer}>
{title}
<View style={{flex: 1}}>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={style.message}
>
{messageText}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
</Animatable.View>
</PanGestureHandler>
);
}

View file

@ -16,13 +16,18 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {
goToScreen,
dismissModal,
dismissAllModals,
resetToChannel,
} from 'app/actions/navigation';
import {
handleSelectChannel,
loadThreadIfNecessary,
setChannelDisplayName,
setChannelLoading,
} from 'app/actions/views/channel';
import {showSearchModal} from 'app/actions/views/search';
import {handleTeamChange} from 'app/actions/views/select_team';
import Permalink from './permalink';
@ -74,7 +79,10 @@ function mapDispatchToProps(dispatch) {
selectPost,
setChannelDisplayName,
setChannelLoading,
showSearchModal,
goToScreen,
dismissModal,
dismissAllModals,
resetToChannel,
}, dispatch),
};
}

View file

@ -57,6 +57,10 @@ 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,
@ -67,7 +71,6 @@ export default class Permalink extends PureComponent {
focusedPostId: PropTypes.string.isRequired,
isPermalink: PropTypes.bool,
myMembers: PropTypes.object.isRequired,
navigator: PropTypes.object,
onClose: PropTypes.func,
onPress: PropTypes.func,
postIds: PropTypes.array,
@ -171,39 +174,29 @@ export default class Permalink extends PureComponent {
}
goToThread = preventDoubleTap((post) => {
const {actions, navigator, theme} = this.props;
const {actions} = this.props;
const channelId = post.channel_id;
const rootId = (post.root_id || post.id);
const screen = 'Thread';
const title = '';
const passProps = {
channelId,
rootId,
};
actions.loadThreadIfNecessary(rootId);
actions.selectPost(rootId);
const options = {
screen: 'Thread',
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
channelId,
rootId,
},
};
navigator.push(options);
actions.goToScreen(screen, title, passProps);
});
handleClose = () => {
const {actions, navigator, onClose} = this.props;
const {actions, onClose} = this.props;
if (this.refs.view) {
this.mounted = false;
this.refs.view.zoomOut().then(() => {
actions.selectPost('');
navigator.dismissModal({animationType: 'none'});
actions.dismissModal();
if (onClose) {
onClose();
@ -232,7 +225,7 @@ export default class Permalink extends PureComponent {
jumpToChannel = (channelId, channelDisplayName) => {
if (channelId) {
const {actions, channelTeamId, currentTeamId, navigator, onClose, theme} = this.props;
const {actions, channelTeamId, currentTeamId, onClose} = this.props;
const currentChannelId = this.props.channelId;
const {
handleSelectChannel,
@ -246,23 +239,13 @@ export default class Permalink extends PureComponent {
if (channelId === currentChannelId) {
EventEmitter.emit('reset_channel');
} else {
navigator.resetTo({
screen: 'Channel',
animated: true,
animationType: 'fade',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
disableTermsModal: true,
},
});
const passProps = {
disableTermsModal: true,
};
actions.resetToChannel(passProps);
}
navigator.dismissAllModals({animationType: 'slide-down'});
actions.dismissAllModals();
if (onClose) {
onClose();
@ -350,7 +333,6 @@ export default class Permalink extends PureComponent {
const {
currentUserId,
focusedPostId,
navigator,
theme,
} = this.props;
const {
@ -395,7 +377,6 @@ export default class Permalink extends PureComponent {
lastPostIndex={Platform.OS === 'android' ? getLastPostIndex(postIdsState) : -1}
currentUserId={currentUserId}
lastViewedAt={0}
navigator={navigator}
highlightPinnedOrFlagged={false}
/>
);

View file

@ -11,13 +11,6 @@ import Permalink from './permalink.js';
jest.mock('react-intl');
describe('Permalink', () => {
const navigator = {
dismissAllModals: jest.fn(),
dismissModal: jest.fn(),
push: jest.fn(),
resetTo: jest.fn(),
};
const actions = {
getPostsAround: jest.fn(),
getPostThread: jest.fn(),
@ -29,6 +22,10 @@ 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 = {
@ -42,7 +39,6 @@ describe('Permalink', () => {
focusedPostId: 'focused_post_id',
isPermalink: true,
myMembers: {},
navigator,
onClose: jest.fn(),
onPress: jest.fn(),
postIds: ['post_id_1', 'focused_post_id', 'post_id_3'],

View file

@ -22,7 +22,7 @@ exports[`ReactionList should match snapshot 1`] = `
}
}
>
<ReactionRow
<Connect(ReactionRow)
emojiName="+1"
teammateNameDisplay="username"
theme={
@ -77,7 +77,7 @@ exports[`ReactionList should match snapshot 1`] = `
}
}
>
<ReactionRow
<Connect(ReactionRow)
emojiName="smile"
teammateNameDisplay="username"
theme={
@ -138,7 +138,7 @@ Array [
}
}
>
<ReactionRow
<Connect(ReactionRow)
emojiName="+1"
teammateNameDisplay="username"
theme={
@ -193,7 +193,7 @@ Array [
}
}
>
<ReactionRow
<Connect(ReactionRow)
emojiName="smile"
teammateNameDisplay="username"
theme={

View file

@ -9,6 +9,8 @@ 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 {getUniqueUserIds} from 'app/utils/reaction';
import ReactionList from './reaction_list';
@ -35,6 +37,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getMissingProfilesByIds,
dismissModal,
}, dispatch),
};
}

View file

@ -28,8 +28,8 @@ export default class ReactionList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
getMissingProfilesByIds: PropTypes.func.isRequired,
dismissModal: PropTypes.func.isRequired,
}).isRequired,
navigator: PropTypes.object,
reactions: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
teammateNameDisplay: PropTypes.string,
@ -107,9 +107,7 @@ export default class ReactionList extends PureComponent {
}
close = () => {
this.props.navigator.dismissModal({
animationType: 'none',
});
this.props.actions.dismissModal();
};
getMissingProfiles = () => {
@ -137,7 +135,6 @@ export default class ReactionList extends PureComponent {
renderReactionRows = () => {
const {
navigator,
teammateNameDisplay,
theme,
} = this.props;
@ -157,7 +154,6 @@ export default class ReactionList extends PureComponent {
>
<ReactionRow
emojiName={emojiName}
navigator={navigator}
teammateNameDisplay={teammateNameDisplay}
theme={theme}
user={userProfilesById[userId]}

View file

@ -15,6 +15,7 @@ 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'}},

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {goToScreen} from 'app/actions/navigation';
import ReactionRow from './reaction_row';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(null, mapDispatchToProps)(ReactionRow);

View file

@ -21,8 +21,10 @@ 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,
navigator: PropTypes.object,
teammateNameDisplay: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
@ -37,26 +39,15 @@ export default class ReactionRow extends React.PureComponent {
};
goToUserProfile = () => {
const {navigator, theme, user} = this.props;
const {actions, user} = this.props;
const {formatMessage} = this.context.intl;
const options = {
screen: 'UserProfile',
title: formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId: user.id,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const screen = 'UserProfile';
const title = formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
const passProps = {
userId: user.id,
};
navigator.push(options);
actions.goToScreen(screen, title, passProps);
};
render() {

View file

@ -9,8 +9,10 @@ import ReactionRow from './reaction_row';
describe('ReactionRow', () => {
const baseProps = {
actions: {
goToScreen: jest.fn(),
},
emojiName: 'smile',
navigator: {},
teammateNameDisplay: 'username',
theme: Preferences.THEMES.default,
user: {id: 'user_id', username: 'username'},

View file

@ -15,7 +15,7 @@ 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} from 'app/actions/navigation';
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';
@ -86,6 +86,8 @@ function mapDispatchToProps(dispatch) {
getMorePostsForSearch,
selectPost,
dismissModal,
goToScreen,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -57,12 +57,13 @@ export default class Search extends PureComponent {
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,
initialValue: PropTypes.string,
isLandscape: PropTypes.bool.isRequired,
navigator: PropTypes.object,
postIds: PropTypes.array,
archivedPostIds: PropTypes.arrayOf(PropTypes.string),
recent: PropTypes.array.isRequired,
@ -183,7 +184,7 @@ export default class Search extends PureComponent {
});
goToThread = (post) => {
const {actions, navigator, theme} = this.props;
const {actions} = this.props;
const channelId = post.channel_id;
const rootId = (post.root_id || post.id);
@ -191,23 +192,14 @@ export default class Search extends PureComponent {
actions.loadThreadIfNecessary(rootId);
actions.selectPost(rootId);
const options = {
screen: 'Thread',
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
channelId,
rootId,
},
const screen = 'Thread';
const title = '';
const passProps = {
channelId,
rootId,
};
navigator.push(options);
actions.goToScreen(screen, title, passProps);
};
handleHashtagPress = (hashtag) => {
@ -389,7 +381,6 @@ export default class Search extends PureComponent {
postId={item}
previewPost={this.previewPost}
goToThread={this.goToThread}
navigator={this.props.navigator}
onHashtagPress={this.handleHashtagPress}
onPermalinkPress={this.handlePermalinkPress}
managedConfig={mattermostManaged.getCachedConfig()}
@ -449,29 +440,24 @@ export default class Search extends PureComponent {
};
showPermalinkView = (postId, isPermalink) => {
const {actions, navigator} = this.props;
const {actions} = this.props;
actions.selectFocusedPostId(postId);
if (!this.showingPermalink) {
const screen = 'Permalink';
const passProps = {
isPermalink,
onClose: this.handleClosePermalink,
};
const options = {
screen: 'Permalink',
animationType: 'none',
backButtonTitle: '',
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: changeOpacity('#000', 0.2),
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
isPermalink,
onClose: this.handleClosePermalink,
layout: {
backgroundColor: changeOpacity('#000', 0.2),
},
};
this.showingPermalink = true;
navigator.showModal(options);
actions.showModalOverCurrentContext(screen, passProps, options);
}
};

View file

@ -9,6 +9,8 @@ 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) {
@ -32,6 +34,7 @@ function mapDispatchToProps(dispatch) {
getChannels,
searchProfiles,
searchChannels,
popTopScreen,
}, dispatch),
};
}

View file

@ -34,12 +34,12 @@ 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,
data: PropTypes.arrayOf(PropTypes.object),
dataSource: PropTypes.string,
navigator: PropTypes.object,
onSelect: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
@ -93,7 +93,7 @@ export default class SelectorScreen extends PureComponent {
};
close = () => {
this.props.navigator.pop({animated: true});
this.props.actions.popTopScreen();
};
handleSelectItem = (id, item) => {

View file

@ -67,6 +67,7 @@ describe('SelectorScreen', () => {
getChannels,
searchProfiles,
searchChannels,
popTopScreen: jest.fn(),
};
const baseProps = {

View file

@ -45,87 +45,6 @@ exports[`TermsOfService should enable/disable navigator buttons on setNavigatorB
disableAtMentions={true}
disableChannelLink={true}
disableHashtags={true}
navigator={
Object {
"dismissAllModals": [MockFunction],
"dismissModal": [MockFunction],
"setButtons": [MockFunction] {
"calls": Array [
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": false,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": false,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
textStyles={
Object {
"code": Object {
@ -261,110 +180,6 @@ exports[`TermsOfService should enable/disable navigator buttons on setNavigatorB
disableAtMentions={true}
disableChannelLink={true}
disableHashtags={true}
navigator={
Object {
"dismissAllModals": [MockFunction],
"dismissModal": [MockFunction],
"setButtons": [MockFunction] {
"calls": Array [
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": false,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": false,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
textStyles={
Object {
"code": Object {
@ -572,87 +387,6 @@ exports[`TermsOfService should match snapshot on enableNavigatorLogout 1`] = `
disableAtMentions={true}
disableChannelLink={true}
disableHashtags={true}
navigator={
Object {
"dismissAllModals": [MockFunction],
"dismissModal": [MockFunction],
"setButtons": [MockFunction] {
"calls": Array [
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": true,
"icon": Object {},
"id": "reject-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
Array [
Object {
"leftButtons": Array [
Object {
"disabled": false,
"icon": Object {},
"id": "close-terms-of-service",
},
],
"rightButtons": Array [
Object {
"disabled": true,
"id": "accept-terms-of-service",
"showAsAction": "always",
"title": undefined,
},
],
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
textStyles={
Object {
"code": Object {

View file

@ -8,6 +8,12 @@ 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) {
@ -25,6 +31,9 @@ function mapDispatchToProps(dispatch) {
getTermsOfService,
logout,
updateMyTermsOfServiceStatus,
setButtons,
dismissModal,
dismissAllModals,
}, dispatch),
};
}

View file

@ -36,10 +36,12 @@ export default class TermsOfService extends PureComponent {
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,
navigator: PropTypes.object,
siteName: PropTypes.string,
theme: PropTypes.object,
};
@ -71,7 +73,7 @@ export default class TermsOfService extends PureComponent {
termsText: '',
};
this.rightButton.title = context.intl.formatMessage({id: 'terms_of_service.agreeButton', defaultMessage: 'I Agree'});
this.rightButton.text = context.intl.formatMessage({id: 'terms_of_service.agreeButton', defaultMessage: 'I Agree'});
this.leftButton.icon = props.closeButton;
this.setNavigatorButtons(false);
@ -106,27 +108,28 @@ export default class TermsOfService extends PureComponent {
}
setNavigatorButtons = (enabled = true) => {
const {actions, componentId} = this.props;
const buttons = {
leftButtons: [{...this.leftButton, disabled: !enabled}],
rightButtons: [{...this.rightButton, disabled: !enabled}],
leftButtons: [{...this.leftButton, enabled}],
rightButtons: [{...this.rightButton, enabled}],
};
this.props.navigator.setButtons(buttons);
actions.setButtons(componentId, buttons);
};
enableNavigatorLogout = () => {
const buttons = {
leftButtons: [{...this.leftButton, id: 'close-terms-of-service', disabled: false}],
rightButtons: [{...this.rightButton, disabled: true}],
leftButtons: [{...this.leftButton, id: 'close-terms-of-service', enabled: true}],
rightButtons: [{...this.rightButton, enabled: false}],
};
this.props.navigator.setButtons(buttons);
this.props.actions.setButtons(buttons);
};
closeTermsAndLogout = () => {
const {actions} = this.props;
this.props.navigator.dismissAllModals();
actions.dismissAllModals();
actions.logout();
};
@ -163,9 +166,7 @@ export default class TermsOfService extends PureComponent {
this.registerUserAction(
true,
() => {
this.props.navigator.dismissModal({
animationType: 'slide-down',
});
this.props.actions.dismissModal();
},
this.handleAcceptTerms
);
@ -234,7 +235,7 @@ export default class TermsOfService extends PureComponent {
};
render() {
const {navigator, theme} = this.props;
const {theme} = this.props;
const styles = getStyleSheet(theme);
const blockStyles = getMarkdownBlockStyles(theme);
@ -267,7 +268,6 @@ export default class TermsOfService extends PureComponent {
>
<Markdown
baseTextStyle={styles.baseText}
navigator={navigator}
textStyles={textStyles}
blockStyles={blockStyles}
value={this.state.termsText}

View file

@ -23,15 +23,13 @@ describe('TermsOfService', () => {
getTermsOfService: jest.fn(),
updateMyTermsOfServiceStatus: jest.fn(),
logout: jest.fn(),
setButtons: jest.fn(),
dismissModal: jest.fn(),
dismissAllModals: jest.fn(),
};
const baseProps = {
actions,
navigator: {
dismissAllModals: jest.fn(),
dismissModal: jest.fn(),
setButtons: jest.fn(),
},
theme: Preferences.THEMES.default,
closeButton: {},
siteName: 'Mattermost',
@ -83,18 +81,18 @@ describe('TermsOfService', () => {
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should call props.navigator.setButtons on setNavigatorButtons', async () => {
test('should call props.actions.setButtons on setNavigatorButtons', async () => {
const wrapper = shallow(
<TermsOfService {...baseProps}/>,
{context: {intl: {formatMessage: jest.fn()}}},
);
wrapper.setState({loading: false, termsId: 1, termsText: 'Terms Text'});
expect(baseProps.navigator.setButtons).toHaveBeenCalledTimes(2);
expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(2);
wrapper.instance().setNavigatorButtons(true);
expect(baseProps.navigator.setButtons).toHaveBeenCalledTimes(3);
expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(3);
wrapper.instance().setNavigatorButtons(false);
expect(baseProps.navigator.setButtons).toHaveBeenCalledTimes(4);
expect(baseProps.actions.setButtons).toHaveBeenCalledTimes(4);
});
test('should enable/disable navigator buttons on setNavigatorButtons true/false', () => {
@ -129,6 +127,6 @@ describe('TermsOfService', () => {
wrapper.setState({loading: false, termsId: 1, termsText: 'Terms Text'});
wrapper.instance().closeTermsAndLogout();
expect(baseProps.navigator.dismissAllModals).toHaveBeenCalledTimes(1);
expect(baseProps.actions.dismissAllModals).toHaveBeenCalledTimes(1);
});
});

View file

@ -18,7 +18,6 @@ import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/ut
export default class TextPreview extends React.PureComponent {
static propTypes = {
componentId: PropTypes.string,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
content: PropTypes.string.isRequired,
};

View file

@ -13,28 +13,6 @@ exports[`thread should match snapshot, has root post 1`] = `
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
onPostPress={[Function]}
postIds={
Array [
@ -76,28 +54,6 @@ exports[`thread should match snapshot, has root post 1`] = `
channelId="channel_id"
channelIsArchived={false}
cursorPositionEvent="onThreadTextBoxCursorChange"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
onCloseChannel={[Function]}
rootId="root_id"
valueEvent="onThreadTextBoxValueChange"
@ -128,28 +84,6 @@ exports[`thread should match snapshot, render footer 1`] = `
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
onPostPress={[Function]}
postIds={
Array [
@ -176,28 +110,6 @@ exports[`thread should match snapshot, render footer 2`] = `
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
onPostPress={[Function]}
postIds={
Array [

View file

@ -10,6 +10,8 @@ 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() {
@ -37,6 +39,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
selectPost,
popTopScreen,
resetToChannel,
}, dispatch),
};
}

View file

@ -19,7 +19,6 @@ export default class ThreadAndroid extends ThreadBase {
const {
channelId,
myMember,
navigator,
postIds,
rootId,
channelIsArchived,
@ -36,7 +35,6 @@ export default class ThreadAndroid extends ThreadBase {
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
lastPostIndex={-1}
navigator={navigator}
onPostPress={this.hideKeyboard}
location={THREAD}
/>
@ -47,7 +45,6 @@ export default class ThreadAndroid extends ThreadBase {
channelIsArchived={channelIsArchived}
rootId={rootId}
channelId={channelId}
navigator={navigator}
onCloseChannel={this.onCloseChannel}
/>
);

View file

@ -29,7 +29,6 @@ export default class ThreadIOS extends ThreadBase {
const {
channelId,
myMember,
navigator,
postIds,
rootId,
channelIsArchived,
@ -47,7 +46,6 @@ export default class ThreadIOS extends ThreadBase {
lastPostIndex={getLastPostIndex(postIds)}
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
navigator={navigator}
onPostPress={this.hideKeyboard}
location={THREAD}
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
@ -77,7 +75,6 @@ export default class ThreadIOS extends ThreadBase {
channelIsArchived={channelIsArchived}
rootId={rootId}
channelId={channelId}
navigator={navigator}
onCloseChannel={this.onCloseChannel}
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}

View file

@ -11,22 +11,22 @@ import PostList from 'app/components/post_list';
import ThreadIOS from './thread.ios';
jest.mock('react-intl');
jest.mock('react-native-navigation', () => ({
Navigation: {
mergeOptions: jest.fn(),
},
}));
describe('thread', () => {
const navigator = {
dismissModal: jest.fn(),
pop: jest.fn(),
resetTo: jest.fn(),
setTitle: jest.fn(),
};
const baseProps = {
actions: {
selectPost: jest.fn(),
popTopScreen: jest.fn(),
resetToChannel: jest.fn(),
},
channelId: 'channel_id',
channelType: General.OPEN_CHANNEL,
displayName: 'channel_display_name',
navigator,
myMember: {last_viewed_at: 0, user_id: 'member_user_id'},
rootId: 'root_id',
theme: Preferences.THEMES.default,
@ -55,35 +55,19 @@ describe('thread', () => {
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should call props.navigator on onCloseChannel', () => {
const channelScreen = {
screen: 'Channel',
title: '',
animated: false,
backButtonTitle: '',
navigatorStyle: {
animated: true,
animationType: 'fade',
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
passProps: {
disableTermsModal: true,
},
test('should call props.actions.resetToChannel on onCloseChannel', () => {
const passProps = {
disableTermsModal: true,
};
const newNavigator = {...navigator};
const wrapper = shallow(
<ThreadIOS
{...baseProps}
navigator={newNavigator}
/>,
{context: {intl: {formatMessage: jest.fn()}}},
);
wrapper.instance().onCloseChannel();
expect(newNavigator.resetTo).toHaveBeenCalledTimes(1);
expect(newNavigator.resetTo).toBeCalledWith(channelScreen);
expect(baseProps.actions.resetToChannel).toHaveBeenCalledTimes(1);
expect(baseProps.actions.resetToChannel).toBeCalledWith(passProps);
});
test('should match snapshot, render footer', () => {

View file

@ -3,8 +3,9 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Keyboard, Platform} from 'react-native';
import {Keyboard} from 'react-native';
import {intlShape} from 'react-intl';
import {Navigation} from 'react-native-navigation';
import {General, RequestStatus} from 'mattermost-redux/constants';
@ -16,12 +17,13 @@ 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,
channelType: PropTypes.string,
displayName: PropTypes.string,
navigator: PropTypes.object,
myMember: PropTypes.object.isRequired,
rootId: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
@ -53,8 +55,12 @@ export default class ThreadBase extends PureComponent {
this.postTextbox = React.createRef();
this.props.navigator.setTitle({
title,
Navigation.mergeOptions(props.componentId, {
topBar: {
title: {
text: title,
},
},
});
this.state = {
@ -82,17 +88,7 @@ export default class ThreadBase extends PureComponent {
}
close = () => {
const {navigator} = this.props;
if (Platform.OS === 'ios') {
navigator.pop({
animated: true,
});
} else {
navigator.dismissModal({
animationType: 'slide-down',
});
}
this.props.actions.popTopScreen();
};
handleAutoComplete = (value) => {
@ -124,22 +120,9 @@ export default class ThreadBase extends PureComponent {
};
onCloseChannel = () => {
this.props.navigator.resetTo({
screen: 'Channel',
title: '',
animated: false,
backButtonTitle: '',
navigatorStyle: {
animated: true,
animationType: 'fade',
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
passProps: {
disableTermsModal: true,
},
});
const passProps = {
disableTermsModal: true,
};
this.props.actions.resetToChannel(passProps);
};
}

View file

@ -15,6 +15,13 @@ 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,
resetToChannel,
goToScreen,
} from 'app/actions/navigation';
import UserProfile from './user_profile';
function mapStateToProps(state, ownProps) {
@ -43,6 +50,10 @@ function mapDispatchToProps(dispatch) {
makeDirectChannel,
setChannelDisplayName,
loadBot,
setButtons,
dismissModal,
resetToChannel,
goToScreen,
}, dispatch),
};
}

View file

@ -33,11 +33,14 @@ 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,
resetToChannel: PropTypes.func.isRequired,
goToScreen: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string,
config: PropTypes.object.isRequired,
currentDisplayName: PropTypes.string,
navigator: PropTypes.object,
teammateNameDisplay: PropTypes.string,
theme: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
@ -61,13 +64,13 @@ export default class UserProfile extends PureComponent {
super(props);
if (props.isMyUser) {
this.rightButton.title = context.intl.formatMessage({id: 'mobile.routes.user_profile.edit', defaultMessage: 'Edit'});
this.rightButton.text = context.intl.formatMessage({id: 'mobile.routes.user_profile.edit', defaultMessage: 'Edit'});
const buttons = {
rightButtons: [this.rightButton],
};
props.navigator.setButtons(buttons);
props.actions.setButtons(props.componentId, buttons);
}
}
@ -97,30 +100,17 @@ export default class UserProfile extends PureComponent {
}
close = () => {
const {navigator, theme} = this.props;
const {actions, fromSettings} = this.props;
if (this.props.fromSettings) {
navigator.dismissModal({
animationType: 'slide-down',
});
if (fromSettings) {
actions.dismissModal();
return;
}
navigator.resetTo({
screen: 'Channel',
animated: true,
navigatorStyle: {
animated: true,
animationType: 'fade',
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
disableTermsModal: true,
},
});
const passProps = {
disableTermsModal: true,
};
actions.resetToChannel(passProps);
};
getDisplayName = () => {
@ -231,27 +221,15 @@ export default class UserProfile extends PureComponent {
};
goToEditProfile = () => {
const {user: currentUser} = this.props;
const {actions, user: currentUser} = this.props;
const {formatMessage} = this.context.intl;
const commandType = 'Push';
const {navigator, theme} = this.props;
const options = {
screen: 'EditProfile',
title: formatMessage({id: 'mobile.routes.edit_profile', defaultMessage: 'Edit Profile'}),
animated: true,
backButtonTitle: '',
passProps: {currentUser, commandType},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
};
const screen = 'EditProfile';
const title = formatMessage({id: 'mobile.routes.edit_profile', defaultMessage: 'Edit Profile'});
const passProps = {currentUser, commandType};
requestAnimationFrame(() => {
navigator.push(options);
actions.goToScreen(screen, title, passProps);
});
};

View file

@ -22,6 +22,10 @@ describe('user_profile', () => {
setChannelDisplayName: jest.fn(),
makeDirectChannel: jest.fn(),
loadBot: jest.fn(),
setButtons: jest.fn(),
dismissModal: jest.fn(),
resetToChannel: jest.fn(),
goToScreen: jest.fn(),
};
const baseProps = {
actions,
@ -29,11 +33,6 @@ describe('user_profile', () => {
ShowEmailAddress: true,
},
teammateNameDisplay: 'username',
navigator: {
resetTo: jest.fn(),
push: jest.fn(),
dismissModal: jest.fn(),
},
teams: [],
theme: Preferences.THEMES.default,
enableTimezone: false,
@ -90,16 +89,9 @@ describe('user_profile', () => {
});
test('should push EditProfile', async () => {
const props = {
...baseProps,
navigator: {
push: jest.fn(),
},
};
const wrapper = shallow(
<UserProfile
{...props}
{...baseProps}
user={user}
/>,
{context: {intl: {formatMessage: jest.fn()}}},
@ -107,18 +99,18 @@ describe('user_profile', () => {
wrapper.instance().goToEditProfile();
setTimeout(() => {
expect(props.navigator.push).toHaveBeenCalledTimes(1);
expect(baseProps.actions.goToScreen).toHaveBeenCalledTimes(1);
}, 16);
});
test('should call goToEditProfile', () => {
const props = {
...baseProps,
navigator: {
push: jest.fn(),
actions: {
...baseProps.actions,
goToScreen: jest.fn(),
},
};
const wrapper = shallow(
<UserProfile
{...props}
@ -130,7 +122,7 @@ describe('user_profile', () => {
const event = {buttonId: wrapper.instance().rightButton.id};
wrapper.instance().navigationButtonPressed(event);
setTimeout(() => {
expect(props.navigator.push).toHaveBeenCalledTimes(1);
expect(baseProps.actions.goToScreen).toHaveBeenCalledTimes(1);
}, 0);
});
@ -147,11 +139,11 @@ describe('user_profile', () => {
const event = {buttonId: 'close-settings'};
wrapper.instance().navigationButtonPressed(event);
expect(props.navigator.dismissModal).toHaveBeenCalledTimes(1);
expect(props.actions.dismissModal).toHaveBeenCalledTimes(1);
props.fromSettings = false;
wrapper.setProps({...props});
wrapper.instance().navigationButtonPressed(event);
expect(props.navigator.resetTo).toHaveBeenCalledTimes(1);
expect(props.actions.resetToChannel).toHaveBeenCalledTimes(1);
});
});

View file

@ -15,6 +15,7 @@ import {
createPostForNotificationReply,
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';
@ -45,7 +46,13 @@ class PushNotificationUtils {
await this.store.dispatch(loadFromPushNotification(notification, true));
if (!EphemeralStore.appStartedFromPushNotification) {
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
EventEmitter.emit('close_channel_drawer');
EventEmitter.emit('close_settings_sidebar');
const {dispatch} = this.store;
dispatch(dismissAllModals());
dispatch(popToRoot());
PushNotifications.resetNotification();
}
};

View file

@ -6,10 +6,8 @@ import IntlWrapper from 'app/components/root';
export function wrapWithContextProvider(Comp, excludeEvents = true) {
return (props) => { //eslint-disable-line react/display-name
const {navigator} = props; //eslint-disable-line react/prop-types
return (
<IntlWrapper
navigator={navigator}
excludeEvents={excludeEvents}
>
<Comp {...props}/>

View file

@ -13,4 +13,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 63cb0d0aef536c8cbf15bf664fcfe31852a13dd1
COCOAPODS: 1.6.1
COCOAPODS: 1.5.3