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

210 lines
9.0 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 { createContext, useCallback, useContext, useEffect, useState } from "react";
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { DIALOG_DROP_TO_REQUESTS_IF_NO_MESSAGES_FROM_ME_COUNT } from "@/app/constants";
import { useQueue } from "@/app/hooks/useQueue";
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
import { DialogStateContext } from "../DialogStateProvider.tsx/DialogStateProvider";
interface DialogListContextValue {
dialogs: DialogRow[];
setDialogs: (dialogs: DialogRow[]) => void;
updateDialog: (dialog_id: string) => void;
loadingDialogs: number; //if > 0 then loading
}
export const DialogListContext = createContext<DialogListContextValue|null>(null);
interface DialogListProviderProps {
children: React.ReactNode;
}
export interface DialogRow {
dialog_id: string;
last_message_id: string;
last_timestamp: number; //timestamp update
last_message_timestamp: number; //message timestamp
is_request: boolean;
/**
* Закреплен ли диалог
*/
pinned?: boolean;
}
export function DialogListProvider(props : DialogListProviderProps) {
const [dialogs, setDialogs] = useState<DialogRow[]>([]);
const {info} = useConsoleLogger('DialogListProvider');
const { allQuery, getQuery, runQuery } = useDatabase();
const publicKey = usePublicKey();
const { inProcess, addToQueue, removeFromQueue, queue } = useQueue<string>();
const systemAccounts = useSystemAccounts();
const { pinned } = useContext(DialogStateContext)!;
const [loadingDialogs, setLoadingDialogs] = useState<number>(0);
useEffect(() => {
initialLoadingFromDatabase();
}, [publicKey]);
useEffect(() => {
//console.info(pinned);
setDialogs((prevDialogs) => {
const newDialogs = prevDialogs.map(d => {
return {
...d,
pinned: pinned.includes(d.dialog_id)
}
});
return newDialogs.sort((a, b) =>
a.pinned === b.pinned ?
b.last_message_timestamp - a.last_message_timestamp
: (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
});
}, [pinned]);
const initialLoadingFromDatabase = async () => {
if(publicKey == ''){
return;
}
const rows = await allQuery(`SELECT * FROM dialogs WHERE account = ? ORDER BY last_timestamp DESC`, [publicKey]);
const loadedDialogs: DialogRow[] = rows.map((row: any) => {
/**
* Если диалог системный (бот, канал и т.д.), то он не может быть запросом
*/
const isRequest = row.is_request
&& !systemAccounts.some(acc => acc.publicKey === row.dialog_id);
return {
dialog_id: row.dialog_id,
last_message_id: row.last_message_id,
last_timestamp: Date.now(),
is_request: isRequest,
last_message_timestamp: row.last_timestamp,
pinned: pinned.includes(row.dialog_id),
}
});
const prepartedDialogsForBatching = loadedDialogs.sort((a, b) => a.pinned === b.pinned ? b.last_message_timestamp - a.last_message_timestamp : (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
if(prepartedDialogsForBatching.length > 0){
/**
* Показываем индикатор загрузки, потому что
* есть баг с отрисовкой большого количества диалогов сразу
* TODO: найти причину бага и исправить
* UI блокируется на время отрисовки большого количества диалогов
* не надо так)))
*/
setLoadingDialogs(prepartedDialogsForBatching.length);
setTimeout(() => {
setLoadingDialogs(0);
setDialogs(prepartedDialogsForBatching);
}, 100);
}
info(`Loaded ${loadedDialogs.length} dialogs from database.`);
}
/**
* Обновляет информацию о диалоге в общем списке диалогов
* @param dialog_id id диалога в формате public_key или #group:group_id
* @returns
*/
const updateDialog = useCallback(async (dialog_id: string) => {
if(inProcess(dialog_id)){
info(`Dialog ${dialog_id} is already being processed, skipping update.`);
setTimeout(() => {
/**
* Попытка обновить диалог еще раз, чтобы избежать гонки
*/
updateDialog(dialog_id);
}, 100);
return;
}
addToQueue(dialog_id);
let last_message : any = null;
if(dialog_id.startsWith('#group:')){
last_message = await getQuery(`SELECT * FROM messages
WHERE to_public_key = ?
AND account = ?
ORDER BY timestamp
DESC LIMIT 1`, [dialog_id, publicKey]);
}else{
last_message = await getQuery(`SELECT * FROM messages
WHERE ((from_public_key = ? AND to_public_key = ?) OR (from_public_key = ? AND to_public_key = ?))
AND account = ?
ORDER BY timestamp
DESC LIMIT 1`, [dialog_id, publicKey, publicKey, dialog_id, publicKey]);
}
let dialogsWithId = await getQuery(`SELECT COUNT(*) as count FROM dialogs WHERE dialog_id = ? AND account = ?`, [dialog_id, publicKey]);
if(!last_message && dialogsWithId.count > 0){
setDialogs((prevDialogs) => {
const filteredDialogs = prevDialogs.filter(d => d.dialog_id !== dialog_id);
return filteredDialogs.sort((a, b) =>
a.pinned === b.pinned ?
b.last_message_timestamp - a.last_message_timestamp
: (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));;
});
info(`Delete dialog ${dialog_id} as it has no messages.`);
await runQuery(`DELETE FROM dialogs WHERE dialog_id = ? AND account = ?`, [dialog_id, publicKey]);
removeFromQueue(dialog_id);
return;
}
if(!last_message){
info(`No messages found for dialog ${dialog_id}, skipping update.`);
removeFromQueue(dialog_id);
return;
}
let messagesFromMeCount = await getQuery(`SELECT COUNT(*) as count FROM messages
WHERE (
(from_public_key = ? AND to_public_key = ?)
OR
(from_public_key = ? AND to_public_key = ?)
)
AND from_public_key = ?
AND account = ? ORDER BY timestamp DESC LIMIT ?`,
[dialog_id, publicKey, publicKey, dialog_id, publicKey, publicKey, DIALOG_DROP_TO_REQUESTS_IF_NO_MESSAGES_FROM_ME_COUNT]);
const updatedDialog: DialogRow = {
dialog_id: dialog_id,
last_message_id: last_message.message_id,
last_timestamp: Date.now(),
is_request: messagesFromMeCount.count <= 0 && !systemAccounts.some(acc => acc.publicKey === dialog_id),
last_message_timestamp: last_message.timestamp,
pinned: pinned.includes(dialog_id)
};
// Обновляем состояние диалога в базе
if(dialogsWithId.count > 0){
await runQuery(`UPDATE dialogs SET last_message_id = ?, last_timestamp = ?, is_request = ? WHERE dialog_id = ? AND account = ?`, [updatedDialog.last_message_id, updatedDialog.last_message_timestamp, updatedDialog.is_request, dialog_id, publicKey]);
}else{
await runQuery(`INSERT INTO dialogs (dialog_id, last_message_id, last_timestamp, is_request, account) VALUES (?, ?, ?, ?, ?)`, [dialog_id, updatedDialog.last_message_id, updatedDialog.last_message_timestamp, updatedDialog.is_request, publicKey]);
}
setDialogs((prevDialogs) => {
const filteredDialogs = prevDialogs.filter(d => d.dialog_id !== dialog_id);
const newDialogs = [updatedDialog, ...filteredDialogs];
return newDialogs.sort((a, b) =>
a.pinned === b.pinned ?
b.last_message_timestamp - a.last_message_timestamp
: (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
});
removeFromQueue(dialog_id);
info(`Dialog ${dialog_id} updated.`);
}, [publicKey, queue, pinned, systemAccounts]);
return (
<DialogListContext.Provider value={{
dialogs,
setDialogs,
updateDialog,
loadingDialogs
}}>
{props.children}
</DialogListContext.Provider>
)
}