diff --git a/src/app/features/room/MediaGallery.tsx b/src/app/features/room/MediaGallery.tsx index c008854d2..0b5ffa055 100644 --- a/src/app/features/room/MediaGallery.tsx +++ b/src/app/features/room/MediaGallery.tsx @@ -13,7 +13,7 @@ import { TooltipProvider, config, } from 'folds'; -import { Direction, EventType, MatrixEvent, MsgType, Room } from 'matrix-js-sdk'; +import { EventType, MsgType, Room } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { mxcUrlToHttp } from '../../utils/matrix'; @@ -65,53 +65,45 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { const useAuthentication = useMediaAuthentication(); const [tab, setTab] = useState('image'); - const [events, setEvents] = useState([]); const [loading, setLoading] = useState(false); - const [paginationToken, setPaginationToken] = useState(null); + const [canLoadMore, setCanLoadMore] = useState(true); const msgtype = TAB_MSGTYPES[tab]; - const loadMedia = useCallback( - async (fromToken: string | null, append: boolean) => { - setLoading(true); - try { - const response = await mx.createMessagesRequest( - room.roomId, - fromToken, - 100, - Direction.Backward, - undefined, - ); - const { end, chunk } = response; - const filtered = chunk - .filter( - (ev) => - ev.type === EventType.RoomMessage && - ev.content?.msgtype === msgtype && - !ev.unsigned?.redacted_because, - ) - .map((ev) => new MatrixEvent(ev)); + // Read already-decrypted events from the live timeline (works for E2EE rooms) + const getFilteredEvents = useCallback(() => { + const timeline = room.getLiveTimeline(); + return timeline + .getEvents() + .filter((ev) => { + if (ev.isRedacted()) return false; + const content = ev.getContent(); + return ev.getType() === EventType.RoomMessage && content.msgtype === msgtype; + }) + .slice() + .reverse(); // newest first + }, [room, msgtype]); - setEvents((prev) => (append ? [...prev, ...filtered] : filtered)); - setPaginationToken(end ?? null); - } catch { - // silently swallow fetch errors — gallery stays showing what it has - } finally { - setLoading(false); - } - }, - [mx, room.roomId, msgtype], - ); + const [events, setEvents] = useState(() => getFilteredEvents()); useEffect(() => { - setEvents([]); - setPaginationToken(null); - loadMedia(null, false).catch(() => undefined); - }, [loadMedia]); + setEvents(getFilteredEvents()); + setCanLoadMore(true); + }, [getFilteredEvents]); - const handleLoadMore = () => { - if (paginationToken) loadMedia(paginationToken, true).catch(() => undefined); - }; + const handleLoadMore = useCallback(async () => { + setLoading(true); + try { + const timeline = room.getLiveTimeline(); + const hasMore = await mx.paginateEventTimeline(timeline, { backwards: true, limit: 100 }); + setEvents(getFilteredEvents()); + setCanLoadMore(hasMore); + } catch { + // silently swallow + } finally { + setLoading(false); + } + }, [mx, room, getFilteredEvents]); return ( 0 && (