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:
parent
9fec29aa92
commit
4a53d47887
17 changed files with 242 additions and 13 deletions
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue