diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/dms/ChatEmptyPill.tsx | 98 | ||||
-rw-r--r-- | src/lib/custom-animations/ShrinkAndPop.ts | 27 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessagesList.tsx | 19 | ||||
-rw-r--r-- | src/screens/Messages/List/ChatListItem.tsx | 7 |
4 files changed, 141 insertions, 10 deletions
diff --git a/src/components/dms/ChatEmptyPill.tsx b/src/components/dms/ChatEmptyPill.tsx new file mode 100644 index 000000000..a6c4906a6 --- /dev/null +++ b/src/components/dms/ChatEmptyPill.tsx @@ -0,0 +1,98 @@ +import React from 'react' +import {Pressable, View} from 'react-native' +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {ScaleAndFadeIn} from 'lib/custom-animations/ScaleAndFade' +import {ShrinkAndPop} from 'lib/custom-animations/ShrinkAndPop' +import {useHaptics} from 'lib/haptics' +import {isWeb} from 'platform/detection' +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable) + +let lastIndex = 0 + +export function ChatEmptyPill() { + const t = useTheme() + const {_} = useLingui() + const playHaptic = useHaptics() + const [promptIndex, setPromptIndex] = React.useState(lastIndex) + + const scale = useSharedValue(1) + + const prompts = React.useMemo(() => { + return [ + _(msg`Say hello!`), + _(msg`Share your favorite feed!`), + _(msg`Tell a joke!`), + _(msg`Share a fun fact!`), + _(msg`Share a cool story!`), + _(msg`Send a neat website!`), + _(msg`Clip 🐴 clop 🐴`), + ] + }, [_]) + + const onPressIn = React.useCallback(() => { + if (isWeb) return + scale.value = withTiming(1.075, {duration: 100}) + }, [scale]) + + const onPressOut = React.useCallback(() => { + if (isWeb) return + scale.value = withTiming(1, {duration: 100}) + }, [scale]) + + const onPress = React.useCallback(() => { + runOnJS(playHaptic)() + let randomPromptIndex = Math.floor(Math.random() * prompts.length) + while (randomPromptIndex === lastIndex) { + randomPromptIndex = Math.floor(Math.random() * prompts.length) + } + setPromptIndex(randomPromptIndex) + lastIndex = randomPromptIndex + }, [playHaptic, prompts.length]) + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{scale: scale.value}], + })) + + return ( + <View + style={[ + a.absolute, + a.w_full, + a.z_10, + a.align_center, + { + bottom: 70, + }, + ]}> + <AnimatedPressable + style={[ + a.px_xl, + a.py_md, + a.rounded_full, + t.atoms.bg_contrast_25, + a.align_center, + animatedStyle, + ]} + entering={ScaleAndFadeIn} + exiting={ShrinkAndPop} + onPress={onPress} + onPressIn={onPressIn} + onPressOut={onPressOut}> + <Text style={[a.font_bold, a.pointer_events_none]} selectable={false}> + {prompts[promptIndex]} + </Text> + </AnimatedPressable> + </View> + ) +} diff --git a/src/lib/custom-animations/ShrinkAndPop.ts b/src/lib/custom-animations/ShrinkAndPop.ts new file mode 100644 index 000000000..ea2386c14 --- /dev/null +++ b/src/lib/custom-animations/ShrinkAndPop.ts @@ -0,0 +1,27 @@ +import {withDelay, withSequence, withTiming} from 'react-native-reanimated' + +export function ShrinkAndPop() { + 'worklet' + + const animations = { + opacity: withDelay(125, withTiming(0, {duration: 125})), + transform: [ + { + scale: withSequence( + withTiming(0.7, {duration: 75}), + withTiming(1.1, {duration: 150}), + ), + }, + ], + } + + const initialValues = { + opacity: 1, + transform: [{scale: 1}], + } + + return { + animations, + initialValues, + } +} diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index e30354508..167cc72bd 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -17,7 +17,7 @@ import {AppBskyRichtextFacet, RichText} from '@atproto/api' import {shortenLinks} from '#/lib/strings/rich-text-manip' import {isIOS, isNative} from '#/platform/detection' -import {useConvoActive} from '#/state/messages/convo' +import {isConvoActive, useConvoActive} from '#/state/messages/convo' import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types' import {useAgent} from '#/state/session' import {ScrollProvider} from 'lib/ScrollContext' @@ -26,6 +26,7 @@ import {List} from 'view/com/util/List' import {ChatDisabled} from '#/screens/Messages/Conversation/ChatDisabled' import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' import {MessageListError} from '#/screens/Messages/Conversation/MessageListError' +import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill' import {MessageItem} from '#/components/dms/MessageItem' import {NewMessagesPill} from '#/components/dms/NewMessagesPill' import {Loader} from '#/components/Loader' @@ -340,18 +341,20 @@ export function MessagesList({ /> </ScrollProvider> <KeyboardStickyView offset={{closed: -bottomOffset, opened: 0}}> - {!blocked ? ( + {convoState.status === ConvoStatus.Disabled ? ( + <ChatDisabled /> + ) : blocked ? ( + footer + ) : ( <> - {convoState.status === ConvoStatus.Disabled ? ( - <ChatDisabled /> - ) : ( - <MessageInput onSendMessage={onSendMessage} /> + {isConvoActive(convoState) && convoState.items.length === 0 && ( + <ChatEmptyPill /> )} + <MessageInput onSendMessage={onSendMessage} /> </> - ) : ( - footer )} </KeyboardStickyView> + {newMessagesPill.show && <NewMessagesPill onPress={scrollToEndOnPress} />} </> ) diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx index 682a2197e..ce0c7eee8 100644 --- a/src/screens/Messages/List/ChatListItem.tsx +++ b/src/screens/Messages/List/ChatListItem.tsx @@ -13,6 +13,7 @@ import {isNative} from '#/platform/detection' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useSession} from '#/state/session' +import {useHaptics} from 'lib/haptics' import {logEvent} from 'lib/statsig/statsig' import {sanitizeDisplayName} from 'lib/strings/display-names' import {TimeElapsed} from '#/view/com/util/TimeElapsed' @@ -70,6 +71,7 @@ function ChatListItemReady({ () => moderateProfile(profile, moderationOpts), [profile, moderationOpts], ) + const playHaptic = useHaptics() const blockInfo = React.useMemo(() => { const modui = moderation.ui('profileView') @@ -134,8 +136,9 @@ function ChatListItemReady({ ) const onLongPress = useCallback(() => { + playHaptic() menuControl.open() - }, [menuControl]) + }, [playHaptic, menuControl]) return ( <View @@ -162,7 +165,7 @@ function ChatListItemReady({ : undefined } onPress={onPress} - onLongPress={isNative ? menuControl.open : undefined} + onLongPress={isNative ? onLongPress : undefined} onAccessibilityAction={onLongPress} style={[ web({ |