* 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
302 lines
8.6 KiB
JavaScript
302 lines
8.6 KiB
JavaScript
// 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,
|
|
AppState,
|
|
Platform,
|
|
} from 'react-native';
|
|
|
|
import DeviceInfo from 'react-native-device-info';
|
|
|
|
import {Client4} from 'mattermost-redux/client';
|
|
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|
|
|
import {
|
|
app,
|
|
store,
|
|
} from 'app/mattermost';
|
|
import {loadFromPushNotification} from 'app/actions/views/root';
|
|
import {ViewTypes} from 'app/constants';
|
|
import PushNotifications from 'app/push_notifications';
|
|
import {stripTrailingSlashes} from 'app/utils/url';
|
|
import {wrapWithContextProvider} from 'app/utils/wrap_context_provider';
|
|
|
|
import ChannelLoader from 'app/components/channel_loader';
|
|
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
|
import Loading from 'app/components/loading';
|
|
import SafeAreaView from 'app/components/safe_area_view';
|
|
import StatusBar from 'app/components/status_bar';
|
|
|
|
const lazyLoadSelectServer = () => {
|
|
return require('app/screens/select_server').default;
|
|
};
|
|
|
|
const lazyLoadChannel = () => {
|
|
return require('app/screens/channel').default;
|
|
};
|
|
|
|
const lazyLoadPushNotifications = () => {
|
|
return require('app/utils/push_notifications').configurePushNotifications;
|
|
};
|
|
|
|
const lazyLoadReplyPushNotifications = () => {
|
|
return require('app/utils/push_notifications').onPushNotificationReply;
|
|
};
|
|
|
|
/**
|
|
* Entry Component:
|
|
* With very little overhead navigate to
|
|
* - Login or
|
|
* - Channel Component
|
|
*
|
|
* The idea is to render something to the user as soon as possible
|
|
*/
|
|
export default class Entry extends PureComponent {
|
|
static propTypes = {
|
|
config: PropTypes.object,
|
|
theme: PropTypes.object,
|
|
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,
|
|
}).isRequired,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
launchLogin: false,
|
|
launchChannel: false,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
|
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
|
|
|
|
EventEmitter.on(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
|
|
EventEmitter.on(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
EventEmitter.off(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
|
|
EventEmitter.off(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
|
|
}
|
|
|
|
handleLaunchLogin = (initializeModules) => {
|
|
this.setState({launchLogin: true});
|
|
|
|
if (initializeModules) {
|
|
this.props.initializeModules();
|
|
}
|
|
};
|
|
|
|
handleLaunchChannel = (initializeModules) => {
|
|
this.setState({launchChannel: true});
|
|
|
|
if (initializeModules) {
|
|
this.props.initializeModules();
|
|
}
|
|
};
|
|
|
|
listenForHydration = () => {
|
|
const {
|
|
actions: {
|
|
autoUpdateTimezone,
|
|
},
|
|
enableTimezone,
|
|
deviceTimezone,
|
|
} = this.props;
|
|
const {getState} = store;
|
|
const state = getState();
|
|
|
|
if (!app.isNotificationsConfigured) {
|
|
this.configurePushNotifications();
|
|
}
|
|
|
|
if (state.views.root.hydrationComplete) {
|
|
this.unsubscribeFromStore();
|
|
|
|
if (enableTimezone) {
|
|
autoUpdateTimezone(deviceTimezone);
|
|
}
|
|
|
|
this.setAppCredentials();
|
|
this.setStartupThemes();
|
|
this.handleNotification();
|
|
|
|
if (Platform.OS === 'android') {
|
|
this.launchForAndroid();
|
|
return;
|
|
}
|
|
|
|
this.launchForiOS();
|
|
}
|
|
};
|
|
|
|
configurePushNotifications = () => {
|
|
const configureNotifications = lazyLoadPushNotifications();
|
|
configureNotifications();
|
|
};
|
|
|
|
setAppCredentials = () => {
|
|
const {
|
|
actions: {
|
|
setDeviceToken,
|
|
},
|
|
} = this.props;
|
|
const {getState} = store;
|
|
const state = getState();
|
|
|
|
const {credentials} = state.entities.general;
|
|
const {currentUserId} = state.entities.users;
|
|
|
|
if (app.deviceToken) {
|
|
setDeviceToken(app.deviceToken);
|
|
}
|
|
|
|
if (credentials.token && credentials.url) {
|
|
Client4.setToken(credentials.token);
|
|
Client4.setUrl(stripTrailingSlashes(credentials.url));
|
|
} else if (app.waitForRehydration) {
|
|
app.waitForRehydration = false;
|
|
}
|
|
|
|
if (currentUserId) {
|
|
Client4.setUserId(currentUserId);
|
|
}
|
|
|
|
app.setAppCredentials(app.deviceToken, currentUserId, credentials.token, credentials.url);
|
|
};
|
|
|
|
setStartupThemes = () => {
|
|
const {theme} = this.props;
|
|
if (app.toolbarBackground === theme.sidebarHeaderBg) {
|
|
return;
|
|
}
|
|
|
|
app.setStartupThemes(
|
|
theme.sidebarHeaderBg,
|
|
theme.sidebarHeaderTextColor,
|
|
theme.centerChannelBg
|
|
);
|
|
};
|
|
|
|
handleNotification = async () => {
|
|
const notification = PushNotifications.getNotification();
|
|
|
|
// If notification exists, it means that the app was started through a reply
|
|
// and the app was not sitting in the background nor opened
|
|
if (notification || app.replyNotificationData) {
|
|
const onPushNotificationReply = lazyLoadReplyPushNotifications();
|
|
const notificationData = notification || app.replyNotificationData;
|
|
const {data, text, badge, completed} = notificationData;
|
|
|
|
// if the notification has a completed property it means that we are replying to a notification
|
|
// and in case it doesn't it means we just opened the notification
|
|
if (completed) {
|
|
onPushNotificationReply(data, text, badge, completed);
|
|
} else {
|
|
await store.dispatch(loadFromPushNotification(notification));
|
|
}
|
|
PushNotifications.resetNotification();
|
|
}
|
|
};
|
|
|
|
launchForAndroid = () => {
|
|
app.launchApp();
|
|
};
|
|
|
|
launchForiOS = () => {
|
|
const appNotActive = AppState.currentState !== 'active';
|
|
|
|
if (appNotActive) {
|
|
// for iOS replying from push notification starts the app in the background
|
|
app.setShouldRelaunchWhenActive(true);
|
|
} else {
|
|
app.launchApp();
|
|
}
|
|
};
|
|
|
|
renderLogin = () => {
|
|
const SelectServer = lazyLoadSelectServer();
|
|
const props = {
|
|
allowOtherServers: app.allowOtherServers,
|
|
navigator: this.props.navigator,
|
|
};
|
|
|
|
return wrapWithContextProvider(SelectServer)(props);
|
|
};
|
|
|
|
renderChannel = () => {
|
|
const ChannelScreen = lazyLoadChannel();
|
|
const props = {
|
|
navigator: this.props.navigator,
|
|
};
|
|
|
|
return wrapWithContextProvider(ChannelScreen, false)(props);
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
navigator,
|
|
isLandscape,
|
|
} = this.props;
|
|
|
|
if (this.state.launchLogin) {
|
|
return this.renderLogin();
|
|
}
|
|
|
|
if (this.state.launchChannel) {
|
|
return this.renderChannel();
|
|
}
|
|
|
|
let toolbar = null;
|
|
let loading = null;
|
|
const backgroundColor = app.appBackground ? app.appBackground : '#ffff';
|
|
if (app.token && app.toolbarBackground) {
|
|
const toolbarTheme = {
|
|
sidebarHeaderBg: app.toolbarBackground,
|
|
sidebarHeaderTextColor: app.toolbarTextColor,
|
|
};
|
|
|
|
toolbar = (
|
|
<View>
|
|
<StatusBar headerColor={app.toolbarBackground}/>
|
|
<EmptyToolbar
|
|
theme={toolbarTheme}
|
|
isLandscape={isLandscape}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
loading = (
|
|
<ChannelLoader
|
|
backgroundColor={backgroundColor}
|
|
channelIsLoading={true}
|
|
/>
|
|
);
|
|
} else {
|
|
loading = <Loading/>;
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView
|
|
navBarBackgroundColor={app.toolbarBackground}
|
|
backgroundColor={backgroundColor}
|
|
navigator={navigator}
|
|
>
|
|
{toolbar}
|
|
{loading}
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
}
|