Skip to content

Commit 8064cb1

Browse files
committed
Settings: Add "Open links with in-app browser" setting.
This will let people choose whether they want to use the in-app browser, or their external browser app. This defaults to enabled on Android, and disabled on iOS. The setting for this could probably be improved - See the discussion [1] on CZO for what this looks like in other apps. I've opted to use a simple switch, since that's what we're already using in this UI, and I don't want to redesign it right now. I expect we will want to redesign it in the future, but given that we have so few settings right now, I'm not too worried about the settings screen being confusing/overwhelming. Another thing that I'd like to improve is the icon - I'm using the Feather icon for Google Chrome, since the only other browser-related icons I could find were the globe icon we're using for the language setting in this same screen, and the compass icon, which is less recognizable than the Chrome one. I'm unhappy with using a branded icon here, but Feather doesn't give us very many options, sadly. [1]: https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1168259
1 parent d18840b commit 8064cb1

File tree

8 files changed

+65
-5
lines changed

8 files changed

+65
-5
lines changed

src/boot/store.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ const migrations: {| [string]: (GlobalState) => GlobalState |} = {
283283
accounts: state.accounts.filter(a => a.email !== ''),
284284
}),
285285

286+
'28': state => ({
287+
...state,
288+
settings: {
289+
...state.settings,
290+
browser: 'default',
291+
},
292+
}),
293+
286294
// TIP: When adding a migration, consider just using `dropCache`.
287295
};
288296

src/common/Icons.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const makeIcon = <Glyphs: string>(
5151
return <FixedIcon name={name} {...props} />;
5252
};
5353

54+
export const IconBrowser = makeIcon(Feather, 'chrome');
5455
export const IconInbox = makeIcon(Feather, 'inbox');
5556
export const IconMention = makeIcon(Feather, 'at-sign');
5657
export const IconSearch = makeIcon(Feather, 'search');

src/message/messagesActions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as NavigationService from '../nav/NavigationService';
33
import type { Narrow, Dispatch, GetState } from '../types';
44
import { getAuth } from '../selectors';
55
import { getMessageIdFromLink, getNarrowFromLink } from '../utils/internalLinks';
6-
import { openLinkExternal } from '../utils/openLink';
6+
import { openLinkWithUserPreference } from '../utils/openLink';
77
import { navigateToChat } from '../nav/navActions';
88
import { FIRST_UNREAD_ANCHOR } from '../anchor';
99
import { getStreamsById } from '../subscriptions/subscriptionSelectors';
@@ -35,10 +35,10 @@ export const messageLinkPress = (href: string) => async (
3535
const anchor = getMessageIdFromLink(href, auth.realm);
3636
dispatch(doNarrow(narrow, anchor));
3737
} else if (!isUrlOnRealm(href, auth.realm)) {
38-
openLinkExternal(href);
38+
openLinkWithUserPreference(href, getState);
3939
} else {
4040
const url =
4141
(await api.tryGetFileTemporaryUrl(href, auth)) ?? new URL(href, auth.realm).toString();
42-
openLinkExternal(url);
42+
openLinkWithUserPreference(url, getState);
4343
}
4444
};

src/reduxTypes.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,29 @@ export type RealmState = {|
263263
// https://github.com/zulip/zulip-mobile/issues/4009#issuecomment-619280681.
264264
export type ThemeName = 'default' | 'night';
265265

266+
/**
267+
* The values for this mean:
268+
*
269+
/* * embedded: The in-app browser
270+
/* * external: The user's default browser app
271+
/* * default: 'external' on iOS, 'embedded' on Android
272+
/*
273+
/* Use the `shouldUseInAppBrowser` function from src/utils/openLink.js in order to
274+
/* parse this.
275+
/*
276+
/* See https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser
277+
/* for the reasoning behind these options.
278+
*/
279+
export type BrowserPreference = 'embedded' | 'external' | 'default';
280+
266281
export type SettingsState = {|
267282
locale: string,
268283
theme: ThemeName,
269284
offlineNotification: boolean,
270285
onlineNotification: boolean,
271286
experimentalFeaturesEnabled: boolean,
272287
streamNotification: boolean,
288+
browser: BrowserPreference,
273289
|};
274290

275291
export type StreamsState = Stream[];

src/settings/SettingsScreen.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { ScrollView } from 'react-native';
66
import type { RouteProp } from '../react-navigation';
77
import type { MainTabsNavigationProp } from '../main/MainTabsScreen';
88
import * as NavigationService from '../nav/NavigationService';
9-
import type { Dispatch } from '../types';
9+
import type { Dispatch, BrowserPreference } from '../types';
1010
import { createStyleSheet } from '../styles';
1111
import { connect } from '../react-redux';
1212
import { getSettings } from '../selectors';
1313
import { OptionButton, OptionRow } from '../common';
1414
import {
15+
IconBrowser,
1516
IconDiagnostics,
1617
IconNotifications,
1718
IconNight,
@@ -25,6 +26,7 @@ import {
2526
navigateToDiagnostics,
2627
navigateToLegal,
2728
} from '../actions';
29+
import { shouldUseInAppBrowser } from '../utils/openLink';
2830

2931
const styles = createStyleSheet({
3032
optionWrapper: {
@@ -37,6 +39,7 @@ type Props = $ReadOnly<{|
3739
route: RouteProp<'settings', void>,
3840

3941
theme: string,
42+
browser: BrowserPreference,
4043
dispatch: Dispatch,
4144
|}>;
4245

@@ -47,7 +50,7 @@ class SettingsScreen extends PureComponent<Props> {
4750
};
4851

4952
render() {
50-
const { theme } = this.props;
53+
const { dispatch, theme, browser } = this.props;
5154

5255
return (
5356
<ScrollView style={styles.optionWrapper}>
@@ -57,6 +60,14 @@ class SettingsScreen extends PureComponent<Props> {
5760
value={theme === 'night'}
5861
onValueChange={this.handleThemeChange}
5962
/>
63+
<OptionRow
64+
Icon={IconBrowser}
65+
label="Open links with in-app browser"
66+
value={shouldUseInAppBrowser(browser)}
67+
onValueChange={value => {
68+
dispatch(settingsChange({ browser: value ? 'embedded' : 'external' }));
69+
}}
70+
/>
6071
<OptionButton
6172
Icon={IconNotifications}
6273
label="Notifications"
@@ -92,4 +103,5 @@ class SettingsScreen extends PureComponent<Props> {
92103

93104
export default connect(state => ({
94105
theme: getSettings(state).theme,
106+
browser: getSettings(state).browser,
95107
}))(SettingsScreen);

src/settings/settingsReducer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const initialState: SettingsState = {
1414
onlineNotification: true,
1515
experimentalFeaturesEnabled: false,
1616
streamNotification: false,
17+
browser: 'default',
1718
};
1819

1920
export default (state: SettingsState = initialState, action: Action): SettingsState => {

src/utils/openLink.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import { NativeModules, Platform, Linking } from 'react-native';
33
import SafariView from 'react-native-safari-view';
44

5+
import type { BrowserPreference, GetState } from '../types';
6+
import { getSettings } from '../selectors';
7+
58
export function openLinkEmbedded(url: string): void {
69
if (Platform.OS === 'ios') {
710
SafariView.show({ url: encodeURI(url) });
@@ -13,3 +16,21 @@ export function openLinkEmbedded(url: string): void {
1316
export function openLinkExternal(url: string): void {
1417
Linking.openURL(url);
1518
}
19+
20+
export function shouldUseInAppBrowser(browser: BrowserPreference): boolean {
21+
if (browser === 'default') {
22+
return Platform.OS === 'android';
23+
} else {
24+
return browser === 'embedded';
25+
}
26+
}
27+
28+
export function openLinkWithUserPreference(url: string, getState: GetState): void {
29+
const state = getState();
30+
const browser = getSettings(state).browser;
31+
if (shouldUseInAppBrowser(browser)) {
32+
openLinkEmbedded(url);
33+
} else {
34+
openLinkExternal(url);
35+
}
36+
}

static/translations/messages_en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"No Internet connection": "No Internet connection",
6262
"Settings": "Settings",
6363
"Night mode": "Night mode",
64+
"Open links with in-app browser": "Open links with in-app browser",
6465
"Language": "Language",
6566
"Arabic": "Arabic",
6667
"Bokmål": "Bokmål",

0 commit comments

Comments
 (0)