import React from 'react'; import { Avatar, Box, Icon, Icons, Text } from 'folds'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import * as css from './styles.css'; import { CallControl } from './CallControl'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { CallEmbed } from '../../plugins/call/CallEmbed'; import { useCallJoined } from '../../hooks/useCallEmbed'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useRoomAvatar, useRoomName } from '../../hooks/useRoomMeta'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { UserAvatar } from '../../components/user-avatar'; import { RoomAvatar } from '../../components/room-avatar'; import { getMemberDisplayName } from '../../utils/room'; import { getMxIdLocalPart, guessDmRoomUserId, mxcUrlToHttp, } from '../../utils/matrix'; type CallStatusProps = { callEmbed: CallEmbed; }; export function CallStatus({ callEmbed }: CallStatusProps) { const { t } = useTranslation(); const { room } = callEmbed; const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const callJoined = useCallJoined(callEmbed); const callSession = useCallSession(room); const callMembers = useCallMembers(room, callSession); const screenSize = useScreenSizeContext(); const compact = screenSize === ScreenSize.Mobile; const { navigateRoom } = useRoomNavigate(); // Member-count gate mirrors the rest of the post-P3c app: 1:1 chrome // surfaces the peer (their avatar + display name); group chrome // surfaces the room itself. Snapshot — call duration is short relative // to membership churn, and `useCallMembers` already reactively covers // the «who's actually live» count below, so peer chrome at most lags // one render behind a third joiner before re-painting. const isOneOnOne = room.getInvitedAndJoinedMemberCount() === 2; const myUserId = mx.getSafeUserId(); const peerCandidate = isOneOnOne ? guessDmRoomUserId(room, myUserId) : undefined; const peerUserId = peerCandidate && peerCandidate !== myUserId ? peerCandidate : undefined; const peerName = peerUserId ? getMemberDisplayName(room, peerUserId) ?? getMxIdLocalPart(peerUserId) ?? peerUserId : undefined; const roomName = useRoomName(room); const avatarMxc = useRoomAvatar(room, isOneOnOne); const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; // Defensive fallback chain — empty roomName + unresolved peer would // otherwise collapse the H4 to "" and leave the pill anonymous. const displayName = (isOneOnOne && peerName) || roomName || peerUserId || room.roomId; // Sub-text is the live state: «Соединение…» until the widget reports // joined, then either «В звонке» (1:1) or «N в звонке» (group). The // pulsing dot is bound to `callJoined` so the user gets instant // visual confirmation that LiveKit is up. const memberCount = callMembers.length; let subText: string; if (!callJoined) { subText = t('Call.connecting'); } else if (isOneOnOne || memberCount <= 1) { subText = t('Call.in_call'); } else { subText = t('Call.in_call_count', { count: memberCount }); } return ( navigateRoom(room.roomId)} aria-label={t('Call.open_call_room')} > {isOneOnOne ? ( } /> ) : ( } /> )} {displayName} {subText} ); }