[🐴] Fully implement keyboard controller (#4106)

* Revert "[🐴] Ensure keyboard gets dismissed when leaving screen (#4104)"

This reverts commit 3ca671d9aa.

* getting somewhere

* remove some now nuneeded code

* fully implement keyboard controller

* onStartReached check

* fix new messages pill alignment

* scroll to end on press

* simplify pill scroll logic

* update comment

* adjust logic on when to hide the pill

* fix backgrounding jank

* improve look of deleting messages

* add double tap on messages

* better onStartReached logic

* nit

* add hit slop to the gesture

* better gestures for press and hold

* nits
This commit is contained in:
Hailey
2024-05-19 19:25:49 -07:00
committed by GitHub
parent 7de0b0a58c
commit 52beb29a0d
10 changed files with 419 additions and 356 deletions
+38 -36
View File
@@ -1,5 +1,6 @@
import React from 'react'
import {Keyboard, Pressable, View} from 'react-native'
import {Keyboard} from 'react-native'
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
import Animated, {
cancelAnimation,
runOnJS,
@@ -15,8 +16,6 @@ import {atoms as a} from '#/alf'
import {MessageMenu} from '#/components/dms/MessageMenu'
import {useMenuControl} from '#/components/Menu'
const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
export function ActionsWrapper({
message,
isFromSelf,
@@ -30,56 +29,59 @@ export function ActionsWrapper({
const menuControl = useMenuControl()
const scale = useSharedValue(1)
const animationDidComplete = useSharedValue(false)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{scale: scale.value}],
}))
// Reanimated's `runOnJS` doesn't like refs, so we can't use `runOnJS(menuControl.open)()`. Instead, we'll use this
// function
const open = React.useCallback(() => {
playHaptic()
Keyboard.dismiss()
menuControl.open()
}, [menuControl])
}, [menuControl, playHaptic])
const shrink = React.useCallback(() => {
'worklet'
cancelAnimation(scale)
scale.value = withTiming(1, {duration: 200}, () => {
animationDidComplete.value = false
})
}, [animationDidComplete, scale])
scale.value = withTiming(1, {duration: 200})
}, [scale])
const grow = React.useCallback(() => {
'worklet'
scale.value = withTiming(1.05, {duration: 450}, finished => {
if (!finished) return
animationDidComplete.value = true
runOnJS(playHaptic)()
runOnJS(open)()
const doubleTapGesture = Gesture.Tap()
.numberOfTaps(2)
.hitSlop(HITSLOP_10)
.onEnd(open)
shrink()
const pressAndHoldGesture = Gesture.LongPress()
.onStart(() => {
scale.value = withTiming(1.05, {duration: 200}, finished => {
if (!finished) return
runOnJS(open)()
shrink()
})
})
}, [scale, animationDidComplete, playHaptic, shrink, open])
.onTouchesUp(shrink)
.onTouchesMove(shrink)
.cancelsTouchesInView(false)
.runOnJS(true)
const composedGestures = Gesture.Exclusive(
doubleTapGesture,
pressAndHoldGesture,
)
return (
<View
style={[
{
maxWidth: '80%',
},
isFromSelf ? a.self_end : a.self_start,
]}>
<AnimatedPressable
style={animatedStyle}
unstable_pressDelay={200}
onPressIn={grow}
onTouchEnd={shrink}
hitSlop={HITSLOP_10}>
<GestureDetector gesture={composedGestures}>
<Animated.View
style={[
{
maxWidth: '80%',
},
isFromSelf ? a.self_end : a.self_start,
animatedStyle,
]}>
{children}
</AnimatedPressable>
<MessageMenu message={message} control={menuControl} />
</View>
<MessageMenu message={message} control={menuControl} />
</Animated.View>
</GestureDetector>
)
}