Flash message bubble when tapping a reply (#10929)
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
FadeOut,
|
FadeOut,
|
||||||
|
interpolateColor,
|
||||||
LayoutAnimationConfig,
|
LayoutAnimationConfig,
|
||||||
LinearTransition,
|
LinearTransition,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
@@ -233,6 +234,15 @@ let MessageItem = ({
|
|||||||
|
|
||||||
const pendingColor = t.palette.primary_300
|
const pendingColor = t.palette.primary_300
|
||||||
|
|
||||||
|
const bubbleColor = isFromSelf
|
||||||
|
? isPending
|
||||||
|
? pendingColor
|
||||||
|
: t.palette.primary_500
|
||||||
|
: t.palette.contrast_50
|
||||||
|
const highlightColor = isFromSelf
|
||||||
|
? t.palette.primary_300
|
||||||
|
: t.palette.primary_100
|
||||||
|
|
||||||
const rt = new RichTextAPI({text: message.text, facets: message.facets})
|
const rt = new RichTextAPI({text: message.text, facets: message.facets})
|
||||||
|
|
||||||
const hasEmbed =
|
const hasEmbed =
|
||||||
@@ -278,7 +288,11 @@ let MessageItem = ({
|
|||||||
}, [highlightKey, highlightSV])
|
}, [highlightKey, highlightSV])
|
||||||
|
|
||||||
const highlightStyle = useAnimatedStyle(() => ({
|
const highlightStyle = useAnimatedStyle(() => ({
|
||||||
opacity: highlightSV.get(),
|
backgroundColor: interpolateColor(
|
||||||
|
highlightSV.get(),
|
||||||
|
[0, 1],
|
||||||
|
[bubbleColor, highlightColor],
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const borderRadiusStyle = useAnimatedStyle(() =>
|
const borderRadiusStyle = useAnimatedStyle(() =>
|
||||||
@@ -427,14 +441,6 @@ let MessageItem = ({
|
|||||||
web: a.mx_lg,
|
web: a.mx_lg,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Negative of `messageInset` so the flash bleeds past the row's horizontal
|
|
||||||
// margin to the screen edges.
|
|
||||||
const flashBleed = platform<number>({
|
|
||||||
android: -a.mx_sm.marginLeft,
|
|
||||||
ios: -a.mx_md.marginLeft,
|
|
||||||
web: -a.mx_lg.marginLeft,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasLargeGapFromPrev && <DateDivider date={message.sentAt} />}
|
{hasLargeGapFromPrev && <DateDivider date={message.sentAt} />}
|
||||||
@@ -444,20 +450,6 @@ let MessageItem = ({
|
|||||||
isFirstInCluster ? a.mt_md : {marginTop: CLUSTERED_MESSAGE_GAP},
|
isFirstInCluster ? a.mt_md : {marginTop: CLUSTERED_MESSAGE_GAP},
|
||||||
hasReactions && {paddingBottom: 26},
|
hasReactions && {paddingBottom: 26},
|
||||||
]}>
|
]}>
|
||||||
<Animated.View
|
|
||||||
pointerEvents="none"
|
|
||||||
style={[
|
|
||||||
a.absolute,
|
|
||||||
{
|
|
||||||
top: -CLUSTERED_MESSAGE_GAP,
|
|
||||||
bottom: -CLUSTERED_MESSAGE_GAP,
|
|
||||||
left: flashBleed,
|
|
||||||
right: flashBleed,
|
|
||||||
backgroundColor: t.palette.primary_100,
|
|
||||||
},
|
|
||||||
highlightStyle,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View style={[a.relative]}>
|
<View style={[a.relative]}>
|
||||||
{showAvatar ? (
|
{showAvatar ? (
|
||||||
<View style={[a.absolute, a.bottom_0, a.z_50]}>{avatar}</View>
|
<View style={[a.absolute, a.bottom_0, a.z_50]}>{avatar}</View>
|
||||||
@@ -508,6 +500,7 @@ let MessageItem = ({
|
|||||||
squaredBottomCorner || hasEmbedAndText
|
squaredBottomCorner || hasEmbedAndText
|
||||||
}
|
}
|
||||||
squaredTopCorner={squaredTopCorner}
|
squaredTopCorner={squaredTopCorner}
|
||||||
|
highlightSV={highlightSV}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{ChatBskyEmbedJoinLink.isView(message.embed) && (
|
{ChatBskyEmbedJoinLink.isView(message.embed) && (
|
||||||
@@ -519,6 +512,7 @@ let MessageItem = ({
|
|||||||
squaredBottomCorner || hasEmbedAndText
|
squaredBottomCorner || hasEmbedAndText
|
||||||
}
|
}
|
||||||
squaredTopCorner={squaredTopCorner}
|
squaredTopCorner={squaredTopCorner}
|
||||||
|
highlightSV={highlightSV}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{rt.text.length > 0 && (
|
{rt.text.length > 0 && (
|
||||||
@@ -535,14 +529,10 @@ let MessageItem = ({
|
|||||||
marginTop: hasEmbedAndText
|
marginTop: hasEmbedAndText
|
||||||
? CLUSTERED_MESSAGE_GAP
|
? CLUSTERED_MESSAGE_GAP
|
||||||
: 0,
|
: 0,
|
||||||
backgroundColor: isFromSelf
|
|
||||||
? isPending
|
|
||||||
? pendingColor
|
|
||||||
: t.palette.primary_500
|
|
||||||
: t.palette.contrast_50,
|
|
||||||
},
|
},
|
||||||
isFromSelf ? a.self_end : a.self_start,
|
isFromSelf ? a.self_end : a.self_start,
|
||||||
borderRadiusStyle,
|
borderRadiusStyle,
|
||||||
|
highlightStyle,
|
||||||
],
|
],
|
||||||
]}>
|
]}>
|
||||||
{replyTo && !isOnlyEmoji(message.text) ? (
|
{replyTo && !isOnlyEmoji(message.text) ? (
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import {memo} from 'react'
|
import {memo} from 'react'
|
||||||
import {useWindowDimensions, View} from 'react-native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
|
import Animated, {
|
||||||
|
interpolateColor,
|
||||||
|
type SharedValue,
|
||||||
|
useAnimatedStyle,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
import {type $Typed, type AppBskyEmbedRecord} from '@atproto/api'
|
import {type $Typed, type AppBskyEmbedRecord} from '@atproto/api'
|
||||||
|
|
||||||
import {atoms as a, native, useTheme, web} from '#/alf'
|
import {atoms as a, native, useTheme, web} from '#/alf'
|
||||||
@@ -15,16 +20,48 @@ let MessageItemEmbed = ({
|
|||||||
isGroupChat,
|
isGroupChat,
|
||||||
squaredTopCorner,
|
squaredTopCorner,
|
||||||
squaredBottomCorner,
|
squaredBottomCorner,
|
||||||
|
highlightSV,
|
||||||
}: {
|
}: {
|
||||||
embed: $Typed<AppBskyEmbedRecord.View>
|
embed: $Typed<AppBskyEmbedRecord.View>
|
||||||
isFromSelf: boolean
|
isFromSelf: boolean
|
||||||
isGroupChat: boolean
|
isGroupChat: boolean
|
||||||
squaredTopCorner: boolean
|
squaredTopCorner: boolean
|
||||||
squaredBottomCorner: boolean
|
squaredBottomCorner: boolean
|
||||||
|
highlightSV: SharedValue<number>
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const screen = useWindowDimensions()
|
const screen = useWindowDimensions()
|
||||||
|
|
||||||
|
const restingColor = isFromSelf ? t.palette.primary_50 : t.palette.contrast_50
|
||||||
|
const highlightColor = isFromSelf
|
||||||
|
? t.palette.primary_300
|
||||||
|
: t.palette.primary_100
|
||||||
|
const highlightStyle = useAnimatedStyle(() => ({
|
||||||
|
backgroundColor: interpolateColor(
|
||||||
|
highlightSV.get(),
|
||||||
|
[0, 1],
|
||||||
|
[restingColor, highlightColor],
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const radiiStyle = isFromSelf
|
||||||
|
? {
|
||||||
|
borderBottomRightRadius: squaredBottomCorner
|
||||||
|
? SQUARED_BORDER_RADIUS
|
||||||
|
: BORDER_RADIUS,
|
||||||
|
borderTopRightRadius: squaredTopCorner
|
||||||
|
? SQUARED_BORDER_RADIUS
|
||||||
|
: BORDER_RADIUS,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
borderBottomLeftRadius: squaredBottomCorner
|
||||||
|
? SQUARED_BORDER_RADIUS
|
||||||
|
: BORDER_RADIUS,
|
||||||
|
borderTopLeftRadius: squaredTopCorner
|
||||||
|
? SQUARED_BORDER_RADIUS
|
||||||
|
: BORDER_RADIUS,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageContextProvider>
|
<MessageContextProvider>
|
||||||
<View
|
<View
|
||||||
@@ -44,37 +81,15 @@ let MessageItemEmbed = ({
|
|||||||
// CLUSTERED_MESSAGE_GAP (2px) is the only spacing applied
|
// CLUSTERED_MESSAGE_GAP (2px) is the only spacing applied
|
||||||
{marginTop: -a.mt_sm.marginTop},
|
{marginTop: -a.mt_sm.marginTop},
|
||||||
]}>
|
]}>
|
||||||
<View>
|
<Animated.View
|
||||||
|
style={[a.rounded_xl, a.overflow_hidden, radiiStyle, highlightStyle]}>
|
||||||
<Embed
|
<Embed
|
||||||
embed={embed}
|
embed={embed}
|
||||||
allowNestedQuotes
|
allowNestedQuotes
|
||||||
viewContext={PostEmbedViewContext.ChatMessage}
|
viewContext={PostEmbedViewContext.ChatMessage}
|
||||||
style={[
|
style={[a.rounded_xl, a.overflow_hidden, a.border_0, radiiStyle]}
|
||||||
a.rounded_xl,
|
|
||||||
a.overflow_hidden,
|
|
||||||
a.border_0,
|
|
||||||
isFromSelf
|
|
||||||
? {
|
|
||||||
backgroundColor: t.palette.primary_50,
|
|
||||||
borderBottomRightRadius: squaredBottomCorner
|
|
||||||
? SQUARED_BORDER_RADIUS
|
|
||||||
: BORDER_RADIUS,
|
|
||||||
borderTopRightRadius: squaredTopCorner
|
|
||||||
? SQUARED_BORDER_RADIUS
|
|
||||||
: BORDER_RADIUS,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
backgroundColor: t.palette.contrast_50,
|
|
||||||
borderBottomLeftRadius: squaredBottomCorner
|
|
||||||
? SQUARED_BORDER_RADIUS
|
|
||||||
: BORDER_RADIUS,
|
|
||||||
borderTopLeftRadius: squaredTopCorner
|
|
||||||
? SQUARED_BORDER_RADIUS
|
|
||||||
: BORDER_RADIUS,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</MessageContextProvider>
|
</MessageContextProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import {memo} from 'react'
|
import {memo} from 'react'
|
||||||
import {useWindowDimensions, View} from 'react-native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
|
import Animated, {
|
||||||
|
interpolateColor,
|
||||||
|
type SharedValue,
|
||||||
|
useAnimatedStyle,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api'
|
import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api'
|
||||||
|
|
||||||
import {useConvoActive} from '#/state/messages/convo'
|
import {useConvoActive} from '#/state/messages/convo'
|
||||||
@@ -17,17 +22,31 @@ let MessageItemInviteEmbed = ({
|
|||||||
isGroupChat,
|
isGroupChat,
|
||||||
squaredTopCorner,
|
squaredTopCorner,
|
||||||
squaredBottomCorner,
|
squaredBottomCorner,
|
||||||
|
highlightSV,
|
||||||
}: {
|
}: {
|
||||||
embed: $Typed<ChatBskyEmbedJoinLink.View>
|
embed: $Typed<ChatBskyEmbedJoinLink.View>
|
||||||
isFromSelf: boolean
|
isFromSelf: boolean
|
||||||
isGroupChat: boolean
|
isGroupChat: boolean
|
||||||
squaredTopCorner: boolean
|
squaredTopCorner: boolean
|
||||||
squaredBottomCorner: boolean
|
squaredBottomCorner: boolean
|
||||||
|
highlightSV: SharedValue<number>
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const screen = useWindowDimensions()
|
const screen = useWindowDimensions()
|
||||||
const convo = useConvoActive()
|
const convo = useConvoActive()
|
||||||
|
|
||||||
|
const restingColor = isFromSelf ? t.palette.primary_50 : t.palette.contrast_50
|
||||||
|
const highlightColor = isFromSelf
|
||||||
|
? t.palette.primary_300
|
||||||
|
: t.palette.primary_100
|
||||||
|
const highlightStyle = useAnimatedStyle(() => ({
|
||||||
|
backgroundColor: interpolateColor(
|
||||||
|
highlightSV.get(),
|
||||||
|
[0, 1],
|
||||||
|
[restingColor, highlightColor],
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
const code = isKnownJoinLinkPreview(embed.joinLinkPreview)
|
const code = isKnownJoinLinkPreview(embed.joinLinkPreview)
|
||||||
? embed.joinLinkPreview.code
|
? embed.joinLinkPreview.code
|
||||||
: undefined
|
: undefined
|
||||||
@@ -49,14 +68,13 @@ let MessageItemInviteEmbed = ({
|
|||||||
maxWidth: 360,
|
maxWidth: 360,
|
||||||
}),
|
}),
|
||||||
]}>
|
]}>
|
||||||
<View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
a.p_md,
|
a.p_md,
|
||||||
a.gap_md,
|
a.gap_md,
|
||||||
a.overflow_hidden,
|
a.overflow_hidden,
|
||||||
isFromSelf
|
isFromSelf
|
||||||
? {
|
? {
|
||||||
backgroundColor: t.palette.primary_50,
|
|
||||||
borderBottomRightRadius: squaredBottomCorner
|
borderBottomRightRadius: squaredBottomCorner
|
||||||
? SQUARED_BORDER_RADIUS
|
? SQUARED_BORDER_RADIUS
|
||||||
: BORDER_RADIUS,
|
: BORDER_RADIUS,
|
||||||
@@ -67,7 +85,6 @@ let MessageItemInviteEmbed = ({
|
|||||||
borderTopLeftRadius: BORDER_RADIUS,
|
borderTopLeftRadius: BORDER_RADIUS,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
backgroundColor: t.palette.contrast_50,
|
|
||||||
borderBottomLeftRadius: squaredBottomCorner
|
borderBottomLeftRadius: squaredBottomCorner
|
||||||
? SQUARED_BORDER_RADIUS
|
? SQUARED_BORDER_RADIUS
|
||||||
: BORDER_RADIUS,
|
: BORDER_RADIUS,
|
||||||
@@ -77,6 +94,7 @@ let MessageItemInviteEmbed = ({
|
|||||||
borderBottomRightRadius: BORDER_RADIUS,
|
borderBottomRightRadius: BORDER_RADIUS,
|
||||||
borderTopRightRadius: BORDER_RADIUS,
|
borderTopRightRadius: BORDER_RADIUS,
|
||||||
},
|
},
|
||||||
|
highlightStyle,
|
||||||
]}>
|
]}>
|
||||||
<ChatInvite.Root
|
<ChatInvite.Root
|
||||||
code={code}
|
code={code}
|
||||||
@@ -85,7 +103,7 @@ let MessageItemInviteEmbed = ({
|
|||||||
hasFixedHeight={false}>
|
hasFixedHeight={false}>
|
||||||
<MessageItemInviteEmbedBody />
|
<MessageItemInviteEmbedBody />
|
||||||
</ChatInvite.Root>
|
</ChatInvite.Root>
|
||||||
</View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</MessageContextProvider>
|
</MessageContextProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user