about summary refs log tree commit diff
path: root/src/components/dms/ActionsWrapper.tsx
blob: 315f459de93c9455d3a38056b11640115e106d90 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React from 'react'
import {Keyboard, Pressable, View} from 'react-native'
import Animated, {
  cancelAnimation,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated'
import {ChatBskyConvoDefs} from '@atproto-labs/api'

import {HITSLOP_10} from 'lib/constants'
import {useHaptics} from 'lib/haptics'
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,
  children,
}: {
  message: ChatBskyConvoDefs.MessageView
  isFromSelf: boolean
  children: React.ReactNode
}) {
  const playHaptic = useHaptics()
  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(() => {
    Keyboard.dismiss()
    menuControl.open()
  }, [menuControl])

  const shrink = React.useCallback(() => {
    'worklet'
    cancelAnimation(scale)
    scale.value = withTiming(1, {duration: 200}, () => {
      animationDidComplete.value = false
    })
  }, [animationDidComplete, scale])

  const grow = React.useCallback(() => {
    'worklet'
    scale.value = withTiming(1.05, {duration: 450}, finished => {
      if (!finished) return
      animationDidComplete.value = true
      runOnJS(playHaptic)()
      runOnJS(open)()

      shrink()
    })
  }, [scale, animationDidComplete, playHaptic, shrink, open])

  return (
    <View
      style={[
        {
          maxWidth: '65%',
        },
        isFromSelf ? a.self_end : a.self_start,
      ]}>
      <AnimatedPressable
        style={animatedStyle}
        unstable_pressDelay={200}
        onPressIn={grow}
        onTouchEnd={shrink}
        hitSlop={HITSLOP_10}>
        {children}
      </AnimatedPressable>
      <MessageMenu message={message} control={menuControl} hideTrigger={true} />
    </View>
  )
}