From 998ee2998657e82395fe4462809c376ffbd03ed8 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Fri, 12 Jan 2024 10:19:56 -0800 Subject: [PATCH] (optional) In app browser (#2490) * add expo web browser + modal * add in app browser option to settings * don't show toggle on web * Tweak browser-choice UIs --------- Co-authored-by: Samuel Newman --- package.json | 2 +- src/state/modals/index.tsx | 6 ++ src/state/persisted/schema.ts | 2 + src/state/preferences/in-app-browser.tsx | 79 +++++++++++++++ src/state/preferences/index.tsx | 5 +- src/view/com/modals/InAppBrowserConsent.tsx | 102 ++++++++++++++++++++ src/view/com/modals/LinkWarning.tsx | 6 +- src/view/com/modals/Modal.tsx | 4 + src/view/com/util/Link.tsx | 12 ++- src/view/screens/Settings.tsx | 18 ++++ yarn.lock | 85 +++++++++++++--- 11 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 src/state/preferences/in-app-browser.tsx create mode 100644 src/view/com/modals/InAppBrowserConsent.tsx diff --git a/package.json b/package.json index 7e63ad9a..6572d868 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "expo-system-ui": "~2.9.2", "expo-task-manager": "~11.7.0", "expo-updates": "~0.24.5", + "expo-web-browser": "^12.5.0", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", "js-sha256": "^0.9.0", @@ -148,7 +149,6 @@ "react-native-get-random-values": "~1.8.0", "react-native-haptic-feedback": "^1.14.0", "react-native-image-crop-picker": "^0.38.1", - "react-native-inappbrowser-reborn": "^3.6.3", "react-native-ios-context-menu": "^1.15.3", "react-native-linear-gradient": "^2.6.2", "react-native-pager-view": "6.2.2", diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 8c32c472..45856e10 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -187,6 +187,11 @@ export interface EmbedConsentModal { onAccept: () => void } +export interface InAppBrowserConsentModal { + name: 'in-app-browser-consent' + href: string +} + export type Modal = // Account | AddAppPasswordModal @@ -231,6 +236,7 @@ export type Modal = | ConfirmModal | LinkWarningModal | EmbedConsentModal + | InAppBrowserConsentModal const ModalContext = React.createContext<{ isModalActive: boolean diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 6a26ceda..a6f2ea06 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -53,6 +53,7 @@ export const schema = z.object({ step: z.string(), }), hiddenPosts: z.array(z.string()).optional(), // should move to server + useInAppBrowser: z.boolean().optional(), }) export type Schema = z.infer @@ -84,4 +85,5 @@ export const defaults: Schema = { step: 'Home', }, hiddenPosts: [], + useInAppBrowser: undefined, } diff --git a/src/state/preferences/in-app-browser.tsx b/src/state/preferences/in-app-browser.tsx new file mode 100644 index 00000000..628663af --- /dev/null +++ b/src/state/preferences/in-app-browser.tsx @@ -0,0 +1,79 @@ +import React from 'react' +import * as persisted from '#/state/persisted' +import {Linking} from 'react-native' +import * as WebBrowser from 'expo-web-browser' +import {isNative} from '#/platform/detection' +import {useModalControls} from '../modals' + +type StateContext = persisted.Schema['useInAppBrowser'] +type SetContext = (v: persisted.Schema['useInAppBrowser']) => void + +const stateContext = React.createContext( + persisted.defaults.useInAppBrowser, +) +const setContext = React.createContext( + (_: persisted.Schema['useInAppBrowser']) => {}, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('useInAppBrowser')) + + const setStateWrapped = React.useCallback( + (inAppBrowser: persisted.Schema['useInAppBrowser']) => { + setState(inAppBrowser) + persisted.write('useInAppBrowser', inAppBrowser) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('useInAppBrowser')) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useInAppBrowser() { + return React.useContext(stateContext) +} + +export function useSetInAppBrowser() { + return React.useContext(setContext) +} + +export function useOpenLink() { + const {openModal} = useModalControls() + const enabled = useInAppBrowser() + + const openLink = React.useCallback( + (url: string, override?: boolean) => { + if (isNative && !url.startsWith('mailto:')) { + if (override === undefined && enabled === undefined) { + openModal({ + name: 'in-app-browser-consent', + href: url, + }) + return + } else if (override ?? enabled) { + WebBrowser.openBrowserAsync(url, { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FULL_SCREEN, + }) + return + } + } + Linking.openURL(url) + }, + [enabled, openModal], + ) + + return openLink +} diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index cc2d9244..a442b763 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -3,6 +3,7 @@ import {Provider as LanguagesProvider} from './languages' import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required' import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts' import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' +import {Provider as InAppBrowserProvider} from './in-app-browser' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' export { @@ -20,7 +21,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/view/com/modals/InAppBrowserConsent.tsx b/src/view/com/modals/InAppBrowserConsent.tsx new file mode 100644 index 00000000..86bb46ca --- /dev/null +++ b/src/view/com/modals/InAppBrowserConsent.tsx @@ -0,0 +1,102 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' + +import {s} from 'lib/styles' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import {ScrollView} from './util' +import {usePalette} from 'lib/hooks/usePalette' + +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useModalControls} from '#/state/modals' +import { + useOpenLink, + useSetInAppBrowser, +} from '#/state/preferences/in-app-browser' + +export const snapPoints = [350] + +export function Component({href}: {href: string}) { + const pal = usePalette('default') + const {closeModal} = useModalControls() + const {_} = useLingui() + const setInAppBrowser = useSetInAppBrowser() + const openLink = useOpenLink() + + const onUseIAB = React.useCallback(() => { + setInAppBrowser(true) + closeModal() + openLink(href, true) + }, [closeModal, setInAppBrowser, href, openLink]) + + const onUseLinking = React.useCallback(() => { + setInAppBrowser(false) + closeModal() + openLink(href, false) + }, [closeModal, setInAppBrowser, href, openLink]) + + return ( + + + How should we open this link? + + + + Your choice will be saved, but can be changed later in settings. + + + +