allow swipe dismissal on iOS

This commit is contained in:
Samuel Newman
2025-09-11 15:07:12 +03:00
parent 60d826c4f0
commit fa52da8628
2 changed files with 38 additions and 15 deletions
+26 -11
View File
@@ -173,8 +173,10 @@ export const ComposePost = ({
imageUris: initImageUris,
videoUri: initVideoUri,
cancelRef,
setIsDirty,
}: Props & {
cancelRef?: React.RefObject<CancelRef | null>
setIsDirty?: React.Dispatch<React.SetStateAction<boolean>>
}) => {
const {currentAccount} = useSession()
const agent = useAgent()
@@ -304,24 +306,37 @@ export const ComposePost = ({
[insets, isKeyboardVisible],
)
const onPressCancel = useCallback(() => {
if (textInput.current?.maybeClosePopup()) {
const isDirty = thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
// very unfortunate, but we need to pass state back up to the parent on iOS
//
// WARNING - if the Modal on iOS thinks it's not dirty,
// `allowSwipeDismissal` will be true, and if we don't then close the composer
// when `onPressCancel` is called it will be bad (might even softlock)
// so we need to keep the parent state and the behaviour of onPressCancel in
// tight sync. do NOT force the modal to stay open without marking it as dirty! -sfn
useEffect(() => {
if (isIOS) {
setIsDirty?.(isDirty)
}
}, [isDirty, setIsDirty])
const onPressCancel = useNonReactiveCallback(() => {
// web only, so it's fine w.r.t. the Modal
const didCloseAutocomplete = textInput.current?.maybeClosePopup()
if (isWeb && didCloseAutocomplete) {
return
} else if (
thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 ||
post.embed.media ||
post.embed.link,
)
) {
} else if (isDirty) {
closeAllDialogs()
Keyboard.dismiss()
discardPromptControl.open()
} else {
onClose()
}
}, [thread, closeAllDialogs, discardPromptControl, onClose])
})
useImperativeHandle(cancelRef, () => ({onPressCancel}))
+12 -4
View File
@@ -1,4 +1,4 @@
import React from 'react'
import {useEffect, useRef, useState} from 'react'
import {Modal, View} from 'react-native'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -11,11 +11,17 @@ export function Composer({}: {winHeight: number}) {
const t = useTheme()
const state = useComposerState()
const ref = useComposerCancelRef()
const [isDirty, setIsDirty] = useState(
!!state?.text ||
!!state?.imageUris ||
!!state?.videoUri ||
!!state?.mention,
)
const open = !!state
const prevOpen = React.useRef(open)
const prevOpen = useRef(open)
React.useEffect(() => {
useEffect(() => {
if (open && !prevOpen.current) {
setFullyExpandedCount(c => c + 1)
} else if (!open && prevOpen.current) {
@@ -31,7 +37,8 @@ export function Composer({}: {winHeight: number}) {
visible={open}
presentationStyle="pageSheet"
animationType="slide"
onRequestClose={() => ref.current?.onPressCancel()}>
onRequestClose={() => ref.current?.onPressCancel()}
allowSwipeDismissal={!isDirty}>
<View style={[t.atoms.bg, a.flex_1]}>
<ComposePost
cancelRef={ref}
@@ -43,6 +50,7 @@ export function Composer({}: {winHeight: number}) {
text={state?.text}
imageUris={state?.imageUris}
videoUri={state?.videoUri}
setIsDirty={setIsDirty}
/>
</View>
</Modal>