Merge branch 'main' into web-layout

This commit is contained in:
Dan Abramov
2024-01-12 21:55:03 +00:00
16 changed files with 460 additions and 49 deletions
+2 -2
View File
@@ -12,7 +12,7 @@
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"build-web": "expo export:web && node ./scripts/post-web-build.js && cp --verbose ./web-build/static/js/*.* ./bskyweb/static/js/",
"build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/",
"build-all": "yarn intl:build && eas build --platform all",
"start": "expo start --dev-client",
"start:prod": "expo start --dev-client --no-dev --minify",
@@ -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",
+8
View File
@@ -22,6 +22,14 @@ export function code3ToCode2(lang: string): string {
return lang
}
export function code3ToCode2Strict(lang: string): string | undefined {
if (lang.length === 3) {
return LANGUAGES_MAP_CODE3[lang]?.code2
}
return undefined
}
export function codeToLanguageName(lang: string): string {
const lang2 = code3ToCode2(lang)
return LANGUAGES_MAP_CODE2[lang2]?.name || lang
+4 -1
View File
@@ -62,6 +62,9 @@ export async function dynamicActivate(locale: AppLanguage) {
export async function useLocaleLanguage() {
const {appLanguage} = useLanguagePrefs()
useEffect(() => {
dynamicActivate(sanitizeAppLanguageSetting(appLanguage))
const sanitizedLanguage = sanitizeAppLanguageSetting(appLanguage)
document.documentElement.lang = sanitizedLanguage
dynamicActivate(sanitizedLanguage)
}, [appLanguage])
}
+6
View File
@@ -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
+2
View File
@@ -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<typeof schema>
@@ -84,4 +85,5 @@ export const defaults: Schema = {
step: 'Home',
},
hiddenPosts: [],
useInAppBrowser: undefined,
}
+79
View File
@@ -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<StateContext>(
persisted.defaults.useInAppBrowser,
)
const setContext = React.createContext<SetContext>(
(_: 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 (
<stateContext.Provider value={state}>
<setContext.Provider value={setStateWrapped}>
{children}
</setContext.Provider>
</stateContext.Provider>
)
}
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
}
+4 -1
View File
@@ -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<{}>) {
<LanguagesProvider>
<AltTextRequiredProvider>
<ExternalEmbedsProvider>
<HiddenPostsProvider>{children}</HiddenPostsProvider>
<HiddenPostsProvider>
<InAppBrowserProvider>{children}</InAppBrowserProvider>
</HiddenPostsProvider>
</ExternalEmbedsProvider>
</AltTextRequiredProvider>
</LanguagesProvider>
+30 -16
View File
@@ -193,11 +193,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)
const clearCurrentAccount = React.useCallback(() => {
logger.debug(
`session: clear current account`,
{},
logger.DebugContext.session,
)
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
queryClient.clear()
setStateAndPersist(s => ({
@@ -322,8 +318,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)
const logout = React.useCallback<ApiContext['logout']>(async () => {
logger.info(`session: logout`)
clearCurrentAccount()
logger.debug(`session: logout`, {}, logger.DebugContext.session)
setStateAndPersist(s => {
return {
...s,
@@ -551,13 +547,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
return persisted.onUpdate(() => {
const session = persisted.get('session')
logger.debug(`session: onUpdate`, {}, logger.DebugContext.session)
logger.info(`session: persisted onUpdate`, {})
if (session.currentAccount) {
if (session.currentAccount && session.currentAccount.refreshJwt) {
if (session.currentAccount?.did !== state.currentAccount?.did) {
logger.debug(
`session: switching account`,
{
logger.info(`session: persisted onUpdate, switching accounts`, {
from: {
did: state.currentAccount?.did,
handle: state.currentAccount?.handle,
@@ -566,15 +560,23 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
did: session.currentAccount.did,
handle: session.currentAccount.handle,
},
},
logger.DebugContext.session,
)
})
initSession(session.currentAccount)
} else {
logger.info(`session: persisted onUpdate, updating session`, {})
/*
* Use updated session in this tab's agent. Do not call
* upsertAccount, since that will only persist the session that's
* already persisted, and we'll get a loop between tabs.
*/
// @ts-ignore we checked for `refreshJwt` above
__globalAgent.session = session.currentAccount
}
} else if (!session.currentAccount && state.currentAccount) {
logger.debug(
`session: logging out`,
`session: persisted onUpdate, logging out`,
{
did: state.currentAccount?.did,
handle: state.currentAccount?.handle,
@@ -582,10 +584,22 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logger.DebugContext.session,
)
/*
* No need to do a hard logout here. If we reach this, tokens for this
* account have already been cleared either by an `expired` event
* handled by `persistSession` (which nukes this accounts tokens only),
* or by a `logout` call which nukes all accounts tokens)
*/
clearCurrentAccount()
}
setState(s => ({
...s,
accounts: session.accounts,
currentAccount: session.currentAccount,
}))
})
}, [state, clearCurrentAccount, initSession])
}, [state, setState, clearCurrentAccount, initSession])
const stateContext = React.useMemo(
() => ({
+2
View File
@@ -45,6 +45,7 @@ import {Gallery} from './photos/Gallery'
import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
import {LabelsBtn} from './labels/LabelsBtn'
import {SelectLangBtn} from './select-language/SelectLangBtn'
import {SuggestedLanguage} from './select-language/SuggestedLanguage'
import {insertMentionAt} from 'lib/strings/mention-manip'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -454,6 +455,7 @@ export const ComposePost = observer(function ComposePost({
))}
</View>
) : null}
<SuggestedLanguage text={richtext.text} />
<View style={[pal.border, styles.bottomBar]}>
{canSelectImages ? (
<>
@@ -0,0 +1,101 @@
import React, {useEffect, useState} from 'react'
import {StyleSheet, View} from 'react-native'
import lande from 'lande'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Text} from '../../util/text/Text'
import {Button} from '../../util/forms/Button'
import {code3ToCode2Strict, codeToLanguageName} from '#/locale/helpers'
import {
toPostLanguages,
useLanguagePrefs,
useLanguagePrefsApi,
} from '#/state/preferences/languages'
import {usePalette} from '#/lib/hooks/usePalette'
import {s} from '#/lib/styles'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
// fallbacks for safari
const onIdle = globalThis.requestIdleCallback || (cb => setTimeout(cb, 1))
const cancelIdle = globalThis.cancelIdleCallback || clearTimeout
export function SuggestedLanguage({text}: {text: string}) {
const [suggestedLanguage, setSuggestedLanguage] = useState<string>()
const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()
const pal = usePalette('default')
const {_} = useLingui()
useEffect(() => {
const textTrimmed = text.trim()
// Don't run the language model on small posts, the results are likely
// to be inaccurate anyway.
if (textTrimmed.length < 40) {
setSuggestedLanguage(undefined)
return
}
const idle = onIdle(() => {
// Only select languages that have a high confidence and convert to code2
const result = lande(textTrimmed).filter(
([lang, value]) => value >= 0.97 && code3ToCode2Strict(lang),
)
setSuggestedLanguage(
result.length > 0 ? code3ToCode2Strict(result[0][0]) : undefined,
)
})
return () => cancelIdle(idle)
}, [text])
return suggestedLanguage &&
!toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage) ? (
<View style={[pal.border, styles.infoBar]}>
<FontAwesomeIcon
icon="language"
style={pal.text as FontAwesomeIconStyle}
size={24}
/>
<Text style={[pal.text, s.flex1]}>
<Trans>
Are you writing in{' '}
<Text type="sm-bold" style={pal.text}>
{codeToLanguageName(suggestedLanguage)}
</Text>
?
</Trans>
</Text>
<Button
type="default"
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
accessibilityLabel={_(
msg`Change post language to ${codeToLanguageName(suggestedLanguage)}`,
)}
accessibilityHint="">
<Text type="button" style={[pal.link, s.fw600]}>
<Trans>Yes</Trans>
</Text>
</Button>
</View>
) : null
}
const styles = StyleSheet.create({
infoBar: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
borderWidth: 1,
borderRadius: 6,
paddingHorizontal: 16,
paddingVertical: 12,
marginHorizontal: 10,
marginBottom: 10,
},
})
+102
View File
@@ -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 (
<ScrollView
testID="inAppBrowserConsentModal"
style={[s.flex1, pal.view, {paddingHorizontal: 20, paddingTop: 10}]}>
<Text style={[pal.text, styles.title]}>
<Trans>How should we open this link?</Trans>
</Text>
<Text style={pal.text}>
<Trans>
Your choice will be saved, but can be changed later in settings.
</Trans>
</Text>
<View style={[styles.btnContainer]}>
<Button
testID="confirmBtn"
type="inverted"
onPress={onUseIAB}
accessibilityLabel={_(msg`Use in-app browser`)}
accessibilityHint=""
label={_(msg`Use in-app browser`)}
labelContainerStyle={{justifyContent: 'center', padding: 8}}
labelStyle={[s.f18]}
/>
<Button
testID="confirmBtn"
type="inverted"
onPress={onUseLinking}
accessibilityLabel={_(msg`Use my default browser`)}
accessibilityHint=""
label={_(msg`Use my default browser`)}
labelContainerStyle={{justifyContent: 'center', padding: 8}}
labelStyle={[s.f18]}
/>
<Button
testID="cancelBtn"
type="default"
onPress={() => {
closeModal()
}}
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
label="Cancel"
labelContainerStyle={{justifyContent: 'center', padding: 8}}
labelStyle={[s.f18]}
/>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
title: {
textAlign: 'center',
fontWeight: 'bold',
fontSize: 24,
marginBottom: 12,
},
btnContainer: {
marginTop: 20,
flexDirection: 'column',
justifyContent: 'center',
rowGap: 10,
},
})
+4 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {Linking, SafeAreaView, StyleSheet, View} from 'react-native'
import {SafeAreaView, StyleSheet, View} from 'react-native'
import {ScrollView} from './util'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text'
@@ -12,6 +12,7 @@ import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useOpenLink} from '#/state/preferences/in-app-browser'
export const snapPoints = ['50%']
@@ -21,10 +22,11 @@ export function Component({text, href}: {text: string; href: string}) {
const {isMobile} = useWebMediaQueries()
const {_} = useLingui()
const potentiallyMisleading = isPossiblyAUrl(text)
const openLink = useOpenLink()
const onPressVisit = () => {
closeModal()
Linking.openURL(href)
openLink(href)
}
return (
+4
View File
@@ -39,6 +39,7 @@ import * as ChangeEmailModal from './ChangeEmail'
import * as SwitchAccountModal from './SwitchAccount'
import * as LinkWarningModal from './LinkWarning'
import * as EmbedConsentModal from './EmbedConsent'
import * as InAppBrowserConsentModal from './InAppBrowserConsent'
const DEFAULT_SNAPPOINTS = ['90%']
const HANDLE_HEIGHT = 24
@@ -180,6 +181,9 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'embed-consent') {
snapPoints = EmbedConsentModal.snapPoints
element = <EmbedConsentModal.Component {...activeModal} />
} else if (activeModal?.name === 'in-app-browser-consent') {
snapPoints = InAppBrowserConsentModal.snapPoints
element = <InAppBrowserConsentModal.Component {...activeModal} />
} else {
return null
}
+9 -3
View File
@@ -1,6 +1,5 @@
import React, {ComponentProps, memo, useMemo} from 'react'
import {
Linking,
GestureResponderEvent,
Platform,
StyleProp,
@@ -31,6 +30,7 @@ import {sanitizeUrl} from '@braintree/sanitize-url'
import {PressableWithHover} from './PressableWithHover'
import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
import {useModalControls} from '#/state/modals'
import {useOpenLink} from '#/state/preferences/in-app-browser'
type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
@@ -65,6 +65,7 @@ export const Link = memo(function Link({
const {closeModal} = useModalControls()
const navigation = useNavigation<NavigationProp>()
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const openLink = useOpenLink()
const onPress = React.useCallback(
(e?: Event) => {
@@ -74,11 +75,12 @@ export const Link = memo(function Link({
navigation,
sanitizeUrl(href),
navigationAction,
openLink,
e,
)
}
},
[closeModal, navigation, navigationAction, href],
[closeModal, navigation, navigationAction, href, openLink],
)
if (noFeedback) {
@@ -172,6 +174,7 @@ export const TextLink = memo(function TextLink({
const {...props} = useLinkProps({to: sanitizeUrl(href)})
const navigation = useNavigation<NavigationProp>()
const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink()
if (warnOnMismatchingLabel && typeof text !== 'string') {
console.error('Unable to detect mismatching label')
@@ -200,6 +203,7 @@ export const TextLink = memo(function TextLink({
navigation,
sanitizeUrl(href),
navigationAction,
openLink,
e,
)
},
@@ -212,6 +216,7 @@ export const TextLink = memo(function TextLink({
text,
warnOnMismatchingLabel,
navigationAction,
openLink,
],
)
const hrefAttrs = useMemo(() => {
@@ -317,6 +322,7 @@ function onPressInner(
navigation: NavigationProp,
href: string,
navigationAction: 'push' | 'replace' | 'navigate' = 'push',
openLink: (href: string) => void,
e?: Event,
) {
let shouldHandle = false
@@ -345,7 +351,7 @@ function onPressInner(
if (shouldHandle) {
href = convertBskyAppUrlIfNeeded(href)
if (newTab || href.startsWith('http') || href.startsWith('mailto')) {
Linking.openURL(href)
openLink(href)
} else {
closeModal() // close any active modals
+26 -2
View File
@@ -70,6 +70,11 @@ import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {
useInAppBrowser,
useSetInAppBrowser,
} from '#/state/preferences/in-app-browser'
import {isNative} from '#/platform/detection'
function SettingsAccountCard({account}: {account: SessionAccount}) {
const pal = usePalette('default')
@@ -146,6 +151,8 @@ export function SettingsScreen({}: Props) {
const setMinimalShellMode = useSetMinimalShellMode()
const requireAltTextEnabled = useRequireAltTextEnabled()
const setRequireAltTextEnabled = useSetRequireAltTextEnabled()
const inAppBrowserPref = useInAppBrowser()
const setUseInAppBrowser = useSetInAppBrowser()
const onboardingDispatch = useOnboardingDispatch()
const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries()
@@ -313,8 +320,14 @@ export function SettingsScreen({}: Props) {
/>
</>
)}
<Text type="lg" style={pal.text}>
{currentAccount.email || '(no email)'}{' '}
<Text
type="lg"
numberOfLines={1}
style={[
pal.text,
{overflow: 'hidden', marginRight: 4, flex: 1},
]}>
{currentAccount.email || '(no email)'}
</Text>
<Link onPress={() => openModal({name: 'change-email'})}>
<Text type="lg" style={pal.link}>
@@ -658,6 +671,17 @@ export function SettingsScreen({}: Props) {
<Trans>Change handle</Trans>
</Text>
</TouchableOpacity>
{isNative && (
<View style={[pal.view, styles.toggleCard]}>
<ToggleButton
type="default-light"
label={_(msg`Open links with in-app browser`)}
labelType="lg"
isSelected={inAppBrowserPref ?? false}
onPress={() => setUseInAppBrowser(!inAppBrowserPref)}
/>
</View>
)}
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
<Trans>Danger Zone</Trans>
+70 -15
View File
@@ -9590,6 +9590,13 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
compare-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/compare-urls/-/compare-urls-2.0.0.tgz#9b378c4abd43980a8700fffec9afb85de4df9075"
integrity sha512-eCJcWn2OYFEIqbm70ta7LQowJOOZZqq1a2YbbFCFI1uwSvj+TWMwXVn7vPR1ceFNcAIt5RSTDbwdlX82gYLTkA==
dependencies:
normalize-url "^2.0.1"
component-type@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9"
@@ -10143,7 +10150,7 @@ decimal.js@^10.2.1, decimal.js@^10.4.2:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
decode-uri-component@^0.2.2:
decode-uri-component@^0.2.0, decode-uri-component@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
@@ -11694,6 +11701,14 @@ expo-updates@~0.24.5:
fbemitter "^3.0.0"
resolve-from "^5.0.0"
expo-web-browser@^12.5.0:
version "12.5.0"
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-12.5.0.tgz#69949c50ef0ef8d2889435ed9e595206cfbcf68b"
integrity sha512-3uDzP19DqcEicLOB4ZH6pGWzxlCQ8mLHSmWMmfXEBhZjooUkHUrysbzkNvQQa24ijy3uoUybX4jW0xPss594kA==
dependencies:
compare-urls "^2.0.0"
url "^0.11.0"
expo@^50.0.0-preview.7:
version "50.0.0-preview.7"
resolved "https://registry.yarnpkg.com/expo/-/expo-50.0.0-preview.7.tgz#d4ac7a9aad0a5e9f03a31b8794e186a1af741edc"
@@ -13348,6 +13363,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3:
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
is-plain-obj@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
is-plain-obj@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
@@ -16146,6 +16166,15 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
normalize-url@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
dependencies:
prepend-http "^2.0.0"
query-string "^5.0.1"
sort-keys "^2.0.0"
normalize-url@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
@@ -16415,11 +16444,6 @@ open@^8.0.4, open@^8.0.9, open@^8.3.0, open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
opencollective-postinstall@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
optionator@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
@@ -17579,6 +17603,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prepend-http@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
@@ -17913,6 +17942,11 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
punycode@^2.1.0, punycode@^2.1.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
@@ -17940,13 +17974,22 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
qs@^6.5.1:
qs@^6.11.2, qs@^6.5.1:
version "6.11.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
dependencies:
side-channel "^1.0.4"
query-string@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
dependencies:
decode-uri-component "^0.2.0"
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
query-string@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
@@ -18185,14 +18228,6 @@ react-native-image-crop-picker@^0.38.1:
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.38.1.tgz#5973b4a8b55835b987e6be2064de411e849ac005"
integrity sha512-cF5UQnWplzHCeiCO+aiGS/0VomWaLmFf3nSsgTMPfY+8+99h8N/eHQvVdSF7RsGw50B8394wGeGyqHjjp8YRWw==
react-native-inappbrowser-reborn@^3.6.3:
version "3.7.0"
resolved "https://registry.yarnpkg.com/react-native-inappbrowser-reborn/-/react-native-inappbrowser-reborn-3.7.0.tgz#849a43c3c7da22b65147649fe596836bcb494083"
integrity sha512-Ia53jYNtFcbNaX5W3QfOmN25I7bcvuDiQmSY5zABXjy4+WI20bPc9ua09li55F8yDCjv3C99jX6vKms68mBV7g==
dependencies:
invariant "^2.2.4"
opencollective-postinstall "^2.0.3"
react-native-ios-context-menu@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-1.15.3.tgz#c02e6a7af2df8c08d0b3e1c8f3395484b3c9c760"
@@ -19461,6 +19496,13 @@ sonic-boom@^3.1.0:
dependencies:
atomic-sleep "^1.0.0"
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==
dependencies:
is-plain-obj "^1.0.0"
source-list-map@^2.0.0, source-list-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -19686,6 +19728,11 @@ streamx@^2.15.0:
fast-fifo "^1.1.0"
queue-tick "^1.0.1"
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@@ -20765,6 +20812,14 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
url@^0.11.0:
version "0.11.3"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.3.tgz#6f495f4b935de40ce4a0a52faee8954244f3d3ad"
integrity sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==
dependencies:
punycode "^1.4.1"
qs "^6.11.2"
use-callback-ref@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5"