From bbff994618e06fdbc4c4e77aac2ea57ccb95d928 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 1 May 2024 17:20:45 -0700 Subject: [PATCH] add animation to press and hold --- src/components/dms/GrowWrapper.tsx | 65 +++++++ src/components/dms/MessageMenu.tsx | 175 ++---------------- .../Messages/Conversation/MessageItem.tsx | 31 ++-- 3 files changed, 98 insertions(+), 173 deletions(-) create mode 100644 src/components/dms/GrowWrapper.tsx 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} + /> + ) }