Enable Android Unbundle strategy (perf part 2) (#1700)

* Enable Android Unbundle strategy

* feedback review

* Update modulePaths

* Add npm start and use it in the Makefile

* feedback review #2
This commit is contained in:
Elias Nahum 2018-05-23 13:23:38 -04:00 committed by GitHub
parent a3eeb23419
commit c171ae4809
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1555 additions and 299 deletions

View file

@ -59,7 +59,7 @@
"func-names": 2,
"func-style": [2, "declaration", { "allowArrowFunctions": true }],
"generator-star-spacing": [0, {"before": false, "after": true}],
"global-require": 2,
"global-require": 0,
"guard-for-in": 2,
"id-blacklist": 0,
"indent": [2, 4, {"SwitchCase": 0}],
@ -261,4 +261,4 @@
"wrap-regex": 2,
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}]
}
}
}

View file

@ -89,7 +89,7 @@ post-install:
start: | pre-run ## Starts the React Native packager server
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start; \
npm start; \
else \
echo React Native packager server already running; \
fi
@ -141,7 +141,7 @@ run: run-ios ## alias for run-ios
run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo Running iOS app in development; \
npm start & echo Running iOS app in development; \
if [ ! -z "${SIMULATOR}" ]; then \
react-native run-ios --simulator="${SIMULATOR}"; \
else \
@ -160,7 +160,7 @@ run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo Running Android app in development; \
npm start & echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
react-native run-android --no-packager --variant=${VARIANT}; \
else \
@ -183,7 +183,7 @@ ifneq ($(IOS_APP_GROUP),)
endif
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo; \
npm start & echo; \
fi
@echo "Building iOS app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
@ -193,7 +193,7 @@ endif
build-android: | pre-run check-style prepare-android-build ## Creates an Android build
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo; \
npm start & echo; \
fi
@echo "Building Android app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
@ -202,7 +202,7 @@ build-android: | pre-run check-style prepare-android-build ## Creates an Android
unsigned-ios: pre-run check-style
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo; \
npm start & echo; \
fi
@echo "Building unsigned iOS app"
ifneq ($(IOS_APP_GROUP),)
@ -221,7 +221,7 @@ endif
unsigned-android: pre-run check-style prepare-android-build
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
node ./node_modules/react-native/local-cli/cli.js start & echo; \
npm start & echo; \
fi
@echo "Building unsigned Android app"
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned

View file

@ -73,7 +73,9 @@ import com.android.build.OutputFile
*/
project.ext.react = [
entryFile: "index.js"
entryFile: "index.js",
bundleCommand: "unbundle",
bundleConfig: "packager/config.js"
]
apply from: "../../node_modules/react-native/react.gradle"

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
@ -11,9 +11,11 @@ import {
handlePasswordChanged,
} from 'app/actions/views/login';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),
},
}));
const mockStore = configureStore([thunk]);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
@ -11,9 +11,11 @@ import {ViewTypes} from 'app/constants';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),
},
}));
const mockStore = configureStore([thunk]);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
@ -11,9 +11,11 @@ import {
handleCommentDraftSelectionChanged,
} from 'app/actions/views/thread';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),
},
}));
const mockStore = configureStore([thunk]);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -15,19 +15,19 @@ import {intlShape} from 'react-intl';
import Icon from 'react-native-vector-icons/Ionicons';
import LinearGradient from 'react-native-linear-gradient';
import FileAttachmentList from 'app/components/file_attachment_list';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import OptionsContext from 'app/components/options_context';
import PostAddChannelMember from 'app/components/post_add_channel_member';
import PostBodyAdditionalContent from 'app/components/post_body_additional_content';
import {emptyFunction} from 'app/utils/general';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import Reactions from 'app/components/reactions';
let FileAttachmentList;
let PostAddChannelMember;
let PostBodyAdditionalContent;
let Reactions;
export default class PostBody extends PureComponent {
static propTypes = {
@ -229,6 +229,31 @@ export default class PostBody extends PureComponent {
}
};
renderAddChannelMember = (style, textStyles) => {
const {onPermalinkPress, onPress, postProps} = this.props;
if (!PostAddChannelMember) {
PostAddChannelMember = require('app/components/post_add_channel_member').default;
}
return (
<View style={style.row}>
<View style={style.flex}>
<PostAddChannelMember
navigator={navigator}
onLongPress={this.showOptionsContext}
onPermalinkPress={onPermalinkPress}
onPostPress={onPress}
textStyles={textStyles}
postId={postProps.add_channel_member.post_id}
userIds={postProps.add_channel_member.user_ids}
usernames={postProps.add_channel_member.usernames}
/>
</View>
</View>
);
};
renderFileAttachments() {
const {
fileIds,
@ -246,6 +271,10 @@ export default class PostBody extends PureComponent {
let attachments;
if (fileIds.length > 0) {
if (!FileAttachmentList) {
FileAttachmentList = require('app/components/file_attachment_list').default;
}
attachments = (
<FileAttachmentList
fileIds={fileIds}
@ -265,6 +294,10 @@ export default class PostBody extends PureComponent {
renderPostAdditionalContent = (blockStyles, messageStyle, textStyles) => {
const {isReplyPost, message, navigator, onPermalinkPress, postId, postProps} = this.props;
if (!PostBodyAdditionalContent) {
PostBodyAdditionalContent = require('app/components/post_body_additional_content').default;
}
return (
<PostBodyAdditionalContent
baseTextStyle={messageStyle}
@ -288,6 +321,10 @@ export default class PostBody extends PureComponent {
return null;
}
if (!Reactions) {
Reactions = require('app/components/reactions').default;
}
return (
<Reactions
postId={postId}
@ -364,7 +401,6 @@ export default class PostBody extends PureComponent {
onFailedPostPress,
onPermalinkPress,
onPress,
postProps,
renderReplyBar,
theme,
toggleSelected,
@ -397,22 +433,7 @@ export default class PostBody extends PureComponent {
);
body = (<View>{messageComponent}</View>);
} else if (isPostAddChannelMember) {
messageComponent = (
<View style={style.row}>
<View style={style.flex}>
<PostAddChannelMember
navigator={navigator}
onLongPress={this.showOptionsContext}
onPermalinkPress={onPermalinkPress}
onPostPress={onPress}
textStyles={textStyles}
postId={postProps.add_channel_member.post_id}
userIds={postProps.add_channel_member.user_ids}
usernames={postProps.add_channel_member.usernames}
/>
</View>
</View>
);
messageComponent = this.renderAddChannelMember(style, textStyles);
} else if (message.length) {
messageComponent = (
<View style={style.row}>

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -14,10 +14,6 @@ import {
import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube';
import youTubeVideoId from 'youtube-video-id';
import youtubePlayIcon from 'assets/images/icons/youtube-play-icon.png';
import MessageAttachments from 'app/components/message_attachments';
import PostAttachmentOpenGraph from 'app/components/post_attachment_opengraph';
import ProgressiveImage from 'app/components/progressive_image';
import CustomPropTypes from 'app/constants/custom_prop_types';
@ -27,6 +23,9 @@ import {isImageLink, isYoutubeLink} from 'app/utils/url';
const MAX_IMAGE_HEIGHT = 150;
let MessageAttachments;
let PostAttachmentOpenGraph;
export default class PostBodyAdditionalContent extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style,
@ -132,6 +131,9 @@ export default class PostBodyAdditionalContent extends PureComponent {
}
if (link && showLinkPreviews) {
if (!PostAttachmentOpenGraph) {
PostAttachmentOpenGraph = require('app/components/post_attachment_opengraph').default;
}
return (
<PostAttachmentOpenGraph
isReplyPost={isReplyPost}
@ -173,7 +175,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
>
<TouchableWithoutFeedback onPress={this.playYouTubeVideo}>
<Image
source={youtubePlayIcon}
source={require('assets/images/icons/youtube-play-icon.png')}
onPress={this.playYouTubeVideo}
/>
</TouchableWithoutFeedback>
@ -261,6 +263,10 @@ export default class PostBodyAdditionalContent extends PureComponent {
const {attachments} = postProps;
if (attachments && attachments.length) {
if (!MessageAttachments) {
MessageAttachments = require('app/components/message_attachments').default;
}
return (
<MessageAttachments
attachments={attachments}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -58,15 +58,18 @@ export default class PostList extends PureComponent {
loadMore: () => true,
};
newMessagesIndex = -1;
scrollToMessageTries = 0;
makeExtraData = makeExtraData();
itemMeasurements = {};
constructor(props) {
super(props);
state = {
managedConfig: {},
scrollToMessage: false,
};
this.newMessagesIndex = -1;
this.makeExtraData = makeExtraData();
this.itemMeasurements = {};
this.state = {
managedConfig: {},
scrollToMessage: false,
};
}
componentWillMount() {
this.listenerId = mattermostManaged.addEventListener('change', this.setManagedConfig);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -11,12 +11,13 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import AttachmentButton from 'app/components/attachment_button';
import Autocomplete from 'app/components/autocomplete';
import FileUploadPreview from 'app/components/file_upload_preview';
import PaperPlane from 'app/components/paper_plane';
import {INITIAL_HEIGHT, INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import Typing from './components/typing';
let PaperPlane = null;
export default class PostTextbox extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@ -282,35 +283,44 @@ export default class PostTextbox extends PureComponent {
const {files, theme} = this.props;
const style = getStyleSheet(theme);
const icon = (
<PaperPlane
height={13}
width={15}
color={theme.buttonColor}
/>
);
const canSend = this.canSend();
const imagesLoading = files.filter((f) => f.loading).length > 0;
let button = null;
const imagesLoading = files.filter((f) => f.loading).length > 0;
if (imagesLoading) {
button = (
<View style={style.sendButtonContainer}>
<View style={[style.sendButton, style.disableButton]}>
{icon}
</View>
</View>
if (canSend || imagesLoading) {
if (!PaperPlane) {
PaperPlane = require('app/components/paper_plane').default;
}
const icon = (
<PaperPlane
height={13}
width={15}
color={theme.buttonColor}
/>
);
} else if (this.canSend()) {
button = (
<TouchableOpacity
onPress={this.handleSendMessage}
style={style.sendButtonContainer}
>
<View style={style.sendButton}>
{icon}
if (imagesLoading) {
button = (
<View style={style.sendButtonContainer}>
<View style={[style.sendButton, style.disableButton]}>
{icon}
</View>
</View>
</TouchableOpacity>
);
);
} else if (canSend) {
button = (
<TouchableOpacity
onPress={this.handleSendMessage}
style={style.sendButtonContainer}
>
<View style={style.sendButton}>
{icon}
</View>
</TouchableOpacity>
);
}
}
return button;

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import keyMirror from 'mattermost-redux/utils/key_mirror';
import RNFetchBlob from 'react-native-fetch-blob';
import RNFetchBlobFS from 'react-native-fetch-blob/fs';
const deviceTypes = keyMirror({
CONNECTION_CHANGED: null,
@ -14,7 +14,7 @@ const deviceTypes = keyMirror({
export default {
...deviceTypes,
DOCUMENTS_PATH: `${RNFetchBlob.fs.dirs.CacheDir}/Documents`,
IMAGES_PATH: `${RNFetchBlob.fs.dirs.CacheDir}/Images`,
VIDEOS_PATH: `${RNFetchBlob.fs.dirs.CacheDir}/Videos`,
DOCUMENTS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Documents`,
IMAGES_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Images`,
VIDEOS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Videos`,
};

View file

@ -1,70 +1,87 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import 'intl';
import {addLocaleData} from 'react-intl';
import deLocaleData from 'react-intl/locale-data/de';
import enLocaleData from 'react-intl/locale-data/en';
import esLocaleData from 'react-intl/locale-data/es';
import frLocaleData from 'react-intl/locale-data/fr';
import itLocaleData from 'react-intl/locale-data/it';
import jaLocaleData from 'react-intl/locale-data/ja';
import koLocaleData from 'react-intl/locale-data/ko';
import nlLocaleData from 'react-intl/locale-data/nl';
import plLocaleData from 'react-intl/locale-data/pl';
import ptLocaleData from 'react-intl/locale-data/pt';
import trLocaleData from 'react-intl/locale-data/tr';
import ruLocaleData from 'react-intl/locale-data/ru';
import zhLocaleData from 'react-intl/locale-data/zh';
import {DEFAULT_LOCALE} from 'mattermost-redux/constants/general';
import de from 'assets/i18n/de.json';
import en from 'assets/i18n/en.json';
import es from 'assets/i18n/es.json';
import fr from 'assets/i18n/fr.json';
import it from 'assets/i18n/it.json';
import ja from 'assets/i18n/ja.json';
import ko from 'assets/i18n/ko.json';
import nl from 'assets/i18n/nl.json';
import pl from 'assets/i18n/pl.json';
import ptBR from 'assets/i18n/pt-BR.json';
import tr from 'assets/i18n/tr.json';
import ru from 'assets/i18n/ru.json';
import zhCN from 'assets/i18n/zh-CN.json';
import zhTW from 'assets/i18n/zh-TW.json';
const TRANSLATIONS = {
de,
en,
es,
fr,
it,
ja,
ko,
nl,
pl,
'pt-BR': ptBR,
tr,
ru,
'zh-CN': zhCN,
'zh-TW': zhTW,
};
const TRANSLATIONS = {en};
export const DEFAULT_LOCALE = 'en';
addLocaleData(deLocaleData);
addLocaleData(enLocaleData);
addLocaleData(esLocaleData);
addLocaleData(frLocaleData);
addLocaleData(itLocaleData);
addLocaleData(jaLocaleData);
addLocaleData(koLocaleData);
addLocaleData(nlLocaleData);
addLocaleData(plLocaleData);
addLocaleData(ptLocaleData);
addLocaleData(trLocaleData);
addLocaleData(ruLocaleData);
addLocaleData(zhLocaleData);
function loadTranslation(locale) {
try {
let localeData;
switch (locale) {
case 'de':
TRANSLATIONS.de = require('assets/i18n/de.json');
localeData = require('react-intl/locale-data/de');
break;
case 'es':
TRANSLATIONS.es = require('assets/i18n/es.json');
localeData = require('react-intl/locale-data/es');
break;
case 'fr':
TRANSLATIONS.fr = require('assets/i18n/fr.json');
localeData = require('react-intl/locale-data/fr');
break;
case 'it':
TRANSLATIONS.it = require('assets/i18n/it.json');
localeData = require('react-intl/locale-data/it');
break;
case 'ja':
TRANSLATIONS.ja = require('assets/i18n/ja.json');
localeData = require('react-intl/locale-data/ja');
break;
case 'ko':
TRANSLATIONS.ko = require('assets/i18n/ko.json');
localeData = require('react-intl/locale-data/ko');
break;
case 'nl':
TRANSLATIONS.nl = require('assets/i18n/nl.json');
localeData = require('react-intl/locale-data/nl');
break;
case 'pl':
TRANSLATIONS.pl = require('assets/i18n/pl.json');
localeData = require('react-intl/locale-data/pl');
break;
case 'pt-BT':
TRANSLATIONS[locale] = require('assets/i18n/pt-BR.json');
localeData = require('react-intl/locale-data/pt');
break;
case 'tr':
TRANSLATIONS.tr = require('assets/i18n/tr.json');
localeData = require('react-intl/locale-data/tr');
break;
case 'ru':
TRANSLATIONS.ru = require('assets/i18n/ru.json');
localeData = require('react-intl/locale-data/ru');
break;
case 'zh-CN':
TRANSLATIONS[locale] = require('assets/i18n/zh-CN.json');
localeData = require('react-intl/locale-data/zh');
break;
case 'zh-TW':
TRANSLATIONS[locale] = require('assets/i18n/zh-TW.json');
localeData = require('react-intl/locale-data/zh');
break;
}
if (localeData) {
addLocaleData(localeData);
}
} catch (e) {
console.error('NO Translation found', e); //eslint-disable-line no-console
}
}
export function getTranslations(locale) {
if (!TRANSLATIONS[locale]) {
loadTranslation(locale);
}
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
}

View file

@ -1,9 +1,9 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// 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 {injectIntl, intlShape} from 'react-intl';
import {intlShape} from 'react-intl';
import {
AppState,
Platform,
@ -12,14 +12,10 @@ import {
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import ClientUpgradeListener from 'app/components/client_upgrade_listener';
import ChannelDrawer from 'app/components/channel_drawer';
import SettingsDrawer from 'app/components/settings_drawer';
import ChannelLoader from 'app/components/channel_loader';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import Loading from 'app/components/loading';
import OfflineIndicator from 'app/components/offline_indicator';
import PostListRetry from 'app/components/post_list_retry';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import {preventDoubleTap} from 'app/utils/tap';
@ -32,7 +28,9 @@ import LocalConfig from 'assets/config';
import ChannelNavBar from './channel_nav_bar';
import ChannelPostList from './channel_post_list';
class Channel extends PureComponent {
let ClientUpgradeListener;
export default class Channel extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
connection: PropTypes.func.isRequired,
@ -49,11 +47,22 @@ class Channel extends PureComponent {
currentChannelId: PropTypes.string,
channelsRequestFailed: PropTypes.bool,
currentTeamId: PropTypes.string,
intl: intlShape.isRequired,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
}
}
componentWillMount() {
EventEmitter.on('leave_team', this.handleLeaveTeam);
@ -67,9 +76,7 @@ class Channel extends PureComponent {
}
componentDidMount() {
if (Platform.OS === 'android') {
AppState.addEventListener('change', this.handleAppStateChange);
}
AppState.addEventListener('change', this.handleAppStateChange);
if (tracker.initialLoad) {
this.props.actions.recordLoadTime('Start time', 'initialLoad');
@ -86,6 +93,10 @@ class Channel extends PureComponent {
if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) {
this.loadChannels(nextProps.currentTeamId);
}
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
}
}
componentDidUpdate() {
@ -100,20 +111,20 @@ class Channel extends PureComponent {
EventEmitter.off('leave_team', this.handleLeaveTeam);
this.networkListener.removeEventListener();
if (Platform.OS === 'android') {
AppState.removeEventListener('change', this.handleAppStateChange);
}
AppState.removeEventListener('change', this.handleAppStateChange);
closeWebSocket();
stopPeriodicStatusUpdates();
}
attachPostTextbox = (ref) => {
attachPostTextBox = (ref) => {
this.postTextbox = ref;
};
blurPostTextBox = () => {
this.postTextbox.getWrappedInstance().blur();
if (this.postTextbox && this.postTextbox.getWrappedInstance()) {
this.postTextbox.getWrappedInstance().blur();
}
};
channelDrawerRef = (ref) => {
@ -129,7 +140,8 @@ class Channel extends PureComponent {
};
goToChannelInfo = preventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
const {intl} = this.context;
const {navigator, theme} = this.props;
const options = {
screen: 'ChannelInfo',
title: intl.formatMessage({id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}),
@ -150,7 +162,7 @@ class Channel extends PureComponent {
}
});
handleAppStateChange = async (appState) => {
handleWebSocket = (open) => {
const {actions} = this.props;
const {
closeWebSocket,
@ -158,9 +170,8 @@ class Channel extends PureComponent {
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates,
} = actions;
const isActive = appState === 'active';
if (isActive) {
if (open) {
initWebSocket(Platform.OS);
startPeriodicStatusUpdates();
} else {
@ -169,23 +180,14 @@ class Channel extends PureComponent {
}
};
handleConnectionChange = (isConnected) => {
const {actions} = this.props;
const {
closeWebSocket,
connection,
initWebSocket,
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates,
} = actions;
handleAppStateChange = async (appState) => {
this.handleWebSocket(appState === 'active');
};
if (isConnected) {
initWebSocket(Platform.OS);
startPeriodicStatusUpdates();
} else {
closeWebSocket(true);
stopPeriodicStatusUpdates();
}
handleConnectionChange = (isConnected) => {
const {connection} = this.props.actions;
this.handleWebSocket(isConnected);
connection(isConnected);
};
@ -225,10 +227,10 @@ class Channel extends PureComponent {
};
render() {
const {intl} = this.context;
const {
channelsRequestFailed,
currentChannelId,
intl,
navigator,
theme,
} = this.props;
@ -237,6 +239,7 @@ class Channel extends PureComponent {
if (!currentChannelId) {
if (channelsRequestFailed) {
const PostListRetry = require('app/components/post_list_retry').default;
return (
<PostListRetry
retry={this.retryLoadChannels}
@ -244,9 +247,11 @@ class Channel extends PureComponent {
/>
);
}
const Loading = require('app/components/channel_loader').default;
return (
<View style={style.loading}>
<Loading/>
<Loading channelIsLoading={true}/>
</View>
);
}
@ -277,10 +282,9 @@ class Channel extends PureComponent {
<ChannelPostList navigator={navigator}/>
</View>
<PostTextbox
ref={this.attachPostTextbox}
ref={this.attachPostTextBox}
navigator={navigator}
/>
<ChannelLoader theme={theme}/>
</KeyboardLayout>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
</SafeAreaView>
@ -301,5 +305,3 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
},
};
});
export default injectIntl(Channel);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import PropTypes from 'prop-types';
@ -13,13 +13,14 @@ import {
import {debounce} from 'mattermost-redux/actions/helpers';
import AnnouncementBanner from 'app/components/announcement_banner';
import ChannelIntro from 'app/components/channel_intro';
import LoadMorePosts from 'app/components/load_more_posts';
import PostList from 'app/components/post_list';
import PostListRetry from 'app/components/post_list_retry';
import RetryBarIndicator from 'app/components/retry_bar_indicator';
import tracker from 'app/utils/time_tracker';
let ChannelIntro = null;
let LoadMorePosts = null;
export default class ChannelPostList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@ -136,6 +137,10 @@ export default class ChannelPostList extends PureComponent {
}
if (this.props.loadMorePostsVisible) {
if (!LoadMorePosts) {
LoadMorePosts = require('app/components/load_more_posts').default;
}
return (
<LoadMorePosts
channelId={this.props.channelId}
@ -145,6 +150,10 @@ export default class ChannelPostList extends PureComponent {
);
}
if (!ChannelIntro) {
ChannelIntro = require('app/components/channel_intro').default;
}
return (
<ChannelIntro
channelId={this.props.channelId}

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
/* eslint-disable global-require*/
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {

View file

@ -1,9 +1,12 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Navigation} from 'react-native-navigation';
import Channel from 'app/screens/channel';
import Entry from 'app/screens/entry';
import SelectServer from 'app/screens/select_server';
import IntlWrapper from 'app/components/root';
function wrapWithContextProvider(Comp, excludeEvents = true) {
@ -21,49 +24,49 @@ function wrapWithContextProvider(Comp, excludeEvents = true) {
}
export function registerScreens(store, Provider) {
Navigation.registerComponent('About', () => wrapWithContextProvider(require('app/screens/about').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(require('app/screens/add_reaction').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(require('app/screens/settings/advanced_settings').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Channel', () => wrapWithContextProvider(require('app/screens/channel').default, false), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(require('app/screens/channel_add_members').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(require('app/screens/channel_info').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(require('app/screens/channel_members').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(require('app/screens/channel_peek').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(require('app/screens/client_upgrade').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ClockDisplay', () => wrapWithContextProvider(require('app/screens/clock_display').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Code', () => wrapWithContextProvider(require('app/screens/code').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(require('app/screens/create_channel').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('DisplaySettings', () => wrapWithContextProvider(require('app/screens/settings/display_settings').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Entry', () => wrapWithContextProvider(require('app/screens/entry').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(require('app/screens/text_preview').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(require('app/screens/login_options').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('LongPost', () => wrapWithContextProvider(require('app/screens/long_post').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('MFA', () => wrapWithContextProvider(require('app/screens/mfa').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(require('app/screens/more_channels').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(require('app/screens/more_dms').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Notification', () => wrapWithContextProvider(require('app/screens/notification').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(require('app/screens/settings/notification_settings').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_email').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('NotificationSettingsMentionsKeywords', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions_keywords').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mobile').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(require('app/screens/options_modal').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(require('app/screens/permalink').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(require('app/screens/recent_mentions').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Root', () => require('app/screens/root').default, store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Search', () => wrapWithContextProvider(require('app/screens/search').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(require('app/screens/select_server').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(require('app/screens/select_team').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Settings', () => wrapWithContextProvider(require('app/screens/settings/general').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('SSO', () => wrapWithContextProvider(require('app/screens/sso').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Table', () => wrapWithContextProvider(require('app/screens/table').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('TableImage', () => wrapWithContextProvider(require('app/screens/table_image').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('Thread', () => wrapWithContextProvider(require('app/screens/thread').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(require('app/screens/user_profile').default), store, Provider); //eslint-disable-line global-require
Navigation.registerComponent('About', () => wrapWithContextProvider(require('app/screens/about').default), store, Provider);
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(require('app/screens/add_reaction').default), store, Provider);
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(require('app/screens/settings/advanced_settings').default), store, Provider);
Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel, false), store, Provider);
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(require('app/screens/channel_add_members').default), store, Provider);
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(require('app/screens/channel_info').default), store, Provider);
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(require('app/screens/channel_members').default), store, Provider);
Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(require('app/screens/channel_peek').default), store, Provider);
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(require('app/screens/client_upgrade').default), store, Provider);
Navigation.registerComponent('ClockDisplay', () => wrapWithContextProvider(require('app/screens/clock_display').default), store, Provider);
Navigation.registerComponent('Code', () => wrapWithContextProvider(require('app/screens/code').default), store, Provider);
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(require('app/screens/create_channel').default), store, Provider);
Navigation.registerComponent('DisplaySettings', () => wrapWithContextProvider(require('app/screens/settings/display_settings').default), store, Provider);
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider);
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider);
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider);
Navigation.registerComponent('Entry', () => wrapWithContextProvider(Entry), store, Provider);
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider);
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider);
Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider);
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(require('app/screens/login_options').default), store, Provider);
Navigation.registerComponent('LongPost', () => wrapWithContextProvider(require('app/screens/long_post').default), store, Provider);
Navigation.registerComponent('MFA', () => wrapWithContextProvider(require('app/screens/mfa').default), store, Provider);
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(require('app/screens/more_channels').default), store, Provider);
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(require('app/screens/more_dms').default), store, Provider);
Navigation.registerComponent('Notification', () => wrapWithContextProvider(require('app/screens/notification').default), store, Provider);
Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(require('app/screens/settings/notification_settings').default), store, Provider);
Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_email').default), store, Provider);
Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions').default), store, Provider);
Navigation.registerComponent('NotificationSettingsMentionsKeywords', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions_keywords').default), store, Provider);
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mobile').default), store, Provider);
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(require('app/screens/options_modal').default), store, Provider);
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(require('app/screens/permalink').default), store, Provider);
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(require('app/screens/recent_mentions').default), store, Provider);
Navigation.registerComponent('Root', () => require('app/screens/root').default, 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('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('UserProfile', () => wrapWithContextProvider(require('app/screens/user_profile').default), store, Provider);
}

View file

@ -1,9 +1,9 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// 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 {injectIntl, intlShape} from 'react-intl';
import {intlShape} from 'react-intl';
import {
ActivityIndicator,
Dimensions,
@ -27,13 +27,10 @@ import {GlobalStyles} from 'app/styles';
import {preventDoubleTap} from 'app/utils/tap';
import tracker from 'app/utils/time_tracker';
import logo from 'assets/images/logo.png';
import {RequestStatus} from 'mattermost-redux/constants';
class Login extends PureComponent {
export default class Login extends PureComponent {
static propTypes = {
intl: intlShape.isRequired,
navigator: PropTypes.object,
theme: PropTypes.object,
actions: PropTypes.shape({
@ -52,6 +49,10 @@ class Login extends PureComponent {
loginRequest: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
@ -66,7 +67,7 @@ class Login extends PureComponent {
componentWillReceiveProps(nextProps) {
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
this.props.actions.handleSuccessfulLogin().then(this.props.actions.getSession).then(this.goToLoadTeam);
this.props.actions.handleSuccessfulLogin().then(this.props.actions.getSession).then(this.goToChannel);
} else if (this.props.loginRequest.status !== nextProps.loginRequest.status && nextProps.loginRequest.status !== RequestStatus.STARTED) {
this.setState({isLoading: false});
}
@ -76,8 +77,9 @@ class Login extends PureComponent {
Dimensions.removeEventListener('change', this.orientationDidChange);
}
goToLoadTeam = (expiresAt) => {
const {intl, navigator} = this.props;
goToChannel = (expiresAt) => {
const {intl} = this.context;
const {navigator} = this.props;
tracker.initialLoad = Date.now();
if (expiresAt) {
@ -110,7 +112,8 @@ class Login extends PureComponent {
};
goToMfa = () => {
const {intl, navigator, theme} = this.props;
const {intl} = this.context;
const {navigator, theme} = this.props;
this.setState({isLoading: false});
@ -152,13 +155,14 @@ class Login extends PureComponent {
}
this.setState({
isLoading: false,
error: {
intl: {
id: msgId,
defaultMessage: '',
values: {
ldapUsername: this.props.config.LdapLoginFieldName ||
this.props.intl.formatMessage({
this.context.intl.formatMessage({
id: 'login.ldapUsernameLower',
defaultMessage: 'AD/LDAP username',
}),
@ -171,6 +175,7 @@ class Login extends PureComponent {
if (!this.props.password) {
this.setState({
isLoading: false,
error: {
intl: {
id: 'login.noPassword',
@ -202,7 +207,7 @@ class Login extends PureComponent {
};
createLoginPlaceholder() {
const {formatMessage} = this.props.intl;
const {formatMessage} = this.context.intl;
const license = this.props.license;
const config = this.props.config;
@ -346,7 +351,7 @@ class Login extends PureComponent {
enableOnAndroid={true}
>
<Image
source={logo}
source={require('assets/images/logo.png')}
/>
<View>
<Text style={GlobalStyles.header}>
@ -379,7 +384,7 @@ class Login extends PureComponent {
value={this.props.password}
onChangeText={this.props.actions.handlePasswordChanged}
style={GlobalStyles.inputBox}
placeholder={this.props.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
placeholder={this.context.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
secureTextEntry={true}
autoCorrect={false}
autoCapitalize='none'
@ -409,5 +414,3 @@ const style = StyleSheet.create({
paddingVertical: 50,
},
});
export default injectIntl(Login);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -14,6 +14,8 @@ import {
} from 'react-native';
import Button from 'react-native-button';
import {RequestStatus} from 'mattermost-redux/constants';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import StatusBar from 'app/components/status_bar';
@ -21,10 +23,6 @@ import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_lo
import {GlobalStyles} from 'app/styles';
import {preventDoubleTap} from 'app/utils/tap';
import logo from 'assets/images/logo.png';
import {RequestStatus} from 'mattermost-redux/constants';
export default class Mfa extends PureComponent {
static propTypes = {
navigator: PropTypes.object,
@ -138,7 +136,7 @@ export default class Mfa extends PureComponent {
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
source={require('assets/images/logo.png')}
/>
<View>
<FormattedText

View file

@ -1,9 +1,9 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// 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 {injectIntl, intlShape} from 'react-intl';
import {intlShape} from 'react-intl';
import {
ActivityIndicator,
Image,
@ -13,27 +13,25 @@ import {
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableWithoutFeedback,
View,
} from 'react-native';
import Button from 'react-native-button';
import urlParse from 'url-parse';
import {Client4} from 'mattermost-redux/client';
import Config from 'assets/config';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import {GlobalStyles} from 'app/styles';
import {preventDoubleTap} from 'app/utils/tap';
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
import {UpgradeTypes} from 'app/constants/view';
import {GlobalStyles} from 'app/styles';
import checkUpgradeType from 'app/utils/client_upgrade';
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
import {preventDoubleTap} from 'app/utils/tap';
import logo from 'assets/images/logo.png';
import LocalConfig from 'assets/config';
class SelectServer extends PureComponent {
export default class SelectServer extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
getPing: PropTypes.func.isRequired,
@ -47,7 +45,6 @@ class SelectServer extends PureComponent {
config: PropTypes.object,
currentVersion: PropTypes.string,
hasConfigAndLicense: PropTypes.bool.isRequired,
intl: intlShape.isRequired,
latestVersion: PropTypes.string,
license: PropTypes.object,
minVersion: PropTypes.string,
@ -56,6 +53,10 @@ class SelectServer extends PureComponent {
theme: PropTypes.object,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
@ -86,7 +87,7 @@ class SelectServer extends PureComponent {
componentWillUpdate(nextProps, nextState) {
if (nextState.connected && nextProps.hasConfigAndLicense && !(this.state.connected && this.props.hasConfigAndLicense)) {
if (Config.EnableMobileClientUpgrade) {
if (LocalConfig.EnableMobileClientUpgrade) {
this.props.actions.setLastUpgradeCheck();
const {currentVersion, minVersion, latestVersion} = nextProps;
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion);
@ -116,7 +117,8 @@ class SelectServer extends PureComponent {
};
handleShowClientUpgrade = (upgradeType) => {
const {intl, theme} = this.props;
const {intl} = this.context;
const {theme} = this.props;
this.props.navigator.push({
screen: 'ClientUpgrade',
@ -124,7 +126,7 @@ class SelectServer extends PureComponent {
backButtonTitle: '',
navigatorStyle: {
navBarHidden: false,
disabledBackGesture: Config.AutoSelectServerUrl,
disabledBackGesture: LocalConfig.AutoSelectServerUrl,
statusBarHidden: true,
statusBarHideWithNavBar: true,
navBarTextColor: theme.sidebarHeaderTextColor,
@ -132,14 +134,15 @@ class SelectServer extends PureComponent {
navBarButtonColor: theme.sidebarHeaderTextColor,
},
passProps: {
closeAction: () => this.handleLoginOptions(this.props),
closeAction: this.handleLoginOptions,
upgradeType,
},
});
}
handleLoginOptions = (props) => {
const {config, intl, license, theme} = props;
handleLoginOptions = (props = this.props) => {
const {intl} = this.context;
const {config, license, theme} = props;
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
@ -164,8 +167,8 @@ class SelectServer extends PureComponent {
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarHidden: Config.AutoSelectServerUrl,
disabledBackGesture: Config.AutoSelectServerUrl,
navBarHidden: LocalConfig.AutoSelectServerUrl,
disabledBackGesture: LocalConfig.AutoSelectServerUrl,
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
@ -184,6 +187,7 @@ class SelectServer extends PureComponent {
};
onClick = preventDoubleTap(async () => {
const urlParse = require('url-parse');
const preUrl = urlParse(this.state.url, true);
const url = stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host);
@ -272,11 +276,12 @@ class SelectServer extends PureComponent {
blur = () => {
if (this.textInput) {
this.textInput.refs.wrappedInstance.blur();
this.textInput.blur();
}
};
render() {
const {formatMessage} = this.context.intl;
const {allowOtherServers} = this.props;
const {
connected,
@ -331,7 +336,7 @@ class SelectServer extends PureComponent {
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
source={require('assets/images/logo.png')}
/>
<View>
@ -341,7 +346,7 @@ class SelectServer extends PureComponent {
defaultMessage='Enter Server URL'
/>
</View>
<TextInputWithLocalizedPlaceholder
<TextInput
ref={this.inputRef}
value={url}
editable={!inputDisabled}
@ -351,7 +356,10 @@ class SelectServer extends PureComponent {
autoCapitalize='none'
autoCorrect={false}
keyboardType='url'
placeholder={{id: 'mobile.components.select_server_view.siteUrlPlaceholder', defaultMessage: 'https://mattermost.example.com'}}
placeholder={formatMessage({
id: 'mobile.components.select_server_view.siteUrlPlaceholder',
defaultMessage: 'https://mattermost.example.com',
})}
returnKeyType='go'
underlineColorAndroid='transparent'
disableFullscreenUI={true}
@ -387,5 +395,3 @@ const style = StyleSheet.create({
marginRight: 5,
},
});
export default injectIntl(SelectServer);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
/* eslint-disable max-nested-callbacks */
@ -8,16 +8,12 @@ import assert from 'assert';
import {ViewTypes} from 'app/constants';
import {messageRetention} from 'app/store/middleware';
jest.mock('react-native-fetch-blob', () => {
return {
DocumentDir: () => null,
polyfill: () => null,
fs: {
dirs: {
},
},
};
});
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),
},
}));
jest.mock('react-native-device-info', () => {
return {

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import 'react-native/Libraries/Core/InitializeCore';
import {AppRegistry, Platform} from 'react-native';
import 'app/mattermost';
@ -9,3 +10,33 @@ import ShareExtension from 'share_extension/android';
if (Platform.OS === 'android') {
AppRegistry.registerComponent('MattermostShare', () => ShareExtension);
}
// Uncomment the snippet below if you want to update the modules
// defined in packager/modulePaths.js so they are included in the main bundle.
/*
/!* eslint-disable no-console *!/
/!* eslint-disable no-undef *!/
if (__DEV__) {
const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds.
filter((moduleId) => modules[moduleId].isInitialized).
map((moduleId) => modules[moduleId].verboseName);
const waitingModuleNames = moduleIds.
filter((moduleId) => !modules[moduleId].isInitialized).
map((moduleId) => modules[moduleId].verboseName);
// make sure that the modules you expect to be waiting are actually waiting
console.log(
'loaded:',
loadedModuleNames,
'waiting:',
waitingModuleNames
);
// grab this text blob, and put it in a file named packager/moduleNames.js
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);
}
*/

View file

@ -98,6 +98,7 @@
"underscore": "1.9.0"
},
"scripts": {
"start": "node ./node_modules/react-native/local-cli/cli.js start --config ../../../../packager/config.js",
"check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet .",
"fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix",
"postinstall": "make post-install",

46
packager/config.js Normal file
View file

@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
/* eslint-disable no-console */
const modulePaths = require('./modulePaths');
const resolve = require('path').resolve;
const fs = require('fs');
const platformRegex = /\.(android\.js|ios\.js)/g;
const modulesRegex = /\/(node_modules)/;
// This script will let the react native packager what modules to include
// in the main bundle and what modules to blacklist from the inline requires
// this modules are taken from the modulePaths.js file
const config = {
getTransformOptions: (entryFile, {platform}) => {
console.log('BUILDING MODULES FOR', platform);
const moduleMap = {};
modulePaths.forEach((path) => {
let realPath = path;
if (platform && platformRegex.test(realPath)) {
realPath = path.replace(platformRegex, `.${platform}.js`);
}
let fsFile = realPath;
if (path.match(modulesRegex).length > 1) {
fsFile = path.replace(modulesRegex, '');
}
if (fs.existsSync(fsFile)) {
moduleMap[resolve(realPath)] = true;
}
});
return {
preloadedModules: moduleMap,
transform: {
inlineRequires: {
blacklist: moduleMap,
},
},
};
},
};
module.exports = config;

View file

@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
/* eslint-disable no-console */
const execSync = require('child_process').execSync;
const fs = require('fs');
const moduleNames = require('./moduleNames');
const pjson = require('../package.json');
const localPrefix = `${pjson.name}/`;
// Transforming to Module Paths
// This script will convert the module names found in moduleNames.js
// to the absolute module path that we need.
const modulePaths = moduleNames.map((moduleName) => {
if (moduleName.startsWith(localPrefix)) {
return `./${moduleName.substring(localPrefix.length)}`;
}
if (moduleName.endsWith('.js')) {
return `./node_modules/${moduleName}`;
}
try {
const result = execSync(
`grep "@providesModule ${moduleName}" $(find . -name ${moduleName}\\\\.js) -l`
).toString().trim().split('\n')[0];
if (result != null) {
return result;
}
} catch (e) {
return null;
}
return null;
});
const paths = modulePaths.filter((path) => path != null).map((path) => `'${path}'`).join(',\n');
const fileData = `module.exports = [${paths}];`;
fs.writeFile('./packager/modulePaths.js', fileData, (err) => {
if (err) {
console.log(err);
}
console.log('Done');
});

1
packager/moduleNames.js Normal file

File diff suppressed because one or more lines are too long

1050
packager/modulePaths.js Normal file

File diff suppressed because it is too large Load diff