Use Reanimated layout animations for Android composer (#8546)

This commit is contained in:
Samuel Newman
2026-06-05 00:37:42 +03:00
committed by GitHub
parent 144ba30ea6
commit f84c85aac6
5 changed files with 38 additions and 50 deletions
+12 -11
View File
@@ -1,29 +1,30 @@
import {useEffect, useRef} from 'react' import {useEffect} from 'react'
import {Modal, View} from 'react-native' import {Modal, View} from 'react-native'
import {SystemBars} from 'react-native-edge-to-edge'
import {useDialogStateControlContext} from '#/state/dialogs'
import {useComposerState} from '#/state/shell/composer' import {useComposerState} from '#/state/shell/composer'
import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer' import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {SheetCompatProvider as TooltipSheetCompatProvider} from '#/components/Tooltip' import {SheetCompatProvider as TooltipSheetCompatProvider} from '#/components/Tooltip'
import {IS_LIQUID_GLASS} from '#/env'
export function Composer({}: {winHeight: number}) { export function Composer() {
const {setFullyExpandedCount} = useDialogStateControlContext()
const t = useTheme() const t = useTheme()
const state = useComposerState() const state = useComposerState()
const ref = useComposerCancelRef() const ref = useComposerCancelRef()
const open = !!state const open = !!state
const prevOpen = useRef(open)
useEffect(() => { useEffect(() => {
if (open && !prevOpen.current) { if (open && !IS_LIQUID_GLASS) {
setFullyExpandedCount(c => c + 1) const entry = SystemBars.pushStackEntry({
} else if (!open && prevOpen.current) { style: {
setFullyExpandedCount(c => c - 1) statusBar: 'light',
},
})
return () => SystemBars.popStackEntry(entry)
} }
prevOpen.current = open }, [open])
}, [open, setFullyExpandedCount])
return ( return (
<Modal <Modal
+22 -30
View File
@@ -1,49 +1,41 @@
import {useEffect} from 'react' import {useEffect} from 'react'
import {Animated, Easing} from 'react-native' import {SystemBars} from 'react-native-edge-to-edge'
import Animated, {
Easing,
SlideInDown,
SlideOutDown,
} from 'react-native-reanimated'
import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue'
import {useComposerState} from '#/state/shell/composer' import {useComposerState} from '#/state/shell/composer'
import {ComposePost} from '#/view/com/composer/Composer'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {ComposePost} from '../com/composer/Composer'
export function Composer({winHeight}: {winHeight: number}) { export function Composer() {
const state = useComposerState() const state = useComposerState()
const t = useTheme() const t = useTheme()
const initInterp = useAnimatedValue(0)
const open = !!state
useEffect(() => { useEffect(() => {
if (state) { if (open) {
Animated.timing(initInterp, { const entry = SystemBars.pushStackEntry({
toValue: 1, style: {
duration: 300, statusBar: t.name !== 'light' ? 'light' : 'dark',
easing: Easing.out(Easing.exp), },
useNativeDriver: true, })
}).start() return () => SystemBars.popStackEntry(entry)
} else {
initInterp.setValue(0)
} }
}, [initInterp, state]) }, [open, t.name])
const wrapperAnimStyle = {
transform: [
{
translateY: initInterp.interpolate({
inputRange: [0, 1],
outputRange: [winHeight, 0],
}),
},
],
}
// rendering if (!open) {
// =
if (!state) {
return null return null
} }
return ( return (
<Animated.View <Animated.View
style={[a.absolute, a.inset_0, t.atoms.bg, wrapperAnimStyle]} style={[a.absolute, a.inset_0, t.atoms.bg]}
entering={SlideInDown.duration(300).easing(Easing.out(Easing.exp))}
exiting={SlideOutDown.duration(200).easing(Easing.in(Easing.quad))}
aria-modal aria-modal
accessibilityViewIsModal> accessibilityViewIsModal>
<ComposePost <ComposePost
+2 -5
View File
@@ -5,18 +5,15 @@ import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {useA11y} from '#/state/a11y' import {useA11y} from '#/state/a11y'
import {useModals} from '#/state/modals' import {useModals} from '#/state/modals'
import {type ComposerOpts, useComposerState} from '#/state/shell/composer' import {type ComposerOpts, useComposerState} from '#/state/shell/composer'
import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer'
import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf' import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf'
import {ComposePost, useComposerCancelRef} from '../com/composer/Composer'
const BOTTOM_BAR_HEIGHT = 61 const BOTTOM_BAR_HEIGHT = 61
export function Composer({}: {winHeight: number}) { export function Composer() {
const state = useComposerState() const state = useComposerState()
const isActive = !!state const isActive = !!state
// rendering
// =
if (!isActive) { if (!isActive) {
return null return null
} }
+1 -3
View File
@@ -51,7 +51,6 @@ import {Composer} from './Composer'
import {DrawerContent} from './Drawer' import {DrawerContent} from './Drawer'
function ShellInner() { function ShellInner() {
const winDim = useWindowDimensions()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const {state: policyUpdateState} = usePolicyUpdateContext() const {state: policyUpdateState} = usePolicyUpdateContext()
@@ -108,8 +107,7 @@ function ShellInner() {
<TabsNavigator layout={drawerLayout} /> <TabsNavigator layout={drawerLayout} />
</ErrorBoundary> </ErrorBoundary>
</View> </View>
<Composer />
<Composer winHeight={winDim.height} />
<ModalsContainer /> <ModalsContainer />
<MutedWordsDialog /> <MutedWordsDialog />
<SigninDialog /> <SigninDialog />
+1 -1
View File
@@ -64,7 +64,7 @@ function ShellInner() {
<ErrorBoundary> <ErrorBoundary>
<FlatNavigator layout={drawerLayout} /> <FlatNavigator layout={drawerLayout} />
</ErrorBoundary> </ErrorBoundary>
<Composer winHeight={0} /> <Composer />
<ModalsContainer /> <ModalsContainer />
<MutedWordsDialog /> <MutedWordsDialog />
<SigninDialog /> <SigninDialog />