diff --git a/app/components/client_upgrade_listener/client_upgrade_listener.js b/app/components/client_upgrade_listener/client_upgrade_listener.js index a2e77abe0..d9a274b60 100644 --- a/app/components/client_upgrade_listener/client_upgrade_listener.js +++ b/app/components/client_upgrade_listener/client_upgrade_listener.js @@ -117,11 +117,7 @@ export default class ClientUpgradeListener extends PureComponent { const {downloadLink} = this.props; const {intl} = this.context; - Linking.canOpenURL(downloadLink).then((supported) => { - if (supported) { - return Linking.openURL(downloadLink); - } - + Linking.openURL(downloadLink).catch(() => { Alert.alert( intl.formatMessage({ id: 'mobile.client_upgrade.download_error.title', @@ -132,8 +128,6 @@ export default class ClientUpgradeListener extends PureComponent { defaultMessage: 'An error occurred while trying to open the download link.', }), ); - - return false; }); this.toggleUpgradeMessage(false); diff --git a/app/components/markdown/markdown_image/markdown_image.js b/app/components/markdown/markdown_image/markdown_image.js index e8b5c9a7c..bc7598d65 100644 --- a/app/components/markdown/markdown_image/markdown_image.js +++ b/app/components/markdown/markdown_image/markdown_image.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {intlShape} from 'react-intl'; import { + Alert, Linking, Platform, StyleSheet, @@ -103,11 +104,19 @@ export default class MarkdownImage extends ImageViewPort { handleLinkPress = () => { const url = normalizeProtocol(this.props.linkDestination); + const {intl} = this.context; - Linking.canOpenURL(url).then((supported) => { - if (supported) { - Linking.openURL(url); - } + Linking.openURL(url).catch(() => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); }); }; diff --git a/app/components/markdown/markdown_link/markdown_link.js b/app/components/markdown/markdown_link/markdown_link.js index 1c1f37046..452690614 100644 --- a/app/components/markdown/markdown_link/markdown_link.js +++ b/app/components/markdown/markdown_link/markdown_link.js @@ -64,22 +64,18 @@ export default class MarkdownLink extends PureComponent { onPermalinkPress(match.postId, match.teamName); } } else { - Linking.canOpenURL(url).then((supported) => { - if (supported) { - Linking.openURL(url); - } else { - const {formatMessage} = this.context.intl; - Alert.alert( - formatMessage({ - id: 'mobile.server_link.error.title', - defaultMessage: 'Link Error', - }), - formatMessage({ - id: 'mobile.server_link.error.text', - defaultMessage: 'The link could not be found on this server.', - }), - ); - } + Linking.openURL(url).catch(() => { + const {formatMessage} = this.context.intl; + Alert.alert( + formatMessage({ + id: 'mobile.server_link.error.title', + defaultMessage: 'Link Error', + }), + formatMessage({ + id: 'mobile.server_link.error.text', + defaultMessage: 'The link could not be found on this server.', + }), + ); }); } }); diff --git a/app/components/message_attachments/attachment_author.js b/app/components/message_attachments/attachment_author.js index c9b408a60..3a15361cf 100644 --- a/app/components/message_attachments/attachment_author.js +++ b/app/components/message_attachments/attachment_author.js @@ -2,9 +2,10 @@ // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; -import {Linking, Text, View} from 'react-native'; +import {Alert, Linking, Text, View} from 'react-native'; import FastImage from 'react-native-fast-image'; import PropTypes from 'prop-types'; +import {intlShape} from 'react-intl'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -16,10 +17,27 @@ export default class AttachmentAuthor extends PureComponent { theme: PropTypes.object.isRequired, }; + static contextTypes = { + intl: intlShape.isRequired, + }; + openLink = () => { const {link} = this.props; - if (link && Linking.canOpenURL(link)) { - Linking.openURL(link); + const {intl} = this.context; + + if (link) { + Linking.openURL(link).catch(() => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); + }); } }; diff --git a/app/components/message_attachments/attachment_title.js b/app/components/message_attachments/attachment_title.js index 12a525748..3f544b76b 100644 --- a/app/components/message_attachments/attachment_title.js +++ b/app/components/message_attachments/attachment_title.js @@ -2,8 +2,9 @@ // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; -import {Linking, Text, View} from 'react-native'; +import {Alert, Linking, Text, View} from 'react-native'; import PropTypes from 'prop-types'; +import {intlShape} from 'react-intl'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; import Markdown from 'app/components/markdown'; @@ -15,10 +16,27 @@ export default class AttachmentTitle extends PureComponent { value: PropTypes.string, }; + static contextTypes = { + intl: intlShape.isRequired, + }; + openLink = () => { const {link} = this.props; - if (link && Linking.canOpenURL(link)) { - Linking.openURL(link); + const {intl} = this.context; + + if (link) { + Linking.openURL(link).catch(() => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); + }); } }; diff --git a/app/screens/client_upgrade/client_upgrade.js b/app/screens/client_upgrade/client_upgrade.js index f9ebe2e98..7f01859ef 100644 --- a/app/screens/client_upgrade/client_upgrade.js +++ b/app/screens/client_upgrade/client_upgrade.js @@ -104,11 +104,7 @@ export default class ClientUpgrade extends PureComponent { const {downloadLink} = this.props; const {intl} = this.context; - Linking.canOpenURL(downloadLink).then((supported) => { - if (supported) { - return Linking.openURL(downloadLink); - } - + Linking.openURL(downloadLink).catch(() => { Alert.alert( intl.formatMessage({ id: 'mobile.client_upgrade.download_error.title', @@ -119,8 +115,6 @@ export default class ClientUpgrade extends PureComponent { defaultMessage: 'An error occurred while trying to open the download link.', }), ); - - return false; }); }; diff --git a/app/screens/settings/general/settings.js b/app/screens/settings/general/settings.js index a9cccf1a5..1e053a8ad 100644 --- a/app/screens/settings/general/settings.js +++ b/app/screens/settings/general/settings.js @@ -5,6 +5,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {intlShape, injectIntl} from 'react-intl'; import { + Alert, Linking, Platform, ScrollView, @@ -135,27 +136,42 @@ class Settings extends PureComponent { }); openErrorEmail = preventDoubleTap(() => { - const {config} = this.props; + const {config, intl} = this.props; const recipient = config.SupportEmail; const subject = `Problem with ${config.SiteName} React Native app`; const mailTo = `mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}`; - Linking.canOpenURL(mailTo).then((supported) => { - if (supported) { - Linking.openURL(mailTo); - this.props.actions.clearErrors(); - } + Linking.openURL(mailTo).then(() => { + this.props.actions.clearErrors(); + }).catch(() => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.mailTo.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.mailTo.error.text', + defaultMessage: 'Unable to open an email client.', + }), + ); }); }); openHelp = preventDoubleTap(() => { - const {config} = this.props; + const {config, intl} = this.props; const link = config.HelpLink ? config.HelpLink.toLowerCase() : ''; - Linking.canOpenURL(link).then((supported) => { - if (supported) { - Linking.openURL(link); - } + Linking.openURL(link).catch(() => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); }); }); diff --git a/app/utils/supported_server.ts b/app/utils/supported_server.ts index 36bea7c84..97e29e533 100644 --- a/app/utils/supported_server.ts +++ b/app/utils/supported_server.ts @@ -36,9 +36,18 @@ function unsupportedServerAdminAlert(formatMessage: FormatMessageType) { style: 'cancel', onPress: () => { const url = 'https://mattermost.com/blog/support-for-esr-5-9-has-ended/'; - if (Linking.canOpenURL(url)) { - Linking.openURL(url); - } + Linking.openURL(url).catch(() => { + Alert.alert( + formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); + }); }, }; const buttons: AlertButton[] = [cancel, learnMore]; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 01fdaf879..4982d25db 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -294,12 +294,16 @@ "mobile.ios.photos_permission_denied_description": "Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.", "mobile.ios.photos_permission_denied_title": "{applicationName} would like to access your photos", "mobile.join_channel.error": "We couldn't join the channel {displayName}. Please check your connection and try again.", + "mobile.link.error.text": "Unable to open the link.", + "mobile.link.error.title": "Error", "mobile.loading_channels": "Loading Channels...", "mobile.loading_members": "Loading Members...", "mobile.loading_options": "Loading Options...", "mobile.loading_posts": "Loading messages...", "mobile.login_options.choose_title": "Choose your login method", "mobile.long_post_title": "{channelName} - Post", + "mobile.mailTo.error.text": "Unable to open an email client.", + "mobile.mailTo.error.title": "Error", "mobile.managed.blocked_by": "Blocked by {vendor}", "mobile.managed.exit": "Exit", "mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.", diff --git a/test/setup.js b/test/setup.js index 9a0a039e7..b718063a3 100644 --- a/test/setup.js +++ b/test/setup.js @@ -165,7 +165,6 @@ jest.mock('react-native-cookies', () => ({ addEventListener: jest.fn(), removeEventListener: jest.fn(), openURL: jest.fn(), - canOpenURL: jest.fn(), getInitialURL: jest.fn(), clearAll: jest.fn(), get: () => Promise.resolve(({