Update context menu presentation (#10426)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"><path fill="#000" fill-rule="evenodd" d="M14.5 8a1 1 0 0 1 .912.59l4.5 10a1 1 0 0 1-1.824.82L16.554 16h-4.108l-1.534 3.41a1 1 0 0 1-1.824-.82l4.5-10A1 1 0 0 1 14.5 8m-1.153 6h2.306L14.5 11.437zM5.053.106a1 1 0 0 1 1.342.447L7.118 2H11a1 1 0 0 1 0 2H9.92c-.28 1.765-1.27 3.24-2.393 4.398a17 17 0 0 0 2.605 1.562q.129.062.197.092l.005.002.049.022.01.005a1 1 0 0 1-.787 1.838h-.002l-.027-.013-.068-.03-.242-.113A19 19 0 0 1 6 9.75a19 19 0 0 1-3.509 2.125l-.068.03-.027.013h-.002a1 1 0 0 1-.788-1.838l.011-.005.049-.022.005-.002q.069-.03.197-.092a17 17 0 0 0 2.604-1.562C3.348 7.239 2.36 5.764 2.08 4H1a1 1 0 0 1 0-2h3.882l-.277-.553A1 1 0 0 1 5.053.106M4.118 4c.27 1.151.98 2.193 1.882 3.1.901-.907 1.612-1.95 1.882-3.1z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 819 B |
@@ -378,7 +378,10 @@ export function Trigger({
|
||||
<View ref={ref} style={[{opacity: context.isOpen ? 0 : 1}, style]}>
|
||||
{children({
|
||||
IS_NATIVE: true,
|
||||
control: {isOpen: context.isOpen, open},
|
||||
control: {
|
||||
isOpen: context.isOpen,
|
||||
open: mode => void open(mode),
|
||||
},
|
||||
state: {
|
||||
pressed: false,
|
||||
hovered: false,
|
||||
@@ -594,10 +597,12 @@ const MENU_WIDTH = 240
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
label,
|
||||
style,
|
||||
align = 'left',
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
label?: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
align?: 'left' | 'right'
|
||||
}) {
|
||||
@@ -709,23 +714,25 @@ export function Outer({
|
||||
]}>
|
||||
{/* innermost element - needs an overflow: hidden for children, but we also need a shadow,
|
||||
so put the shadow on the scaling element and the overflow on the innermost element */}
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}>
|
||||
{label ? (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.pl_md,
|
||||
a.pt_md,
|
||||
a.pr_lg,
|
||||
a.pb_md,
|
||||
a.text_xs,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{label}
|
||||
</Text>
|
||||
) : null}
|
||||
{flattenReactChildren(children).map((child, i) => {
|
||||
return isValidElement(child) &&
|
||||
(child.type === Item || child.type === Divider) ? (
|
||||
<Fragment key={i}>
|
||||
{i > 0 ? (
|
||||
<View
|
||||
style={[a.border_b, t.atoms.border_contrast_low]}
|
||||
/>
|
||||
) : null}
|
||||
{cloneElement(child, {
|
||||
// @ts-expect-error not typed
|
||||
style: {
|
||||
@@ -736,6 +743,7 @@ export function Outer({
|
||||
</Fragment>
|
||||
) : null
|
||||
})}
|
||||
{label ? <View style={[a.pb_md]} /> : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
@@ -840,13 +848,10 @@ export function Item({
|
||||
!unstyled && [
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
a.px_md,
|
||||
a.px_2xl,
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
t.atoms.border_contrast_low,
|
||||
{minHeight: 44, paddingVertical: 10},
|
||||
{gap: 6, minHeight: 44, paddingVertical: 10},
|
||||
(focused || pressed || context.hoveredMenuItem === id) &&
|
||||
!rest.disabled &&
|
||||
t.atoms.bg_contrast_50,
|
||||
@@ -877,7 +882,6 @@ export function ItemText({children, style}: ItemTextProps) {
|
||||
a.text_md,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
{paddingTop: 3},
|
||||
style,
|
||||
disabled && t.atoms.text_contrast_low,
|
||||
]}>
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
import {type StyleProp, type ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {type AuxiliaryViewProps} from './types'
|
||||
|
||||
export * from '#/components/Menu'
|
||||
export {
|
||||
ContainerItem,
|
||||
type MenuControlProps as ContextMenuControlProps,
|
||||
Divider,
|
||||
Group,
|
||||
Item,
|
||||
ItemIcon,
|
||||
ItemRadio,
|
||||
ItemText,
|
||||
LabelText,
|
||||
Root,
|
||||
Trigger,
|
||||
useMenuContext as useContextMenuContext,
|
||||
useMenuControl as useContextMenuControl,
|
||||
} from '#/components/Menu'
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
return children
|
||||
@@ -10,3 +29,34 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||
export function AuxiliaryView({}: AuxiliaryViewProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
label,
|
||||
style,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
label?: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Menu.Outer style={style}>
|
||||
{label ? (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.pl_sm,
|
||||
a.pt_md,
|
||||
a.pr_lg,
|
||||
a.pb_md,
|
||||
a.text_xs,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{label}
|
||||
</Text>
|
||||
) : null}
|
||||
{children}
|
||||
</Menu.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,22 +11,17 @@ export function ActionsWrapper({
|
||||
isFromSelf,
|
||||
senderProfile,
|
||||
children,
|
||||
onTap,
|
||||
}: {
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
hasReactions?: boolean
|
||||
isFromSelf: boolean
|
||||
senderProfile?: bsky.profile.AnyProfileView
|
||||
children: React.ReactNode
|
||||
onTap?: () => void
|
||||
}) {
|
||||
const {t: l} = useLingui()
|
||||
|
||||
return (
|
||||
<MessageContextMenu
|
||||
message={message}
|
||||
senderProfile={senderProfile}
|
||||
onTap={onTap}>
|
||||
<MessageContextMenu message={message} senderProfile={senderProfile}>
|
||||
{trigger =>
|
||||
// will always be true, since this file is platform split
|
||||
trigger.IS_NATIVE && (
|
||||
|
||||
@@ -20,14 +20,12 @@ export function ActionsWrapper({
|
||||
isFromSelf,
|
||||
senderProfile,
|
||||
children,
|
||||
onTap,
|
||||
}: {
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
hasReactions?: boolean
|
||||
isFromSelf: boolean
|
||||
senderProfile?: bsky.profile.AnyProfileView
|
||||
children: React.ReactNode
|
||||
onTap?: () => void
|
||||
}) {
|
||||
const viewRef = useRef(null)
|
||||
const t = useTheme()
|
||||
@@ -140,13 +138,10 @@ export function ActionsWrapper({
|
||||
}}
|
||||
</MessageContextMenu>
|
||||
</View>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityHint={l`Click to view the date and time`}
|
||||
onPress={onTap}
|
||||
<View
|
||||
style={[{maxWidth: '80%'}, isFromSelf ? a.align_end : a.align_start]}>
|
||||
{children}
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import {createContext, useCallback, useContext, useState} from 'react'
|
||||
|
||||
type DateDividerToggleContextType = {
|
||||
isDividerToggled: (id: string) => boolean
|
||||
toggleDivider: (id: string) => void
|
||||
}
|
||||
|
||||
const DateDividerToggleContext = createContext<DateDividerToggleContextType>({
|
||||
isDividerToggled: () => false,
|
||||
toggleDivider: () => {},
|
||||
})
|
||||
|
||||
export function DateDividerToggleProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [toggledIds, setToggledIds] = useState(new Set<string>())
|
||||
|
||||
const toggleDivider = useCallback((id: string) => {
|
||||
setToggledIds(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) next.delete(id)
|
||||
else next.add(id)
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
const isDividerToggled = useCallback(
|
||||
(id: string) => toggledIds.has(id),
|
||||
[toggledIds],
|
||||
)
|
||||
|
||||
return (
|
||||
<DateDividerToggleContext.Provider
|
||||
value={{isDividerToggled, toggleDivider}}>
|
||||
{children}
|
||||
</DateDividerToggleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useDateDividerToggle() {
|
||||
return useContext(DateDividerToggleContext)
|
||||
}
|
||||
@@ -11,10 +11,7 @@ import {
|
||||
useContextMenuContext,
|
||||
useContextMenuMenuContext,
|
||||
} from '#/components/ContextMenu/context'
|
||||
import {
|
||||
EmojiHeartEyes_Stroke2_Corner0_Rounded as EmojiHeartEyesIcon,
|
||||
EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon,
|
||||
} from '#/components/icons/Emoji'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import {type TriggerProps} from '#/components/Menu/types'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {EmojiPopup} from './EmojiPopup'
|
||||
@@ -37,11 +34,6 @@ export function EmojiReactionPicker({
|
||||
const [layout, setLayout] = useState({width: 0, height: 0})
|
||||
const {width: screenWidth} = useWindowDimensions()
|
||||
|
||||
// 1 in 100 chance of showing heart eyes icon
|
||||
const EmojiIcon = useMemo(() => {
|
||||
return Math.random() < 0.01 ? EmojiHeartEyesIcon : EmojiSmileIcon
|
||||
}, [])
|
||||
|
||||
const position = useMemo(() => {
|
||||
return {
|
||||
x:
|
||||
@@ -76,7 +68,7 @@ export function EmojiReactionPicker({
|
||||
t.atoms.border_contrast_low,
|
||||
a.shadow_md,
|
||||
]}>
|
||||
{['👍', '😆', '❤️', '👀', '😢'].map(emoji => {
|
||||
{['❤️', '👍', '😆', '👀', '😢'].map(emoji => {
|
||||
const alreadyReacted = hasAlreadyReacted(
|
||||
message,
|
||||
currentAccount?.did,
|
||||
@@ -123,16 +115,12 @@ export function EmojiReactionPicker({
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
t.scheme === 'light'
|
||||
? t.atoms.bg_contrast_25
|
||||
: t.atoms.bg_contrast_50,
|
||||
t.atoms.bg_contrast_50,
|
||||
{height: 40, width: 40},
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<EmojiIcon size="xl" fill={t.palette.contrast_400} />
|
||||
<PlusIcon size="md" fill={t.palette.contrast_1000} />
|
||||
</View>
|
||||
</EmojiPopup>
|
||||
</View>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {DropdownMenu} from 'radix-ui'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, flatten, useTheme} from '#/alf'
|
||||
import * as EmojiPicker from '#/components/EmojiPicker'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {hasAlreadyReacted, hasReachedReactionLimit} from './util'
|
||||
@@ -70,7 +70,7 @@ function MenuInner({
|
||||
) : (
|
||||
<Menu.Outer style={[a.rounded_full]}>
|
||||
<View style={[a.flex_row, a.gap_xs]}>
|
||||
{['👍', '😆', '❤️', '👀', '😢'].map(emoji => {
|
||||
{['❤️', '👍', '😆', '👀', '😢'].map(emoji => {
|
||||
const alreadyReacted = hasAlreadyReacted(
|
||||
message,
|
||||
currentAccount?.did,
|
||||
@@ -118,10 +118,11 @@ function MenuInner({
|
||||
style={flatten([
|
||||
a.rounded_full,
|
||||
{height: 34, width: 34},
|
||||
t.atoms.bg_contrast_50,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
])}>
|
||||
<DotGridIcon size="lg" style={t.atoms.text_contrast_medium} />
|
||||
<PlusIcon size="md" style={t.atoms.text_contrast_medium} />
|
||||
</Pressable>
|
||||
</DropdownMenu.Item>
|
||||
</View>
|
||||
|
||||
@@ -15,10 +15,10 @@ import {atoms as a} from '#/alf'
|
||||
import * as ContextMenu from '#/components/ContextMenu'
|
||||
import {type TriggerProps} from '#/components/ContextMenu/types'
|
||||
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
|
||||
import {BubbleQuestion_Stroke2_Corner0_Rounded as TranslateIcon} from '#/components/icons/Bubble'
|
||||
import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
|
||||
import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag'
|
||||
import {Language_Stroke2_Corner2_Rounded as LanguageIcon} from '#/components/icons/Language'
|
||||
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {ReportDialog} from '#/components/moderation/ReportDialog'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {usePromptControl} from '#/components/Prompt'
|
||||
@@ -33,14 +33,12 @@ export let MessageContextMenu = ({
|
||||
message,
|
||||
senderProfile,
|
||||
children,
|
||||
onTap,
|
||||
}: {
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
senderProfile?: bsky.profile.AnyProfileView
|
||||
children: TriggerProps['children']
|
||||
onTap?: () => void
|
||||
}): React.ReactNode => {
|
||||
const {t: l} = useLingui()
|
||||
const {t: l, i18n} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -133,13 +131,15 @@ export let MessageContextMenu = ({
|
||||
label={l`Message options`}
|
||||
contentLabel={l`Message from @${
|
||||
sender?.handle ?? 'unknown' // should always be defined
|
||||
}: ${message.text}`}
|
||||
onTap={onTap}>
|
||||
}: ${message.text}`}>
|
||||
{children}
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Outer
|
||||
align={isFromSelf ? 'right' : 'left'}
|
||||
label={l`Sent at ${i18n.date(new Date(message.sentAt), {
|
||||
timeStyle: 'short',
|
||||
})}`}
|
||||
style={[isFromSelf && isGroupChatEnabled ? null : a.ml_sm]}>
|
||||
{message.text.length > 0 && (
|
||||
<>
|
||||
@@ -147,35 +147,34 @@ export let MessageContextMenu = ({
|
||||
testID="messageDropdownTranslateBtn"
|
||||
label={l`Translate`}
|
||||
onPress={onPressTranslateMessage}>
|
||||
<ContextMenu.ItemIcon icon={LanguageIcon} position="left" />
|
||||
<ContextMenu.ItemText>{l`Translate`}</ContextMenu.ItemText>
|
||||
<ContextMenu.ItemIcon icon={TranslateIcon} position="right" />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
testID="messageDropdownCopyBtn"
|
||||
label={l`Copy message text`}
|
||||
onPress={onCopyMessage}>
|
||||
<ContextMenu.ItemIcon icon={ClipboardIcon} position="left" />
|
||||
<ContextMenu.ItemText>
|
||||
{l`Copy message text`}
|
||||
</ContextMenu.ItemText>
|
||||
<ContextMenu.ItemIcon icon={ClipboardIcon} position="right" />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Divider />
|
||||
</>
|
||||
)}
|
||||
<ContextMenu.Item
|
||||
testID="messageDropdownDeleteBtn"
|
||||
label={l`Delete message for me`}
|
||||
onPress={() => deleteControl.open()}>
|
||||
<ContextMenu.ItemIcon icon={TrashIcon} position="left" />
|
||||
<ContextMenu.ItemText>{l`Delete for me`}</ContextMenu.ItemText>
|
||||
<ContextMenu.ItemIcon icon={TrashIcon} position="right" />
|
||||
</ContextMenu.Item>
|
||||
{!isFromSelf && (
|
||||
<ContextMenu.Item
|
||||
testID="messageDropdownReportBtn"
|
||||
label={l`Report message`}
|
||||
onPress={() => reportControl.open()}>
|
||||
<ContextMenu.ItemIcon icon={FlagIcon} position="left" />
|
||||
<ContextMenu.ItemText>{l`Report`}</ContextMenu.ItemText>
|
||||
<ContextMenu.ItemIcon icon={WarningIcon} position="right" />
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
</ContextMenu.Outer>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {memo, useEffect, useMemo, useRef} from 'react'
|
||||
import {memo, useEffect, useMemo} from 'react'
|
||||
import {
|
||||
type GestureResponderEvent,
|
||||
LayoutAnimation,
|
||||
Pressable,
|
||||
type StyleProp,
|
||||
type TextStyle,
|
||||
@@ -43,7 +42,6 @@ import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {DateDivider} from './DateDivider'
|
||||
import {useDateDividerToggle} from './DateDividerToggle'
|
||||
import {MessageItemEmbed} from './MessageItemEmbed'
|
||||
import {ReactionsDialog} from './ReactionsDialog'
|
||||
|
||||
@@ -56,8 +54,6 @@ const DISPLAY_NAME_INSET = 22
|
||||
const CLUSTERED_MESSAGE_THRESHOLD_MS = 5 * 60 * 1000
|
||||
const MESSAGE_GAP_THRESHOLD_MS = 60 * 60 * 1000
|
||||
|
||||
const TAP_AND_DRAG_DELAY_MS = 100
|
||||
|
||||
function isWithinClusterBoundary({
|
||||
isPending,
|
||||
adjacentMessage,
|
||||
@@ -104,7 +100,6 @@ let MessageItem = ({
|
||||
const profile = item.relatedProfiles.get(item.message.sender.did)
|
||||
|
||||
const reactionsControl = useDialogControl()
|
||||
const reactionTapRef = useRef(false)
|
||||
|
||||
const {message, nextMessage, prevMessage} = item
|
||||
const isPending = item.type === 'pending-message'
|
||||
@@ -148,16 +143,9 @@ let MessageItem = ({
|
||||
new Date(prevMessage.sentAt).getTime() >
|
||||
MESSAGE_GAP_THRESHOLD_MS
|
||||
|
||||
const {isDividerToggled, toggleDivider} = useDateDividerToggle()
|
||||
const isDateDividerToggled = isDividerToggled(message.id)
|
||||
const isNextDateDividerToggled =
|
||||
nextMessage != null && isDividerToggled(nextMessage.id)
|
||||
|
||||
const effectiveFirstInCluster = isFirstInCluster || isDateDividerToggled
|
||||
const effectiveLastInCluster = isLastInCluster || isNextDateDividerToggled
|
||||
const isInCluster = !(effectiveFirstInCluster && effectiveLastInCluster)
|
||||
const isInCluster = !(isFirstInCluster && isLastInCluster)
|
||||
const isInMiddleOfCluster =
|
||||
isInCluster && !effectiveFirstInCluster && !effectiveLastInCluster
|
||||
isInCluster && !isFirstInCluster && !isLastInCluster
|
||||
|
||||
const hasReactions = message.reactions && message.reactions.length > 0
|
||||
const prevHasReactions =
|
||||
@@ -168,12 +156,12 @@ let MessageItem = ({
|
||||
!hasReactions &&
|
||||
!isNextEmojiOnly &&
|
||||
isInCluster &&
|
||||
(isInMiddleOfCluster || effectiveFirstInCluster)
|
||||
(isInMiddleOfCluster || isFirstInCluster)
|
||||
const squaredTopCorner =
|
||||
!prevHasReactions &&
|
||||
!isPrevEmojiOnly &&
|
||||
isInCluster &&
|
||||
(isInMiddleOfCluster || effectiveLastInCluster)
|
||||
(isInMiddleOfCluster || isLastInCluster)
|
||||
|
||||
const pendingColor = t.palette.primary_300
|
||||
|
||||
@@ -336,16 +324,6 @@ let MessageItem = ({
|
||||
transform: [{translateY: -8}],
|
||||
},
|
||||
]}
|
||||
onPressIn={() => {
|
||||
// Don't toggle the date divider when tapping a reaction.
|
||||
reactionTapRef.current = true
|
||||
}}
|
||||
onPressOut={() => {
|
||||
// Include a delay here to account for tap-and-drag before release.
|
||||
setTimeout(() => {
|
||||
reactionTapRef.current = false
|
||||
}, TAP_AND_DRAG_DELAY_MS)
|
||||
}}
|
||||
onPress={isGroupChat ? reactionsControl.open : undefined}>
|
||||
{groupedReactions.map(group => (
|
||||
<Animated.View
|
||||
@@ -407,13 +385,13 @@ let MessageItem = ({
|
||||
return (
|
||||
<>
|
||||
<LayoutAnimationConfig skipExiting skipEntering>
|
||||
{(hasLargeGapFromPrev || isDateDividerToggled) && (
|
||||
{hasLargeGapFromPrev && (
|
||||
<Animated.View entering={native(FadeIn)} exiting={native(FadeOut)}>
|
||||
<DateDivider date={message.sentAt} />
|
||||
</Animated.View>
|
||||
)}
|
||||
</LayoutAnimationConfig>
|
||||
<View style={[messageInset, effectiveFirstInCluster && a.mt_md]}>
|
||||
<View style={[messageInset, isFirstInCluster && a.mt_md]}>
|
||||
<View style={[a.relative]}>
|
||||
{showAvatar ? (
|
||||
<View
|
||||
@@ -450,16 +428,7 @@ let MessageItem = ({
|
||||
hasReactions={hasReactions}
|
||||
isFromSelf={isFromSelf}
|
||||
message={message}
|
||||
senderProfile={profile}
|
||||
onTap={() => {
|
||||
if (reactionTapRef.current) return
|
||||
if (!hasLargeGapFromPrev) {
|
||||
LayoutAnimation.configureNext(
|
||||
LayoutAnimation.Presets.easeInEaseOut,
|
||||
)
|
||||
toggleDivider(message.id)
|
||||
}
|
||||
}}>
|
||||
senderProfile={profile}>
|
||||
{AppBskyEmbedRecord.isView(message.embed) && (
|
||||
<MessageItemEmbed
|
||||
embed={message.embed}
|
||||
@@ -480,7 +449,7 @@ let MessageItem = ({
|
||||
a.py_sm,
|
||||
a.px_md,
|
||||
{
|
||||
marginTop: effectiveFirstInCluster
|
||||
marginTop: isFirstInCluster
|
||||
? 0
|
||||
: CLUSTERED_MESSAGE_GAP,
|
||||
backgroundColor: isFromSelf
|
||||
@@ -523,7 +492,7 @@ let MessageItem = ({
|
||||
</ActionsWrapper>
|
||||
</View>
|
||||
</View>
|
||||
{effectiveLastInCluster && (
|
||||
{isLastInCluster && (
|
||||
<MessageItemMetadata
|
||||
item={item}
|
||||
style={[isFromSelf ? a.text_right : a.text_left]}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const Language_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M14.5 8a1 1 0 0 1 .912.59l4.5 10a1 1 0 0 1-1.824.82L16.554 16h-4.108l-1.534 3.41a1 1 0 0 1-1.824-.82l4.5-10A1 1 0 0 1 14.5 8m-1.153 6h2.306L14.5 11.437zM5.053.106a1 1 0 0 1 1.342.447L7.118 2H11a1 1 0 0 1 0 2H9.92C9.64 5.765 8.65 7.24 7.527 8.398a17 17 0 0 0 2.605 1.562q.129.062.197.092l.005.002.049.022.01.005a1 1 0 0 1-.787 1.838h-.002l-.027-.013-.068-.03q-.088-.04-.242-.113A18.878 18.878 0 0 1 6 9.75a19 19 0 0 1-3.509 2.125l-.068.03-.027.013h-.002a1 1 0 0 1-.788-1.838l.011-.005.049-.022.005-.002q.069-.03.197-.092a17.176 17.176 0 0 0 2.604-1.562C3.348 7.239 2.36 5.764 2.08 4H1a1 1 0 0 1 0-2h3.882l-.277-.553A1 1 0 0 1 5.053.106M4.118 4C4.388 5.151 5.098 6.193 6 7.1 6.901 6.193 7.612 5.15 7.882 4z',
|
||||
})
|
||||
@@ -55,7 +55,6 @@ import {MessageInput} from '#/screens/Messages/components/MessageInput'
|
||||
import {MessageListError} from '#/screens/Messages/components/MessageListError'
|
||||
import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
|
||||
import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill'
|
||||
import {DateDividerToggleProvider} from '#/components/dms/DateDividerToggle'
|
||||
import {MessageItem} from '#/components/dms/MessageItem'
|
||||
import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
|
||||
import {SystemMessageItem} from '#/components/dms/SystemMessageItem'
|
||||
@@ -410,7 +409,7 @@ export function MessagesList({
|
||||
)
|
||||
|
||||
return (
|
||||
<DateDividerToggleProvider>
|
||||
<>
|
||||
<KeyboardGestureArea
|
||||
interpolator="ios"
|
||||
// HACKFIX: https://github.com/kirillzyusko/react-native-keyboard-controller/issues/1419
|
||||
@@ -514,7 +513,7 @@ export function MessagesList({
|
||||
</KeyboardGestureArea>
|
||||
|
||||
{newMessagesPill.show && <NewMessagesPill onPress={scrollToEndOnPress} />}
|
||||
</DateDividerToggleProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user