Automated cherry pick of #4304 (#4306)

* Fix infinite skeleton in different use cases

* Apply suggestions from code review

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>

* Update app/utils/teams.js

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
This commit is contained in:
Mattermost Build
2020-05-20 01:08:58 +02:00
committed by GitHub
parent ea0f8ab5f0
commit dba3278c9f
32 changed files with 247 additions and 188 deletions

View File

@@ -368,11 +368,15 @@ async function getProfilesFromPromises(promises: Array<Promise<ActionResult>>):
return null;
}
const result = await Promise.all(promises);
const data = result.filter((p: any) => !p.error);
try {
const result = await Promise.all(promises);
const data = result.filter((p: any) => !p.error);
return {
type: UserTypes.RECEIVED_BATCHED_PROFILES_IN_CHANNEL,
data,
};
return {
type: UserTypes.RECEIVED_BATCHED_PROFILES_IN_CHANNEL,
data,
};
} catch {
return null;
}
}

View File

@@ -26,7 +26,7 @@ import {
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
import {getTeamByName} from '@mm-redux/selectors/entities/teams';
import {getChannelByName as selectChannelByName} from '@mm-redux/utils/channel_utils';
import {getChannelByName as selectChannelByName, getChannelsIdForTeam} from '@mm-redux/utils/channel_utils';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {loadSidebarDirectMessagesProfiles} from '@actions/helpers/channels';
@@ -224,10 +224,15 @@ export function handleSelectChannel(channelId) {
const channel = channels[channelId];
const member = myMembers[channelId];
dispatch(loadPostsIfNecessaryWithRetry(channelId));
if (channel) {
dispatch(loadPostsIfNecessaryWithRetry(channelId));
if (channel && currentChannelId !== channelId) {
const actions = markAsViewedAndReadBatch(state, channelId, currentChannelId);
let previousChannelId = null;
if (currentChannelId !== channelId) {
previousChannelId = currentChannelId;
}
const actions = markAsViewedAndReadBatch(state, channelId, previousChannelId);
actions.push({
type: ChannelTypes.SELECT_CHANNEL,
data: channelId,
@@ -237,10 +242,11 @@ export function handleSelectChannel(channelId) {
teamId: channel.team_id || currentTeamId,
},
});
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
}
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
}
};
}
@@ -321,7 +327,9 @@ export function markAsViewedAndReadBatch(state, channelId, prevChannelId = '', m
const prevChannel = (!prevChanManuallyUnread && prevChannelId) ? channels[prevChannelId] : null; // May be null since prevChannelId is optional
if (markOnServer) {
Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId);
Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId).catch(() => {
// do nothing just adding the handler to avoid the warning
});
}
if (member) {
@@ -600,7 +608,12 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
return async (dispatch, getState) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
const data = {sync: true, teamId};
const data = {
sync: true,
teamId,
teamChannels: getChannelsIdForTeam(state, teamId),
};
const actions = [];
if (currentUserId) {
@@ -639,12 +652,17 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
}
if (rolesToLoad.size > 0) {
data.roles = await Client4.getRolesByNames(Array.from(rolesToLoad));
if (data.roles.length) {
actions.push({
type: RoleTypes.RECEIVED_ROLES,
data: data.roles,
});
try {
data.roles = await Client4.getRolesByNames(Array.from(rolesToLoad));
if (data.roles.length) {
actions.push({
type: RoleTypes.RECEIVED_ROLES,
data: data.roles,
});
}
} catch {
//eslint-disable-next-line no-console
console.log('Could not retrieve channel members roles for the user');
}
}

View File

@@ -320,7 +320,7 @@ describe('Actions.Views.Channel', () => {
teamId: currentTeamId,
},
};
if (channelId.includes('not') || channelId === currentChannelId) {
if (channelId.includes('not')) {
expect(selectChannelWithMember).toBe(undefined);
} else {
expect(selectChannelWithMember).toStrictEqual(expectedSelectChannelWithMember);

View File

@@ -87,7 +87,10 @@ export function getPosts(channelId, page = 0, perPage = Posts.POST_CHUNK_SIZE) {
const postForChannel = postsInChannel[channelId];
const data = await Client4.getPosts(channelId, page, perPage);
const posts = Object.values(data.posts);
const actions = [];
const actions = [{
type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
failed: false,
}];
if (posts?.length) {
actions.push(receivedPosts(data));

View File

@@ -32,10 +32,16 @@ export function selectDefaultTeam() {
const state = getState();
const {ExperimentalPrimaryTeam} = getConfig(state);
const {teams: allTeams, myMembers} = state.entities.teams;
const teams = Object.keys(myMembers).map((key) => allTeams[key]);
const {teams, myMembers} = state.entities.teams;
const myTeams = Object.keys(teams).reduce((result, id) => {
if (myMembers[id]) {
result.push(teams[id]);
}
let defaultTeam = selectFirstAvailableTeam(teams, ExperimentalPrimaryTeam);
return result;
}, []);
let defaultTeam = selectFirstAvailableTeam(myTeams, ExperimentalPrimaryTeam);
if (defaultTeam) {
dispatch(handleTeamChange(defaultTeam.id));

View File

@@ -183,13 +183,17 @@ export function login(loginId, password, mfaToken, ldapOnly = false) {
}
export function ssoLogin(token) {
return async (dispatch) => {
return async (dispatch, getState) => {
const state = getState();
const deviceToken = state.entities?.general?.deviceToken;
Client4.setToken(token);
await setCSRFFromCookie(Client4.getUrl());
const result = await dispatch(loadMe());
if (!result.error) {
dispatch(completeLogin(result.data.user));
dispatch(completeLogin(result.data.user, deviceToken));
}
return result;

View File

@@ -3,39 +3,48 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import {Text, View} from 'react-native';
import {intlShape} from 'react-intl';
import Button from 'react-native-button';
import FormattedText from 'app/components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Cloud from './cloud';
export default class FailedNetworkAction extends PureComponent {
static propTypes = {
onRetry: PropTypes.func.isRequired,
actionId: PropTypes.string,
actionDefaultMessage: PropTypes.string,
errorId: PropTypes.string,
errorDefaultMessage: PropTypes.string,
actionText: PropTypes.string,
errorMessage: PropTypes.string,
errorTitle: PropTypes.string,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
static defaultProps = {
actionId: t('mobile.failed_network_action.retry'),
actionDefaultMessage: 'try again',
errorId: t('mobile.failed_network_action.shortDescription'),
errorDefaultMessage: 'Messages will load when you have an internet connection or {tryAgainAction}.',
showAction: true,
};
render() {
const {actionId, actionDefaultMessage, errorId, errorDefaultMessage, onRetry, theme} = this.props;
const {formatMessage} = this.context.intl;
const {onRetry, theme} = this.props;
const style = getStyleFromTheme(theme);
const errorTitle = {
id: t('mobile.failed_network_action.title'),
const actionText = this.props.actionText || formatMessage({
id: 'mobile.failed_network_action.retry',
defaultMessage: 'Try again',
});
const errorTitle = this.props.errorTitle || formatMessage({
id: 'mobile.failed_network_action.title',
defaultMessage: 'No internet connection',
};
});
const errorMessage = this.props.errorMessage || formatMessage({
id: 'mobile.failed_network_action.shortDescription',
defaultMessage: 'Messages will load when you have an internet connection.',
});
return (
<View style={style.container}>
@@ -44,26 +53,14 @@ export default class FailedNetworkAction extends PureComponent {
height={76}
width={76}
/>
<FormattedText
id={errorTitle.id}
defaultMessage={errorTitle.defaultMessage}
style={style.title}
/>
<FormattedText
id={errorId}
defaultMessage={errorDefaultMessage}
style={style.description}
values={{
tryAgainAction: (
<FormattedText
id={actionId}
defaultMessage={actionDefaultMessage}
style={style.link}
onPress={onRetry}
/>
),
}}
/>
<Text style={style.title}>{errorTitle}</Text>
<Text style={style.description}>{errorMessage}</Text>
<Button
onPress={onRetry}
containerStyle={style.buttonContainer}
>
<Text style={style.link}>{actionText}</Text>
</Button>
</View>
);
}
@@ -92,7 +89,16 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
textAlign: 'center',
},
link: {
color: theme.linkColor,
color: theme.buttonColor,
fontSize: 15,
},
buttonContainer: {
backgroundColor: theme.buttonBg,
borderRadius: 5,
height: 42,
justifyContent: 'center',
marginTop: 20,
paddingHorizontal: 12,
},
};
});

View File

@@ -68,7 +68,7 @@ export default class Root extends PureComponent {
const {currentUrl, theme} = this.props;
const {intl} = this.providerRef.getChildContext();
let passProps = null;
let passProps = {theme};
const options = {topBar: {}};
if (Platform.OS === 'android') {
options.topBar.rightButtons = [{
@@ -87,6 +87,7 @@ export default class Root extends PureComponent {
if (screen === 'SelectTeam') {
passProps = {
...passProps,
currentUrl,
userWithoutTeams: true,
};

View File

@@ -113,11 +113,15 @@ export default class MainSidebarIOS extends MainSidebarBase {
};
render() {
const {children} = this.props;
const {children, currentUserId} = this.props;
const {deviceWidth, openDrawerOffset} = this.state;
const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView && this.state.permanentSidebar;
const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset);
if (!currentUserId) {
return null;
}
return (
<DrawerLayout
ref={this.drawerRef}

View File

@@ -71,7 +71,7 @@ export default class MainSidebarBase extends Component {
const condition = nextProps.currentTeamId !== currentTeamId ||
nextProps.teamsCount !== teamsCount ||
nextProps.theme !== theme;
nextProps.theme !== theme || this.props.children !== nextProps.children;
if (Platform.OS === 'ios') {
return condition ||
@@ -263,6 +263,10 @@ export default class MainSidebarBase extends Component {
selectChannel = (channel, currentChannelId, closeDrawer = true) => {
const {logChannelSwitch, handleSelectChannel} = this.props.actions;
if (channel.id === currentChannelId) {
return;
}
logChannelSwitch(channel.id, currentChannelId);
tracker.channelSwitch = Date.now();

View File

@@ -10,10 +10,10 @@ import TeamIcon from './team_icon';
function mapStateToProps(state, ownProps) {
const team = getTeam(state, ownProps.teamId);
const lastIconUpdate = team.last_team_icon_update;
const lastIconUpdate = team?.last_team_icon_update;
return {
displayName: team.display_name,
displayName: team?.display_name,
lastIconUpdate,
theme: getTheme(state),
};

View File

@@ -87,7 +87,7 @@ export default class TeamIcon extends React.PureComponent {
} else {
teamIconContent = (
<Text style={[styles.text, styleText]}>
{displayName.substr(0, 2).toUpperCase()}
{displayName?.substr(0, 2).toUpperCase()}
</Text>
);
}

View File

@@ -365,14 +365,14 @@ function myMembers(state: RelationOneToOne<Channel, ChannelMembership> = {}, act
case ChannelTypes.RECEIVED_MY_CHANNELS_WITH_MEMBERS: { // Used by the mobile app
const nextState: any = {...state};
const current = Object.values(nextState);
const {sync, channelMembers} = action.data;
const {sync, teamChannels, channelMembers} = action.data;
let hasNewValues = channelMembers && channelMembers.length > 0;
// Remove existing channel memberships when the user is no longer a member
if (sync) {
current.forEach((member: ChannelMembership) => {
const id = member.channel_id;
if (channelMembers.find((cm: ChannelMembership) => cm.channel_id !== id)) {
if (channelMembers.find((cm: ChannelMembership) => cm.channel_id !== id && teamChannels.includes(id))) {
delete nextState[id];
hasNewValues = true;
}

View File

@@ -11,7 +11,7 @@ export default ((state: Array<{error: any;displayable?: boolean;date: string}> =
return nextState;
}
case ErrorTypes.LOG_ERROR: {
const nextState = [...state];
const nextState = state.length ? [...state] : [];
const {displayable, error} = action;
nextState.push({
displayable,

View File

@@ -31,18 +31,10 @@ export default class ChannelAndroid extends ChannelBase {
render() {
const {theme} = this.props;
const channelLoadingOrFailed = this.renderLoadingOrFailedChannel();
if (channelLoadingOrFailed) {
return channelLoadingOrFailed;
}
let component = this.renderLoadingOrFailedChannel();
const drawerContent = (
<>
<ChannelNavBar
openMainSidebar={this.openMainSidebar}
openSettingsSidebar={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
if (!component) {
component = (
<KeyboardLayout>
<View style={style.flex}>
<ChannelPostList/>
@@ -52,6 +44,17 @@ export default class ChannelAndroid extends ChannelBase {
screenId={this.props.componentId}
/>
</KeyboardLayout>
);
}
const drawerContent = (
<>
<ChannelNavBar
openMainSidebar={this.openMainSidebar}
openSettingsSidebar={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
{component}
<NetworkIndicator/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
</>

View File

@@ -54,10 +54,27 @@ export default class ChannelIOS extends ChannelBase {
render() {
const {currentChannelId, theme} = this.props;
let component = this.renderLoadingOrFailedChannel();
let renderDraftArea = false;
const channelLoadingOrFailed = this.renderLoadingOrFailedChannel();
if (channelLoadingOrFailed) {
return channelLoadingOrFailed;
if (!component) {
renderDraftArea = true;
component = (
<>
<ChannelPostList
updateNativeScrollView={this.updateNativeScrollView}
/>
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
<Autocomplete
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
onChangeText={this.handleAutoComplete}
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
/>
</View>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
</>
);
}
const style = getStyle(theme);
@@ -71,19 +88,9 @@ export default class ChannelIOS extends ChannelBase {
openSettingsSidebar={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<ChannelPostList
updateNativeScrollView={this.updateNativeScrollView}
/>
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
<Autocomplete
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
onChangeText={this.handleAutoComplete}
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
/>
</View>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
{component}
</SafeAreaView>
{renderDraftArea &&
<KeyboardTrackingView
ref={this.keyboardTracker}
scrollViewNativeID={currentChannelId}
@@ -96,6 +103,7 @@ export default class ChannelIOS extends ChannelBase {
screenId={this.props.componentId}
/>
</KeyboardTrackingView>
}
</>
);

View File

@@ -7,14 +7,11 @@ import {intlShape} from 'react-intl';
import {
Keyboard,
StyleSheet,
View,
} from 'react-native';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {showModal, showModalOverCurrentContext} from '@actions/navigation';
import LocalConfig from '@assets/config';
import SafeAreaView from '@components/safe_area_view';
import EmptyToolbar from '@components/start/empty_toolbar';
import {NavigationTypes} from '@constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import EphemeralStore from '@store/ephemeral_store';
@@ -30,19 +27,19 @@ export let ClientUpgradeListener;
export default class ChannelBase extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
getChannelStats: PropTypes.func.isRequired,
loadChannelsForTeam: PropTypes.func.isRequired,
selectDefaultTeam: PropTypes.func.isRequired,
selectInitialChannel: PropTypes.func.isRequired,
recordLoadTime: PropTypes.func.isRequired,
getChannelStats: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string.isRequired,
currentChannelId: PropTypes.string,
currentTeamId: PropTypes.string,
isLandscape: PropTypes.bool,
disableTermsModal: PropTypes.bool,
teamName: PropTypes.string,
theme: PropTypes.object.isRequired,
showTermsOfService: PropTypes.bool,
disableTermsModal: PropTypes.bool,
skipMetrics: PropTypes.bool,
};
@@ -213,9 +210,10 @@ export default class ChannelBase extends PureComponent {
};
renderLoadingOrFailedChannel() {
const {formatMessage} = this.context.intl;
const {
currentChannelId,
isLandscape,
teamName,
theme,
} = this.props;
@@ -223,37 +221,28 @@ export default class ChannelBase extends PureComponent {
if (!currentChannelId) {
if (channelsRequestFailed) {
const FailedNetworkAction = require('app/components/failed_network_action').default;
const title = formatMessage({id: 'mobile.failed_network_action.teams_title', defaultMessage: 'Something went wrong'});
const message = formatMessage({
id: 'mobile.failed_network_action.teams_channel_description',
defaultMessage: 'Channels could not be loaded for {teamName}.',
}, {teamName});
return (
<SafeAreaView>
<View style={style.flex}>
<EmptyToolbar
theme={theme}
isLandscape={isLandscape}
/>
<FailedNetworkAction
onRetry={this.retryLoadChannels}
theme={theme}
/>
</View>
</SafeAreaView>
<FailedNetworkAction
errorMessage={message}
errorTitle={title}
onRetry={this.retryLoadChannels}
theme={theme}
/>
);
}
const Loading = require('app/components/channel_loader').default;
return (
<SafeAreaView>
<View style={style.flex}>
<EmptyToolbar
theme={theme}
isLandscape={isLandscape}
/>
<Loading
channelIsLoading={true}
color={theme.centerChannelColor}
/>
</View>
</SafeAreaView>
<Loading
channelIsLoading={true}
color={theme.centerChannelColor}
/>
);
}

View File

@@ -4,30 +4,24 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from '@mm-redux/actions/users';
import {loadChannelsForTeam, selectInitialChannel} from '@actions/views/channel';
import {recordLoadTime} from '@actions/views/root';
import {selectDefaultTeam} from '@actions/views/select_team';
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {shouldShowTermsOfService} from '@mm-redux/selectors/entities/users';
import {getChannelStats} from '@mm-redux/actions/channels';
import {
loadChannelsForTeam,
selectInitialChannel,
} from 'app/actions/views/channel';
import {connection} from 'app/actions/device';
import {recordLoadTime} from 'app/actions/views/root';
import {logout} from 'app/actions/views/user';
import {selectDefaultTeam} from 'app/actions/views/select_team';
import {isLandscape} from 'app/selectors/device';
import Channel from './channel';
function mapStateToProps(state) {
const currentTeam = getCurrentTeam(state);
return {
currentTeamId: getCurrentTeamId(state),
currentTeamId: currentTeam?.id,
currentChannelId: getCurrentChannelId(state),
isLandscape: isLandscape(state),
teamName: currentTeam?.display_name,
theme: getTheme(state),
showTermsOfService: shouldShowTermsOfService(state),
};
@@ -37,14 +31,10 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getChannelStats,
connection,
loadChannelsForTeam,
logout,
selectDefaultTeam,
selectInitialChannel,
recordLoadTime,
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates,
}, dispatch),
};
}

View File

@@ -10,11 +10,10 @@ exports[`ErrorTeamsList should match snapshot 1`] = `
>
<Connect(StatusBar) />
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
errorMessage="Teams could not be loaded."
errorTitle="Something went wrong"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -3,6 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
InteractionManager,
StyleSheet,
@@ -26,6 +27,10 @@ export default class ErrorTeamsList extends PureComponent {
theme: PropTypes.object,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
@@ -67,16 +72,25 @@ export default class ErrorTeamsList extends PureComponent {
}
render() {
const {formatMessage} = this.context.intl;
const {theme} = this.props;
if (this.state.loading) {
return <Loading color={theme.centerChannelColor}/>;
}
const title = formatMessage({id: 'mobile.failed_network_action.teams_title', defaultMessage: 'Something went wrong'});
const message = formatMessage({
id: 'mobile.failed_network_action.teams_description',
defaultMessage: 'Teams could not be loaded.',
});
return (
<View style={style.container}>
<StatusBar/>
<FailedNetworkAction
errorMessage={message}
errorTitle={title}
onRetry={this.getUserInfo}
theme={theme}
/>

View File

@@ -2,11 +2,11 @@
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import FailedNetworkAction from '@components/failed_network_action';
import Preferences from '@mm-redux/constants/preferences';
import {shallowWithIntl} from 'test/intl-test-helper.js';
import FailedNetworkAction from 'app/components/failed_network_action';
import ErrorTeamsList from './error_teams_list';
describe('ErrorTeamsList', () => {
@@ -28,7 +28,7 @@ describe('ErrorTeamsList', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(
const wrapper = shallowWithIntl(
<ErrorTeamsList {...baseProps}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@@ -51,7 +51,7 @@ describe('ErrorTeamsList', () => {
actions,
};
const wrapper = shallow(
const wrapper = shallowWithIntl(
<ErrorTeamsList {...newProps}/>,
);

View File

@@ -40,11 +40,8 @@ exports[`FlaggedPosts should match snapshot when getFlaggedPosts failed 1`] = `
>
<Connect(StatusBar) />
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -16,10 +16,10 @@ export function registerScreens(store, Provider) {
Root = require('app/components/root').default;
}
const wrapper = (Comp) => (props) => ( // eslint-disable-line react/display-name
const wrapper = (Comp, excludeEvents = true) => (props) => ( // eslint-disable-line react/display-name
<Provider store={store}>
<ThemeProvider>
<Root>
<Root excludeEvents={excludeEvents}>
<Comp {...props}/>
</Root>
</ThemeProvider>
@@ -29,7 +29,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('About', () => wrapper(require('app/screens/about').default), () => require('app/screens/about').default);
Navigation.registerComponent('AddReaction', () => wrapper(require('app/screens/add_reaction').default), () => require('app/screens/add_reaction').default);
Navigation.registerComponent('AdvancedSettings', () => wrapper(require('app/screens/settings/advanced_settings').default), () => require('app/screens/settings/advanced_settings').default);
Navigation.registerComponent('Channel', () => wrapper(require('app/screens/channel').default), () => require('app/screens/channel').default);
Navigation.registerComponent('Channel', () => wrapper(require('app/screens/channel').default, false), () => require('app/screens/channel').default);
Navigation.registerComponent('ChannelAddMembers', () => wrapper(require('app/screens/channel_add_members').default), () => require('app/screens/channel_add_members').default);
Navigation.registerComponent('ChannelInfo', () => wrapper(require('app/screens/channel_info').default), () => require('app/screens/channel_info').default);
Navigation.registerComponent('ChannelMembers', () => wrapper(require('app/screens/channel_members').default), () => require('app/screens/channel_members').default);
@@ -67,7 +67,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('RecentMentions', () => wrapper(require('app/screens/recent_mentions').default), () => require('app/screens/recent_mentions').default);
Navigation.registerComponent('Search', () => wrapper(require('app/screens/search').default), () => require('app/screens/search').default);
Navigation.registerComponent('SelectorScreen', () => wrapper(require('app/screens/selector_screen').default), () => require('app/screens/selector_screen').default);
Navigation.registerComponent('SelectServer', () => wrapper(require('app/screens/select_server').default), () => require('app/screens/select_server').default);
Navigation.registerComponent('SelectServer', () => wrapper(require('app/screens/select_server').default, false), () => require('app/screens/select_server').default);
Navigation.registerComponent('SelectTeam', () => wrapper(require('app/screens/select_team').default), () => require('app/screens/select_team').default);
Navigation.registerComponent('SelectTimezone', () => wrapper(require('app/screens/settings/timezone/select_timezone').default), () => require('app/screens/settings/timezone/select_timezone').default);
Navigation.registerComponent('Settings', () => wrapper(require('app/screens/settings/general').default), () => require('app/screens/settings/general').default);

View File

@@ -40,11 +40,8 @@ exports[`PinnedPosts should match snapshot when getPinnedPosts failed 1`] = `
>
<Connect(StatusBar) />
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -40,11 +40,8 @@ exports[`RecentMentions should match snapshot when getRecentMentions failed 1`]
>
<Connect(StatusBar) />
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -2,11 +2,8 @@
exports[`SelectTeam should match snapshot for fail of teams 1`] = `
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -32,7 +32,7 @@ class Settings extends PureComponent {
currentTeamId: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
currentUrl: PropTypes.string.isRequired,
errors: PropTypes.array.isRequired,
errors: PropTypes.object.isRequired,
intl: intlShape.isRequired,
joinableTeams: PropTypes.array.isRequired,
theme: PropTypes.object,

View File

@@ -288,11 +288,8 @@ exports[`TermsOfService should match snapshot for fail of get terms 1`] = `
>
<Connect(StatusBar) />
<FailedNetworkAction
actionDefaultMessage="try again"
actionId="mobile.failed_network_action.retry"
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
errorId="mobile.failed_network_action.shortDescription"
onRetry={[Function]}
showAction={true}
theme={
Object {
"awayIndicator": "#ffbc42",

View File

@@ -93,6 +93,12 @@ export function getStateForReset(initialState, currentState) {
},
teams: {
currentTeamId,
teams: {
[currentTeamId]: currentState.entities.teams.teams[currentTeamId],
},
myMembers: {
[currentTeamId]: currentState.entities.teams.myMembers[currentTeamId],
},
},
preferences,
},

View File

@@ -37,6 +37,16 @@ describe('getStateForReset', () => {
},
teams: {
currentTeamId,
teams: {
[currentTeamId]: {
id: 'currentTeamId',
name: 'test',
display_name: 'Test',
},
},
myMembers: {
[currentTeamId]: {},
},
},
preferences: {
myPreferences: {
@@ -74,10 +84,12 @@ describe('getStateForReset', () => {
expect(users.profiles[currentUserId]).toBeDefined();
});
it('should keep the current team ID', () => {
it('should keep the current team', () => {
const resetState = getStateForReset(initialState, currentState);
const {teams} = resetState.entities;
expect(teams.currentTeamId).toEqual(currentTeamId);
expect(teams.teams[currentTeamId]).toEqual(currentState.entities.teams.teams[currentTeamId]);
expect(teams.myMembers[currentTeamId]).toEqual(currentState.entities.teams.myMembers[currentTeamId]);
});
it('should keep theme preferences', () => {

View File

@@ -2,10 +2,10 @@
// See LICENSE.txt for license information.
// @flow
export function selectFirstAvailableTeam(teams, primaryTeam) {
export function selectFirstAvailableTeam(teams, primaryTeamName) {
let defaultTeam;
if (primaryTeam) {
defaultTeam = teams.find((t) => t.name === primaryTeam.toLowerCase());
if (primaryTeamName) {
defaultTeam = teams.find((t) => t?.name === primaryTeamName.toLowerCase());
}
if (!defaultTeam) {

View File

@@ -253,8 +253,11 @@
"mobile.extension.permission": "Mattermost needs access to the device storage to share files.",
"mobile.extension.team_required": "You must belong to a team before you can share files.",
"mobile.extension.title": "Share in Mattermost",
"mobile.failed_network_action.retry": "try again",
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection or {tryAgainAction}.",
"mobile.failed_network_action.retry": "Try again",
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection.",
"mobile.failed_network_action.teams_channel_description": "Channels could not be loaded for {teamName}.",
"mobile.failed_network_action.teams_description": "Teams could not be loaded.",
"mobile.failed_network_action.teams_title": "Something went wrong",
"mobile.failed_network_action.title": "No internet connection",
"mobile.file_upload.browse": "Browse Files",
"mobile.file_upload.camera_photo": "Take Photo",