diff --git a/src/components/dms/GrowWrapper.tsx b/src/components/dms/GrowWrapper.tsx
new file mode 100644
index 0000000000..4999c0e23a
--- /dev/null
+++ b/src/components/dms/GrowWrapper.tsx
@@ -0,0 +1,65 @@
+import React, {useCallback, useImperativeHandle} from 'react'
+import {Pressable} from 'react-native'
+import Animated, {
+ cancelAnimation,
+ runOnJS,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+} from 'react-native-reanimated'
+
+const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
+
+export interface GrowWrapperRef {
+ reset: () => void
+}
+
+export const GrowWrapper = React.forwardRef(function GrowWrapper(
+ {
+ onOpenMenu,
+ children,
+ }: {
+ onOpenMenu: () => unknown
+ children: React.ReactNode
+ },
+ ref,
+) {
+ useImperativeHandle(ref, () => ({
+ reset,
+ }))
+
+ const scale = useSharedValue(1)
+ const animationDidComplete = useSharedValue(false)
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [{scale: scale.value}],
+ }))
+
+ const reset = useCallback(() => {
+ cancelAnimation(scale)
+ scale.value = withTiming(1, {duration: 200})
+ }, [scale])
+
+ const onTouchStart = React.useCallback(() => {
+ scale.value = withTiming(1.05, {duration: 750}, () => {
+ animationDidComplete.value = true
+ runOnJS(onOpenMenu)()
+ })
+ }, [scale, animationDidComplete, onOpenMenu])
+
+ const onTouchEnd = React.useCallback(() => {
+ if (!animationDidComplete.value) {
+ reset()
+ }
+ }, [animationDidComplete, reset])
+
+ return (
+
+ {children}
+
+ )
+})
diff --git a/src/components/dms/MessageMenu.tsx b/src/components/dms/MessageMenu.tsx
index 0e267159a7..43190e2b91 100644
--- a/src/components/dms/MessageMenu.tsx
+++ b/src/components/dms/MessageMenu.tsx
@@ -1,173 +1,30 @@
import React from 'react'
-import {Pressable} from 'react-native'
-import {AppBskyActorDefs} from '@atproto/api'
+import {View} from 'react-native'
import {ChatBskyConvoDefs} from '@atproto-labs/api'
-import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {useNavigation} from '@react-navigation/native'
-import {NavigationProp} from '#/lib/routes/types'
-import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
-import {
- useMuteConvo,
- useUnmuteConvo,
-} from '#/state/queries/messages/mute-conversation'
-import * as Toast from '#/view/com/util/Toast'
-import {atoms as a, useTheme} from '#/alf'
-import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft'
-import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
-import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
-import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
-import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
-import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck'
-import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
-import * as Menu from '#/components/Menu'
-import * as Prompt from '#/components/Prompt'
+import {atoms as a} from '#/alf'
+import * as Dialog from '#/components/Dialog'
-let ConvoMenu = ({
- convo,
- profile,
- onUpdateConvo,
+export let MessageMenu = ({
+ message,
+ onClose,
control,
- hideTrigger,
}: {
- convo: ChatBskyConvoDefs.ConvoView
- profile: AppBskyActorDefs.ProfileViewBasic
- onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void
- control?: Menu.MenuControlProps
- hideTrigger?: boolean
+ message: ChatBskyConvoDefs.MessageView
+ onClose: () => void
+ control: Dialog.DialogControlProps
}): React.ReactNode => {
- const navigation = useNavigation()
const {_} = useLingui()
- const t = useTheme()
- const leaveConvoControl = Prompt.usePromptControl()
-
- const onNavigateToProfile = useCallback(() => {
- navigation.navigate('Profile', {name: profile.did})
- }, [navigation, profile.did])
-
- const {mutate: muteConvo} = useMuteConvo(convo.id, {
- onSuccess: data => {
- onUpdateConvo?.(data.convo)
- Toast.show(_(msg`Chat muted`))
- },
- onError: () => {
- Toast.show(_(msg`Could not mute chat`))
- },
- })
-
- const {mutate: unmuteConvo} = useUnmuteConvo(convo.id, {
- onSuccess: data => {
- onUpdateConvo?.(data.convo)
- Toast.show(_(msg`Chat unmuted`))
- },
- onError: () => {
- Toast.show(_(msg`Could not unmute chat`))
- },
- })
-
- const {mutate: leaveConvo} = useLeaveConvo(convo.id, {
- onSuccess: () => {
- navigation.popToTop()
- },
- onError: () => {
- Toast.show(_(msg`Could not leave chat`))
- },
- })
return (
- <>
-
- {!hideTrigger && (
-
- {({props, state}) => (
-
-
-
- )}
-
- )}
-
-
-
-
- Go to profile
-
-
-
- (convo?.muted ? unmuteConvo() : muteConvo())}>
-
- {convo?.muted ? (
- Unmute notifications
- ) : (
- Mute notifications
- )}
-
-
-
-
- {/* TODO(samuel): implement these */}
-
- {}}
- disabled>
-
- Block account
-
-
-
- {}}
- disabled>
-
- Report account
-
-
-
-
-
-
-
- Leave conversation
-
-
-
-
-
-
+
+
- leaveConvo()}
- />
- >
+
+
+
+
)
}
-ConvoMenu = React.memo(ConvoMenu)
-
-export {ConvoMenu}
+MessageMenu = React.memo(MessageMenu)
diff --git a/src/screens/Messages/Conversation/MessageItem.tsx b/src/screens/Messages/Conversation/MessageItem.tsx
index 4ffdae13ec..f6c90d6a78 100644
--- a/src/screens/Messages/Conversation/MessageItem.tsx
+++ b/src/screens/Messages/Conversation/MessageItem.tsx
@@ -1,20 +1,19 @@
import React, {useCallback, useMemo} from 'react'
-import {Pressable, StyleProp, TextStyle, View} from 'react-native'
+import {StyleProp, TextStyle, View} from 'react-native'
import {ChatBskyConvoDefs} from '@atproto-labs/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSession} from '#/state/session'
import {useHaptics} from 'lib/haptics'
-import {isNative, isWeb} from 'platform/detection'
+import {isNative} from 'platform/detection'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
+import {GrowWrapper, GrowWrapperRef} from '#/components/dms/GrowWrapper'
+import {MessageMenu} from '#/components/dms/MessageMenu'
import {Text} from '#/components/Typography'
-function onPressPlaceholder() {
- // Placeholder onPress function
-}
-
export function MessageItem({
item,
next,
@@ -29,6 +28,9 @@ export function MessageItem({
const {currentAccount} = useSession()
const playHaptic = useHaptics()
+ const control = useDialogControl()
+ const itemRef = React.useRef(null)
+
const isFromSelf = item.sender?.did === currentAccount?.did
const isNextFromSelf =
@@ -59,15 +61,11 @@ export function MessageItem({
if (isNative) {
playHaptic()
}
-
- // Open menu
- }, [playHaptic])
+ control.open()
+ }, [control, playHaptic])
return (
-
+
-
+ itemRef.current?.reset()}
+ control={control}
+ />
+
)
}