From 8ba1b10ce0d278a88e37d6b6c277a41673392877 Mon Sep 17 00:00:00 2001
From: Hailey <me@haileyok.com>
Date: Thu, 2 May 2024 13:54:17 -0700
Subject: [Clipclops] Message actions for native and web (#3807)

* haptic on long press

* add animation to press and hold

* eslint disable for now

* adjust styles

* dont trigger if animation is cancelled

* organize

* add a delete menu

* reset scale automatically

* message actions dialog

cleanup

center the trigger

handle focus/unfocus better

make triggers accessible

weg dropdown menu

add a wep specific wrapper

decrease press delay

add report button

improve shrink logic

use `self_end` instead of `margin: auto`

rm extra `?`

move `MessageItem` to `components`

add delete button

* rm some padding

* update after merge

* fix merge

* web only types

* fix crash

* add an explanation

* fix web types

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
---
 src/components/dms/ActionsWrapper.tsx | 82 +++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 src/components/dms/ActionsWrapper.tsx

(limited to 'src/components/dms/ActionsWrapper.tsx')

diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx
new file mode 100644
index 000000000..107e5eb8e
--- /dev/null
+++ b/src/components/dms/ActionsWrapper.tsx
@@ -0,0 +1,82 @@
+import React, {useCallback} from 'react'
+import {Pressable, View} from 'react-native'
+import Animated, {
+  cancelAnimation,
+  runOnJS,
+  useAnimatedStyle,
+  useSharedValue,
+  withTiming,
+} from 'react-native-reanimated'
+import {ChatBskyConvoDefs} from '@atproto-labs/api'
+
+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 = useCallback(() => {
+    menuControl.open()
+  }, [menuControl])
+
+  const shrink = 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: 750}, 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}>
+        {children}
+      </AnimatedPressable>
+      <MessageMenu message={message} control={menuControl} hideTrigger={true} />
+    </View>
+  )
+}
-- 
cgit 1.4.1

