diff --git a/app/components/formatted_date.js b/app/components/formatted_date.js
index 370e51222..8641688b1 100644
--- a/app/components/formatted_date.js
+++ b/app/components/formatted_date.js
@@ -3,35 +3,33 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
+import moment from 'moment-timezone';
import {Text} from 'react-native';
-class FormattedDate extends React.PureComponent {
+export default class FormattedDate extends React.PureComponent {
static propTypes = {
- intl: intlShape.isRequired,
- value: PropTypes.any.isRequired,
format: PropTypes.string,
- children: PropTypes.func,
+ timeZone: PropTypes.string,
+ value: PropTypes.any.isRequired,
+ };
+
+ static defaultProps = {
+ format: 'ddd, MMM DD, YYYY',
};
render() {
const {
- intl,
+ format,
+ timeZone,
value,
- children,
...props
} = this.props;
- Reflect.deleteProperty(props, 'format');
-
- const formattedDate = intl.formatDate(value, this.props);
-
- if (typeof children === 'function') {
- return children(formattedDate);
+ let formattedDate = moment(value).format(format);
+ if (timeZone) {
+ formattedDate = moment.tz(value, timeZone).format(format);
}
return {formattedDate};
}
}
-
-export default injectIntl(FormattedDate);
diff --git a/app/components/formatted_time.js b/app/components/formatted_time.js
index 9f9ea57dd..64cc06569 100644
--- a/app/components/formatted_time.js
+++ b/app/components/formatted_time.js
@@ -3,7 +3,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {intlShape} from 'react-intl';
import {Text} from 'react-native';
import moment from 'moment-timezone';
@@ -15,30 +14,19 @@ export default class FormattedTime extends React.PureComponent {
hour12: PropTypes.bool,
};
- static contextTypes = {
- intl: intlShape.isRequired,
- };
-
getFormattedTime = () => {
- const {intl} = this.context;
-
const {
value,
timeZone,
hour12,
} = this.props;
+ const format = hour12 ? 'hh:mm A' : 'HH:mm';
if (timeZone) {
- const format = hour12 ? 'hh:mm A' : 'HH:mm';
return moment.tz(value, timeZone).format(format);
}
- // If no timezone is defined fallback to the previous implementation
- return intl.formatDate(new Date(value), {
- hour: 'numeric',
- minute: 'numeric',
- hour12,
- });
+ return moment(value).format(format);
};
render() {
diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js
index 850a55eb6..10afaf367 100644
--- a/app/components/post_header/post_header.js
+++ b/app/components/post_header/post_header.js
@@ -202,7 +202,10 @@ export default class PostHeader extends PureComponent {
dateComponent = (
-
+
-
-
-
diff --git a/app/i18n/index.js b/app/i18n/index.js
index 7cc58dd93..5271638ed 100644
--- a/app/i18n/index.js
+++ b/app/i18n/index.js
@@ -4,6 +4,7 @@
import 'intl';
import {addLocaleData} from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
+import moment from 'moment';
import en from 'assets/i18n/en.json';
@@ -16,72 +17,92 @@ addLocaleData(enLocaleData);
function loadTranslation(locale) {
try {
let localeData;
+ let momentData;
switch (locale) {
case 'de':
TRANSLATIONS.de = require('assets/i18n/de.json');
localeData = require('react-intl/locale-data/de');
+ momentData = require('moment/locale/de');
break;
case 'es':
TRANSLATIONS.es = require('assets/i18n/es.json');
localeData = require('react-intl/locale-data/es');
+ momentData = require('moment/locale/es');
break;
case 'fr':
TRANSLATIONS.fr = require('assets/i18n/fr.json');
localeData = require('react-intl/locale-data/fr');
+ momentData = require('moment/locale/fr');
break;
case 'it':
TRANSLATIONS.it = require('assets/i18n/it.json');
localeData = require('react-intl/locale-data/it');
+ momentData = require('moment/locale/it');
break;
case 'ja':
TRANSLATIONS.ja = require('assets/i18n/ja.json');
localeData = require('react-intl/locale-data/ja');
+ momentData = require('moment/locale/ja');
break;
case 'ko':
TRANSLATIONS.ko = require('assets/i18n/ko.json');
localeData = require('react-intl/locale-data/ko');
+ momentData = require('moment/locale/ko');
break;
case 'nl':
TRANSLATIONS.nl = require('assets/i18n/nl.json');
localeData = require('react-intl/locale-data/nl');
+ momentData = require('moment/locale/nl');
break;
case 'pl':
TRANSLATIONS.pl = require('assets/i18n/pl.json');
localeData = require('react-intl/locale-data/pl');
+ momentData = require('moment/locale/pl');
break;
case 'pt-BR':
TRANSLATIONS[locale] = require('assets/i18n/pt-BR.json');
localeData = require('react-intl/locale-data/pt');
+ momentData = require('moment/locale/pt-br');
break;
case 'ro':
TRANSLATIONS.ro = require('assets/i18n/ro.json');
localeData = require('react-intl/locale-data/ro');
+ momentData = require('moment/locale/ro');
break;
case 'ru':
TRANSLATIONS.ru = require('assets/i18n/ru.json');
localeData = require('react-intl/locale-data/ru');
+ momentData = require('moment/locale/ru');
break;
case 'tr':
TRANSLATIONS.tr = require('assets/i18n/tr.json');
localeData = require('react-intl/locale-data/tr');
+ momentData = require('moment/locale/tr');
break;
case 'uk':
TRANSLATIONS.tr = require('assets/i18n/uk.json');
localeData = require('react-intl/locale-data/uk');
+ momentData = require('moment/locale/uk');
break;
case 'zh-CN':
TRANSLATIONS[locale] = require('assets/i18n/zh-CN.json');
localeData = require('react-intl/locale-data/zh');
+ momentData = require('moment/locale/zh-cn');
break;
case 'zh-TW':
TRANSLATIONS[locale] = require('assets/i18n/zh-TW.json');
localeData = require('react-intl/locale-data/zh');
+ momentData = require('moment/locale/zh-tw');
break;
}
if (localeData) {
addLocaleData(localeData);
}
+
+ if (momentData) {
+ moment.updateLocale(locale, momentData);
+ }
} catch (e) {
console.error('NO Translation found', e); //eslint-disable-line no-console
}
diff --git a/app/init/global_event_handler.test.js b/app/init/global_event_handler.test.js
index a3abef9f5..ec0fe40f0 100644
--- a/app/init/global_event_handler.test.js
+++ b/app/init/global_event_handler.test.js
@@ -4,25 +4,35 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
+import intitialState from 'app/initial_state';
import PushNotification from 'app/push_notifications';
import GlobalEventHandler from './global_event_handler';
jest.mock('app/init/credentials', () => ({
+ getCurrentServerUrl: jest.fn().mockResolvedValue(''),
getAppCredentials: jest.fn(),
removeAppCredentials: jest.fn(),
}));
+jest.mock('react-native-notifications', () => ({
+ addEventListener: jest.fn(),
+ cancelAllLocalNotifications: jest.fn(),
+ setBadgesCount: jest.fn(),
+ NotificationAction: jest.fn(),
+ NotificationCategory: jest.fn(),
+}));
+
const mockStore = configureMockStore([thunk]);
-const store = mockStore({});
+const store = mockStore(intitialState);
GlobalEventHandler.store = store;
// TODO: Add Android test as part of https://mattermost.atlassian.net/browse/MM-17110
describe('GlobalEventHandler', () => {
- it('should clear notifications on logout', () => {
+ it('should clear notifications on logout', async () => {
const clearNotifications = jest.spyOn(PushNotification, 'clearNotifications');
- GlobalEventHandler.onLogout();
+ await GlobalEventHandler.onLogout();
expect(clearNotifications).toHaveBeenCalled();
});
-});
\ No newline at end of file
+});
diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js
index f5ae75dd1..f8d4a0ecf 100644
--- a/app/screens/channel_info/channel_info_header.js
+++ b/app/screens/channel_info/channel_info_header.js
@@ -36,6 +36,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
isArchived: PropTypes.bool.isRequired,
isBot: PropTypes.bool.isRequired,
isGroupConstrained: PropTypes.bool,
+ timeZone: PropTypes.string,
};
static contextTypes = {
@@ -98,6 +99,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
isArchived,
isBot,
isGroupConstrained,
+ timeZone,
} = this.props;
const style = getStyleSheet(theme);
@@ -128,76 +130,76 @@ export default class ChannelInfoHeader extends React.PureComponent {
{displayName}
+ {this.renderHasGuestText(style)}
{purpose.length > 0 &&
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
}
{header.length > 0 &&
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
}
{isGroupConstrained &&
-
-
-
+
+
+
}
{creator &&
-
-
-
-
+
+
+
+
}
);
diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js
index daf752bf7..6351aa02d 100644
--- a/app/screens/channel_info/index.js
+++ b/app/screens/channel_info/index.js
@@ -29,6 +29,8 @@ import {getCurrentUserId, getUser, getStatusForUserId, getCurrentUserRoles} from
import {areChannelMentionsIgnored, getUserIdFromChannelName, isChannelMuted, showDeleteOption, showManagementOptions} from 'mattermost-redux/utils/channel_utils';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
+import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
+import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
import {
popTopScreen,
@@ -45,6 +47,7 @@ import {
selectPenultimateChannel,
setChannelDisplayName,
} from 'app/actions/views/channel';
+import {isLandscape} from 'app/selectors/device';
import ChannelInfo from './channel_info';
@@ -87,6 +90,12 @@ function mapStateToProps(state) {
const canEditChannel = !channelIsReadOnly && showManagementOptions(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin);
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
+ const enableTimezone = isTimezoneEnabled(state);
+ let timeZone = null;
+ if (enableTimezone) {
+ timeZone = getUserCurrentTimezone(currentUser.timezone);
+ }
+
return {
canDeleteChannel: showDeleteOption(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin),
viewArchivedChannels,
@@ -103,6 +112,8 @@ function mapStateToProps(state) {
theme: getTheme(state),
canManageUsers,
isBot,
+ isLandscape: isLandscape(state),
+ timeZone,
};
}
diff --git a/test/setup.js b/test/setup.js
index 0ce565914..fd80f20bd 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -8,6 +8,7 @@ configure({adapter: new Adapter()});
const mockImpl = new MockAsyncStorage();
jest.mock('@react-native-community/async-storage', () => mockImpl);
+global.window = {};
/* eslint-disable no-console */
@@ -38,6 +39,10 @@ jest.mock('NativeModules', () => {
SECURITY_LEVEL_SECURE_SOFTWARE: 'SOFTWARE',
SECURITY_LEVEL_SECURE_HARDWARE: 'HARDWARE',
},
+ RNCNetInfo: {
+ addEventListener: jest.fn(),
+ getCurrentState: jest.fn().mockResolvedValue({isConnected: true}),
+ },
};
});
jest.mock('NativeEventEmitter');
@@ -48,6 +53,7 @@ jest.mock('react-native-device-info', () => {
getBuildNumber: () => '0',
getModel: () => 'iPhone X',
isTablet: () => false,
+ getDeviceLocale: () => 'en-US',
};
});