This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
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>
)
}