import {useRef, useState} from 'react'
import {
LayoutAnimation,
Pressable,
type ScrollView,
useWindowDimensions,
View,
} from 'react-native'
import Animated from 'react-native-reanimated'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {HITSLOP_10} from '#/lib/constants'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {sanitizeHandle} from '#/lib/strings/handles'
import {type ActiveConvoStates, useConvoActive} from '#/state/messages/convo'
import {useSession} from '#/state/session'
import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme, web} from '#/alf'
import * as Dialog from '#/components/Dialog'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky'
type Reaction = {
key: string
value: string
senders: ChatBskyConvoDefs.ReactionViewSender[]
count: number
}
export function ReactionsDialog({
control,
members,
message,
reactions,
groupedReactions,
}: {
control: Dialog.DialogControlProps
members: bsky.profile.AnyProfileView[]
message: ChatBskyConvoDefs.MessageView
reactions?: ChatBskyConvoDefs.ReactionView[]
groupedReactions?: Reaction[]
}) {
const {t: l} = useLingui()
const {height: screenHeight} = useWindowDimensions()
const {currentAccount} = useSession()
const convo = useConvoActive()
const [selected, setSelected] = useState('all')
const handleFilter = (value: string) => {
setSelected(value)
}
const filteredReactions = reactions?.filter(
r => selected === 'all' || r.value === selected,
)
const header = (
<>
Reactions
>
)
return (
setSelected('all')}
nativeOptions={{
preventExpansion: true,
minHeight: screenHeight / 2,
maxHeight: screenHeight / 2,
}}>
{IS_NATIVE ? header : null}
{filteredReactions
?.sort((a, b) => {
if (a.sender.did === currentAccount?.did) return -1
if (b.sender.did === currentAccount?.did) return 1
return 0
})
.map(reaction => {
const sender = members.find(m => m.did === reaction.sender.did)
if (!sender) return null
return (
)
})}
)
}
function ReactionRow({
control,
convo,
currentAccount,
message,
profile,
reaction,
allReactions,
selected,
setSelected,
}: {
control: Dialog.DialogControlProps
convo: ActiveConvoStates
currentAccount?: bsky.profile.AnyProfileView
message: ChatBskyConvoDefs.MessageView
profile: bsky.profile.AnyProfileView
reaction: ChatBskyConvoDefs.ReactionView
allReactions: ChatBskyConvoDefs.ReactionView[]
selected: string
setSelected: React.Dispatch>
}) {
const t = useTheme()
const {t: l} = useLingui()
const isFromSelf = currentAccount?.did === profile.did
const displayName = createSanitizedDisplayName(profile, true)
const handle = sanitizeHandle(profile?.handle ?? '', '@')
const handleOnPress = () => {
const remainingReactions =
allReactions?.filter(
r =>
!(r.value === reaction.value && r.sender.did === currentAccount?.did),
) ?? []
if (remainingReactions.length === 0) {
control.close()
} else if (
selected !== 'all' &&
!remainingReactions.some(r => r.value === reaction.value)
) {
// tab no longer exists
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setSelected('all')
}
convo
.removeReaction(message.id, reaction.value)
.catch(() => Toast.show(l`Failed to remove emoji reaction`))
}
const inner = (
<>
{displayName}
{isFromSelf ? l`Tap to remove` : handle}
{reaction.value}
>
)
if (isFromSelf) {
return (
{inner}
)
}
return (
{inner}
)
}
function ReactionTabs({
groupedReactions,
selected,
totalReactions,
onFilter,
}: {
groupedReactions?: Reaction[]
selected: string
totalReactions: number
onFilter: (value: string) => void
}) {
const t = useTheme()
const {t: l} = useLingui()
const scrollViewRef = useRef(null)
const scrollState = useRef({x: 0, width: 0})
const tabLayouts = useRef