import { useAtomValue } from 'jotai'; import React, { ChangeEventHandler, useCallback, useMemo, useRef, useState } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import FocusTrap from 'focus-trap-react'; import { Avatar, Box, Button, Header, Icon, IconButton, Icons, Input, MenuItem, Modal, Overlay, OverlayBackdrop, OverlayCenter, Scroll, Text, config, } from 'folds'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { useAllJoinedRoomsSet, useGetRoom } from '../hooks/useGetRoom'; import { allRoomsAtom } from '../state/room-list/roomList'; import { mDirectAtom } from '../state/mDirectList'; import { useDirects, useRooms } from '../state/hooks/roomList'; import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../utils/room'; import { RoomAvatar, RoomIcon } from '../components/room-avatar'; import { nameInitials } from '../utils/common'; import { useMediaAuthentication } from '../hooks/useMediaAuthentication'; import { factoryRoomIdByActivity } from '../utils/sort'; import { SearchItemStrGetter, useAsyncSearch, UseAsyncSearchOptions, } from '../hooks/useAsyncSearch'; import { VirtualTile } from '../components/virtualizer'; import { AndroidSharePayload } from '../utils/androidShare'; import { stopPropagation } from '../utils/keyboard'; const SEARCH_OPTS: UseAsyncSearchOptions = { limit: 200, matchOptions: { contain: true, }, }; type ShareRoomPickerProps = { /** The pending share payload to display a preview of. */ share: AndroidSharePayload; /** Called when the user picks a room. */ onPick: (roomId: string) => void; /** Called when the user dismisses without picking. */ onDismiss: () => void; }; /** * Full-screen modal that lets the user pick a room to send * an incoming Android share intent into. */ export function ShareRoomPicker({ share, onPick, onDismiss }: ShareRoomPickerProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const scrollRef = useRef(null); const [confirmRoomId, setConfirmRoomId] = useState(null); const mDirects = useAtomValue(mDirectAtom); const allRoomsSet = useAllJoinedRoomsSet(); const getRoom = useGetRoom(allRoomsSet); const rooms = useRooms(mx, allRoomsAtom, mDirects); const directs = useDirects(mx, allRoomsAtom, mDirects); const allItems = useMemo( () => [...rooms, ...directs].sort(factoryRoomIdByActivity(mx)), [mx, rooms, directs] ); const getRoomNameStr: SearchItemStrGetter = useCallback( (rId) => getRoom(rId)?.name ?? rId, [getRoom] ); const [searchResult, searchRoom, resetSearch] = useAsyncSearch( allItems, getRoomNameStr, SEARCH_OPTS ); const items = searchResult ? searchResult.items : allItems; const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => scrollRef.current, estimateSize: () => 48, overscan: 8, }); const vItems = virtualizer.getVirtualItems(); const handleSearchChange: ChangeEventHandler = (evt) => { const value = evt.currentTarget.value.trim(); if (!value) { resetSearch(); return; } searchRoom(value); }; const handleRoomClick: React.MouseEventHandler = (evt) => { const roomId = evt.currentTarget.getAttribute('data-room-id'); if (roomId) { setConfirmRoomId(roomId); } }; const handleConfirm = useCallback(() => { if (confirmRoomId) { onPick(confirmRoomId); } }, [confirmRoomId, onPick]); const handleCancelConfirm = useCallback(() => { setConfirmRoomId(null); }, []); const previewText = share.text?.slice(0, 80) ?? share.subject?.slice(0, 80); const fileCount = share.files.length; return ( <>
Share to Room {(previewText || fileCount > 0) && ( {previewText ? `"${previewText}${(share.text?.length ?? 0) > 80 ? '…' : ''}"` : `${fileCount} file${fileCount !== 1 ? 's' : ''}`} )}
} placeholder="Search rooms…" size="400" variant="Background" outlined autoFocus style={{ width: '100%' }} /> {vItems.length === 0 && ( {searchResult ? 'No Match Found' : 'No Rooms'} {searchResult ? `No rooms found for "${searchResult.query}".` : 'You have no rooms to share into.'} )} {vItems.map((vItem) => { const roomId = items[vItem.index]; const room = getRoom(roomId); if (!room) return null; const isDm = mDirects.has(roomId); const avatarSrc = isDm ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication); return ( {avatarSrc ? ( ( {nameInitials(room.name)} )} /> ) : ( )} } > {room.name} ); })}
{confirmRoomId && (() => { const room = getRoom(confirmRoomId); const roomName = room?.name ?? confirmRoomId; const isDm = mDirects.has(confirmRoomId); return ( }> Share to {isDm ? 'Person' : 'Room'}? Send to {roomName}? ); })()} ); }