Timezone feature (#1456)
* Add automatic and manual timezone support Update users timezone and scrollTo selected timezone in SelectTimezone coponent Clean styles for SelectTimezone screen Add auto-timezone update when login or enter app Hide timezone feature behind config Fix requested changes Parse SupportedTimezone from config Add localTime and localized post stamps Add trailing commas Include all timezone util methods from redux Remove EnableTimezoneSelection flag WIP get timezones from server Pull supportedTimezones from api Minor fixes Remove wrapWithPreventDoubleTap Revert back to react-intl formatDate Include timeZone prop in FormattedTime Refactor Timezone row into component Minor cosmetic changes Add minimum server support for the timezone feature Move getSupportedTimezones to componentWillMount Move autoUpdateTimezone function to handleSuccessfulLogin Specify user timezone in profile_header Remove format props from FormattedTime Add ExperimentalTimezone flag Replace Client().getServerVersion() with entities.general.serverVersion Add isTimezoneEnabled helper function Move isMinimumServerVersion to utils/timezone.js * Fix style errors * Remove date-time-format-timezone polyfill * Feedback changes * Use timezone selector from redux * Explicitly pass hour12 props to intl.formatDate * Update package-lock * Revert iOS project file changes * Fix license header * Include timezone related paths in modulePaths * Fix license header * Fix minor issue with rebasing * Fix issue with getconfig in GeneralSettings * Update package-lock
This commit is contained in:
parent
830022cb82
commit
583d6b74ee
21 changed files with 715 additions and 23 deletions
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
import {getDataRetentionPolicy} from 'mattermost-redux/actions/general';
|
||||
import {GeneralTypes} from 'mattermost-redux/action_types';
|
||||
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {app} from 'app/mattermost';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from 'app/utils/timezone';
|
||||
|
||||
export function handleLoginIdChanged(loginId) {
|
||||
return async (dispatch, getState) => {
|
||||
|
|
@ -40,6 +42,11 @@ export function handleSuccessfulLogin() {
|
|||
|
||||
app.setAppCredentials(deviceToken, currentUserId, token, url);
|
||||
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
if (enableTimezone) {
|
||||
dispatch(autoUpdateTimezone(getDeviceTimezone()));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -3,35 +3,37 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
export default class FormattedTime extends React.PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.any.isRequired,
|
||||
timeZone: PropTypes.string,
|
||||
children: PropTypes.func,
|
||||
hour12: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
children,
|
||||
timeZone,
|
||||
hour12,
|
||||
} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
const date = new Date(value);
|
||||
const militaryTime = !hour12;
|
||||
|
||||
const hour = militaryTime ? date.getHours() : (date.getHours() % 12 || 12);
|
||||
let minute = date.getMinutes();
|
||||
minute = minute >= 10 ? minute : ('0' + minute);
|
||||
let time = '';
|
||||
|
||||
if (!militaryTime) {
|
||||
time = (date.getHours() >= 12 ? ' PM' : ' AM');
|
||||
}
|
||||
|
||||
const formattedTime = hour + ':' + minute + time;
|
||||
const timezoneProps = timeZone ? {timeZone} : {};
|
||||
const formattedTime = intl.formatDate(value, {
|
||||
...timezoneProps,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12,
|
||||
});
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(formattedTime);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import {connect} from 'react-redux';
|
|||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getBool, getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getUser, getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {fromAutoResponder} from 'app/utils/general';
|
||||
import {isTimezoneEnabled} from 'app/utils/timezone';
|
||||
|
||||
import PostHeader from './post_header';
|
||||
|
||||
|
|
@ -22,8 +24,11 @@ function makeMapStateToProps() {
|
|||
const post = getPost(state, ownProps.postId);
|
||||
const commentedOnUser = getUser(state, ownProps.commentedOnUserId);
|
||||
const user = getUser(state, post.user_id) || {};
|
||||
const currentUser = getCurrentUser(state);
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
const militaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const userTimezone = enableTimezone && getUserCurrentTimezone(currentUser.timezone);
|
||||
|
||||
return {
|
||||
commentedOnDisplayName: ownProps.commentedOnUserId ? displayUsername(commentedOnUser, teammateNameDisplay) : '',
|
||||
|
|
@ -39,6 +44,7 @@ function makeMapStateToProps() {
|
|||
overrideUsername: post.props && post.props.override_username,
|
||||
theme: getTheme(state),
|
||||
username: user.username,
|
||||
userTimezone,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ export default class PostHeader extends PureComponent {
|
|||
showFullDate: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
username: PropTypes.string,
|
||||
userTimezone: PropTypes.string,
|
||||
isFlagged: PropTypes.bool,
|
||||
enableTimezone: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -172,6 +174,7 @@ export default class PostHeader extends PureComponent {
|
|||
createAt,
|
||||
isPendingOrFailedPost,
|
||||
isSearchResult,
|
||||
userTimezone,
|
||||
militaryTime,
|
||||
onPress,
|
||||
renderReplies,
|
||||
|
|
@ -192,6 +195,7 @@ export default class PostHeader extends PureComponent {
|
|||
</Text>
|
||||
<Text style={style.time}>
|
||||
<FormattedTime
|
||||
timeZone={userTimezone}
|
||||
hour12={!militaryTime}
|
||||
value={createAt}
|
||||
/>
|
||||
|
|
@ -202,6 +206,7 @@ export default class PostHeader extends PureComponent {
|
|||
dateComponent = (
|
||||
<Text style={style.time}>
|
||||
<FormattedTime
|
||||
timeZone={userTimezone}
|
||||
hour12={!militaryTime}
|
||||
value={createAt}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
inputHeight: PropTypes.number,
|
||||
inputBorderRadius: PropTypes.number,
|
||||
blurOnSubmit: PropTypes.bool,
|
||||
showArrow: PropTypes.bool,
|
||||
value: PropTypes.string,
|
||||
containerStyle: CustomPropTypes.Style,
|
||||
};
|
||||
|
|
@ -52,6 +53,7 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
blurOnSubmit: true,
|
||||
placeholder: 'Search',
|
||||
showCancelButton: true,
|
||||
showArrow: true,
|
||||
placeholderTextColor: changeOpacity('#000', 0.5),
|
||||
containerStyle: {},
|
||||
onSearchButtonPress: () => true,
|
||||
|
|
@ -156,6 +158,7 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
tintColorSearch,
|
||||
containerStyle,
|
||||
value,
|
||||
showArrow,
|
||||
} = this.props;
|
||||
const {isFocused} = this.state;
|
||||
|
||||
|
|
@ -192,7 +195,7 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
},
|
||||
]}
|
||||
>
|
||||
{isFocused ?
|
||||
{isFocused && showArrow ?
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.onCancelButtonPress}
|
||||
style={{paddingRight: 15}}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ export default class Entry extends PureComponent {
|
|||
navigator: PropTypes.object,
|
||||
isLandscape: PropTypes.bool,
|
||||
hydrationComplete: PropTypes.bool,
|
||||
enableTimezone: PropTypes.bool,
|
||||
deviceTimezone: PropTypes.string,
|
||||
initializeModules: PropTypes.func.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
setDeviceToken: PropTypes.func.isRequired,
|
||||
|
|
@ -106,6 +108,13 @@ export default class Entry extends PureComponent {
|
|||
};
|
||||
|
||||
listenForHydration = () => {
|
||||
const {
|
||||
actions: {
|
||||
autoUpdateTimezone,
|
||||
},
|
||||
enableTimezone,
|
||||
deviceTimezone,
|
||||
} = this.props;
|
||||
const {getState} = store;
|
||||
const state = getState();
|
||||
|
||||
|
|
@ -116,6 +125,10 @@ export default class Entry extends PureComponent {
|
|||
if (state.views.root.hydrationComplete) {
|
||||
this.unsubscribeFromStore();
|
||||
|
||||
if (enableTimezone) {
|
||||
autoUpdateTimezone(deviceTimezone);
|
||||
}
|
||||
|
||||
this.setAppCredentials();
|
||||
this.setStartupThemes();
|
||||
this.handleNotification();
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from 'app/utils/timezone';
|
||||
|
||||
const lazyLoadEntry = () => {
|
||||
return require('./entry').default;
|
||||
|
|
@ -16,11 +18,16 @@ const lazyLoadEntry = () => {
|
|||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const deviceTimezone = getDeviceTimezone();
|
||||
|
||||
return {
|
||||
config,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
hydrationComplete: state.views.root.hydrationComplete,
|
||||
enableTimezone,
|
||||
deviceTimezone,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -28,6 +35,7 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
setDeviceToken,
|
||||
autoUpdateTimezone,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,13 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('Search', () => wrapWithContextProvider(require('app/screens/search').default), store, Provider);
|
||||
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
|
||||
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(require('app/screens/select_team').default), store, Provider);
|
||||
Navigation.registerComponent('SelectTimezone', () => wrapWithContextProvider(require('app/screens/timezone/select_timezone').default), store, Provider);
|
||||
Navigation.registerComponent('Settings', () => wrapWithContextProvider(require('app/screens/settings/general').default), store, Provider);
|
||||
Navigation.registerComponent('SSO', () => wrapWithContextProvider(require('app/screens/sso').default), store, Provider);
|
||||
Navigation.registerComponent('Table', () => wrapWithContextProvider(require('app/screens/table').default), store, Provider);
|
||||
Navigation.registerComponent('TableImage', () => wrapWithContextProvider(require('app/screens/table_image').default), store, Provider);
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(require('app/screens/text_preview').default), store, Provider);
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(require('app/screens/thread').default), store, Provider);
|
||||
Navigation.registerComponent('TimezoneSettings', () => wrapWithContextProvider(require('app/screens/timezone').default), store, Provider);
|
||||
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(require('app/screens/user_profile').default), store, Provider);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class DisplaySettings extends PureComponent {
|
|||
static propTypes = {
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
enableTimezone: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -53,12 +54,30 @@ export default class DisplaySettings extends PureComponent {
|
|||
this.setState({showClockDisplaySettings: true});
|
||||
});
|
||||
|
||||
goToTimezoneSettings = preventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
navigator.push({
|
||||
screen: 'TimezoneSettings',
|
||||
title: intl.formatMessage({id: 'mobile.advanced_settings.timezone', defaultMessage: 'Timezone'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
closeClockDisplaySettings = () => {
|
||||
this.setState({showClockDisplaySettings: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const {theme, enableTimezone} = this.props;
|
||||
const {showClockDisplaySettings} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -72,6 +91,24 @@ export default class DisplaySettings extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
let timezoneOption;
|
||||
|
||||
const disableClockDisplaySeparator = enableTimezone;
|
||||
if (enableTimezone) {
|
||||
timezoneOption = (
|
||||
<SettingsItem
|
||||
defaultMessage='Timezone'
|
||||
i18nId='mobile.advanced_settings.timezone'
|
||||
iconName='ios-globe'
|
||||
iconType='ion'
|
||||
onPress={this.goToTimezoneSettings}
|
||||
separator={false}
|
||||
showArrow={false}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
|
|
@ -83,10 +120,11 @@ export default class DisplaySettings extends PureComponent {
|
|||
iconName='ios-time'
|
||||
iconType='ion'
|
||||
onPress={this.goToClockDisplaySettings}
|
||||
separator={false}
|
||||
separator={disableClockDisplaySeparator}
|
||||
showArrow={false}
|
||||
theme={theme}
|
||||
/>
|
||||
{timezoneOption}
|
||||
<View style={style.divider}/>
|
||||
</View>
|
||||
{clockDisplayModal}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {isTimezoneEnabled} from 'app/utils/timezone';
|
||||
|
||||
import DisplaySettings from './display_settings';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
|
||||
return {
|
||||
enableTimezone,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
39
app/screens/timezone/index.js
Normal file
39
app/screens/timezone/index.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getSupportedTimezones} from 'mattermost-redux/actions/general';
|
||||
import {getSupportedTimezones as getTimezones} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getUserTimezone} from 'mattermost-redux/selectors/entities/timezone';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {updateUser} from 'app/actions/views/edit_profile';
|
||||
|
||||
import Timezone from './timezone';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const timezones = getTimezones(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
const userTimezone = getUserTimezone(state, currentUser.id);
|
||||
|
||||
return {
|
||||
user: currentUser,
|
||||
theme: getTheme(state),
|
||||
userTimezone,
|
||||
timezones,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getSupportedTimezones,
|
||||
updateUser,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Timezone);
|
||||
29
app/screens/timezone/select_timezone/index.js
Normal file
29
app/screens/timezone/select_timezone/index.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getSupportedTimezones} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import SelectTimezone from './select_timezone';
|
||||
|
||||
function mapStateToProps(state, props) {
|
||||
const {selectedTimezone} = props;
|
||||
const supportedTimezones = getSupportedTimezones(state);
|
||||
|
||||
let index = 0;
|
||||
|
||||
const timezoneIndex = supportedTimezones.findIndex((timezone) => timezone === selectedTimezone);
|
||||
if (timezoneIndex > 0) {
|
||||
index = timezoneIndex;
|
||||
}
|
||||
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
timezones: supportedTimezones,
|
||||
initialScrollIndex: index,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SelectTimezone);
|
||||
159
app/screens/timezone/select_timezone/select_timezone.js
Normal file
159
app/screens/timezone/select_timezone/select_timezone.js
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View,
|
||||
FlatList,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {getTimezoneRegion} from 'mattermost-redux/utils/timezone_utils';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import SelectTimezoneRow from './select_timezone_row';
|
||||
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const ITEM_HEIGHT = 45;
|
||||
const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS;
|
||||
|
||||
export default class Timezone extends PureComponent {
|
||||
static propTypes = {
|
||||
selectedTimezone: PropTypes.string.isRequired,
|
||||
initialScrollIndex: PropTypes.number.isRequired,
|
||||
timezones: PropTypes.array.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onBack: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
timezones: props.timezones,
|
||||
};
|
||||
}
|
||||
|
||||
filteredTimezones = (timezonePrefix) => {
|
||||
if (timezonePrefix.length === 0) {
|
||||
return this.state.timezones;
|
||||
}
|
||||
|
||||
const lowerCasePrefix = timezonePrefix.toLowerCase();
|
||||
|
||||
return this.state.timezones.filter((t) => (
|
||||
getTimezoneRegion(t).toLowerCase().indexOf(lowerCasePrefix) >= 0 ||
|
||||
t.toLowerCase().indexOf(lowerCasePrefix) >= 0
|
||||
));
|
||||
};
|
||||
|
||||
timezoneSelected = (timezone) => {
|
||||
this.props.onBack(timezone);
|
||||
this.props.navigator.pop();
|
||||
};
|
||||
|
||||
handleTextChanged = (value) => {
|
||||
this.setState({value});
|
||||
};
|
||||
|
||||
keyExtractor = (item) => item;
|
||||
|
||||
getItemLayout = (data, index) => ({
|
||||
length: ITEM_HEIGHT,
|
||||
offset: ITEM_HEIGHT * index,
|
||||
index,
|
||||
});
|
||||
|
||||
renderItem = ({item: timezone}) => {
|
||||
return (
|
||||
<SelectTimezoneRow
|
||||
theme={this.props.theme}
|
||||
timezone={timezone}
|
||||
selectedTimezone={this.props.selectedTimezone}
|
||||
onPress={this.timezoneSelected}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, initialScrollIndex} = this.props;
|
||||
const {value} = this.state;
|
||||
const {intl} = this.context;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const searchBarInput = {
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.2),
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<View style={style.header}>
|
||||
<SearchBar
|
||||
ref='searchBar'
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={Platform.OS === 'ios' ? 33 : 46}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
titleCancelColor={theme.sidebarHeaderTextColor}
|
||||
onChangeText={this.handleTextChanged}
|
||||
autoCapitalize='none'
|
||||
value={value}
|
||||
containerStyle={style.searchBarContainer}
|
||||
showArrow={false}
|
||||
/>
|
||||
</View>
|
||||
<FlatList
|
||||
data={this.filteredTimezones(value)}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
getItemLayout={this.getItemLayout}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={15}
|
||||
initialScrollIndex={initialScrollIndex}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
78
app/screens/timezone/select_timezone/select_timezone_row.js
Normal file
78
app/screens/timezone/select_timezone/select_timezone_row.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import CheckMark from 'app/components/checkmark';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const ITEM_HEIGHT = 45;
|
||||
|
||||
export default class SelectTimezoneRow extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
timezone: PropTypes.string.isRequired,
|
||||
selectedTimezone: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
timezoneSelected = () => {
|
||||
const {timezone, onPress} = this.props;
|
||||
onPress(timezone);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, timezone, selectedTimezone} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const selected = timezone === selectedTimezone && (
|
||||
<CheckMark
|
||||
width={12}
|
||||
height={12}
|
||||
color={theme.linkColor}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.itemContainer}
|
||||
key={timezone}
|
||||
onPress={this.timezoneSelected}
|
||||
>
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemText}>
|
||||
{timezone}
|
||||
</Text>
|
||||
</View>
|
||||
{selected}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
itemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
paddingHorizontal: 15,
|
||||
height: ITEM_HEIGHT,
|
||||
},
|
||||
item: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
232
app/screens/timezone/timezone.js
Normal file
232
app/screens/timezone/timezone.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {getTimezoneRegion} from 'mattermost-redux/utils/timezone_utils';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import Section from 'app/screens/settings/section';
|
||||
import SectionItem from 'app/screens/settings/section_item';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {getDeviceTimezone} from 'app/utils/timezone';
|
||||
|
||||
export default class Timezone extends PureComponent {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
timezones: PropTypes.array.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
userTimezone: PropTypes.shape({
|
||||
useAutomaticTimezone: PropTypes.bool.isRequired,
|
||||
automaticTimezone: PropTypes.string.isRequired,
|
||||
manualTimezone: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
actions: PropTypes.shape({
|
||||
getSupportedTimezones: PropTypes.func.isRequired,
|
||||
updateUser: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
useAutomaticTimezone: props.userTimezone.useAutomaticTimezone,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {actions: {getSupportedTimezones}, timezones} = this.props;
|
||||
|
||||
if (timezones.length === 0) {
|
||||
getSupportedTimezones();
|
||||
}
|
||||
}
|
||||
|
||||
updateAutomaticTimezone = (useAutomaticTimezone) => {
|
||||
const {userTimezone: {manualTimezone}} = this.props;
|
||||
let automaticTimezone = '';
|
||||
|
||||
this.setState({useAutomaticTimezone});
|
||||
if (useAutomaticTimezone) {
|
||||
automaticTimezone = getDeviceTimezone();
|
||||
this.submitUser({
|
||||
useAutomaticTimezone,
|
||||
automaticTimezone,
|
||||
manualTimezone,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (manualTimezone.length > 0) {
|
||||
// Preserve state change in server if manualTimezone exists
|
||||
this.submitUser({
|
||||
useAutomaticTimezone,
|
||||
automaticTimezone,
|
||||
manualTimezone,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateManualTimezone = (manualTimezone) => {
|
||||
this.submitUser({
|
||||
useAutomaticTimezone: false,
|
||||
automaticTimezone: '',
|
||||
manualTimezone,
|
||||
});
|
||||
};
|
||||
|
||||
submitUser = ({
|
||||
useAutomaticTimezone,
|
||||
automaticTimezone,
|
||||
manualTimezone,
|
||||
}) => {
|
||||
const {user} = this.props;
|
||||
|
||||
const timezone = {
|
||||
useAutomaticTimezone: useAutomaticTimezone.toString(),
|
||||
automaticTimezone,
|
||||
manualTimezone,
|
||||
};
|
||||
|
||||
const updatedUser = {
|
||||
...user,
|
||||
timezone,
|
||||
};
|
||||
|
||||
this.props.actions.updateUser(updatedUser);
|
||||
};
|
||||
|
||||
goToSelectTimezone = () => {
|
||||
const {
|
||||
userTimezone: {manualTimezone},
|
||||
navigator,
|
||||
theme,
|
||||
} = this.props;
|
||||
const {intl} = this.context;
|
||||
this.goingBack = false;
|
||||
|
||||
navigator.push({
|
||||
backButtonTitle: '',
|
||||
screen: 'SelectTimezone',
|
||||
title: intl.formatMessage({id: 'mobile.timezone_settings.select', defaultMessage: 'Select Timezone'}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
passProps: {
|
||||
selectedTimezone: manualTimezone,
|
||||
onBack: this.updateManualTimezone,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
userTimezone: {
|
||||
automaticTimezone,
|
||||
manualTimezone,
|
||||
},
|
||||
} = this.props;
|
||||
const {useAutomaticTimezone} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<View style={style.wrapper}>
|
||||
<Section
|
||||
disableHeader={true}
|
||||
theme={theme}
|
||||
>
|
||||
<View style={style.divider}/>
|
||||
<SectionItem
|
||||
label={(
|
||||
<FormattedText
|
||||
id='mobile.timezone_settings.automatically'
|
||||
defaultMessage='Set automatically'
|
||||
/>
|
||||
)}
|
||||
description={(
|
||||
<Text>
|
||||
{useAutomaticTimezone && getTimezoneRegion(automaticTimezone)}
|
||||
</Text>
|
||||
)}
|
||||
action={this.updateAutomaticTimezone}
|
||||
actionType='toggle'
|
||||
selected={useAutomaticTimezone}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
{!useAutomaticTimezone && (
|
||||
<View>
|
||||
<View style={style.separator}/>
|
||||
<SectionItem
|
||||
label={(
|
||||
<FormattedText
|
||||
id='mobile.timezone_settings.manual'
|
||||
defaultMessage='Change timezone'
|
||||
/>
|
||||
)}
|
||||
description={(
|
||||
<Text>
|
||||
{getTimezoneRegion(manualTimezone)}
|
||||
</Text>
|
||||
)}
|
||||
action={this.goToSelectTimezone}
|
||||
actionType='arrow'
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={style.divider}/>
|
||||
</Section>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
paddingTop: 35,
|
||||
},
|
||||
}),
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
marginLeft: 15,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -7,14 +7,19 @@ import {connect} from 'react-redux';
|
|||
import {setChannelDisplayName} from 'app/actions/views/channel';
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTeammateNameDisplaySetting, getTheme, getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {isTimezoneEnabled} from 'app/utils/timezone';
|
||||
|
||||
import UserProfile from './user_profile';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const config = getConfig(state);
|
||||
const {createChannel: createChannelRequest} = state.requests.channels;
|
||||
const militaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
|
||||
return {
|
||||
config,
|
||||
|
|
@ -22,6 +27,8 @@ function mapStateToProps(state, ownProps) {
|
|||
currentDisplayName: state.views.channel.displayName,
|
||||
user: state.entities.users.profiles[ownProps.userId],
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
enableTimezone,
|
||||
militaryTime,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ import {
|
|||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
|
||||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import FormattedTime from 'app/components/formatted_time';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
|
|
@ -33,6 +36,8 @@ export default class UserProfile extends PureComponent {
|
|||
teammateNameDisplay: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
militaryTime: PropTypes.bool.isRequired,
|
||||
enableTimezone: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -91,6 +96,34 @@ export default class UserProfile extends PureComponent {
|
|||
return null;
|
||||
};
|
||||
|
||||
buildTimezoneBlock = () => {
|
||||
const {theme, user, militaryTime} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
||||
const currentTimezone = getUserCurrentTimezone(user.timezone);
|
||||
if (!currentTimezone) {
|
||||
return null;
|
||||
}
|
||||
const nowDate = new Date();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<FormattedText
|
||||
id='mobile.routes.user_profile.local_time'
|
||||
defaultMessage='LOCAL TIME'
|
||||
style={style.header}
|
||||
/>
|
||||
<Text style={style.text}>
|
||||
<FormattedTime
|
||||
timeZone={currentTimezone}
|
||||
hour12={!militaryTime}
|
||||
value={nowDate}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
sendMessage = async () => {
|
||||
const {intl} = this.context;
|
||||
const {actions, currentDisplayName, teammateNameDisplay, user} = this.props;
|
||||
|
|
@ -162,7 +195,7 @@ export default class UserProfile extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {config, theme, user} = this.props;
|
||||
const {config, theme, user, enableTimezone} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
||||
if (!user) {
|
||||
|
|
@ -186,6 +219,7 @@ export default class UserProfile extends PureComponent {
|
|||
<Text style={style.username}>{`@${user.username}`}</Text>
|
||||
</View>
|
||||
<View style={style.content}>
|
||||
{enableTimezone && this.buildTimezoneBlock()}
|
||||
{this.buildDisplayBlock('username')}
|
||||
{config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')}
|
||||
{this.buildDisplayBlock('position')}
|
||||
|
|
|
|||
16
app/utils/timezone.js
Normal file
16
app/utils/timezone.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
export function getDeviceTimezone() {
|
||||
return DeviceInfo.getTimezone();
|
||||
}
|
||||
|
||||
export function isTimezoneEnabled(state) {
|
||||
const {config} = state.entities.general;
|
||||
const serverVersion = state.entities.general.serverVersion;
|
||||
return config.ExperimentalTimezone === 'true' && isMinimumServerVersion(serverVersion, 4, 9);
|
||||
}
|
||||
|
|
@ -2229,6 +2229,7 @@
|
|||
"mobile.advanced_settings.reset_message": "\nThis will reset all offline data and restart the app. You will be automatically logged back in once the app restarts.\n",
|
||||
"mobile.advanced_settings.reset_title": "Reset Cache",
|
||||
"mobile.advanced_settings.title": "Advanced Settings",
|
||||
"mobile.advanced_settings.timezone": "Timezone",
|
||||
"mobile.android.camera_permission_denied_description": "To take photos and videos with your camera, please change your permission settings.",
|
||||
"mobile.android.camera_permission_denied_title": "Camera access is required",
|
||||
"mobile.android.permission_denied_dismiss": "Dismiss",
|
||||
|
|
@ -2500,6 +2501,7 @@
|
|||
"mobile.routes.thread_dm": "Direct Message Thread",
|
||||
"mobile.routes.user_profile": "Profile",
|
||||
"mobile.routes.user_profile.send_message": "Send Message",
|
||||
"mobile.routes.user_profile.local_time": "LOCAL TIME",
|
||||
"mobile.search.from_modifier_description": "to find posts from specific users",
|
||||
"mobile.search.from_modifier_title": "username",
|
||||
"mobile.search.in_modifier_description": "to find posts in specific channels",
|
||||
|
|
@ -2533,6 +2535,9 @@
|
|||
"mobile.share_extension.send": "Send",
|
||||
"mobile.share_extension.team": "Team",
|
||||
"mobile.suggestion.members": "Members",
|
||||
"mobile.timezone_settings.automatically": "Set automatically",
|
||||
"mobile.timezone_settings.manual": "Change timezone",
|
||||
"mobile.timezone_settings.select": "Select Timezone",
|
||||
"mobile.video.save_error_message": "To save the video file you need to download it first.",
|
||||
"mobile.video.save_error_title": "Save Video Error",
|
||||
"mobile.video_playback.failed_description": "An error occurred while trying to play the video.\n",
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -5155,7 +5155,7 @@
|
|||
},
|
||||
"commonmark": {
|
||||
"version": "github:mattermost/commonmark.js#e5c34706cafa2924370772ff47937b833648a6de",
|
||||
"from": "github:mattermost/commonmark.js#e5c34706cafa2924370772ff47937b833648a6de",
|
||||
"from": "commonmark@github:mattermost/commonmark.js#e5c34706cafa2924370772ff47937b833648a6de",
|
||||
"requires": {
|
||||
"entities": "~ 1.1.1",
|
||||
"mdurl": "~ 1.0.1",
|
||||
|
|
@ -15141,7 +15141,7 @@
|
|||
},
|
||||
"redux-offline": {
|
||||
"version": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
|
||||
"from": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
|
||||
"from": "redux-offline@git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
|
||||
"requires": {
|
||||
"redux-persist": "^4.5.0"
|
||||
}
|
||||
|
|
@ -15714,7 +15714,7 @@
|
|||
},
|
||||
"rn-placeholder": {
|
||||
"version": "github:enahum/rn-placeholder#4b065f892d7a7f9d921a969f2e72e609ebc0a212",
|
||||
"from": "github:enahum/rn-placeholder#4b065f892d7a7f9d921a969f2e72e609ebc0a212",
|
||||
"from": "rn-placeholder@github:enahum/rn-placeholder#4b065f892d7a7f9d921a969f2e72e609ebc0a212",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,6 +338,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/app/utils/tap.js',
|
||||
'./node_modules/app/utils/theme.js',
|
||||
'./node_modules/app/utils/time_tracker.js',
|
||||
'./node_modules/app/utils/timezone.js',
|
||||
'./node_modules/app/utils/tooltip.js',
|
||||
'./node_modules/app/utils/url.js',
|
||||
'./node_modules/app/utils/wrap_context_provider.js',
|
||||
|
|
@ -690,6 +691,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/node_modules/mattermost-redux/actions/roles.js',
|
||||
'./node_modules/node_modules/mattermost-redux/actions/search.js',
|
||||
'./node_modules/node_modules/mattermost-redux/actions/teams.js',
|
||||
'./node_modules/node_modules/mattermost-redux/actions/timezone.js',
|
||||
'./node_modules/node_modules/mattermost-redux/actions/users.js',
|
||||
'./node_modules/node_modules/mattermost-redux/actions/websocket.js',
|
||||
'./node_modules/node_modules/mattermost-redux/client/client.js',
|
||||
|
|
@ -762,6 +764,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/node_modules/mattermost-redux/selectors/entities/preferences.js',
|
||||
'./node_modules/node_modules/mattermost-redux/selectors/entities/roles.js',
|
||||
'./node_modules/node_modules/mattermost-redux/selectors/entities/teams.js',
|
||||
'./node_modules/node_modules/mattermost-redux/selectors/entities/timezone.js',
|
||||
'./node_modules/node_modules/mattermost-redux/selectors/entities/typing.js',
|
||||
'./node_modules/node_modules/mattermost-redux/selectors/entities/users.js',
|
||||
'./node_modules/node_modules/mattermost-redux/store/configureStore.prod.js',
|
||||
|
|
@ -781,6 +784,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
|
|||
'./node_modules/node_modules/mattermost-redux/utils/preference_utils.js',
|
||||
'./node_modules/node_modules/mattermost-redux/utils/team_utils.js',
|
||||
'./node_modules/node_modules/mattermost-redux/utils/theme_utils.js',
|
||||
'./node_modules/node_modules/mattermost-redux/utils/timezone_utils.js',
|
||||
'./node_modules/node_modules/mattermost-redux/utils/user_utils.js',
|
||||
'./node_modules/node_modules/mdurl/decode.js',
|
||||
'./node_modules/node_modules/mdurl/encode.js',
|
||||
|
|
|
|||
Loading…
Reference in a new issue