From d180f324c5731b23527a4c11512fa5a3e55cd7e0 Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Mon, 23 Jan 2017 11:20:11 -0800 Subject: [PATCH] Select last channel viewed (#172) * Select last channel viewed Now saving last channel id to local storage Reworked local storage saving * Feedback review --- app/actions/storage/index.js | 65 ++++++++++++------- app/actions/views/channel.js | 18 ++++- app/actions/views/login.js | 14 +++- app/actions/views/select_team.js | 23 +++++++ .../channel_drawer/channel_drawer.js | 10 ++- .../channel_drawer_container.js | 8 ++- app/scenes/login/login.js | 2 +- app/scenes/login/login_container.js | 4 +- app/scenes/select_team/select_team.js | 5 +- .../select_team/select_team_container.js | 9 +-- 10 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 app/actions/views/select_team.js diff --git a/app/actions/storage/index.js b/app/actions/storage/index.js index 5ae724feb..651f3bf26 100644 --- a/app/actions/storage/index.js +++ b/app/actions/storage/index.js @@ -3,19 +3,22 @@ import {AsyncStorage} from 'react-native'; import {batchActions} from 'redux-batched-actions'; -import {GeneralTypes, TeamsTypes, UsersTypes} from 'service/constants'; -import Client from 'service/client'; +import {ChannelTypes, GeneralTypes, TeamsTypes, UsersTypes} from 'service/constants'; export function loadStorage() { return async (dispatch, getState) => { try { const data = JSON.parse(await AsyncStorage.getItem('storage')); - const {token, url, currentTeamId} = data; + + const {token, url, currentTeamId, ...otherStorage} = data; const credentials = {token, url}; + const currentChannelId = otherStorage[currentTeamId] ? otherStorage[currentTeamId].currentChannelId : ''; + dispatch(batchActions([ {type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: credentials}, - {type: TeamsTypes.SELECT_TEAM, data: currentTeamId} + {type: TeamsTypes.SELECT_TEAM, data: currentTeamId}, + {type: ChannelTypes.SELECT_CHANNEL, data: currentChannelId} ]), getState); } catch (error) { // Error loading data @@ -24,23 +27,41 @@ export function loadStorage() { }; } -export function saveStorage(data = {}) { - return async (dispatch, getState) => { - try { - const clientData = { - token: Client.getToken(), - url: Client.getUrl() - }; +// Passing in a blank key of null or '' merges the data into the current storage. +// Could maybe use some rework +export async function updateStorage(key, data) { + try { + const currentStorage = JSON.parse(await AsyncStorage.getItem('storage')); - const mergedStorageData = Object.assign({}, data, clientData); - - await AsyncStorage.setItem('storage', JSON.stringify(mergedStorageData)); - dispatch({type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data}, getState); - } catch (error) { - // Error saving data - dispatch({type: GeneralTypes.REMOVED_APP_CREDENTIALS, error}, getState); + let mergedData; + if (key !== null && key.length > 0) { + const keyData = currentStorage[key]; + if (typeof data === 'string') { + mergedData = Object.assign({}, {[key]: data}); + } else if (typeof data === 'object') { + mergedData = Object.assign({}, {[key]: {...keyData, ...data}}); + } + } else { + mergedData = data; } - }; + + const mergedStorageData = Object.assign({}, currentStorage, mergedData); + + await saveStorage(mergedStorageData); + + return mergedStorageData; + } catch (error) { + // TODO: Need to handle this error + return null; + } +} + +async function saveStorage(data) { + try { + await AsyncStorage.setItem('storage', JSON.stringify(data)); + } catch (error) { + throw error; + } } export function removeStorage() { @@ -48,7 +69,7 @@ export function removeStorage() { try { await AsyncStorage.removeItem('storage'); } catch (error) { - // Error removing data + // TODO: Error removing data } dispatch({type: UsersTypes.RESET_LOGOUT_STATE}, getState); }; @@ -56,6 +77,6 @@ export function removeStorage() { export default { loadStorage, - saveStorage, - removeStorage + removeStorage, + updateStorage }; diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 990e30482..54ef78804 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -4,6 +4,7 @@ import {batchActions} from 'redux-batched-actions'; import {ViewTypes} from 'app/constants'; +import {updateStorage} from 'app/actions/storage'; import {fetchMyChannelsAndMembers, getMyChannelMembers, selectChannel} from 'service/actions/channels'; import {getPosts} from 'service/actions/posts'; @@ -94,7 +95,14 @@ export function loadPostsIfNecessary(channel) { export function selectInitialChannel(teamId) { return async (dispatch, getState) => { - const channels = getState().entities.channels.channels; + const state = getState(); + const channels = state.entities.channels.channels; + const currentChannelId = state.entities.channels.currentId; + + if (channels[currentChannelId] && channels[currentChannelId].team_id === teamId) { + await selectChannel(currentChannelId)(dispatch, getState); + return; + } const channel = Object.values(channels).find((c) => c.team_id === teamId && c.name === Constants.DEFAULT_CHANNEL); if (channel) { @@ -107,6 +115,14 @@ export function selectInitialChannel(teamId) { }; } +export function handleSelectChannel(channelId) { + return async (dispatch, getState) => { + const currentTeamId = getState().entities.teams.currentId; + await updateStorage(currentTeamId, {currentChannelId: channelId}); + await selectChannel(channelId)(dispatch, getState); + }; +} + export function handlePostDraftChanged(postDraft) { return async (dispatch, getState) => { dispatch({ diff --git a/app/actions/views/login.js b/app/actions/views/login.js index dcedf9e23..78ce77192 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -2,6 +2,8 @@ // See License.txt for license information. import {ViewTypes} from 'app/constants'; +import {updateStorage} from 'app/actions/storage'; +import Client from 'service/client'; export function handleLoginIdChanged(loginId) { return async (dispatch, getState) => { @@ -21,7 +23,17 @@ export function handlePasswordChanged(password) { }; } +export function handleSuccessfulLogin() { + return async () => { + await updateStorage(null, { + url: Client.getUrl(), + token: Client.getToken() + }); + }; +} + export default { handleLoginIdChanged, - handlePasswordChanged + handlePasswordChanged, + handleSuccessfulLogin }; diff --git a/app/actions/views/select_team.js b/app/actions/views/select_team.js new file mode 100644 index 000000000..62a950126 --- /dev/null +++ b/app/actions/views/select_team.js @@ -0,0 +1,23 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {batchActions} from 'redux-batched-actions'; + +import {ChannelTypes, TeamsTypes} from 'service/constants'; +import {updateStorage} from 'app/actions/storage'; + +export function handleTeamChange(team) { + return async (dispatch, getState) => { + const storage = await updateStorage('currentTeamId', team.id); + const lastChannelForTeam = storage[team.id] ? storage[team.id].currentChannelId : ''; + + dispatch(batchActions([ + {type: TeamsTypes.SELECT_TEAM, data: team.id}, + {type: ChannelTypes.SELECT_CHANNEL, data: lastChannelForTeam} + ]), getState); + }; +} + +export default { + handleTeamChange +}; diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 3cb2a1a48..c467d8864 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -12,7 +12,8 @@ export default class ChannelDrawer extends React.Component { static propTypes = { children: React.PropTypes.element.isRequired, actions: React.PropTypes.shape({ - selectChannel: React.PropTypes.func.isRequired, + updateStorage: React.PropTypes.func.isRequired, + handleSelectChannel: React.PropTypes.func.isRequired, viewChannel: React.PropTypes.func.isRequired, closeDMChannel: React.PropTypes.func.isRequired, closeChannelDrawer: React.PropTypes.func.isRequired @@ -29,6 +30,7 @@ export default class ChannelDrawer extends React.Component { super(props); this.handleBackButton = this.handleBackButton.bind(this); + this.selectChannel = this.selectChannel.bind(this); } componentDidMount() { @@ -62,6 +64,10 @@ export default class ChannelDrawer extends React.Component { return false; } + selectChannel(channelId) { + this.props.actions.handleSelectChannel(channelId); + } + render() { const { currentChannel, @@ -92,7 +98,7 @@ export default class ChannelDrawer extends React.Component { channels={channels} channelMembers={channelMembers} theme={theme} - onSelectChannel={this.props.actions.selectChannel} + onSelectChannel={this.selectChannel} onViewChannel={this.props.actions.viewChannel} handleCloseDM={this.props.actions.closeDMChannel} closeChannelDrawer={this.props.actions.closeChannelDrawer} diff --git a/app/components/channel_drawer/channel_drawer_container.js b/app/components/channel_drawer/channel_drawer_container.js index bbcc094dc..42c3ccf54 100644 --- a/app/components/channel_drawer/channel_drawer_container.js +++ b/app/components/channel_drawer/channel_drawer_container.js @@ -4,10 +4,11 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {selectChannel, viewChannel} from 'service/actions/channels'; -import {closeDMChannel} from 'app/actions/views/channel'; +import {viewChannel} from 'service/actions/channels'; import {getChannelsByCategory} from 'service/selectors/entities/channels'; +import {closeDMChannel, handleSelectChannel} from 'app/actions/views/channel'; import {closeChannelDrawer} from 'app/actions/views/drawer'; +import {updateStorage} from 'app/actions/storage'; import ChannelDrawer from './channel_drawer'; function mapStateToProps(state, ownProps) { @@ -24,7 +25,8 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - selectChannel, + updateStorage, + handleSelectChannel, viewChannel, closeDMChannel, closeChannelDrawer diff --git a/app/scenes/login/login.js b/app/scenes/login/login.js index 2e4332cf9..1dab9dcba 100644 --- a/app/scenes/login/login.js +++ b/app/scenes/login/login.js @@ -35,7 +35,7 @@ class Login extends Component { componentWillReceiveProps(nextProps) { if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) { - this.props.actions.saveStorage().then(this.props.actions.goToSelectTeam); + this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToSelectTeam); } } diff --git a/app/scenes/login/login_container.js b/app/scenes/login/login_container.js index 73e5be109..823b5006b 100644 --- a/app/scenes/login/login_container.js +++ b/app/scenes/login/login_container.js @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {getClientConfig, getLicenseConfig} from 'service/actions/general'; import LoginActions from 'app/actions/views/login'; -import * as StorageActions from 'app/actions/storage'; +import {updateRootStorage} from 'app/actions/storage'; import {goToSelectTeam} from 'app/actions/navigation'; import {login} from 'service/actions/users'; @@ -27,7 +27,7 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ ...LoginActions, - ...StorageActions, + updateRootStorage, login, getClientConfig, getLicenseConfig, diff --git a/app/scenes/select_team/select_team.js b/app/scenes/select_team/select_team.js index 86ab31d77..5a8807b35 100644 --- a/app/scenes/select_team/select_team.js +++ b/app/scenes/select_team/select_team.js @@ -55,10 +55,7 @@ export default class SelectTeam extends Component { } onSelectTeam(team) { - this.props.actions.selectTeam(team).then(() => { - this.props.actions.saveStorage({currentTeamId: team.id}). - then(this.props.actions.goToChannelView()); - }); + this.props.actions.handleTeamChange(team).then(this.props.actions.goToChannelView); } render() { diff --git a/app/scenes/select_team/select_team_container.js b/app/scenes/select_team/select_team_container.js index a33ec1715..fbacab8e2 100644 --- a/app/scenes/select_team/select_team_container.js +++ b/app/scenes/select_team/select_team_container.js @@ -4,9 +4,9 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {selectTeam} from 'service/actions/teams'; +import {handleTeamChange} from 'app/actions/views/select_team'; import {init as websocket} from 'service/actions/websocket'; -import {saveStorage} from 'app/actions/storage'; +import {updateRootStorage, loadTeamStorage} from 'app/actions/storage'; import {goToChannelView} from 'app/actions/navigation'; import {getCurrentTeam} from 'service/selectors/entities/teams'; @@ -26,8 +26,9 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ goToChannelView, - saveStorage, - selectTeam, + updateRootStorage, + loadTeamStorage, + handleTeamChange, websocket }, dispatch) };