refactor(chat): show images in page overlay
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatState } from "@/stores/chat/chat-context";
|
||||
@@ -17,8 +19,14 @@ import {
|
||||
ChatUnlockDialogs,
|
||||
ExternalBrowserDialog,
|
||||
FirstRechargeOfferBanner,
|
||||
FullscreenImageViewer,
|
||||
PwaInstallOverlay,
|
||||
} from "./components";
|
||||
import {
|
||||
buildChatImageOverlayUrl,
|
||||
buildChatWithoutImageOverlayUrl,
|
||||
getChatImageOverlayMessageId,
|
||||
} from "./chat-image-overlay-url";
|
||||
import {
|
||||
deriveIsGuest,
|
||||
isChatDevelopmentEnvironment,
|
||||
@@ -31,15 +39,44 @@ import { useExternalBrowserPrompt } from "./hooks/use-external-browser-prompt";
|
||||
import styles from "./components/chat-screen.module.css";
|
||||
|
||||
export function ChatScreen() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const state = useChatState();
|
||||
const authState = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const imageMessageId = getChatImageOverlayMessageId(searchParams);
|
||||
const imageReturnUrl = imageMessageId
|
||||
? buildChatImageOverlayUrl(imageMessageId)
|
||||
: ROUTES.chat;
|
||||
const {
|
||||
unlockPaywallRequest,
|
||||
requestMessageUnlock,
|
||||
closeInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog,
|
||||
} = useChatUnlockNavigationFlow({ returnUrl: ROUTES.chat });
|
||||
} = useChatUnlockNavigationFlow({
|
||||
returnUrl: ROUTES.chat,
|
||||
ignoredKind: "image",
|
||||
});
|
||||
const {
|
||||
unlockPaywallRequest: imageUnlockPaywallRequest,
|
||||
requestMessageUnlock: requestImageUnlock,
|
||||
closeInsufficientCreditsDialog: closeImageInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog: confirmImageInsufficientCreditsDialog,
|
||||
} = useChatUnlockNavigationFlow({
|
||||
returnUrl: imageReturnUrl,
|
||||
expectedKind: "image",
|
||||
expectedMessageId: imageMessageId ?? undefined,
|
||||
enabled: imageMessageId !== null,
|
||||
});
|
||||
const selectedImageMessage = useMemo(
|
||||
() =>
|
||||
imageMessageId
|
||||
? state.messages.find(
|
||||
(item) => item.id === imageMessageId && item.imageUrl,
|
||||
) ?? null
|
||||
: null,
|
||||
[imageMessageId, state.messages],
|
||||
);
|
||||
|
||||
// isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段)
|
||||
const isGuest = deriveIsGuest(authState.loginStatus);
|
||||
@@ -68,6 +105,21 @@ export function ChatScreen() {
|
||||
loginStatus: authState.loginStatus,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageMessageId || !state.historyLoaded || selectedImageMessage) {
|
||||
return;
|
||||
}
|
||||
router.replace(buildChatWithoutImageOverlayUrl(searchParams), {
|
||||
scroll: false,
|
||||
});
|
||||
}, [
|
||||
imageMessageId,
|
||||
router,
|
||||
searchParams,
|
||||
selectedImageMessage,
|
||||
state.historyLoaded,
|
||||
]);
|
||||
|
||||
function handleUnlockPrivateMessage(messageId: string): void {
|
||||
requestMessageUnlock(messageId, "private");
|
||||
}
|
||||
@@ -76,6 +128,21 @@ export function ChatScreen() {
|
||||
requestMessageUnlock(messageId, "voice");
|
||||
}
|
||||
|
||||
function handleOpenImage(messageId: string): void {
|
||||
router.push(buildChatImageOverlayUrl(messageId), { scroll: false });
|
||||
}
|
||||
|
||||
function handleCloseImageViewer(): void {
|
||||
router.replace(buildChatWithoutImageOverlayUrl(searchParams), {
|
||||
scroll: false,
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnlockImagePaywall(): void {
|
||||
if (!imageMessageId) return;
|
||||
requestImageUnlock(imageMessageId, "image");
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileShell>
|
||||
<div className={styles.shell}>
|
||||
@@ -110,6 +177,7 @@ export function ChatScreen() {
|
||||
unlockingMessageId={state.unlockingMessageId}
|
||||
onUnlockPrivateMessage={handleUnlockPrivateMessage}
|
||||
onUnlockVoiceMessage={handleUnlockVoiceMessage}
|
||||
onOpenImage={handleOpenImage}
|
||||
/>
|
||||
|
||||
{messageLimitBanner.visible ? (
|
||||
@@ -141,6 +209,27 @@ export function ChatScreen() {
|
||||
onCloseInsufficientCreditsDialog={closeInsufficientCreditsDialog}
|
||||
onConfirmInsufficientCreditsDialog={confirmInsufficientCreditsDialog}
|
||||
/>
|
||||
<ChatUnlockDialogs
|
||||
unlockPaywallRequest={imageUnlockPaywallRequest}
|
||||
onCloseInsufficientCreditsDialog={closeImageInsufficientCreditsDialog}
|
||||
onConfirmInsufficientCreditsDialog={
|
||||
confirmImageInsufficientCreditsDialog
|
||||
}
|
||||
/>
|
||||
|
||||
{selectedImageMessage?.imageUrl ? (
|
||||
<FullscreenImageViewer
|
||||
messageId={selectedImageMessage.id}
|
||||
imageUrl={selectedImageMessage.imageUrl}
|
||||
imagePaywalled={selectedImageMessage.imagePaywalled === true}
|
||||
isUnlockingImagePaywall={
|
||||
state.isUnlockingMessage &&
|
||||
state.unlockingMessageId === selectedImageMessage.id
|
||||
}
|
||||
onUnlockImagePaywall={handleUnlockImagePaywall}
|
||||
onClose={handleCloseImageViewer}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</MobileShell>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user