about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/auth/server-input/index.tsx6
-rw-r--r--src/view/com/composer/Composer.tsx570
-rw-r--r--src/view/com/composer/GifAltText.tsx9
-rw-r--r--src/view/com/composer/photos/EditImageDialog.web.tsx1
-rw-r--r--src/view/com/composer/photos/Gallery.tsx13
-rw-r--r--src/view/com/composer/photos/ImageAltTextDialog.tsx9
-rw-r--r--src/view/com/composer/photos/SelectGifBtn.tsx5
-rw-r--r--src/view/com/composer/photos/SelectPhotoBtn.tsx14
-rw-r--r--src/view/com/composer/threadgate/ThreadgateBtn.tsx5
-rw-r--r--src/view/com/composer/videos/SubtitleDialog.tsx8
-rw-r--r--src/view/com/util/UserAvatar.tsx12
-rw-r--r--src/view/com/util/UserBanner.tsx6
-rw-r--r--src/view/com/util/forms/PostDropdownBtn.tsx16
-rw-r--r--src/view/com/util/post-ctrls/RepostButton.tsx5
-rw-r--r--src/view/screens/Settings/DisableEmail2FADialog.tsx1
-rw-r--r--src/view/screens/Settings/ExportCarDialog.tsx1
-rw-r--r--src/view/screens/Storybook/Dialogs.tsx14
-rw-r--r--src/view/shell/Composer.ios.tsx77
-rw-r--r--src/view/shell/index.tsx37
19 files changed, 414 insertions, 395 deletions
diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx
index fb69e1d9c..74b0d2315 100644
--- a/src/view/com/auth/server-input/index.tsx
+++ b/src/view/com/auth/server-input/index.tsx
@@ -66,12 +66,8 @@ export function ServerInputDialog({
   ])
 
   return (
-    <Dialog.Outer
-      control={control}
-      nativeOptions={{sheet: {snapPoints: ['100%']}}}
-      onClose={onClose}>
+    <Dialog.Outer control={control} onClose={onClose}>
       <Dialog.Handle />
-
       <Dialog.ScrollableInner
         accessibilityDescribedBy="dialog-description"
         accessibilityLabelledBy="dialog-title">
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 227964907..e03c64a42 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -114,11 +114,14 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+import {createPortalGroup} from '#/components/Portal'
 import * as Prompt from '#/components/Prompt'
 import {Text as NewText} from '#/components/Typography'
 import {composerReducer, createComposerState} from './state/composer'
 import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
 
+const Portal = createPortalGroup()
+
 const MAX_IMAGES = 4
 
 type CancelRef = {
@@ -613,296 +616,313 @@ export const ComposePost = ({
   const keyboardVerticalOffset = useKeyboardVerticalOffset()
 
   return (
-    <KeyboardAvoidingView
-      testID="composePostView"
-      behavior={isIOS ? 'padding' : 'height'}
-      keyboardVerticalOffset={keyboardVerticalOffset}
-      style={a.flex_1}>
-      <View style={[a.flex_1, viewStyles]} aria-modal accessibilityViewIsModal>
-        <Animated.View
-          style={topBarAnimatedStyle}
-          layout={native(LinearTransition)}>
-          <View style={styles.topbarInner}>
-            <Button
-              label={_(msg`Cancel`)}
-              variant="ghost"
-              color="primary"
-              shape="default"
-              size="small"
-              style={[
-                a.rounded_full,
-                a.py_sm,
-                {paddingLeft: 7, paddingRight: 7},
-              ]}
-              onPress={onPressCancel}
-              accessibilityHint={_(
-                msg`Closes post composer and discards post draft`,
-              )}>
-              <ButtonText style={[a.text_md]}>
-                <Trans>Cancel</Trans>
-              </ButtonText>
-            </Button>
-            <View style={a.flex_1} />
-            {isProcessing ? (
-              <>
-                <Text style={pal.textLight}>{processingState}</Text>
-                <View style={styles.postBtn}>
-                  <ActivityIndicator />
-                </View>
-              </>
-            ) : (
-              <View style={[styles.postBtnWrapper]}>
-                <LabelsBtn
-                  labels={labels}
-                  onChange={setLabels}
-                  hasMedia={hasMedia}
-                />
-                {canPost ? (
-                  <Button
-                    testID="composerPublishBtn"
-                    label={
-                      replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
-                    }
-                    variant="solid"
-                    color="primary"
-                    shape="default"
-                    size="small"
-                    style={[a.rounded_full, a.py_sm]}
-                    onPress={() => onPressPublish()}
-                    disabled={videoState.status !== 'idle' && publishOnUpload}>
-                    <ButtonText style={[a.text_md]}>
-                      {replyTo ? (
-                        <Trans context="action">Reply</Trans>
-                      ) : (
-                        <Trans context="action">Post</Trans>
-                      )}
-                    </ButtonText>
-                  </Button>
-                ) : (
-                  <View style={[styles.postBtn, pal.btn]}>
-                    <Text style={[pal.textLight, s.f16, s.bold]}>
-                      <Trans context="action">Post</Trans>
-                    </Text>
+    <Portal.Provider>
+      <KeyboardAvoidingView
+        testID="composePostView"
+        behavior={isIOS ? 'padding' : 'height'}
+        keyboardVerticalOffset={keyboardVerticalOffset}
+        style={a.flex_1}>
+        <View
+          style={[a.flex_1, viewStyles]}
+          aria-modal
+          accessibilityViewIsModal>
+          <Animated.View
+            style={topBarAnimatedStyle}
+            layout={native(LinearTransition)}>
+            <View style={styles.topbarInner}>
+              <Button
+                label={_(msg`Cancel`)}
+                variant="ghost"
+                color="primary"
+                shape="default"
+                size="small"
+                style={[
+                  a.rounded_full,
+                  a.py_sm,
+                  {paddingLeft: 7, paddingRight: 7},
+                ]}
+                onPress={onPressCancel}
+                accessibilityHint={_(
+                  msg`Closes post composer and discards post draft`,
+                )}>
+                <ButtonText style={[a.text_md]}>
+                  <Trans>Cancel</Trans>
+                </ButtonText>
+              </Button>
+              <View style={a.flex_1} />
+              {isProcessing ? (
+                <>
+                  <Text style={pal.textLight}>{processingState}</Text>
+                  <View style={styles.postBtn}>
+                    <ActivityIndicator />
                   </View>
-                )}
-              </View>
-            )}
-          </View>
-
-          {isAltTextRequiredAndMissing && (
-            <View style={[styles.reminderLine, pal.viewLight]}>
-              <View style={styles.errorIcon}>
-                <FontAwesomeIcon
-                  icon="exclamation"
-                  style={{color: colors.red4}}
-                  size={10}
-                />
-              </View>
-              <Text style={[pal.text, a.flex_1]}>
-                <Trans>One or more images is missing alt text.</Trans>
-              </Text>
+                </>
+              ) : (
+                <View style={[styles.postBtnWrapper]}>
+                  <LabelsBtn
+                    labels={labels}
+                    onChange={setLabels}
+                    hasMedia={hasMedia}
+                  />
+                  {canPost ? (
+                    <Button
+                      testID="composerPublishBtn"
+                      label={
+                        replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
+                      }
+                      variant="solid"
+                      color="primary"
+                      shape="default"
+                      size="small"
+                      style={[a.rounded_full, a.py_sm]}
+                      onPress={() => onPressPublish()}
+                      disabled={
+                        videoState.status !== 'idle' && publishOnUpload
+                      }>
+                      <ButtonText style={[a.text_md]}>
+                        {replyTo ? (
+                          <Trans context="action">Reply</Trans>
+                        ) : (
+                          <Trans context="action">Post</Trans>
+                        )}
+                      </ButtonText>
+                    </Button>
+                  ) : (
+                    <View style={[styles.postBtn, pal.btn]}>
+                      <Text style={[pal.textLight, s.f16, s.bold]}>
+                        <Trans context="action">Post</Trans>
+                      </Text>
+                    </View>
+                  )}
+                </View>
+              )}
             </View>
-          )}
-          <ErrorBanner
-            error={error}
-            videoState={videoState}
-            clearError={() => setError('')}
-            clearVideo={clearVideo}
-          />
-        </Animated.View>
-        <Animated.ScrollView
-          layout={native(LinearTransition)}
-          onScroll={scrollHandler}
-          style={styles.scrollView}
-          keyboardShouldPersistTaps="always"
-          onContentSizeChange={onScrollViewContentSizeChange}
-          onLayout={onScrollViewLayout}>
-          {replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
 
-          <View
-            style={[
-              styles.textInputLayout,
-              isNative && styles.textInputLayoutMobile,
-            ]}>
-            <UserAvatar
-              avatar={currentProfile?.avatar}
-              size={50}
-              type={currentProfile?.associated?.labeler ? 'labeler' : 'user'}
-            />
-            <TextInput
-              ref={textInput}
-              richtext={richtext}
-              placeholder={selectTextInputPlaceholder}
-              autoFocus
-              setRichText={setRichText}
-              onPhotoPasted={onPhotoPasted}
-              onPressPublish={() => onPressPublish()}
-              onNewLink={onNewLink}
-              onError={setError}
-              accessible={true}
-              accessibilityLabel={_(msg`Write post`)}
-              accessibilityHint={_(
-                msg`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`,
-              )}
+            {isAltTextRequiredAndMissing && (
+              <View style={[styles.reminderLine, pal.viewLight]}>
+                <View style={styles.errorIcon}>
+                  <FontAwesomeIcon
+                    icon="exclamation"
+                    style={{color: colors.red4}}
+                    size={10}
+                  />
+                </View>
+                <Text style={[pal.text, a.flex_1]}>
+                  <Trans>One or more images is missing alt text.</Trans>
+                </Text>
+              </View>
+            )}
+            <ErrorBanner
+              error={error}
+              videoState={videoState}
+              clearError={() => setError('')}
+              clearVideo={clearVideo}
             />
-          </View>
-
-          <Gallery images={images} dispatch={dispatch} />
-          {images.length === 0 && extLink && (
-            <View style={a.relative}>
-              <ExternalEmbed
-                link={extLink}
-                gif={extGif}
-                onRemove={() => {
-                  if (extGif) {
-                    dispatch({type: 'embed_remove_gif'})
-                  } else {
-                    dispatch({type: 'embed_remove_link'})
-                  }
-                  setExtLink(undefined)
-                  setExtGif(undefined)
-                }}
+          </Animated.View>
+          <Animated.ScrollView
+            layout={native(LinearTransition)}
+            onScroll={scrollHandler}
+            style={styles.scrollView}
+            keyboardShouldPersistTaps="always"
+            onContentSizeChange={onScrollViewContentSizeChange}
+            onLayout={onScrollViewLayout}>
+            {replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
+
+            <View
+              style={[
+                styles.textInputLayout,
+                isNative && styles.textInputLayoutMobile,
+              ]}>
+              <UserAvatar
+                avatar={currentProfile?.avatar}
+                size={50}
+                type={currentProfile?.associated?.labeler ? 'labeler' : 'user'}
               />
-              <GifAltText
-                link={extLink}
-                gif={extGif}
-                onSubmit={handleChangeGifAltText}
+              <TextInput
+                ref={textInput}
+                richtext={richtext}
+                placeholder={selectTextInputPlaceholder}
+                autoFocus
+                setRichText={setRichText}
+                onPhotoPasted={onPhotoPasted}
+                onPressPublish={() => onPressPublish()}
+                onNewLink={onNewLink}
+                onError={setError}
+                accessible={true}
+                accessibilityLabel={_(msg`Write post`)}
+                accessibilityHint={_(
+                  msg`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`,
+                )}
               />
             </View>
-          )}
-          <LayoutAnimationConfig skipExiting>
-            {hasVideo && (
-              <Animated.View
-                style={[a.w_full, a.mt_lg]}
-                entering={native(ZoomIn)}
-                exiting={native(ZoomOut)}>
-                {videoState.asset &&
-                  (videoState.status === 'compressing' ? (
-                    <VideoTranscodeProgress
-                      asset={videoState.asset}
-                      progress={videoState.progress}
-                      clear={clearVideo}
-                    />
-                  ) : videoState.video ? (
-                    <VideoPreview
-                      asset={videoState.asset}
-                      video={videoState.video}
-                      setDimensions={updateVideoDimensions}
-                      clear={clearVideo}
-                    />
-                  ) : null)}
-                <SubtitleDialogBtn
-                  defaultAltText={videoState.altText}
-                  saveAltText={altText =>
-                    dispatch({
-                      type: 'embed_update_video',
-                      videoAction: {
-                        type: 'update_alt_text',
-                        altText,
-                        signal: videoState.abortController.signal,
-                      },
-                    })
-                  }
-                  captions={videoState.captions}
-                  setCaptions={updater => {
-                    dispatch({
-                      type: 'embed_update_video',
-                      videoAction: {
-                        type: 'update_captions',
-                        updater,
-                        signal: videoState.abortController.signal,
-                      },
-                    })
+
+            <Gallery
+              images={images}
+              dispatch={dispatch}
+              Portal={Portal.Portal}
+            />
+            {images.length === 0 && extLink && (
+              <View style={a.relative}>
+                <ExternalEmbed
+                  link={extLink}
+                  gif={extGif}
+                  onRemove={() => {
+                    if (extGif) {
+                      dispatch({type: 'embed_remove_gif'})
+                    } else {
+                      dispatch({type: 'embed_remove_link'})
+                    }
+                    setExtLink(undefined)
+                    setExtGif(undefined)
                   }}
                 />
-              </Animated.View>
+                <GifAltText
+                  link={extLink}
+                  gif={extGif}
+                  onSubmit={handleChangeGifAltText}
+                  Portal={Portal.Portal}
+                />
+              </View>
             )}
-          </LayoutAnimationConfig>
-          <View style={!hasVideo ? [a.mt_md] : []}>
-            {quote ? (
-              <View style={[s.mt5, s.mb2, isWeb && s.mb10]}>
-                <View style={{pointerEvents: 'none'}}>
-                  <QuoteEmbed quote={quote} />
-                </View>
-                {quote.uri !== initQuote?.uri && (
-                  <QuoteX
-                    onRemove={() => {
-                      dispatch({type: 'embed_remove_quote'})
-                      setQuote(undefined)
+            <LayoutAnimationConfig skipExiting>
+              {hasVideo && (
+                <Animated.View
+                  style={[a.w_full, a.mt_lg]}
+                  entering={native(ZoomIn)}
+                  exiting={native(ZoomOut)}>
+                  {videoState.asset &&
+                    (videoState.status === 'compressing' ? (
+                      <VideoTranscodeProgress
+                        asset={videoState.asset}
+                        progress={videoState.progress}
+                        clear={clearVideo}
+                      />
+                    ) : videoState.video ? (
+                      <VideoPreview
+                        asset={videoState.asset}
+                        video={videoState.video}
+                        setDimensions={updateVideoDimensions}
+                        clear={clearVideo}
+                      />
+                    ) : null)}
+                  <SubtitleDialogBtn
+                    defaultAltText={videoState.altText}
+                    saveAltText={altText =>
+                      dispatch({
+                        type: 'embed_update_video',
+                        videoAction: {
+                          type: 'update_alt_text',
+                          altText,
+                          signal: videoState.abortController.signal,
+                        },
+                      })
+                    }
+                    captions={videoState.captions}
+                    setCaptions={updater => {
+                      dispatch({
+                        type: 'embed_update_video',
+                        videoAction: {
+                          type: 'update_captions',
+                          updater,
+                          signal: videoState.abortController.signal,
+                        },
+                      })
                     }}
+                    Portal={Portal.Portal}
                   />
-                )}
-              </View>
-            ) : null}
-          </View>
-        </Animated.ScrollView>
-        <SuggestedLanguage text={richtext.text} />
-
-        {replyTo ? null : (
-          <ThreadgateBtn
-            postgate={postgate}
-            onChangePostgate={setPostgate}
-            threadgateAllowUISettings={threadgateAllowUISettings}
-            onChangeThreadgateAllowUISettings={
-              onChangeThreadgateAllowUISettings
-            }
-            style={bottomBarAnimatedStyle}
-          />
-        )}
-        <View
-          style={[
-            t.atoms.bg,
-            t.atoms.border_contrast_medium,
-            styles.bottomBar,
-          ]}>
-          {videoState.status !== 'idle' && videoState.status !== 'done' ? (
-            <VideoUploadToolbar state={videoState} />
-          ) : (
-            <ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
-              <SelectPhotoBtn
-                size={images.length}
-                disabled={!canSelectImages}
-                onAdd={onImageAdd}
-              />
-              <SelectVideoBtn
-                onSelectVideo={selectVideo}
-                disabled={!canSelectImages || images?.length > 0}
-                setError={setError}
-              />
-              <OpenCameraBtn disabled={!canSelectImages} onAdd={onImageAdd} />
-              <SelectGifBtn
-                onClose={focusTextInput}
-                onSelectGif={onSelectGif}
-                disabled={hasMedia}
-              />
-              {!isMobile ? (
-                <Button
-                  onPress={onEmojiButtonPress}
-                  style={a.p_sm}
-                  label={_(msg`Open emoji picker`)}
-                  accessibilityHint={_(msg`Open emoji picker`)}
-                  variant="ghost"
-                  shape="round"
-                  color="primary">
-                  <EmojiSmile size="lg" />
-                </Button>
+                </Animated.View>
+              )}
+            </LayoutAnimationConfig>
+            <View style={!hasVideo ? [a.mt_md] : []}>
+              {quote ? (
+                <View style={[s.mt5, s.mb2, isWeb && s.mb10]}>
+                  <View style={{pointerEvents: 'none'}}>
+                    <QuoteEmbed quote={quote} />
+                  </View>
+                  {quote.uri !== initQuote?.uri && (
+                    <QuoteX
+                      onRemove={() => {
+                        dispatch({type: 'embed_remove_quote'})
+                        setQuote(undefined)
+                      }}
+                    />
+                  )}
+                </View>
               ) : null}
-            </ToolbarWrapper>
+            </View>
+          </Animated.ScrollView>
+          <SuggestedLanguage text={richtext.text} />
+
+          {replyTo ? null : (
+            <ThreadgateBtn
+              postgate={postgate}
+              onChangePostgate={setPostgate}
+              threadgateAllowUISettings={threadgateAllowUISettings}
+              onChangeThreadgateAllowUISettings={
+                onChangeThreadgateAllowUISettings
+              }
+              style={bottomBarAnimatedStyle}
+              Portal={Portal.Portal}
+            />
           )}
-          <View style={a.flex_1} />
-          <SelectLangBtn />
-          <CharProgress count={graphemeLength} />
+          <View
+            style={[
+              t.atoms.bg,
+              t.atoms.border_contrast_medium,
+              styles.bottomBar,
+            ]}>
+            {videoState.status !== 'idle' && videoState.status !== 'done' ? (
+              <VideoUploadToolbar state={videoState} />
+            ) : (
+              <ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
+                <SelectPhotoBtn
+                  size={images.length}
+                  disabled={!canSelectImages}
+                  onAdd={onImageAdd}
+                />
+                <SelectVideoBtn
+                  onSelectVideo={selectVideo}
+                  disabled={!canSelectImages || images?.length > 0}
+                  setError={setError}
+                />
+                <OpenCameraBtn disabled={!canSelectImages} onAdd={onImageAdd} />
+                <SelectGifBtn
+                  onClose={focusTextInput}
+                  onSelectGif={onSelectGif}
+                  disabled={hasMedia}
+                  Portal={Portal.Portal}
+                />
+                {!isMobile ? (
+                  <Button
+                    onPress={onEmojiButtonPress}
+                    style={a.p_sm}
+                    label={_(msg`Open emoji picker`)}
+                    accessibilityHint={_(msg`Open emoji picker`)}
+                    variant="ghost"
+                    shape="round"
+                    color="primary">
+                    <EmojiSmile size="lg" />
+                  </Button>
+                ) : null}
+              </ToolbarWrapper>
+            )}
+            <View style={a.flex_1} />
+            <SelectLangBtn />
+            <CharProgress count={graphemeLength} />
+          </View>
         </View>
-      </View>
-      <Prompt.Basic
-        control={discardPromptControl}
-        title={_(msg`Discard draft?`)}
-        description={_(msg`Are you sure you'd like to discard this draft?`)}
-        onConfirm={onClose}
-        confirmButtonCta={_(msg`Discard`)}
-        confirmButtonColor="negative"
-      />
-    </KeyboardAvoidingView>
+        <Prompt.Basic
+          control={discardPromptControl}
+          title={_(msg`Discard draft?`)}
+          description={_(msg`Are you sure you'd like to discard this draft?`)}
+          onConfirm={onClose}
+          confirmButtonCta={_(msg`Discard`)}
+          confirmButtonColor="negative"
+          Portal={Portal.Portal}
+        />
+      </KeyboardAvoidingView>
+      <Portal.Outlet />
+    </Portal.Provider>
   )
 }
 
diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx
index a05607c76..3479fb973 100644
--- a/src/view/com/composer/GifAltText.tsx
+++ b/src/view/com/composer/GifAltText.tsx
@@ -20,6 +20,7 @@ import * as Dialog from '#/components/Dialog'
 import * as TextField from '#/components/forms/TextField'
 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
 import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
+import {PortalComponent} from '#/components/Portal'
 import {Text} from '#/components/Typography'
 import {GifEmbed} from '../util/post-embeds/GifEmbed'
 import {AltTextReminder} from './photos/Gallery'
@@ -28,10 +29,12 @@ export function GifAltText({
   link: linkProp,
   gif,
   onSubmit,
+  Portal,
 }: {
   link: ExternalEmbedDraft
   gif?: Gif
   onSubmit: (alt: string) => void
+  Portal: PortalComponent
 }) {
   const control = Dialog.useDialogControl()
   const {_} = useLingui()
@@ -96,9 +99,7 @@ export function GifAltText({
 
       <AltTextReminder />
 
-      <Dialog.Outer
-        control={control}
-        nativeOptions={isAndroid ? {sheet: {snapPoints: ['100%']}} : {}}>
+      <Dialog.Outer control={control} Portal={Portal}>
         <Dialog.Handle />
         <AltTextInner
           onSubmit={onPressSubmit}
@@ -185,6 +186,8 @@ function AltTextInner({
         </View>
       </View>
       <Dialog.Close />
+      {/* Maybe fix this later -h */}
+      {isAndroid ? <View style={{height: 300}} /> : null}
     </Dialog.ScrollableInner>
   )
 }
diff --git a/src/view/com/composer/photos/EditImageDialog.web.tsx b/src/view/com/composer/photos/EditImageDialog.web.tsx
index 0afb83ed9..ebe528abc 100644
--- a/src/view/com/composer/photos/EditImageDialog.web.tsx
+++ b/src/view/com/composer/photos/EditImageDialog.web.tsx
@@ -20,6 +20,7 @@ import {EditImageDialogProps} from './EditImageDialog'
 export const EditImageDialog = (props: EditImageDialogProps) => {
   return (
     <Dialog.Outer control={props.control}>
+      <Dialog.Handle />
       <EditImageInner key={props.image.source.id} {...props} />
     </Dialog.Outer>
   )
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx
index 5ff7042bc..3958a85c0 100644
--- a/src/view/com/composer/photos/Gallery.tsx
+++ b/src/view/com/composer/photos/Gallery.tsx
@@ -21,6 +21,7 @@ import {ComposerImage, cropImage} from '#/state/gallery'
 import {Text} from '#/view/com/util/text/Text'
 import {useTheme} from '#/alf'
 import * as Dialog from '#/components/Dialog'
+import {PortalComponent} from '#/components/Portal'
 import {ComposerAction} from '../state/composer'
 import {EditImageDialog} from './EditImageDialog'
 import {ImageAltTextDialog} from './ImageAltTextDialog'
@@ -30,6 +31,7 @@ const IMAGE_GAP = 8
 interface GalleryProps {
   images: ComposerImage[]
   dispatch: (action: ComposerAction) => void
+  Portal: PortalComponent
 }
 
 export let Gallery = (props: GalleryProps): React.ReactNode => {
@@ -57,7 +59,12 @@ interface GalleryInnerProps extends GalleryProps {
   containerInfo: Dimensions
 }
 
-const GalleryInner = ({images, containerInfo, dispatch}: GalleryInnerProps) => {
+const GalleryInner = ({
+  images,
+  containerInfo,
+  dispatch,
+  Portal,
+}: GalleryInnerProps) => {
   const {isMobile} = useWebMediaQueries()
 
   const {altTextControlStyle, imageControlsStyle, imageStyle} =
@@ -111,6 +118,7 @@ const GalleryInner = ({images, containerInfo, dispatch}: GalleryInnerProps) => {
               onRemove={() => {
                 dispatch({type: 'embed_remove_image', image})
               }}
+              Portal={Portal}
             />
           )
         })}
@@ -127,6 +135,7 @@ type GalleryItemProps = {
   imageStyle?: ViewStyle
   onChange: (next: ComposerImage) => void
   onRemove: () => void
+  Portal: PortalComponent
 }
 
 const GalleryItem = ({
@@ -136,6 +145,7 @@ const GalleryItem = ({
   imageStyle,
   onChange,
   onRemove,
+  Portal,
 }: GalleryItemProps): React.ReactNode => {
   const {_} = useLingui()
   const t = useTheme()
@@ -230,6 +240,7 @@ const GalleryItem = ({
         control={altTextControl}
         image={image}
         onChange={onChange}
+        Portal={Portal}
       />
 
       <EditImageDialog
diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx
index 123e1066a..16ce4351a 100644
--- a/src/view/com/composer/photos/ImageAltTextDialog.tsx
+++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx
@@ -5,25 +5,26 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {MAX_ALT_TEXT} from '#/lib/constants'
-import {isWeb} from '#/platform/detection'
+import {isAndroid, isWeb} from '#/platform/detection'
 import {ComposerImage} from '#/state/gallery'
 import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import * as TextField from '#/components/forms/TextField'
+import {PortalComponent} from '#/components/Portal'
 import {Text} from '#/components/Typography'
 
 type Props = {
   control: Dialog.DialogOuterProps['control']
   image: ComposerImage
   onChange: (next: ComposerImage) => void
+  Portal: PortalComponent
 }
 
 export const ImageAltTextDialog = (props: Props): React.ReactNode => {
   return (
-    <Dialog.Outer control={props.control}>
+    <Dialog.Outer control={props.control} Portal={props.Portal}>
       <Dialog.Handle />
-
       <ImageAltTextInner {...props} />
     </Dialog.Outer>
   )
@@ -116,6 +117,8 @@ const ImageAltTextInner = ({
           </ButtonText>
         </Button>
       </View>
+      {/* Maybe fix this later -h */}
+      {isAndroid ? <View style={{height: 300}} /> : null}
     </Dialog.ScrollableInner>
   )
 }
diff --git a/src/view/com/composer/photos/SelectGifBtn.tsx b/src/view/com/composer/photos/SelectGifBtn.tsx
index d13df0a11..d482e0783 100644
--- a/src/view/com/composer/photos/SelectGifBtn.tsx
+++ b/src/view/com/composer/photos/SelectGifBtn.tsx
@@ -9,14 +9,16 @@ import {atoms as a, useTheme} from '#/alf'
 import {Button} from '#/components/Button'
 import {GifSelectDialog} from '#/components/dialogs/GifSelect'
 import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
+import {PortalComponent} from '#/components/Portal'
 
 type Props = {
   onClose: () => void
   onSelectGif: (gif: Gif) => void
   disabled?: boolean
+  Portal?: PortalComponent
 }
 
-export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
+export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) {
   const {_} = useLingui()
   const ref = useRef<{open: () => void}>(null)
   const t = useTheme()
@@ -46,6 +48,7 @@ export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
         controlRef={ref}
         onClose={onClose}
         onSelectGif={onSelectGif}
+        Portal={Portal}
       />
     </>
   )
diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx
index 34ead3d9a..37bfbafe6 100644
--- a/src/view/com/composer/photos/SelectPhotoBtn.tsx
+++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx
@@ -9,6 +9,7 @@ import {isNative} from '#/platform/detection'
 import {ComposerImage, createComposerImage} from '#/state/gallery'
 import {atoms as a, useTheme} from '#/alf'
 import {Button} from '#/components/Button'
+import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
 import {Image_Stroke2_Corner0_Rounded as Image} from '#/components/icons/Image'
 
 type Props = {
@@ -21,23 +22,26 @@ export function SelectPhotoBtn({size, disabled, onAdd}: Props) {
   const {_} = useLingui()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
   const t = useTheme()
+  const sheetWrapper = useSheetWrapper()
 
   const onPressSelectPhotos = useCallback(async () => {
     if (isNative && !(await requestPhotoAccessIfNeeded())) {
       return
     }
 
-    const images = await openPicker({
-      selectionLimit: 4 - size,
-      allowsMultipleSelection: true,
-    })
+    const images = await sheetWrapper(
+      openPicker({
+        selectionLimit: 4 - size,
+        allowsMultipleSelection: true,
+      }),
+    )
 
     const results = await Promise.all(
       images.map(img => createComposerImage(img)),
     )
 
     onAdd(results)
-  }, [requestPhotoAccessIfNeeded, size, onAdd])
+  }, [requestPhotoAccessIfNeeded, size, onAdd, sheetWrapper])
 
   return (
     <Button
diff --git a/src/view/com/composer/threadgate/ThreadgateBtn.tsx b/src/view/com/composer/threadgate/ThreadgateBtn.tsx
index b0806180c..7e57a57d4 100644
--- a/src/view/com/composer/threadgate/ThreadgateBtn.tsx
+++ b/src/view/com/composer/threadgate/ThreadgateBtn.tsx
@@ -13,6 +13,7 @@ import * as Dialog from '#/components/Dialog'
 import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
 import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
 import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
+import {PortalComponent} from '#/components/Portal'
 
 export function ThreadgateBtn({
   postgate,
@@ -20,6 +21,7 @@ export function ThreadgateBtn({
   threadgateAllowUISettings,
   onChangeThreadgateAllowUISettings,
   style,
+  Portal,
 }: {
   postgate: AppBskyFeedPostgate.Record
   onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
@@ -28,6 +30,8 @@ export function ThreadgateBtn({
   onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
 
   style?: StyleProp<AnimatedStyle<ViewStyle>>
+
+  Portal: PortalComponent
 }) {
   const {_} = useLingui()
   const t = useTheme()
@@ -77,6 +81,7 @@ export function ThreadgateBtn({
         onChangePostgate={onChangePostgate}
         threadgateAllowUISettings={threadgateAllowUISettings}
         onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
+        Portal={Portal}
       />
     </>
   )
diff --git a/src/view/com/composer/videos/SubtitleDialog.tsx b/src/view/com/composer/videos/SubtitleDialog.tsx
index f66684d4e..04522ee1d 100644
--- a/src/view/com/composer/videos/SubtitleDialog.tsx
+++ b/src/view/com/composer/videos/SubtitleDialog.tsx
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
 import {MAX_ALT_TEXT} from '#/lib/constants'
 import {useEnforceMaxGraphemeCount} from '#/lib/strings/helpers'
 import {LANGUAGES} from '#/locale/languages'
-import {isAndroid, isWeb} from '#/platform/detection'
+import {isWeb} from '#/platform/detection'
 import {useLanguagePrefs} from '#/state/preferences'
 import {atoms as a, useTheme, web} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -17,6 +17,7 @@ import {CC_Stroke2_Corner0_Rounded as CCIcon} from '#/components/icons/CC'
 import {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/icons/PageText'
 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
+import {PortalComponent} from '#/components/Portal'
 import {Text} from '#/components/Typography'
 import {SubtitleFilePicker} from './SubtitleFilePicker'
 
@@ -29,6 +30,7 @@ interface Props {
   captions: CaptionsTrack[]
   saveAltText: (altText: string) => void
   setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
+  Portal: PortalComponent
 }
 
 export function SubtitleDialogBtn(props: Props) {
@@ -56,9 +58,7 @@ export function SubtitleDialogBtn(props: Props) {
           {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
         </ButtonText>
       </Button>
-      <Dialog.Outer
-        control={control}
-        nativeOptions={isAndroid ? {sheet: {snapPoints: ['60%']}} : {}}>
+      <Dialog.Outer control={control} Portal={props.Portal}>
         <Dialog.Handle />
         <SubtitleDialogInner {...props} />
       </Dialog.Outer>
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 2b4376b69..43555ccb4 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -20,6 +20,7 @@ import {isAndroid, isNative, isWeb} from '#/platform/detection'
 import {precacheProfile} from '#/state/queries/profile'
 import {HighPriorityImage} from '#/view/com/util/images/Image'
 import {tokens, useTheme} from '#/alf'
+import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
 import {
   Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
   Camera_Stroke2_Corner0_Rounded as Camera,
@@ -271,6 +272,7 @@ let EditableUserAvatar = ({
   const {_} = useLingui()
   const {requestCameraAccessIfNeeded} = useCameraPermission()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+  const sheetWrapper = useSheetWrapper()
 
   const aviStyle = useMemo(() => {
     if (type === 'algo' || type === 'list') {
@@ -306,9 +308,11 @@ let EditableUserAvatar = ({
       return
     }
 
-    const items = await openPicker({
-      aspect: [1, 1],
-    })
+    const items = await sheetWrapper(
+      openPicker({
+        aspect: [1, 1],
+      }),
+    )
     const item = items[0]
     if (!item) {
       return
@@ -332,7 +336,7 @@ let EditableUserAvatar = ({
         logger.error('Failed to crop banner', {error: e})
       }
     }
-  }, [onSelectNewAvatar, requestPhotoAccessIfNeeded])
+  }, [onSelectNewAvatar, requestPhotoAccessIfNeeded, sheetWrapper])
 
   const onRemoveAvatar = React.useCallback(() => {
     onSelectNewAvatar(null)
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index 13f4081fc..622cb2129 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -17,6 +17,7 @@ import {logger} from '#/logger'
 import {isAndroid, isNative} from '#/platform/detection'
 import {EventStopper} from '#/view/com/util/EventStopper'
 import {tokens, useTheme as useAlfTheme} from '#/alf'
+import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
 import {
   Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
   Camera_Stroke2_Corner0_Rounded as Camera,
@@ -43,6 +44,7 @@ export function UserBanner({
   const {_} = useLingui()
   const {requestCameraAccessIfNeeded} = useCameraPermission()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+  const sheetWrapper = useSheetWrapper()
 
   const onOpenCamera = React.useCallback(async () => {
     if (!(await requestCameraAccessIfNeeded())) {
@@ -60,7 +62,7 @@ export function UserBanner({
     if (!(await requestPhotoAccessIfNeeded())) {
       return
     }
-    const items = await openPicker()
+    const items = await sheetWrapper(openPicker())
     if (!items[0]) {
       return
     }
@@ -80,7 +82,7 @@ export function UserBanner({
         logger.error('Failed to crop banner', {error: e})
       }
     }
-  }, [onSelectNewBanner, requestPhotoAccessIfNeeded])
+  }, [onSelectNewBanner, requestPhotoAccessIfNeeded, sheetWrapper])
 
   const onRemoveBanner = React.useCallback(() => {
     onSelectNewBanner?.(null)
diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx
index 33287564a..cd1f2d3de 100644
--- a/src/view/com/util/forms/PostDropdownBtn.tsx
+++ b/src/view/com/util/forms/PostDropdownBtn.tsx
@@ -240,8 +240,8 @@ let PostDropdownBtn = ({
     Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
   }, [_, richText])
 
-  const onPressTranslate = React.useCallback(() => {
-    openLink(translatorUrl)
+  const onPressTranslate = React.useCallback(async () => {
+    await openLink(translatorUrl)
   }, [openLink, translatorUrl])
 
   const onHidePost = React.useCallback(() => {
@@ -439,7 +439,7 @@ let PostDropdownBtn = ({
               <Menu.Item
                 testID="postDropdownSendViaDMBtn"
                 label={_(msg`Send via direct message`)}
-                onPress={sendViaChatControl.open}>
+                onPress={() => sendViaChatControl.open()}>
                 <Menu.ItemText>
                   <Trans>Send via direct message</Trans>
                 </Menu.ItemText>
@@ -467,7 +467,7 @@ let PostDropdownBtn = ({
               <Menu.Item
                 testID="postDropdownEmbedBtn"
                 label={_(msg`Embed post`)}
-                onPress={embedPostControl.open}>
+                onPress={() => embedPostControl.open()}>
                 <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
                 <Menu.ItemIcon icon={CodeBrackets} position="right" />
               </Menu.Item>
@@ -542,7 +542,7 @@ let PostDropdownBtn = ({
                           ? _(msg`Hide reply for me`)
                           : _(msg`Hide post for me`)
                       }
-                      onPress={hidePromptControl.open}>
+                      onPress={() => hidePromptControl.open()}>
                       <Menu.ItemText>
                         {isReply
                           ? _(msg`Hide reply for me`)
@@ -630,7 +630,9 @@ let PostDropdownBtn = ({
                     <Menu.Item
                       testID="postDropdownEditPostInteractions"
                       label={_(msg`Edit interaction settings`)}
-                      onPress={postInteractionSettingsDialogControl.open}
+                      onPress={() =>
+                        postInteractionSettingsDialogControl.open()
+                      }
                       {...(isAuthor
                         ? Platform.select({
                             web: {
@@ -649,7 +651,7 @@ let PostDropdownBtn = ({
                     <Menu.Item
                       testID="postDropdownDeleteBtn"
                       label={_(msg`Delete post`)}
-                      onPress={deletePromptControl.open}>
+                      onPress={() => deletePromptControl.open()}>
                       <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText>
                       <Menu.ItemIcon icon={Trash} position="right" />
                     </Menu.Item>
diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx
index 0ecdf25b9..9be72ae23 100644
--- a/src/view/com/util/post-ctrls/RepostButton.tsx
+++ b/src/view/com/util/post-ctrls/RepostButton.tsx
@@ -86,7 +86,9 @@ let RepostButton = ({
           </Text>
         ) : undefined}
       </Button>
-      <Dialog.Outer control={dialogControl}>
+      <Dialog.Outer
+        control={dialogControl}
+        nativeOptions={{preventExpansion: true}}>
         <Dialog.Handle />
         <Dialog.Inner label={_(msg`Repost or quote post`)}>
           <View style={a.gap_xl}>
@@ -155,7 +157,6 @@ let RepostButton = ({
             </View>
             <Button
               label={_(msg`Cancel quote post`)}
-              onAccessibilityEscape={close}
               onPress={close}
               size="large"
               variant="solid"
diff --git a/src/view/screens/Settings/DisableEmail2FADialog.tsx b/src/view/screens/Settings/DisableEmail2FADialog.tsx
index a27cff9a3..e4341fcd2 100644
--- a/src/view/screens/Settings/DisableEmail2FADialog.tsx
+++ b/src/view/screens/Settings/DisableEmail2FADialog.tsx
@@ -79,7 +79,6 @@ export function DisableEmail2FADialog({
   return (
     <Dialog.Outer control={control}>
       <Dialog.Handle />
-
       <Dialog.ScrollableInner
         accessibilityDescribedBy="dialog-description"
         accessibilityLabelledBy="dialog-title">
diff --git a/src/view/screens/Settings/ExportCarDialog.tsx b/src/view/screens/Settings/ExportCarDialog.tsx
index a6ddb3820..1d8d26471 100644
--- a/src/view/screens/Settings/ExportCarDialog.tsx
+++ b/src/view/screens/Settings/ExportCarDialog.tsx
@@ -53,7 +53,6 @@ export function ExportCarDialog({
   return (
     <Dialog.Outer control={control}>
       <Dialog.Handle />
-
       <Dialog.ScrollableInner
         accessibilityDescribedBy="dialog-description"
         accessibilityLabelledBy="dialog-title">
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 3a9f67de8..a0a2a2755 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -2,8 +2,8 @@ import React from 'react'
 import {View} from 'react-native'
 import {useNavigation} from '@react-navigation/native'
 
+import {NavigationProp} from '#/lib/routes/types'
 import {useDialogStateControlContext} from '#/state/dialogs'
-import {NavigationProp} from 'lib/routes/types'
 import {atoms as a} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
@@ -179,19 +179,13 @@ export function Dialogs() {
       </Prompt.Outer>
 
       <Dialog.Outer control={basic}>
-        <Dialog.Handle />
-
         <Dialog.Inner label="test">
           <H3 nativeID="dialog-title">Dialog</H3>
           <P nativeID="dialog-description">A basic dialog</P>
         </Dialog.Inner>
       </Dialog.Outer>
 
-      <Dialog.Outer
-        control={scrollable}
-        nativeOptions={{sheet: {snapPoints: ['100%']}}}>
-        <Dialog.Handle />
-
+      <Dialog.Outer control={scrollable}>
         <Dialog.ScrollableInner
           accessibilityDescribedBy="dialog-description"
           accessibilityLabelledBy="dialog-title">
@@ -230,8 +224,6 @@ export function Dialogs() {
       </Dialog.Outer>
 
       <Dialog.Outer control={testDialog}>
-        <Dialog.Handle />
-
         <Dialog.ScrollableInner
           accessibilityDescribedBy="dialog-description"
           accessibilityLabelledBy="dialog-title">
@@ -356,8 +348,6 @@ export function Dialogs() {
 
       {shouldRenderUnmountTest && (
         <Dialog.Outer control={unmountTestDialog}>
-          <Dialog.Handle />
-
           <Dialog.Inner label="test">
             <H3 nativeID="dialog-title">Unmount Test Dialog</H3>
             <P nativeID="dialog-description">Will unmount in about 5 seconds</P>
diff --git a/src/view/shell/Composer.ios.tsx b/src/view/shell/Composer.ios.tsx
index 18410bf39..02efad878 100644
--- a/src/view/shell/Composer.ios.tsx
+++ b/src/view/shell/Composer.ios.tsx
@@ -1,19 +1,28 @@
-import React, {useLayoutEffect} from 'react'
+import React from 'react'
 import {Modal, View} from 'react-native'
-import {StatusBar} from 'expo-status-bar'
-import * as SystemUI from 'expo-system-ui'
 
+import {useDialogStateControlContext} from '#/state/dialogs'
 import {useComposerState} from '#/state/shell/composer'
 import {atoms as a, useTheme} from '#/alf'
-import {getBackgroundColor, useThemeName} from '#/alf/util/useColorModeTheme'
 import {ComposePost, useComposerCancelRef} from '../com/composer/Composer'
 
 export function Composer({}: {winHeight: number}) {
+  const {setFullyExpandedCount} = useDialogStateControlContext()
   const t = useTheme()
   const state = useComposerState()
   const ref = useComposerCancelRef()
 
   const open = !!state
+  const prevOpen = React.useRef(open)
+
+  React.useEffect(() => {
+    if (open && !prevOpen.current) {
+      setFullyExpandedCount(c => c + 1)
+    } else if (!open && prevOpen.current) {
+      setFullyExpandedCount(c => c - 1)
+    }
+    prevOpen.current = open
+  }, [open, setFullyExpandedCount])
 
   return (
     <Modal
@@ -24,56 +33,18 @@ export function Composer({}: {winHeight: number}) {
       animationType="slide"
       onRequestClose={() => ref.current?.onPressCancel()}>
       <View style={[t.atoms.bg, a.flex_1]}>
-        <Providers open={open}>
-          <ComposePost
-            cancelRef={ref}
-            replyTo={state?.replyTo}
-            onPost={state?.onPost}
-            quote={state?.quote}
-            quoteCount={state?.quoteCount}
-            mention={state?.mention}
-            text={state?.text}
-            imageUris={state?.imageUris}
-            videoUri={state?.videoUri}
-          />
-        </Providers>
+        <ComposePost
+          cancelRef={ref}
+          replyTo={state?.replyTo}
+          onPost={state?.onPost}
+          quote={state?.quote}
+          quoteCount={state?.quoteCount}
+          mention={state?.mention}
+          text={state?.text}
+          imageUris={state?.imageUris}
+          videoUri={state?.videoUri}
+        />
       </View>
     </Modal>
   )
 }
-
-function Providers({
-  children,
-  open,
-}: {
-  children: React.ReactNode
-  open: boolean
-}) {
-  // on iOS, it's a native formSheet. We use FullWindowOverlay to make
-  // the dialogs appear over it
-  return (
-    <>
-      {children}
-      <IOSModalBackground active={open} />
-    </>
-  )
-}
-
-// Generally, the backdrop of the app is the theme color, but when this is open
-// we want it to be black due to the modal being a form sheet.
-function IOSModalBackground({active}: {active: boolean}) {
-  const theme = useThemeName()
-
-  useLayoutEffect(() => {
-    SystemUI.setBackgroundColorAsync('black')
-
-    return () => {
-      SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
-    }
-  }, [theme])
-
-  // Set the status bar to light - however, only if the modal is active
-  // If we rely on this component being mounted to set this,
-  // there'll be a delay before it switches back to default.
-  return active ? <StatusBar style="light" animated /> : null
-}
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
index aed92cbb7..8bc3de24d 100644
--- a/src/view/shell/index.tsx
+++ b/src/view/shell/index.tsx
@@ -13,6 +13,14 @@ import * as NavigationBar from 'expo-navigation-bar'
 import {StatusBar} from 'expo-status-bar'
 import {useNavigation, useNavigationState} from '@react-navigation/native'
 
+import {useDedupe} from '#/lib/hooks/useDedupe'
+import {useNotificationsHandler} from '#/lib/hooks/useNotificationHandler'
+import {usePalette} from '#/lib/hooks/usePalette'
+import {useNotificationsRegistration} from '#/lib/notifications/notifications'
+import {isStateAtTabRoot} from '#/lib/routes/helpers'
+import {useTheme} from '#/lib/ThemeContext'
+import {isAndroid, isIOS} from '#/platform/detection'
+import {useDialogStateControlContext} from '#/state/dialogs'
 import {useSession} from '#/state/session'
 import {
   useIsDrawerOpen,
@@ -20,17 +28,9 @@ import {
   useSetDrawerOpen,
 } from '#/state/shell'
 import {useCloseAnyActiveElement} from '#/state/util'
-import {useDedupe} from 'lib/hooks/useDedupe'
-import {useNotificationsHandler} from 'lib/hooks/useNotificationHandler'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useNotificationsRegistration} from 'lib/notifications/notifications'
-import {isStateAtTabRoot} from 'lib/routes/helpers'
-import {useTheme} from 'lib/ThemeContext'
-import {isAndroid} from 'platform/detection'
-import {useDialogStateContext} from 'state/dialogs'
-import {Lightbox} from 'view/com/lightbox/Lightbox'
-import {ModalsContainer} from 'view/com/modals/Modal'
-import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
+import {Lightbox} from '#/view/com/lightbox/Lightbox'
+import {ModalsContainer} from '#/view/com/modals/Modal'
+import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
 import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
 import {SigninDialog} from '#/components/dialogs/Signin'
 import {Outlet as PortalOutlet} from '#/components/Portal'
@@ -61,7 +61,6 @@ function ShellInner() {
   const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
   const {hasSession} = useSession()
   const closeAnyActiveElement = useCloseAnyActiveElement()
-  const {importantForAccessibility} = useDialogStateContext()
 
   useNotificationsRegistration()
   useNotificationsHandler()
@@ -101,9 +100,7 @@ function ShellInner() {
 
   return (
     <>
-      <Animated.View
-        style={containerPadding}
-        importantForAccessibility={importantForAccessibility}>
+      <Animated.View style={containerPadding}>
         <ErrorBoundary>
           <Drawer
             renderDrawerContent={renderDrawerContent}
@@ -127,6 +124,7 @@ function ShellInner() {
 }
 
 export const Shell: React.FC = function ShellImpl() {
+  const {fullyExpandedCount} = useDialogStateControlContext()
   const pal = usePalette('default')
   const theme = useTheme()
   React.useEffect(() => {
@@ -140,7 +138,14 @@ export const Shell: React.FC = function ShellImpl() {
   }, [theme])
   return (
     <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
-      <StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
+      <StatusBar
+        style={
+          theme.colorScheme === 'dark' || (isIOS && fullyExpandedCount > 0)
+            ? 'light'
+            : 'dark'
+        }
+        animated
+      />
       <RoutesContainer>
         <ShellInner />
       </RoutesContainer>