diff --git a/src/App.native.tsx b/src/App.native.tsx
index 87429d8458..8ad62b1295 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -72,6 +72,7 @@ import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
+import {Provider as BlockingAnnouncementsPortalProvider} from '#/components/dialogs/BlockingAnnouncements'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
@@ -220,16 +221,18 @@ function App() {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 1f795cb3e0..2abada05d7 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -60,6 +60,7 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo
import {Provider as PortalProvider} from '#/components/Portal'
import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext'
import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
+import {Provider as BlockingAnnouncementsPortalProvider} from '#/components/dialogs/BlockingAnnouncements'
import {ToastContainer} from '#/components/Toast'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder'
@@ -196,11 +197,13 @@ function App() {
+
+
diff --git a/src/components/FocusScope/index.tsx b/src/components/FocusScope/index.tsx
new file mode 100644
index 0000000000..37538265b2
--- /dev/null
+++ b/src/components/FocusScope/index.tsx
@@ -0,0 +1,106 @@
+import {
+ Children,
+ cloneElement,
+ ReactNode,
+ useRef,
+ useMemo,
+ useCallback,
+ useEffect,
+ isValidElement,
+ FunctionComponentElement,
+} from 'react'
+import {
+ AccessibilityInfo,
+ Pressable,
+ View,
+ Text,
+ findNodeHandle,
+} from 'react-native'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useA11y} from '#/state/a11y'
+
+export function FocusScope({children}: {children: ReactNode}) {
+ const {screenReaderEnabled} = useA11y()
+
+ return screenReaderEnabled ? {children} : children
+}
+
+function FocusTrap({children}: {children: ReactNode}) {
+ const {_} = useLingui()
+ const child = useRef(null)
+
+ const content = useMemo(() => {
+ return Children.toArray(children).map((node, i) => {
+ if (i === 0 && isValidElement(node)) {
+ return cloneElement(node as FunctionComponentElement, {
+ ref: child,
+ })
+ }
+ return node
+ })
+ }, [children])
+
+ const focus = useCallback((ref: View | null) => {
+ if (!ref) return
+ const node = findNodeHandle(ref)
+ if (node) {
+ AccessibilityInfo.setAccessibilityFocus(node)
+ }
+ }, [])
+
+ useEffect(() => {
+ setTimeout(() => {
+ focus(child.current)
+ }, 1e3)
+ }, [focus])
+
+ return (
+ <>
+ {
+ switch (event.nativeEvent.actionName) {
+ case 'activate': {
+ focus(child.current)
+ }
+ }
+ }}>
+
+
+ {content}
+ {
+ switch (event.nativeEvent.actionName) {
+ case 'activate': {
+ focus(child.current)
+ }
+ }
+ }}>
+
+
+ >
+ )
+}
+
+function Noop() {
+ return (
+
+ {' '}
+
+ )
+}
diff --git a/src/components/dialogs/BlockingAnnouncements/AnnouncementDialog.tsx b/src/components/dialogs/BlockingAnnouncements/AnnouncementDialog.tsx
index 90201da905..58254abcc7 100644
--- a/src/components/dialogs/BlockingAnnouncements/AnnouncementDialog.tsx
+++ b/src/components/dialogs/BlockingAnnouncements/AnnouncementDialog.tsx
@@ -5,7 +5,8 @@ import {View, ScrollView, useWindowDimensions} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {LinearGradient} from 'expo-linear-gradient'
-import {isNative} from '#/platform/detection'
+import {FocusScope} from '#/components/FocusScope'
+import {isNative, isAndroid} from '#/platform/detection'
import {atoms as a, useTheme, useBreakpoints, web, flatten} from '#/alf'
import {transparentifyColor} from '#/alf/util/colorGeneration'
import {useA11y} from '#/state/a11y'
@@ -49,7 +50,7 @@ export function AnnouncementDialog({
const insets = useSafeAreaInsets()
return (
- <>
+
- {/* */}
-
{!gtPhone && (
)}
-
- {children}
-
- {/* */}
+
+
+ {children}
+
+
- >
+
)
}
diff --git a/src/components/dialogs/BlockingAnnouncements/PolicyUpdate20250801.tsx b/src/components/dialogs/BlockingAnnouncements/PolicyUpdate20250801.tsx
index 8774ea17db..833fd45d8a 100644
--- a/src/components/dialogs/BlockingAnnouncements/PolicyUpdate20250801.tsx
+++ b/src/components/dialogs/BlockingAnnouncements/PolicyUpdate20250801.tsx
@@ -12,7 +12,10 @@ import {
} from '#/components/dialogs/BlockingAnnouncements/common'
import {InlineLinkText} from '#/components/Link'
import {Span, Text} from '#/components/Typography'
-import {AnnouncementDialog, useAnnouncementDialogContext} from '#/components/dialogs/BlockingAnnouncements/AnnouncementDialog'
+import {
+ AnnouncementDialog,
+ useAnnouncementDialogContext,
+} from '#/components/dialogs/BlockingAnnouncements/AnnouncementDialog'
export function useLocalState() {
return useAnnouncementState({
@@ -111,6 +114,8 @@ export function Announcement() {