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

166 lines
9.4 KiB
TypeScript
Raw Permalink 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 { useUserInformation } from "@/app/providers/InformationProvider/useUserInformation";
import { useRosettaColors } from "@/app/hooks/useRosettaColors";
import { Avatar, Badge, Box, Divider, Flex, Loader, Skeleton, Text, useComputedColorScheme, useMantineTheme } from "@mantine/core";
import { IconAlertCircle, IconBellOff, IconBookmark, IconCheck, IconChecks, IconClock, IconPin } from "@tabler/icons-react";
import { usePublicKey } from "@/app/providers/AccountProvider/usePublicKey";
import { VerifiedBadge } from "../VerifiedBadge/VerifiedBadge";
import { OnlineState } from "@/app/providers/ProtocolProvider/protocol/packets/packet.onlinestate";
import { DeliveredMessageState } from "@/app/providers/DialogProvider/DialogProvider";
import { dotMessageIfNeeded, isMessageDeliveredByTime } from "@/app/utils/utils";
import { usePacket } from "@/app/providers/ProtocolProvider/usePacket";
import { useRef, 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 { useDialogInfo } from "@/app/providers/DialogListProvider/useDialogInfo";
import { useDialogContextMenu } from "@/app/hooks/useDialogContextMenu";
import { useDialogPin } from "@/app/providers/DialogStateProvider.tsx/useDialogPin";
import { useDialogMute } from "@/app/providers/DialogStateProvider.tsx/useDialogMute";
export interface DialogProps extends DialogRow {
onClickDialog: (dialog: string) => void;
}
export function Dialog(props : DialogProps) {
const colors = useRosettaColors();
const theme = useMantineTheme();
const computedTheme = useComputedColorScheme();
const publicKey = usePublicKey();
/**
* Принимает public_key оппонента, для групп
* есть отдельный компонент GroupDialog
*/
const opponent = props.dialog_id;
const {isMuted} = useDialogMute(opponent);
const {isPinned} = useDialogPin(opponent);
const [userInfo] = useUserInformation(opponent);
const {lastMessage, unreaded, loading} = useDialogInfo(props);
const lastMessageFromMe = lastMessage.from_me == 1;
const fromMe = opponent == publicKey;
const [userTypeing, setUserTypeing] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout>(undefined);
const avatars = useAvatars(opponent);
const [сurrentDialogPublicKeyView] = useMemory("current-dialog-public-key-view", "", true);
const {openContextMenu} = useDialogContextMenu();
const isInCurrentDialog = props.dialog_id == сurrentDialogPublicKeyView;
const currentDialogColor = computedTheme == 'dark' ? '#2a6292' :'#438fd1';
usePacket(0x0B, (packet : PacketTyping) => {
if(packet.getFromPublicKey() == opponent && packet.getToPublicKey() == publicKey && !fromMe){
console.info("User typeing packet received in Dialog");
setUserTypeing(true);
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
setUserTypeing(false);
}, 3000);
}
}, [opponent]);
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'}>
{
fromMe ?
<Avatar
size={50}
color={'blue'}
variant={'filled'}
>
<IconBookmark stroke={2} size={20}></IconBookmark>
</Avatar> :
<Box style={{ position: 'relative', display: 'inline-block' }}>
<Avatar src={avatars.length > 0 ? avatars[0].avatar : undefined} variant={isInCurrentDialog ? 'filled' : 'light'} name={userInfo.title} size={50} color={'initials'} />
{userInfo.online == OnlineState.ONLINE && (
<Box
style={{
position: 'absolute',
width: 12,
height: 12,
backgroundColor: colors.brandColor,
borderRadius: '50%',
border: computedTheme == 'dark' ? '2px solid #1A1B1E' : '2px solid #FFFFFF',
bottom: 4,
right: 0,
}}
/>
)}
</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}>
{fromMe ? "Saved messages" : dotMessageIfNeeded(userInfo.title, 15)}
</Text>
<VerifiedBadge color={isInCurrentDialog ? 'white' : ''} size={15} verified={userInfo.verified}></VerifiedBadge>
{isMuted && <IconBellOff color={isInCurrentDialog ? '#fff' : colors.chevrons.active} size={13}></IconBellOff>}
{isPinned && <IconPin color={isInCurrentDialog ? '#fff' : colors.chevrons.active} size={13}></IconPin>}
</Flex>
{!userTypeing && <>
<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 && <TextParser __reserved_1={isInCurrentDialog} noHydrate={true} text={lastMessage.plain_message}></TextParser>}
</Text>
</>}
{userTypeing && <>
<Flex gap={5} align={'center'}>
<Text c={isInCurrentDialog ? '#fff' : theme.colors.blue[3]} fz={12}>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 && <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>}
</Flex>
</Flex>
</Flex>
<Divider></Divider>
</Box>
)
}