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,210 @@
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>
)
}