feat: improve media viewer and push delivery stability
- add unified Android media viewer with swipe navigation, pinch-to-zoom and swipe-to-dismiss\n- move circle videos out of media gallery and surface them in voice/chat info flows\n- align web chat info handling for circle videos and media viewer exclusions\n- stabilize realtime and tablet chat shell updates already staged in this batch\n- fix Celery push delivery loop handling so FCM jobs can read tokens reliably in worker processes
This commit is contained in:
@@ -104,9 +104,21 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
|
||||
const count = chat.members_count ?? members.length;
|
||||
return count <= 1;
|
||||
}, [chat, isGroupLike, myRoleNormalized, members.length]);
|
||||
const photoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("image/")).sort((a, b) => b.id - a.id), [attachments]);
|
||||
const videoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("video/")).sort((a, b) => b.id - a.id), [attachments]);
|
||||
const voiceAttachments = useMemo(() => attachments.filter((item) => item.message_type === "voice").sort((a, b) => b.id - a.id), [attachments]);
|
||||
const photoAttachments = useMemo(
|
||||
() => attachments.filter((item) => item.file_type.startsWith("image/")).sort((a, b) => b.id - a.id),
|
||||
[attachments]
|
||||
);
|
||||
const videoAttachments = useMemo(
|
||||
() =>
|
||||
attachments
|
||||
.filter((item) => item.file_type.startsWith("video/") && item.message_type !== "circle_video")
|
||||
.sort((a, b) => b.id - a.id),
|
||||
[attachments]
|
||||
);
|
||||
const voiceAttachments = useMemo(
|
||||
() => attachments.filter((item) => item.message_type === "voice" || item.message_type === "circle_video").sort((a, b) => b.id - a.id),
|
||||
[attachments]
|
||||
);
|
||||
const audioAttachments = useMemo(
|
||||
() =>
|
||||
attachments
|
||||
@@ -871,10 +883,15 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
|
||||
.slice(0, 120)
|
||||
.map((item) => (
|
||||
<button
|
||||
className="group relative aspect-square overflow-hidden rounded-md border border-slate-700/70 bg-slate-900"
|
||||
className={`group relative aspect-square overflow-hidden border border-slate-700/70 bg-slate-900 ${item.message_type === "circle_video" ? "rounded-full" : "rounded-md"}`}
|
||||
key={`media-item-${item.id}`}
|
||||
onClick={() => {
|
||||
if (item.message_type === "circle_video") {
|
||||
jumpToMessage(item.message_id);
|
||||
return;
|
||||
}
|
||||
const mediaItems = [...photoAttachments, ...videoAttachments]
|
||||
.filter((it) => it.message_type !== "circle_video")
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.map((it) => ({ url: it.file_url, type: it.file_type.startsWith("video/") ? "video" as const : "image" as const, messageId: it.message_id }));
|
||||
const idx = mediaItems.findIndex((it) => it.url === item.file_url && it.messageId === item.message_id);
|
||||
|
||||
@@ -1100,6 +1100,7 @@ function renderMessageContent(
|
||||
|
||||
if (messageType === "image" || messageType === "video" || messageType === "circle_video") {
|
||||
const mediaItems = attachments
|
||||
.filter((item) => item.message_type !== "circle_video")
|
||||
.filter((item) => item.file_type.startsWith("image/") || item.file_type.startsWith("video/"))
|
||||
.map((item) => ({
|
||||
url: item.file_url,
|
||||
@@ -1113,13 +1114,14 @@ function renderMessageContent(
|
||||
}
|
||||
if (mediaItems.length === 1) {
|
||||
const item = mediaItems[0];
|
||||
const isCircleVideo = messageType === "circle_video";
|
||||
const blockViewerOpen = isStickerOrGifMedia(item.url);
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<button
|
||||
className="relative block overflow-hidden rounded-xl bg-slate-950/30"
|
||||
className={`relative block overflow-hidden bg-slate-950/30 ${isCircleVideo ? "aspect-square w-56 rounded-full" : "rounded-xl"}`}
|
||||
onClick={() => {
|
||||
if (blockViewerOpen) {
|
||||
if (blockViewerOpen || isCircleVideo) {
|
||||
return;
|
||||
}
|
||||
opts.onOpenMedia(item.url, item.type);
|
||||
@@ -1131,10 +1133,15 @@ function renderMessageContent(
|
||||
type="button"
|
||||
>
|
||||
{item.type === "image" ? (
|
||||
<img alt="attachment" className="max-h-80 rounded-xl object-cover" draggable={false} src={item.url} />
|
||||
<img
|
||||
alt="attachment"
|
||||
className={isCircleVideo ? "h-full w-full object-cover" : "max-h-80 rounded-xl object-cover"}
|
||||
draggable={false}
|
||||
src={item.url}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<video className="max-h-80 rounded-xl" muted src={item.url} />
|
||||
<video className={isCircleVideo ? "h-full w-full object-cover" : "max-h-80 rounded-xl"} muted src={item.url} />
|
||||
<span className="absolute inset-0 flex items-center justify-center text-3xl text-white/85">▶</span>
|
||||
</>
|
||||
)}
|
||||
@@ -1410,6 +1417,7 @@ function collectMediaItems(
|
||||
const attachments = attachmentsByMessage[message.id] ?? [];
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.file_url) continue;
|
||||
if (attachment.message_type === "circle_video") continue;
|
||||
if (!attachment.file_type.startsWith("image/") && !attachment.file_type.startsWith("video/")) continue;
|
||||
if (isStickerOrGifMedia(attachment.file_url)) continue;
|
||||
const type = attachment.file_type.startsWith("image/") ? "image" : "video";
|
||||
@@ -1419,7 +1427,7 @@ function collectMediaItems(
|
||||
items.push({ url: attachment.file_url, type });
|
||||
}
|
||||
if (attachments.length === 0 && message.text && /^https?:\/\//i.test(message.text)) {
|
||||
if (message.type !== "image" && message.type !== "video" && message.type !== "circle_video") continue;
|
||||
if (message.type !== "image" && message.type !== "video") continue;
|
||||
if (isStickerOrGifMedia(message.text)) continue;
|
||||
const type = message.type === "image" ? "image" : "video";
|
||||
const key = `${type}:${message.text}`;
|
||||
|
||||
Reference in New Issue
Block a user