PLT-5598 Android back button support (#388)

* Add shadow to the drawer

* PLT-5964 open a DM with someone outside the team

* PLT-5598 Android back button support
This commit is contained in:
enahum 2017-03-23 19:10:48 -03:00 committed by GitHub
parent 9fec29aa92
commit 4a53d47887
17 changed files with 242 additions and 13 deletions

View file

@ -68,3 +68,11 @@ export function handleRemoveFile(clientId, channelId, rootId) {
rootId
};
}
export function handleRemoveLastFile(channelId, rootId) {
return {
type: ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT,
channelId,
rootId
};
}

View file

@ -3,7 +3,7 @@
import {getDirectChannelName} from 'mattermost-redux/utils/channel_utils';
import {createDirectChannel} from 'mattermost-redux/actions/channels';
import {getTeamMember} from 'mattermost-redux/actions/teams';
import {getProfilesByIds, getStatusesByIds} from 'mattermost-redux/actions/users';
import {handleSelectChannel, toggleDMChannel} from 'app/actions/views/channel';
export function makeDirectChannel(otherUserId) {
@ -15,7 +15,8 @@ export function makeDirectChannel(otherUserId) {
const channel = Object.values(channels).find((c) => c.name === channelName);
const {currentTeamId} = state.entities.teams;
await getTeamMember(currentTeamId, otherUserId)(dispatch, getState);
getProfilesByIds([otherUserId])(dispatch, getState);
getStatusesByIds([otherUserId])(dispatch, getState);
if (channel && myMembers[channel.id]) {
await toggleDMChannel(otherUserId, 'true')(dispatch, getState);

View file

@ -3,6 +3,9 @@
import React, {PropTypes, PureComponent} from 'react';
import {
BackAndroid,
Keyboard,
Platform,
StyleSheet,
TouchableHighlight,
View,
@ -35,6 +38,7 @@ export default class PostTextbox extends PureComponent {
actions: PropTypes.shape({
closeModal: PropTypes.func.isRequired,
createPost: PropTypes.func.isRequired,
handleRemoveLastFile: PropTypes.func.isRequired,
handleUploadFiles: PropTypes.func.isRequired,
showOptionsModal: PropTypes.func.isRequired,
userTyping: PropTypes.func.isRequired
@ -56,6 +60,20 @@ export default class PostTextbox extends PureComponent {
};
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
BackAndroid.addEventListener('hardwareBackPress', this.handleAndroidBack);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
BackAndroid.removeEventListener('hardwareBackPress', this.handleAndroidBack);
}
}
blur = () => {
this.refs.input.getWrappedInstance().blur();
};
@ -66,6 +84,19 @@ export default class PostTextbox extends PureComponent {
});
};
handleAndroidKeyboard = () => {
this.blur();
};
handleAndroidBack = () => {
const {channelId, files, rootId} = this.props;
if (files.length) {
this.props.actions.handleRemoveLastFile(channelId, rootId);
return true;
}
return false;
};
sendMessage = () => {
if ((this.props.value.trim().length === 0 && this.props.files.length === 0) || this.props.uploadFileRequestStatus === RequestStatus.STARTED) {
return;

View file

@ -7,7 +7,7 @@ import {createPost} from 'mattermost-redux/actions/posts';
import {userTyping} from 'mattermost-redux/actions/websocket';
import {showOptionsModal, requestCloseModal} from 'app/actions/navigation';
import {handleUploadFiles} from 'app/actions/views/file_upload';
import {handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
import {getTheme} from 'app/selectors/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getUsersTyping} from 'mattermost-redux/selectors/entities/typing';
@ -29,6 +29,7 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
createPost,
closeModal: requestCloseModal,
handleRemoveLastFile,
handleUploadFiles,
showOptionsModal,
userTyping

View file

@ -2,7 +2,16 @@
// See License.txt for license information.
import React, {Component, PropTypes} from 'react';
import {Alert, AppState, AsyncStorage, InteractionManager, StatusBar, View} from 'react-native';
import {
Alert,
AppState,
AsyncStorage,
BackAndroid,
InteractionManager,
Platform,
StatusBar,
View
} from 'react-native';
import {IntlProvider} from 'react-intl';
import DeviceInfo from 'react-native-device-info';
import semver from 'semver';
@ -22,8 +31,11 @@ export default class Root extends Component {
currentTeamId: PropTypes.string,
currentChannelId: PropTypes.string,
locale: PropTypes.string.isRequired,
navigation: PropTypes.object.isRequired,
actions: PropTypes.shape({
closeDrawers: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
goBack: PropTypes.func.isRequired,
loadConfigAndLicense: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired,
setAppState: PropTypes.func.isRequired,
@ -44,11 +56,19 @@ export default class Root extends Component {
AppState.addEventListener('change', this.handleAppStateChange);
EventEmitter.on(Constants.CONFIG_CHANGED, this.handleConfigChanged);
Client.setUserAgent(DeviceInfo.getUserAgent());
if (Platform.OS === 'android') {
BackAndroid.addEventListener('hardwareBackPress', this.handleAndroidBack);
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
EventEmitter.off(Constants.CONFIG_CHANGED, this.handleConfigChanged);
if (Platform.OS === 'android') {
BackAndroid.removeEventListener('hardwareBackPress', this.handleAndroidBack);
}
}
handleAppStateChange(appState) {
@ -59,6 +79,30 @@ export default class Root extends Component {
}
}
handleAndroidBack = () => {
const {index, isModal, leftDrawerOpen, modal} = this.props.navigation;
const {closeDrawers, closeModal, goBack} = this.props.actions;
if (isModal) {
if (modal.index > 0) {
goBack();
return true;
}
closeModal();
return true;
}
if (leftDrawerOpen) {
closeDrawers();
return true;
} else if (index > 0) {
goBack();
return true;
}
return false;
};
handleVersionUpgrade = async () => {
const {closeDrawers, logout, unrenderDrawer} = this.props.actions;

View file

@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import Config from 'assets/config.json';
import {closeDrawers, unrenderDrawer} from 'app/actions/navigation';
import {closeDrawers, closeModal, goBack, unrenderDrawer} from 'app/actions/navigation';
import {flushToStorage} from 'app/actions/storage';
import {goToNotification, loadConfigAndLicense, queueNotification} from 'app/actions/views/root';
import {setAppState, setDeviceToken} from 'mattermost-redux/actions/general';
@ -29,7 +29,8 @@ function mapStateToProps(state, ownProps) {
...ownProps,
currentTeamId,
currentChannelId,
locale
locale,
navigation: state.navigation
};
}
@ -37,6 +38,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
closeDrawers,
closeModal,
goBack,
loadConfigAndLicense,
logout,
goToNotification,

View file

@ -293,6 +293,9 @@ class Router extends Component {
useInteractionManager={false}
tweenHandler={this.handleDrawerTween}
elevation={-5}
styles={{
main: {shadowColor: '#000000', shadowOpacity: 0.8, shadowRadius: 5}
}}
>
<Drawer
open={false}

View file

@ -95,6 +95,19 @@ function drafts(state = {}, action) {
[action.channelId]: Object.assign({}, state[action.channelId], {files})
};
}
case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: {
if (action.rootId) {
return state;
}
const files = [...state[action.channelId].files];
files.splice(-1);
return {
...state,
[action.channelId]: Object.assign({}, state[action.channelId], {files})
};
}
default:
return state;
}

View file

@ -82,6 +82,31 @@ function drafts(state = {}, action) {
[action.rootId]: Object.assign({}, state[action.rootId], {files: []})
};
}
case ViewTypes.REMOVE_FILE_FROM_POST_DRAFT: {
if (!action.rootId) {
return state;
}
const files = state[action.rootId].files.filter((file) => (file.clientId !== action.clientId));
return {
...state,
[action.rootId]: Object.assign({}, state[action.rootId], {files})
};
}
case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: {
if (!action.rootId) {
return state;
}
const files = [...state[action.rootId].files];
files.splice(-1);
return {
...state,
[action.rootId]: Object.assign({}, state[action.rootId], {files})
};
}
default:
return state;
}

View file

@ -3,6 +3,8 @@
import React, {PropTypes, PureComponent} from 'react';
import {
Keyboard,
Platform,
ScrollView,
StyleSheet,
View
@ -81,8 +83,18 @@ export default class AccountNotifications extends PureComponent {
this.props.subscribeToHeaderEvent(SAVE_NOTIFY_PROPS, this.saveUserNotifyProps);
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillUnmount() {
this.props.unsubscribeFromHeaderEvent(SAVE_NOTIFY_PROPS);
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillReceiveProps(nextProps) {
@ -99,6 +111,10 @@ export default class AccountNotifications extends PureComponent {
}
}
handleAndroidKeyboard = () => {
this.refs.mention_keys.getWrappedInstance().blur();
};
setStateFromNotifyProps = (notifyProps) => {
const mentionKeys = (notifyProps.mention_keys || '').split(',');
const usernameMentionIndex = mentionKeys.indexOf(this.props.currentUser.username);
@ -262,6 +278,7 @@ export default class AccountNotifications extends PureComponent {
theme={theme}
>
<TextInputWithLocalizedPlaceholder
ref='mention_keys'
value={this.state.mention_keys}
onChangeText={this.updateMentionKeys}
style={style.input}

View file

@ -15,6 +15,18 @@ import {getTheme} from 'app/selectors/preferences';
function ChannelTitle(props) {
const channelName = props.currentChannel.display_name;
let icon;
if (channelName) {
icon = (
<Icon
style={{marginHorizontal: 6}}
size={12}
name='chevron-down'
color={props.theme.sidebarHeaderTextColor}
/>
);
}
return (
<TouchableOpacity
style={{flexDirection: 'row', flex: 1}}
@ -24,12 +36,7 @@ function ChannelTitle(props) {
<Text style={{color: props.theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold'}}>
{channelName}
</Text>
<Icon
style={{marginHorizontal: 6}}
size={12}
name='chevron-down'
color={props.theme.sidebarHeaderTextColor}
/>
{icon}
</View>
</TouchableOpacity>
);

View file

@ -6,6 +6,7 @@ import {injectIntl, intlShape} from 'react-intl';
import {
Image,
Keyboard,
Platform,
Text,
TextInput,
TouchableWithoutFeedback,
@ -50,12 +51,28 @@ class Login extends Component {
};
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillReceiveProps(nextProps) {
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToLoadTeam);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
handleAndroidKeyboard = () => {
this.blur();
};
blur = () => {
this.loginId.blur();
this.passwd.blur();

View file

@ -5,6 +5,7 @@ import React, {Component} from 'react';
import {
Image,
Keyboard,
Platform,
TouchableWithoutFeedback,
View
} from 'react-native';
@ -40,6 +41,12 @@ export default class Mfa extends Component {
};
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillReceiveProps(nextProps) {
// In case the login is successful the previous scene (login) will take care of the transition
if (this.props.loginRequest.status === RequestStatus.STARTED &&
@ -48,6 +55,16 @@ export default class Mfa extends Component {
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
handleAndroidKeyboard = () => {
this.blur();
};
handleInput = (token) => {
this.setState({
token,

View file

@ -4,6 +4,8 @@
import React, {PropTypes, PureComponent} from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {
Keyboard,
Platform,
InteractionManager,
StyleSheet,
TouchableOpacity,
@ -95,6 +97,10 @@ class MoreChannels extends PureComponent {
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
InteractionManager.runAfterInteractions(() => {
this.props.actions.getMoreChannels(this.props.currentTeamId, 0);
});
@ -103,6 +109,10 @@ class MoreChannels extends PureComponent {
componentWillUnmount() {
this.props.unsubscribeFromHeaderEvent('close');
this.props.unsubscribeFromHeaderEvent('new_channel');
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
filterChannels = (channels, term) => {
@ -111,6 +121,10 @@ class MoreChannels extends PureComponent {
});
};
handleAndroidKeyboard = () => {
this.onSearchButtonPress();
};
searchBarRef = (ref) => {
this.searchBar = ref;
};

View file

@ -4,6 +4,8 @@
import React, {PropTypes, PureComponent} from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {
Keyboard,
Platform,
InteractionManager,
StyleSheet,
TouchableOpacity,
@ -86,6 +88,10 @@ class MoreDirectMessages extends PureComponent {
}
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
InteractionManager.runAfterInteractions(() => {
this.props.actions.getProfiles(0);
});
@ -93,8 +99,16 @@ class MoreDirectMessages extends PureComponent {
componentWillUnmount() {
this.props.unsubscribeFromHeaderEvent('close');
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
handleAndroidKeyboard = () => {
this.onSearchButtonPress();
};
searchBarRef = (ref) => {
this.searchBar = ref;
};

View file

@ -5,6 +5,7 @@ import React, {PropTypes, PureComponent} from 'react';
import {
Image,
Keyboard,
Platform,
StatusBar,
TouchableWithoutFeedback,
View
@ -50,6 +51,9 @@ export default class SelectServer extends PureComponent {
componentDidMount() {
this.props.actions.unrenderDrawer();
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillReceiveProps(nextProps) {
@ -60,6 +64,16 @@ export default class SelectServer extends PureComponent {
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
handleAndroidKeyboard = () => {
this.blur();
};
onClick = async () => {
const url = this.props.serverUrl;
let error = null;

View file

@ -9,7 +9,7 @@
"harmony-reflect": "1.5.1",
"intl": "1.2.5",
"isomorphic-fetch": "2.2.1",
"mattermost-redux": "mattermost/mattermost-redux#c0451f9fe8a6df3330c4b5d415a208e85ed08cba",
"mattermost-redux": "mattermost/mattermost-redux#release-3.7",
"react": "15.4.1",
"react-addons-pure-render-mixin": "15.4.1",
"react-intl": "2.2.2",