新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
45 lines
2.1 KiB
TypeScript
45 lines
2.1 KiB
TypeScript
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
|
|
|
|
interface UnreadContextValue {
|
|
hasUnread: boolean; // 推播觸發的提示 或 任一房間有未讀,兩者只要有一個成立即為 true
|
|
markUnread: () => void; // Q-4:收到推播時呼叫
|
|
markRead: () => void; // Q-4:使用者切回聊天分頁時呼叫,只清推播提示,不動各房未讀數
|
|
roomUnreadCounts: Record<string, number>; // T20-P2:各進行中房間的未讀數(來自大廳聚合查詢)
|
|
setRoomUnreadCounts: (counts: Record<string, number>) => void;
|
|
totalRoomUnread: number;
|
|
}
|
|
|
|
const UnreadContext = createContext<UnreadContextValue | null>(null);
|
|
|
|
// Q-4:聊天分頁的未讀/推播提示紅點狀態,由根佈局的推播接收監聽器寫入,
|
|
// 由聊天分頁在取得焦點時清除(見 app/(tabs)/index.tsx 的 useFocusEffect)。
|
|
// T20-P2:擴充承接大廳多房未讀——分頁籤紅點現在同時反映「有推播尚未看」與「任一房間有未讀訊息」,
|
|
// 兩個來源各自獨立更新,互不覆蓋(推播清除不會把房間未讀數也清空,反之亦然)。
|
|
export function UnreadProvider({ children }: { children: ReactNode }) {
|
|
const [pushUnread, setPushUnread] = useState(false);
|
|
const [roomUnreadCounts, setRoomUnreadCounts] = useState<Record<string, number>>({});
|
|
const markUnread = useCallback(() => setPushUnread(true), []);
|
|
const markRead = useCallback(() => setPushUnread(false), []);
|
|
const totalRoomUnread = useMemo(
|
|
() => Object.values(roomUnreadCounts).reduce((sum, count) => sum + count, 0),
|
|
[roomUnreadCounts],
|
|
);
|
|
const hasUnread = pushUnread || totalRoomUnread > 0;
|
|
|
|
return (
|
|
<UnreadContext.Provider
|
|
value={{ hasUnread, markUnread, markRead, roomUnreadCounts, setRoomUnreadCounts, totalRoomUnread }}
|
|
>
|
|
{children}
|
|
</UnreadContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useUnread(): UnreadContextValue {
|
|
const ctx = useContext(UnreadContext);
|
|
if (!ctx) {
|
|
throw new Error("useUnread 必須在 UnreadProvider 底下使用");
|
|
}
|
|
return ctx;
|
|
}
|