Fix login transition (#410)

* Fix login transition

* Add instant transition to modal

* Fix transition to show loader on first login

* Add loader to load_team

* Remove back button
This commit is contained in:
Chris Duarte 2017-03-28 19:59:01 -07:00 committed by enahum
parent 5d997ae22b
commit bc18d60883
15 changed files with 103 additions and 31 deletions

View file

@ -303,8 +303,9 @@ export function leaveChannel(channel, reset = false) {
};
}
export function setChannelLoading() {
export function setChannelLoading(loading = true) {
return {
type: ViewTypes.SET_CHANNEL_LOADER
type: ViewTypes.SET_CHANNEL_LOADER,
loading
};
}

View file

@ -1,14 +1,36 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {NavigationTypes} from 'app/constants';
import {NavigationTypes, ViewTypes} from 'app/constants';
import Routes from 'app/navigation/routes';
export function goToChannelView() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_RESET,
routes: [Routes.ChannelView]
}, getState);
const state = getState();
if (state.views.login.loginId) {
dispatch({
type: NavigationTypes.NAVIGATION_REPLACE_AT_ROOT,
route: Routes.ChannelView
}, getState);
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
}, getState);
setTimeout(() => {
dispatch({
type: ViewTypes.APPLICATION_INITIALIZED
}, getState);
}, 800); // modal close takes 400ms
} else {
dispatch({
type: NavigationTypes.NAVIGATION_RESET,
routes: [Routes.ChannelView]
}, getState);
dispatch({
type: ViewTypes.APPLICATION_INITIALIZED
}, getState);
}
};
}

View file

@ -18,9 +18,8 @@ import {markChannelAsRead, viewChannel} from 'mattermost-redux/actions/channels'
export function goToSelectServer() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_RESET,
routes: [Routes.SelectServer],
index: 0
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.SelectServer
}, getState);
};
}

View file

@ -14,6 +14,7 @@ const NavigationTypes = keyMirror({
NAVIGATION_JUMP: null,
NAVIGATION_JUMP_TO_INDEX: null,
NAVIGATION_REPLACE: null,
NAVIGATION_REPLACE_AT_ROOT: null,
NAVIGATION_RESET: null,
NAVIGATION_MODAL: null,
NAVIGATION_CLOSE_MODAL: null,

View file

@ -4,6 +4,8 @@
import keyMirror from 'mattermost-redux/utils/key_mirror';
const ViewTypes = keyMirror({
APPLICATION_INITIALIZED: null,
SERVER_URL_CHANGED: null,
LOGIN_ID_CHANGED: null,

View file

@ -3,7 +3,8 @@
import React, {PropTypes, PureComponent} from 'react';
import {
Animated
Animated,
InteractionManager
} from 'react-native';
const {View: AnimatedView} = Animated;
@ -21,11 +22,13 @@ export default class NavigationModal extends PureComponent {
]),
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
duration: PropTypes.number,
show: PropTypes.bool
}
static defaultProps = {
animationType: ANIMATION_TYPES.SlideFromBottom,
duration: 400,
show: false
}
@ -62,9 +65,13 @@ export default class NavigationModal extends PureComponent {
const animationType = nextProps.show ? nextProps.animationType : this.props.animationType;
if (animationType === ANIMATION_TYPES.SlideFromBottom) {
this.slideFromBottomAnimationRunner(nextProps);
InteractionManager.runAfterInteractions(() => {
this.slideFromBottomAnimationRunner(nextProps);
});
} else if (animationType === ANIMATION_TYPES.Fade) {
this.fadeAnimationRunner(nextProps);
InteractionManager.runAfterInteractions(() => {
this.fadeAnimationRunner(nextProps);
});
}
}
}
@ -81,7 +88,7 @@ export default class NavigationModal extends PureComponent {
});
const setOpacityToFull = Animated.timing(this.state.opacity, {
toValue: 1,
duration: 400
duration: nextProps.duration
});
Animated.sequence([
setOpacityToZero,
@ -99,7 +106,7 @@ export default class NavigationModal extends PureComponent {
} else {
const setOpacityToZero = Animated.timing(this.state.opacity, {
toValue: 0,
duration: 400
duration: nextProps.duration
});
const setTopToDeviceHeight = Animated.timing(this.state.top, {
toValue: this.props.deviceHeight,
@ -130,7 +137,7 @@ export default class NavigationModal extends PureComponent {
Animated.timing(this.state.top, {
toValue: animateValue,
duration: 400
duration: nextProps.duration
}).start(() => {
// Once the scene has finished sliding down we can release the child scene
// which will unmount the scene correctly.

View file

@ -75,7 +75,12 @@ class Router extends Component {
return {};
}
return Object.assign({}, route.navigationProps, route.component.navigationProps);
let component = route.component;
if (!component) {
component = {};
}
return Object.assign({}, route.navigationProps, component.navigationProps);
};
renderTransition = (transitionProps) => {
@ -326,6 +331,7 @@ class Router extends Component {
animationType={modalNavigationProps.modalAnimationType}
deviceHeight={this.state.deviceHeight}
deviceWidth={this.state.deviceWidth}
duration={modalNavigationProps.duration}
show={modalVisible}
>
<NavigationExperimental.Transitioner

View file

@ -187,7 +187,9 @@ export const Routes = {
key: 'SelectServer',
component: SelectServer,
navigationProps: {
title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'}
title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'},
duration: 0,
modalAnimationType: 'fade'
}
},
SelectTeam: {

View file

@ -151,7 +151,7 @@ export default function(state = initialState, action) {
case NavigationTypes.NAVIGATION_RESET:
return {
...state,
...NavigationExperimental.StateUtils.reset(state, action.routes, action.index),
...NavigationExperimental.StateUtils.reset(state, action.routes),
modal: {
index: 0,
requestClose: false,
@ -174,6 +174,9 @@ export default function(state = initialState, action) {
return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route);
}
case NavigationTypes.NAVIGATION_REPLACE_AT_ROOT:
return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route);
case NavigationTypes.NAVIGATION_MODAL: {
const modal = {
index: 0,

View file

@ -118,13 +118,23 @@ function loading(state = false, action) {
case ChannelTypes.SELECT_CHANNEL:
return false;
case ViewTypes.SET_CHANNEL_LOADER:
return true;
return action.loading;
default:
return state;
}
}
function appInitializing(state = true, action) {
switch (action.type) {
case ViewTypes.APPLICATION_INITIALIZED:
return false;
default:
return state;
}
}
export default combineReducers({
appInitializing,
drafts,
loading
});

View file

@ -23,6 +23,7 @@ export default class ChannelPostList extends PureComponent {
getPostsBefore: PropTypes.func.isRequired,
goToThread: PropTypes.func.isRequired
}).isRequired,
applicationInitializing: PropTypes.bool.isRequired,
channel: PropTypes.object.isRequired,
currentTeamId: PropTypes.string.isRequired,
channelIsLoading: PropTypes.bool,
@ -64,7 +65,9 @@ export default class ChannelPostList extends PureComponent {
if (hasFirstPost) {
this.setState({hasFirstPost});
}
this.loaderAnimationRunner();
if (!nextProps.applicationInitializing) {
this.loaderAnimationRunner();
}
} else {
this.setState({
didInitialPostsLoad: false,
@ -112,9 +115,9 @@ export default class ChannelPostList extends PureComponent {
};
render() {
const {channelIsLoading, posts, postsRequests, theme} = this.props;
const {applicationInitializing, channelIsLoading, posts, postsRequests, theme} = this.props;
let component;
if (!channelIsLoading && posts && (postsRequests.getPosts.status !== RequestStatus.STARTED || !this.state.didInitialPostsLoad)) {
if (!applicationInitializing && !channelIsLoading && posts && (postsRequests.getPosts.status !== RequestStatus.STARTED || !this.state.didInitialPostsLoad)) {
component = (
<PostList
posts={posts}

View file

@ -68,6 +68,7 @@ const getPostsInCurrentChannelWithReplyProps = createSelector(
function mapStateToProps(state, ownProps) {
return {
...ownProps,
applicationInitializing: state.views.channel.appInitializing,
currentTeamId: getCurrentTeamId(state),
channelIsLoading: state.views.channel.loading,
myMember: getCurrentChannelMembership(state),

View file

@ -1,10 +1,13 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {PropTypes, PureComponent} from 'react';
import React, {PropTypes, PureComponent} from 'react';
import {View} from 'react-native';
import {RequestStatus} from 'mattermost-redux/constants';
import ChannelLoader from 'app/components/channel_loader';
export default class LoadTeam extends PureComponent {
static propTypes = {
notification: PropTypes.object,
@ -17,9 +20,14 @@ export default class LoadTeam extends PureComponent {
goToChannelView: PropTypes.func.isRequired,
goToNotification: PropTypes.func.isRequired,
handleTeamChange: PropTypes.func.isRequired
}).isRequired
}).isRequired,
theme: PropTypes.object.isRequired
};
static navigationProps = {
renderBackButton: () => null
}
componentDidMount() {
const {notification, currentTeam, myMembers, teams} = this.props;
const {clearNotification, goToNotification} = this.props.actions;
@ -55,6 +63,10 @@ export default class LoadTeam extends PureComponent {
}
render() {
return null;
return (
<View style={{flex: 1}}>
<ChannelLoader theme={this.props.theme}/>
</View>
);
}
}

View file

@ -2,18 +2,22 @@
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {goToChannelView} from 'app/actions/views/load_team';
import {clearNotification, goToNotification} from 'app/actions/views/root';
import {handleTeamChange} from 'app/actions/views/select_team';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {getTheme} from 'app/selectors/preferences';
import navigationSceneConnect from '../navigationSceneConnect';
import LoadTeam from './load_team.js';
function mapStateToProps(state) {
return {
config: state.entities.general.config,
theme: getTheme(state),
teamsRequest: state.requests.teams.allTeams,
teams: state.entities.teams.teams,
currentTeam: getCurrentTeam(state),
@ -33,4 +37,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadTeam);
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(LoadTeam);

View file

@ -21,7 +21,6 @@ import {SplashScreenTypes} from 'app/constants';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import Loading from 'app/components/loading';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import {GlobalStyles} from 'app/styles';
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
@ -116,7 +115,7 @@ export default class SelectServer extends PureComponent {
licenseRequest.status === RequestStatus.STARTED;
if (isLoading) {
return <Loading/>;
return null;
}
const error = pingRequest.error || configRequest.error || licenseRequest.error;