diff --git a/jest/test-utils.tsx b/jest/test-utils.tsx
index 0a22d792b..264b31fae 100644
--- a/jest/test-utils.tsx
+++ b/jest/test-utils.tsx
@@ -1,22 +1,18 @@
-import React from 'react'
-import {render} from '@testing-library/react-native'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
-import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
-import {RootStoreProvider, RootStoreModel} from '../src/state'
+import {render} from '@testing-library/react-native'
+
import {ThemeProvider} from '../src/lib/ThemeContext'
+import {type RootStoreModel, RootStoreProvider} from '../src/state'
const customRender = (ui: any, rootStore: RootStoreModel) =>
render(
- // eslint-disable-next-line react-native/no-inline-styles
-
-
-
- {ui}
-
-
-
+
+
+ {ui}
+
+
,
)
diff --git a/package.json b/package.json
index df8155d0c..7a26f88e0 100644
--- a/package.json
+++ b/package.json
@@ -196,7 +196,6 @@
"react-native-progress": "bluesky-social/react-native-progress",
"react-native-qrcode-styled": "^0.3.3",
"react-native-reanimated": "^3.19.1",
- "react-native-root-siblings": "^5.0.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 104a7ecae..30a5e8129 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -4,7 +4,6 @@ import '#/view/icons'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
-import {RootSiblingParent} from 'react-native-root-siblings'
import {
initialWindowMetrics,
SafeAreaProvider,
@@ -84,7 +83,11 @@ if (isIOS) {
}
if (isAndroid) {
// iOS is handled by the config plugin -sfn
- ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)
+ ScreenOrientation.lockAsync(
+ ScreenOrientation.OrientationLock.PORTRAIT_UP,
+ ).catch(error =>
+ logger.debug('Could not lock orientation', {safeMessage: error}),
+ )
}
/**
@@ -133,64 +136,62 @@ function InnerApp() {
-
-
-
-
-
-
-
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 569c9be79..b7cba6122 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -3,7 +3,6 @@ import '#/view/icons'
import './style.css'
import React, {useEffect, useState} from 'react'
-import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -111,62 +110,60 @@ function InnerApp() {
-
-
-
-
-
-
-
-
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/alf/util/systemUI.ts b/src/alf/util/systemUI.ts
index 9e5769c4c..d8c899cb1 100644
--- a/src/alf/util/systemUI.ts
+++ b/src/alf/util/systemUI.ts
@@ -1,14 +1,20 @@
import * as SystemUI from 'expo-system-ui'
+import {logger} from '#/logger'
import {isAndroid} from '#/platform/detection'
import {type Theme} from '../types'
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
if (isAndroid) {
- if (themeType === 'theme') {
- SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
- } else {
- SystemUI.setBackgroundColorAsync('black')
+ try {
+ if (themeType === 'theme') {
+ SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
+ } else {
+ SystemUI.setBackgroundColorAsync('black')
+ }
+ } catch (error) {
+ // Can reject with 'The current activity is no longer available' - no big deal
+ logger.debug('Could not set system UI theme', {safeMessage: error})
}
}
}
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index 421a7fe9d..2b533b599 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -165,8 +165,8 @@ export function useLink({
if (isNative && screen !== 'NotFound') {
const state = navigation.getState()
// if screen is not in the current navigator, it means it's
- // most likely a tab screen
- if (!state.routeNames.includes(screen)) {
+ // most likely a tab screen. note: state can be undefined
+ if (!state?.routeNames.includes(screen)) {
const parent = navigation.getParent()
if (
parent &&
diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts
index 9c5750606..466d03208 100644
--- a/src/lib/hooks/useOTAUpdates.ts
+++ b/src/lib/hooks/useOTAUpdates.ts
@@ -10,6 +10,7 @@ import {
useUpdates,
} from 'expo-updates'
+import {isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isIOS} from '#/platform/detection'
import {IS_TESTFLIGHT} from '#/env'
@@ -145,8 +146,10 @@ export function useOTAUpdates() {
} else {
logger.debug('No update available.')
}
- } catch (e) {
- logger.error('OTA Update Error', {error: `${e}`})
+ } catch (err) {
+ if (!isNetworkError(err)) {
+ logger.error('OTA Update Error', {safeMessage: err})
+ }
}
}, 10e3)
}, [])
@@ -154,8 +157,10 @@ export function useOTAUpdates() {
const onIsTestFlight = React.useCallback(async () => {
try {
await updateTestflight()
- } catch (e: any) {
- logger.error('Internal OTA Update Error', {error: `${e}`})
+ } catch (err: any) {
+ if (!isNetworkError(err)) {
+ logger.error('Internal OTA Update Error', {safeMessage: err})
+ }
}
}, [])
diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx
index 88d9a47d0..e56bdc2bd 100644
--- a/src/state/feed-feedback.tsx
+++ b/src/state/feed-feedback.tsx
@@ -28,9 +28,21 @@ import {useAgent} from './session'
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
-export const DIRECT_FEEDBACK_INTERACTIONS = new Set<
+export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
AppBskyFeedDefs.Interaction['event']
->(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore'])
+>([
+ // These are explicit actions and are therefore fine to send.
+ 'app.bsky.feed.defs#requestLess',
+ 'app.bsky.feed.defs#requestMore',
+ // These can be inferred from the firehose and are therefore fine to send.
+ 'app.bsky.feed.defs#interactionLike',
+ 'app.bsky.feed.defs#interactionQuote',
+ 'app.bsky.feed.defs#interactionReply',
+ 'app.bsky.feed.defs#interactionRepost',
+ // This can be inferred from pagination requests for everything except the very last page
+ // so it is fine to send. It is crucial for third party algorithmic feeds to receive these.
+ 'app.bsky.feed.defs#interactionSeen',
+])
const logger = Logger.create(Logger.Context.FeedFeedback)
@@ -228,7 +240,7 @@ function isInteractionAllowed(
return false
}
const isDiscover = isDiscoverFeed(feed.feedDescriptor)
- return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction)
+ return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction)
}
function toString(interaction: AppBskyFeedDefs.Interaction): string {
diff --git a/src/state/geolocation/useSyncedDeviceGeolocation.ts b/src/state/geolocation/useSyncedDeviceGeolocation.ts
index 602f29a30..fea6198d4 100644
--- a/src/state/geolocation/useSyncedDeviceGeolocation.ts
+++ b/src/state/geolocation/useSyncedDeviceGeolocation.ts
@@ -1,10 +1,45 @@
import {useEffect, useRef} from 'react'
import * as Location from 'expo-location'
+import {createPermissionHook} from 'expo-modules-core'
import {logger} from '#/state/geolocation/logger'
import {getDeviceGeolocation} from '#/state/geolocation/util'
import {device, useStorage} from '#/storage'
+/**
+ * Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available.
+ * We need to catch and ignore it, since it's effectively denied.
+ * @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293
+ */
+const useForegroundPermissions = createPermissionHook({
+ getMethod: () =>
+ Location.getForegroundPermissionsAsync().catch(error => {
+ logger.debug(
+ 'useForegroundPermission: error getting location permissions',
+ {safeMessage: error},
+ )
+ return {
+ status: Location.PermissionStatus.DENIED,
+ granted: false,
+ canAskAgain: false,
+ expires: 0,
+ }
+ }),
+ requestMethod: () =>
+ Location.requestForegroundPermissionsAsync().catch(error => {
+ logger.debug(
+ 'useForegroundPermission: error requesting location permissions',
+ {safeMessage: error},
+ )
+ return {
+ status: Location.PermissionStatus.DENIED,
+ granted: false,
+ canAskAgain: false,
+ expires: 0,
+ }
+ }),
+})
+
/**
* Hook to get and sync the device geolocation from the device GPS and store it
* using device storage. If permissions are not granted, it will clear any cached
@@ -12,7 +47,7 @@ import {device, useStorage} from '#/storage'
*/
export function useSyncedDeviceGeolocation() {
const synced = useRef(false)
- const [status] = Location.useForegroundPermissions()
+ const [status] = useForegroundPermissions()
const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [
'deviceGeolocation',
])
diff --git a/yarn.lock b/yarn.lock
index 8c09828e7..3d5ab1fa3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17064,11 +17064,6 @@ react-native-reanimated@^3.19.1:
invariant "^2.2.4"
react-native-is-edge-to-edge "1.1.7"
-react-native-root-siblings@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c"
- integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g==
-
react-native-safe-area-context@~5.6.0:
version "5.6.1"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"