186 lines
4.7 KiB
TypeScript
186 lines
4.7 KiB
TypeScript
import OpenIMSDK, {
|
|
LoginStatus,
|
|
OpenIMEvent,
|
|
type ConversationItem as OpenIMConversationItem,
|
|
type MessageItem,
|
|
} from "@openim/rn-client-sdk";
|
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
|
import type { ConversationItem } from "@/features/im/types/conversation";
|
|
|
|
function sortConversations(items: OpenIMConversationItem[]) {
|
|
return [...items].sort((a, b) => {
|
|
if (a.isPinned !== b.isPinned) {
|
|
return a.isPinned ? -1 : 1;
|
|
}
|
|
|
|
return (b.latestMsgSendTime ?? 0) - (a.latestMsgSendTime ?? 0);
|
|
});
|
|
}
|
|
|
|
function upsertConversations(
|
|
current: OpenIMConversationItem[],
|
|
changed: OpenIMConversationItem[]
|
|
) {
|
|
if (!changed.length) {
|
|
return current;
|
|
}
|
|
|
|
const map = new Map(current.map((item) => [item.conversationID, item]));
|
|
changed.forEach((item) => {
|
|
map.set(item.conversationID, item);
|
|
});
|
|
|
|
return sortConversations([...map.values()]);
|
|
}
|
|
|
|
function formatRelativeTime(timestamp: number) {
|
|
if (!timestamp) {
|
|
return "";
|
|
}
|
|
|
|
const now = Date.now();
|
|
const diff = Math.max(0, now - timestamp);
|
|
const minute = 60 * 1000;
|
|
const hour = 60 * minute;
|
|
const day = 24 * hour;
|
|
|
|
if (diff < minute) {
|
|
return "刚刚";
|
|
}
|
|
if (diff < hour) {
|
|
return `${Math.floor(diff / minute)}分钟前`;
|
|
}
|
|
if (diff < day) {
|
|
return `${Math.floor(diff / hour)}小时前`;
|
|
}
|
|
if (diff < day * 2) {
|
|
return "昨天";
|
|
}
|
|
|
|
const date = new Date(timestamp);
|
|
const month = `${date.getMonth() + 1}`.padStart(2, "0");
|
|
const dayOfMonth = `${date.getDate()}`.padStart(2, "0");
|
|
return `${month}-${dayOfMonth}`;
|
|
}
|
|
|
|
function parseLatestMessagePreview(latestMsg: string, draftText: string) {
|
|
if (draftText) {
|
|
return `[草稿] ${draftText}`;
|
|
}
|
|
|
|
if (!latestMsg) {
|
|
return "";
|
|
}
|
|
|
|
try {
|
|
const message = JSON.parse(latestMsg) as MessageItem;
|
|
if (message.textElem?.content) {
|
|
return message.textElem.content;
|
|
}
|
|
if (message.quoteElem?.text) {
|
|
return `[引用] ${message.quoteElem.text}`;
|
|
}
|
|
if (message.pictureElem) {
|
|
return "[图片]";
|
|
}
|
|
if (message.videoElem) {
|
|
return "[视频]";
|
|
}
|
|
if (message.fileElem) {
|
|
return "[文件]";
|
|
}
|
|
if (message.soundElem) {
|
|
return "[语音]";
|
|
}
|
|
if (message.notificationElem) {
|
|
return "[系统通知]";
|
|
}
|
|
} catch {
|
|
return latestMsg;
|
|
}
|
|
|
|
return "[暂不支持的消息]";
|
|
}
|
|
|
|
function toViewModel(item: OpenIMConversationItem): ConversationItem {
|
|
const title = item.showName || item.userID || item.groupID || "未命名会话";
|
|
|
|
return {
|
|
id: item.conversationID,
|
|
kind: "direct",
|
|
title,
|
|
preview: parseLatestMessagePreview(item.latestMsg, item.draftText),
|
|
timeLabel: formatRelativeTime(item.latestMsgSendTime),
|
|
avatarUrl: item.faceURL || undefined,
|
|
unreadCount: item.unreadCount,
|
|
};
|
|
}
|
|
|
|
export function useConversationList() {
|
|
const [conversations, setConversations] = useState<OpenIMConversationItem[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const reload = useCallback(async () => {
|
|
try {
|
|
setError(null);
|
|
const status = await OpenIMSDK.getLoginStatus();
|
|
const logged = status === LoginStatus.Logged;
|
|
setIsLoggedIn(logged);
|
|
|
|
if (!logged) {
|
|
setConversations([]);
|
|
return;
|
|
}
|
|
|
|
const list = await OpenIMSDK.getAllConversationList();
|
|
setConversations(sortConversations(list));
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "拉取会话失败";
|
|
setError(message);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
reload();
|
|
}, [reload]);
|
|
|
|
useEffect(() => {
|
|
const onConversationChanged = (changed: OpenIMConversationItem[]) => {
|
|
setConversations((prev) => upsertConversations(prev, changed));
|
|
};
|
|
|
|
const onNewConversation = (created: OpenIMConversationItem[]) => {
|
|
setConversations((prev) => upsertConversations(prev, created));
|
|
};
|
|
|
|
const onRecvNewMessages = () => {
|
|
reload();
|
|
};
|
|
|
|
OpenIMSDK.on(OpenIMEvent.OnConversationChanged, onConversationChanged);
|
|
OpenIMSDK.on(OpenIMEvent.OnNewConversation, onNewConversation);
|
|
OpenIMSDK.on(OpenIMEvent.OnRecvNewMessages, onRecvNewMessages);
|
|
|
|
return () => {
|
|
OpenIMSDK.off(OpenIMEvent.OnConversationChanged, onConversationChanged);
|
|
OpenIMSDK.off(OpenIMEvent.OnNewConversation, onNewConversation);
|
|
OpenIMSDK.off(OpenIMEvent.OnRecvNewMessages, onRecvNewMessages);
|
|
};
|
|
}, [reload]);
|
|
|
|
const items = useMemo(() => conversations.map(toViewModel), [conversations]);
|
|
|
|
return {
|
|
items,
|
|
isLoading,
|
|
isLoggedIn,
|
|
error,
|
|
reload,
|
|
};
|
|
}
|