Files
desktop/app/components/GroupDialog/GroupDialog.tsx
rosetta 83f38dc63f 'init'
2026-01-30 05:01:05 +02:00

170 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useRosettaColors } from "@/app/hooks/useRosettaColors";
import { Avatar, Badge, Box, Divider, Flex, Loader, Skeleton, Text, useComputedColorScheme, useMantineTheme } from "@mantine/core";
import { IconAlertCircle, IconBellOff, IconCheck, IconChecks, IconClock, IconPin, IconUsers } from "@tabler/icons-react";
import { DeliveredMessageState } from "@/app/providers/DialogProvider/DialogProvider";
import { dotMessageIfNeeded, isMessageDeliveredByTime } from "@/app/utils/utils";
import { usePacket } from "@/app/providers/ProtocolProvider/usePacket";
import { useEffect, useState } from "react";
import { PacketTyping } from "@/app/providers/ProtocolProvider/protocol/packets/packet.typeing";
import { useAvatars } from "@/app/providers/AvatarProvider/useAvatars";
import { TextParser } from "../TextParser/TextParser";
import { useMemory } from "@/app/providers/MemoryProvider/useMemory";
import { DialogRow } from "@/app/providers/DialogListProvider/DialogListProvider";
import { useGroupInformation } from "@/app/providers/InformationProvider/useGroupInformation";
import { useDialogInfo } from "@/app/providers/DialogListProvider/useDialogInfo";
import { useUserInformation } from "@/app/providers/InformationProvider/useUserInformation";
import { useDialogContextMenu } from "@/app/hooks/useDialogContextMenu";
import { useDialogMute } from "@/app/providers/DialogStateProvider.tsx/useDialogMute";
import { useDialogPin } from "@/app/providers/DialogStateProvider.tsx/useDialogPin";
import { useMentions } from "@/app/providers/DialogStateProvider.tsx/useMentions";
export interface DialogProps extends DialogRow {
onClickDialog: (dialog: string) => void;
}
export function GroupDialog(props : DialogProps) {
const colors = useRosettaColors();
const theme = useMantineTheme();
const computedTheme = useComputedColorScheme();
/**
* Принимает #group:group_id, для
* диалогов между пользователями есть просто public_key собеседника
*/
const groupId = props.dialog_id;
const {isMuted} = useDialogMute(groupId);
const {isPinned} = useDialogPin(groupId);
const {groupInfo} = useGroupInformation(groupId);
const {lastMessage, unreaded, loading} = useDialogInfo(props);
const lastMessageFromMe = lastMessage.from_me == 1;
const [usersTypeing, setUsersTypeing] = useState<{
timeout: NodeJS.Timeout | null,
fromPublicKey: string
}[]>([]);
const avatars = useAvatars(groupId);
const [сurrentDialogPublicKeyView] = useMemory("current-dialog-public-key-view", "", true);
const [userInfo] = useUserInformation(lastMessage.from_public_key);
const [typingUser] = useUserInformation(usersTypeing[0]?.fromPublicKey || '');
const isInCurrentDialog = props.dialog_id == сurrentDialogPublicKeyView;
const currentDialogColor = computedTheme == 'dark' ? '#2a6292' :'#438fd1';
const {openContextMenu} = useDialogContextMenu();
const {isMentioned} = useMentions();
useEffect(() => {
clearUsersTypeing();
}, [props.dialog_id]);
const clearUsersTypeing = () => {
usersTypeing.forEach(ut => {
if(ut.timeout){
clearTimeout(ut.timeout);
}
});
setUsersTypeing([]);
}
usePacket(0x0B, (packet : PacketTyping) => {
if(packet.getToPublicKey() == props.dialog_id){
setUsersTypeing((prev) => [...prev, {
fromPublicKey: packet.getFromPublicKey(),
timeout: setTimeout(() => {
setUsersTypeing((prev) => {
return prev.filter(ut => ut.fromPublicKey != packet.getFromPublicKey());
});
}, 3000)
}]);
}
}, [props.dialog_id]);
return (
<Box style={{
cursor: 'pointer',
userSelect: 'none',
backgroundColor: isInCurrentDialog ? currentDialogColor : 'unset',
}} onClick={() => props.onClickDialog(props.dialog_id)} onContextMenu={() => {
openContextMenu(props.dialog_id)
}}>
<Flex p={'sm'} gap={'sm'}>
<Box style={{ position: 'relative', display: 'inline-block' }}>
<Avatar src={avatars.length > 0 ? avatars[0].avatar : undefined} variant={isInCurrentDialog ? 'filled' : 'light'} name={groupInfo.title} size={50} color={'initials'} />
</Box>
<Flex w={'100%'} justify={'space-between'} direction={'row'} gap={'sm'}>
<Flex direction={'column'} gap={3}>
<Flex align={'center'} gap={5}>
<Text size={'sm'} c={computedTheme == 'light' && !isInCurrentDialog ? 'black' : 'white'} fw={500}>
{dotMessageIfNeeded(groupInfo.title, 15)}
</Text>
<IconUsers color={isInCurrentDialog ? '#fff' : colors.chevrons.active} size={13}></IconUsers>
{isMuted && <IconBellOff color={isInCurrentDialog ? '#fff' : colors.chevrons.active} size={13}></IconBellOff>}
{isPinned && <IconPin color={isInCurrentDialog ? '#fff' : colors.chevrons.active} size={13}></IconPin>}
</Flex>
{usersTypeing.length <= 0 && <>
<Text component="div" c={
isInCurrentDialog ? '#fff' : colors.chevrons.active
} size={'xs'} style={{
maxWidth: '130px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{loading && <Skeleton height={10} mt={4} width={100}></Skeleton>}
{!loading && (
<>
<span style={{
color: isInCurrentDialog ? '#fff' : theme.colors.blue[6]
}}>{userInfo.title}: </span>
<TextParser
__reserved_1={isInCurrentDialog}
noHydrate={true}
text={lastMessage.plain_message}
></TextParser>
</>
)}
</Text>
</>}
{usersTypeing.length > 0 && <>
<Flex gap={5} align={'center'}>
<Text c={isInCurrentDialog ? '#fff' : theme.colors.blue[3]} fz={12}>{typingUser.title} {usersTypeing.length > 1 && 'and ' + (usersTypeing.length - 1)} typing </Text>
<Loader size={15} color={isInCurrentDialog ? '#fff' : theme.colors.blue[3]} type={'dots'}></Loader>
</Flex>
</>}
</Flex>
<Flex direction={'column'} align={'flex-end'} gap={8}>
{!loading && (
<Text c={сurrentDialogPublicKeyView == props.dialog_id ? '#fff' : colors.chevrons.active} fz={10}>
{new Date(lastMessage.timestamp).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}
</Text>
)}
{loading && (
<Skeleton height={8} mt={4} width={30}></Skeleton>
)}
{lastMessage.delivered == DeliveredMessageState.DELIVERED && <>
{lastMessageFromMe && unreaded > 0 &&
<IconCheck stroke={3} color={isInCurrentDialog ? '#fff' : theme.colors.blue[3]} size={14}></IconCheck>}
{lastMessageFromMe && unreaded <= 0 &&
<IconChecks stroke={3} color={isInCurrentDialog ? '#fff' : theme.colors.blue[3]} size={14}></IconChecks>}
</>}
{(lastMessage.delivered == DeliveredMessageState.WAITING && (isMessageDeliveredByTime(lastMessage.timestamp, lastMessage.attachments.length))) && <>
<IconClock stroke={2} size={13} color={theme.colors.gray[5]}></IconClock>
</>}
{!loading && (lastMessage.delivered == DeliveredMessageState.ERROR || (!isMessageDeliveredByTime(lastMessage.timestamp, lastMessage.attachments.length) && lastMessage.delivered != DeliveredMessageState.DELIVERED)) && (
<IconAlertCircle stroke={3} size={15} color={colors.error}></IconAlertCircle>
)}
{unreaded > 0 && !lastMessageFromMe && !isMentioned(props.dialog_id) && <Badge
color={isInCurrentDialog ? 'white' : (isMuted ? colors.chevrons.active : colors.brandColor)}
c={isInCurrentDialog ? colors.brandColor : 'white'}
size={'sm'} circle={unreaded < 10}>{unreaded > 99 ? '99+' : unreaded}</Badge>}
{isMentioned(props.dialog_id) && !lastMessageFromMe && <Badge size={'sm'} circle c={isInCurrentDialog ? colors.brandColor : 'white'} color={isInCurrentDialog ? 'white' : colors.brandColor}>@</Badge>}
</Flex>
</Flex>
</Flex>
<Divider></Divider>
</Box>
)
}