diff --git a/android/app/build.gradle b/android/app/build.gradle index 30d96cf21..775a4bc46 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -112,7 +112,7 @@ android { defaultConfig { applicationId "com.mattermost.rnbeta" minSdkVersion 21 - targetSdkVersion 23 + targetSdkVersion 26 versionCode 133 versionName "1.11.0" ndk { @@ -187,8 +187,8 @@ configurations.all { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation "com.android.support:appcompat-v7:25.0.1" - implementation 'com.android.support:percent:25.3.1' + implementation "com.android.support:appcompat-v7:26.0.1" + implementation 'com.android.support:percent:26.0.1' implementation "com.facebook.react:react-native:+" // From node_modules implementation project(':react-native-document-picker') implementation project(':react-native-keychain') @@ -211,7 +211,7 @@ dependencies { implementation project(':react-native-youtube') implementation project(':react-native-sentry') implementation project(':react-native-exception-handler') - implementation project(':react-native-fetch-blob') + implementation project(':rn-fetch-blob') // For animated GIF support implementation 'com.facebook.fresco:animated-base-support:1.3.0' diff --git a/android/settings.gradle b/android/settings.gradle index 4af58e508..e257dd140 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -13,8 +13,8 @@ include ':react-native-sentry' project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android') include ':react-native-exception-handler' project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-exception-handler/android') -include ':react-native-fetch-blob' -project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android') +include ':rn-fetch-blob' +project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/rn-fetch-blob/android') include ':jail-monkey' project(':jail-monkey').projectDir = new File(rootProject.projectDir, '../node_modules/jail-monkey/android') include ':react-native-local-auth' diff --git a/app/actions/views/login.test.js b/app/actions/views/login.test.js index 4e0dbe304..ba1ca9c9a 100644 --- a/app/actions/views/login.test.js +++ b/app/actions/views/login.test.js @@ -11,7 +11,7 @@ import { handlePasswordChanged, } from 'app/actions/views/login'; -jest.mock('react-native-fetch-blob/fs', () => ({ +jest.mock('rn-fetch-blob/fs', () => ({ dirs: { DocumentDir: () => jest.fn(), CacheDir: () => jest.fn(), diff --git a/app/actions/views/select_server.test.js b/app/actions/views/select_server.test.js index e080e83eb..d1718b48a 100644 --- a/app/actions/views/select_server.test.js +++ b/app/actions/views/select_server.test.js @@ -11,7 +11,7 @@ import {ViewTypes} from 'app/constants'; import {handleServerUrlChanged} from 'app/actions/views/select_server'; -jest.mock('react-native-fetch-blob/fs', () => ({ +jest.mock('rn-fetch-blob/fs', () => ({ dirs: { DocumentDir: () => jest.fn(), CacheDir: () => jest.fn(), diff --git a/app/actions/views/thread.test.js b/app/actions/views/thread.test.js index 870e481cd..be0067c96 100644 --- a/app/actions/views/thread.test.js +++ b/app/actions/views/thread.test.js @@ -11,7 +11,7 @@ import { handleCommentDraftSelectionChanged, } from 'app/actions/views/thread'; -jest.mock('react-native-fetch-blob/fs', () => ({ +jest.mock('rn-fetch-blob/fs', () => ({ dirs: { DocumentDir: () => jest.fn(), CacheDir: () => jest.fn(), diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js index 689723bd5..c2112dbe2 100644 --- a/app/components/emoji/emoji.js +++ b/app/components/emoji/emoji.js @@ -10,9 +10,9 @@ import { StyleSheet, Text, } from 'react-native'; -import FastImage from 'react-native-fast-image'; import CustomPropTypes from 'app/constants/custom_prop_types'; +import ImageCacheManager from 'app/utils/image_cache_manager'; const scaleEmojiBasedOnDevice = (size) => { if (Platform.OS === 'ios') { @@ -46,7 +46,6 @@ export default class Emoji extends React.PureComponent { literal: PropTypes.string, size: PropTypes.number, textStyle: CustomPropTypes.Style, - token: PropTypes.string.isRequired, }; static defaultProps = { @@ -60,6 +59,7 @@ export default class Emoji extends React.PureComponent { super(props); this.state = { + imageUrl: null, originalWidth: 0, originalHeight: 0, }; @@ -67,22 +67,23 @@ export default class Emoji extends React.PureComponent { componentWillMount() { this.mounted = true; - if (!this.props.displayTextOnly && this.props.imageUrl && this.props.isCustomEmoji) { - this.updateImageHeight(this.props.imageUrl); + if (!this.props.displayTextOnly && this.props.imageUrl) { + ImageCacheManager.cache(this.props.imageUrl, this.props.imageUrl, this.updateImageHeight); } } componentWillReceiveProps(nextProps) { if (nextProps.emojiName !== this.props.emojiName) { this.setState({ + imageUrl: null, originalWidth: 0, originalHeight: 0, }); } - if (!nextProps.displayTextOnly && nextProps.imageUrl && nextProps.isCustomEmoji && + if (!nextProps.displayTextOnly && nextProps.imageUrl && nextProps.imageUrl !== this.props.imageUrl) { - this.updateImageHeight(nextProps.imageUrl); + ImageCacheManager.cache(nextProps.imageUrl, nextProps.imageUrl, this.updateImageHeight); } } @@ -91,24 +92,30 @@ export default class Emoji extends React.PureComponent { } updateImageHeight = (imageUrl) => { - Image.getSize(imageUrl, (originalWidth, originalHeight) => { + let prefix = ''; + if (Platform.OS === 'android') { + prefix = 'file://'; + } + + const uri = `${prefix}${imageUrl}`; + Image.getSize(uri, (originalWidth, originalHeight) => { if (this.mounted) { this.setState({ + imageUrl: uri, originalWidth, originalHeight, }); } }); - } + }; render() { const { literal, textStyle, - token, - imageUrl, displayTextOnly, } = this.props; + const {imageUrl} = this.state; let size = this.props.size; let fontSize = size; @@ -122,13 +129,6 @@ export default class Emoji extends React.PureComponent { return {literal}; } - const source = { - uri: imageUrl, - headers: { - Authorization: `Bearer ${token}`, - }, - }; - let width = size; let height = size; let {originalHeight, originalWidth} = this.state; @@ -157,16 +157,9 @@ export default class Emoji extends React.PureComponent { // force a new image to be rendered when the size changes const key = Platform.OS === 'android' ? (height + '-' + width) : null; - let ImageComponent; - if (Platform.OS === 'android') { - ImageComponent = Image; - } else { - ImageComponent = FastImage; - } - if (!imageUrl) { return ( - @@ -174,10 +167,10 @@ export default class Emoji extends React.PureComponent { } return ( - ); diff --git a/app/components/emoji/index.js b/app/components/emoji/index.js index 1410f591a..5ed36dfc4 100644 --- a/app/components/emoji/index.js +++ b/app/components/emoji/index.js @@ -38,7 +38,6 @@ function mapStateToProps(state, ownProps) { imageUrl, isCustomEmoji, displayTextOnly, - token: state.entities.general.credentials.token, }; } diff --git a/app/components/file_attachment_list/file_attachment_document.js b/app/components/file_attachment_list/file_attachment_document.js index ceb412d61..699a640f4 100644 --- a/app/components/file_attachment_list/file_attachment_document.js +++ b/app/components/file_attachment_list/file_attachment_document.js @@ -14,7 +14,7 @@ import { View, } from 'react-native'; import OpenFile from 'react-native-doc-viewer'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {CircularProgress} from 'react-native-circular-progress'; import {intlShape} from 'react-intl'; import tinyColor from 'tinycolor2'; diff --git a/app/components/file_upload_preview/file_upload_item/file_upload_item.js b/app/components/file_upload_preview/file_upload_item/file_upload_item.js index 10d6dbeff..65b315e34 100644 --- a/app/components/file_upload_preview/file_upload_item/file_upload_item.js +++ b/app/components/file_upload_preview/file_upload_item/file_upload_item.js @@ -4,7 +4,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Platform, StyleSheet, Text, View} from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {AnimatedCircularProgress} from 'react-native-circular-progress'; import {Client4} from 'mattermost-redux/client'; @@ -13,7 +13,9 @@ import FileAttachmentImage from 'app/components/file_attachment_list/file_attach import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon'; import FileUploadRetry from 'app/components/file_upload_preview/file_upload_retry'; import FileUploadRemove from 'app/components/file_upload_preview/file_upload_remove'; +import mattermostBucket from 'app/mattermost_bucket'; import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file'; +import LocalConfig from 'assets/config'; export default class FileUploadItem extends PureComponent { static propTypes = { @@ -110,7 +112,7 @@ export default class FileUploadItem extends PureComponent { return false; }; - uploadFile = () => { + uploadFile = async () => { const {channelId, file} = this.props; const fileData = buildFileUploadData(file); @@ -134,7 +136,12 @@ export default class FileUploadItem extends PureComponent { Client4.trackEvent('api', 'api_files_upload'); - this.uploadPromise = RNFetchBlob.fetch('POST', Client4.getFilesRoute(), headers, data); + const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const options = { + timeout: 10000, + certificate, + }; + this.uploadPromise = RNFetchBlob.config(options).fetch('POST', Client4.getFilesRoute(), headers, data); this.uploadPromise.uploadProgress(this.handleUploadProgress); this.uploadPromise.then(this.handleUploadCompleted).catch(this.handleUploadError); }; diff --git a/app/constants/device.js b/app/constants/device.js index 02e03ba00..97223e3ce 100644 --- a/app/constants/device.js +++ b/app/constants/device.js @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import keyMirror from 'mattermost-redux/utils/key_mirror'; -import RNFetchBlobFS from 'react-native-fetch-blob/fs'; +import RNFetchBlobFS from 'rn-fetch-blob/fs'; const deviceTypes = keyMirror({ CONNECTION_CHANGED: null, diff --git a/app/fetch_preconfig.js b/app/fetch_preconfig.js index 281726e1c..bf9064da2 100644 --- a/app/fetch_preconfig.js +++ b/app/fetch_preconfig.js @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Platform} from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {Client4} from 'mattermost-redux/client'; import {General} from 'mattermost-redux/constants'; @@ -100,15 +100,25 @@ Client4.doFetchWithResponse = async (url, options) => { }; }; -if (Platform.OS === 'ios') { - mattermostBucket.getPreference('cert', LocalConfig.AppGroupId).then((certificate) => { - window.fetch = new RNFetchBlob.polyfill.Fetch({ +const initFetchConfig = async () => { + let fetchConfig = {}; + if (Platform.OS === 'ios') { + const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + fetchConfig = { auto: true, certificate, - }).build(); - }); -} else { - window.fetch = new RNFetchBlob.polyfill.Fetch({ - auto: true, - }).build(); -} + }; + window.fetch = new RNFetchBlob.polyfill.Fetch(fetchConfig).build(); + } else { + fetchConfig = { + auto: true, + }; + window.fetch = new RNFetchBlob.polyfill.Fetch(fetchConfig).build(); + } + + return true; +}; + +initFetchConfig(); + +export default initFetchConfig; diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index b0cd2f412..11d8d435e 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -5,7 +5,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; import {View} from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {Client4} from 'mattermost-redux/client'; @@ -20,6 +20,8 @@ import ErrorText from 'app/components/error_text'; import StatusBar from 'app/components/status_bar/index'; import ProfilePicture from 'app/components/profile_picture'; import AttachmentButton from 'app/components/attachment_button'; +import mattermostBucket from 'app/mattermost_bucket'; +import LocalConfig from 'assets/config'; import EditProfileItem from './edit_profile_item'; @@ -191,7 +193,7 @@ export default class EditProfile extends PureComponent { this.emitCanUpdateAccount(true); }; - uploadProfileImage = () => { + uploadProfileImage = async () => { const {profileImage} = this.state; const {currentUser} = this.props; const fileData = buildFileUploadData(profileImage); @@ -208,7 +210,13 @@ export default class EditProfile extends PureComponent { type: fileData.type, }; - return RNFetchBlob.fetch('POST', `${Client4.getUserRoute(currentUser.id)}/image`, headers, [fileInfo]); + const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const options = { + timeout: 10000, + certificate, + }; + + return RNFetchBlob.config(options).fetch('POST', `${Client4.getUserRoute(currentUser.id)}/image`, headers, [fileInfo]); }; updateField = (field) => { diff --git a/app/screens/image_preview/downloader.android.js b/app/screens/image_preview/downloader.android.js index 9a0ff9e24..3dfea675d 100644 --- a/app/screens/image_preview/downloader.android.js +++ b/app/screens/image_preview/downloader.android.js @@ -10,7 +10,7 @@ import { TouchableOpacity, View, } from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {intlShape} from 'react-intl'; import {Client4} from 'mattermost-redux/client'; diff --git a/app/screens/image_preview/downloader.ios.js b/app/screens/image_preview/downloader.ios.js index 84612a267..0121cc75f 100644 --- a/app/screens/image_preview/downloader.ios.js +++ b/app/screens/image_preview/downloader.ios.js @@ -4,7 +4,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Alert, Animated, CameraRoll, InteractionManager, StyleSheet, Text, TouchableOpacity, View} from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {CircularProgress} from 'react-native-circular-progress'; import Icon from 'react-native-vector-icons/Ionicons'; import {intlShape} from 'react-intl'; diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index c7b50bdec..79a5bedc9 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -16,7 +16,7 @@ import { TouchableOpacity, View, } from 'react-native'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import Icon from 'react-native-vector-icons/Ionicons'; import LinearGradient from 'react-native-linear-gradient'; import {intlShape} from 'react-intl'; diff --git a/app/screens/image_preview/video_preview.js b/app/screens/image_preview/video_preview.js index ba545585a..20bc6cd40 100644 --- a/app/screens/image_preview/video_preview.js +++ b/app/screens/image_preview/video_preview.js @@ -11,7 +11,7 @@ import { View, } from 'react-native'; import Video from 'react-native-video'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {intlShape} from 'react-intl'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index 760fb607d..a9418377e 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; import { ActivityIndicator, + DeviceEventEmitter, Image, Keyboard, KeyboardAvoidingView, @@ -17,7 +18,7 @@ import { View, } from 'react-native'; import Button from 'react-native-button'; -import RNFetchBlob from 'react-native-fetch-blob'; +import RNFetchBlob from 'rn-fetch-blob'; import {Client4} from 'mattermost-redux/client'; @@ -25,6 +26,7 @@ import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; import QuickTextInput from 'app/components/quick_text_input'; import {UpgradeTypes} from 'app/constants/view'; +import fetchConfig from 'app/fetch_preconfig'; import mattermostBucket from 'app/mattermost_bucket'; import PushNotifications from 'app/push_notifications'; import {GlobalStyles} from 'app/styles'; @@ -82,13 +84,14 @@ export default class SelectServer extends PureComponent { if (!allowOtherServers && serverUrl) { // If the app is managed or AutoSelectServerUrl is true in the Config, the server url is set and the user can't change it // we automatically trigger the ping to move to the next screen - this.onClick(); + this.handleConnect(); } if (Platform.OS === 'android') { Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard); } + this.certificateListener = DeviceEventEmitter.addListener('RNFetchBlobCertificate', this.selectCertificate); this.props.navigator.setOnNavigatorEvent(this.handleNavigatorEvent); } @@ -110,11 +113,24 @@ export default class SelectServer extends PureComponent { } componentWillUnmount() { + this.certificateListener.remove(); if (Platform.OS === 'android') { Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard); } } + blur = () => { + if (this.textInput) { + this.textInput.blur(); + } + }; + + getUrl = () => { + const urlParse = require('url-parse'); + const preUrl = urlParse(this.state.url, true); + return stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host + preUrl.pathname); + }; + goToNextScreen = (screen, title) => { const {navigator, theme} = this.props; navigator.push({ @@ -132,11 +148,84 @@ export default class SelectServer extends PureComponent { }); }; + handleAndroidKeyboard = () => { + this.blur(); + }; + + handleConnect = preventDoubleTap(async () => { + const url = this.getUrl(); + + Keyboard.dismiss(); + + if (this.state.connecting || this.state.connected) { + this.cancelPing(); + + return; + } + + if (!isValidUrl(url)) { + this.setState({ + error: { + intl: { + id: 'mobile.server_url.invalid_format', + defaultMessage: 'URL must start with http:// or https://', + }, + }, + }); + + return; + } + + mattermostBucket.removePreference('cert', LocalConfig.AppGroupId); + await fetchConfig(); + this.pingServer(url); + }); + + handleLoginOptions = (props = this.props) => { + const {intl} = this.context; + const {config, license} = props; + const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true'; + const gitlabEnabled = config.EnableSignUpWithGitLab === 'true'; + + let options = 0; + if (samlEnabled || gitlabEnabled) { + options += 1; + } + + let screen; + let title; + if (options) { + screen = 'LoginOptions'; + title = intl.formatMessage({id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'}); + } else { + screen = 'Login'; + title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'}); + } + + this.props.actions.resetPing(); + + if (Platform.OS === 'ios') { + if (config.ExperimentalClientSideCertEnable === 'true' && config.ExperimentalClientSideCertCheck === 'primary') { + // log in automatically and send directly to the channel screen + this.loginWithCertificate(); + return; + } + + setTimeout(() => { + this.goToNextScreen(screen, title); + }, 350); + } else { + this.goToNextScreen(screen, title); + } + }; + handleNavigatorEvent = (event) => { - if (event.id === 'didDisappear') { + switch (event.id) { + case 'didDisappear': this.setState({ connected: false, }); + break; } }; @@ -164,52 +253,14 @@ export default class SelectServer extends PureComponent { }); }; - handleLoginOptions = (props = this.props) => { - const {intl} = this.context; - const {config, license} = props; - const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true'; - const gitlabEnabled = config.EnableSignUpWithGitLab === 'true'; - - let options = 0; - if (samlEnabled || gitlabEnabled) { - options += 1; - } - - let screen; - let title; - if (options) { - screen = 'LoginOptions'; - title = intl.formatMessage({id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'}); - } else { - screen = 'Login'; - title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'}); - } - - this.props.actions.resetPing(); - - if (Platform.OS === 'ios' && LocalConfig.ExperimentalClientSideCertEnable) { - if (config.ExperimentalClientSideCertEnable === 'true' && config.ExperimentalClientSideCertCheck === 'primary') { - // log in automatically and send directly to the channel screen - this.loginWithCertificate(); - return; - } - - setTimeout(() => { - this.goToNextScreen(screen, title); - }, 350); - } else { - this.goToNextScreen(screen, title); - } - }; - - handleAndroidKeyboard = () => { - this.blur(); - }; - handleTextChanged = (url) => { this.setState({url}); }; + inputRef = (ref) => { + this.textInput = ref; + }; + loginWithCertificate = async () => { const {intl, navigator} = this.props; @@ -248,48 +299,6 @@ export default 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 + preUrl.pathname); - - Keyboard.dismiss(); - - if (this.state.connecting || this.state.connected) { - this.cancelPing(); - - return; - } - - if (!isValidUrl(url)) { - this.setState({ - error: { - intl: { - id: 'mobile.server_url.invalid_format', - defaultMessage: 'URL must start with http:// or https://', - }, - }, - }); - - return; - } - - if (LocalConfig.ExperimentalClientSideCertEnable && Platform.OS === 'ios') { - RNFetchBlob.cba.selectCertificate((certificate) => { - if (certificate) { - mattermostBucket.setPreference('cert', certificate, LocalConfig.AppGroupId); - window.fetch = new RNFetchBlob.polyfill.Fetch({ - auto: true, - certificate, - }).build(); - this.pingServer(url); - } - }); - } else { - this.pingServer(url); - } - }); - pingServer = (url) => { const { getPing, @@ -345,14 +354,16 @@ export default class SelectServer extends PureComponent { }); }; - inputRef = (ref) => { - this.textInput = ref; - }; - - blur = () => { - if (this.textInput) { - this.textInput.blur(); - } + selectCertificate = () => { + const url = this.getUrl(); + RNFetchBlob.cba.selectCertificate((certificate) => { + if (certificate) { + mattermostBucket.setPreference('cert', certificate, LocalConfig.AppGroupId); + fetchConfig().then(() => { + this.pingServer(url); + }); + } + }); }; render() { @@ -426,7 +437,7 @@ export default class SelectServer extends PureComponent { value={url} editable={!inputDisabled} onChangeText={this.handleTextChanged} - onSubmitEditing={this.onClick} + onSubmitEditing={this.handleConnect} style={inputStyle} autoCapitalize='none' autoCorrect={false} @@ -440,7 +451,7 @@ export default class SelectServer extends PureComponent { disableFullscreenUI={true} />