diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 01cb16e37d..8af053fd54 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -7,7 +7,7 @@ import { type Page } from "@playwright/test"; -import { test, expect } from "../../../element-web-test"; +import { expect, test } from "../../../element-web-test"; test.describe("Room list", () => { test.use({ @@ -144,6 +144,32 @@ test.describe("Room list", () => { }); }); + test.describe("Avatar decoration", () => { + test.use({ labsFlags: ["feature_video_rooms", "feature_new_room_list"] }); + + test("should be a public room", { tag: "@screenshot" }, async ({ page, app, user }) => { + // @ts-ignore Visibility enum is not accessible + await app.client.createRoom({ name: "public room", visibility: "public" }); + const roomListView = getRoomList(page); + const publicRoom = roomListView.getByRole("gridcell", { name: "public room" }); + + await expect(publicRoom).toBeVisible(); + await expect(publicRoom).toMatchScreenshot("room-list-item-public.png"); + }); + + test("should be a video room", { tag: "@screenshot" }, async ({ page, app, user }) => { + await page.getByTestId("room-list-panel").getByRole("button", { name: "Add" }).click(); + await page.getByRole("menuitem", { name: "New video room" }).click(); + await page.getByRole("textbox", { name: "Name" }).fill("video room"); + await page.getByRole("button", { name: "Create video room" }).click(); + + const roomListView = getRoomList(page); + const videoRoom = roomListView.getByRole("gridcell", { name: "video room" }); + await expect(videoRoom).toBeVisible(); + await expect(videoRoom).toMatchScreenshot("room-list-item-video.png"); + }); + }); + test.describe("Notification decoration", () => { test("should render the invitation decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => { const roomListView = getRoomList(page); diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png index 43cae04270..6f2daa3017 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png index 7adeb314fd..2a75d0d91c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index b0cab67c66..f4aa4d56a9 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png index 05e2172e24..cfa08c2008 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png index 89a975c309..28d5719733 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png index eab0846f76..60f4916270 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index f9417d52da..6257aa7af8 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index d5a2d8a4c0..cad0f83b4a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png new file mode 100644 index 0000000000..e83fee3ed2 Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png index 35bcbea2a1..72f0c42425 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png new file mode 100644 index 0000000000..41881886f6 Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png index 35de485457..1f7a84c9a6 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 4691d71e25..b58a53a325 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -116,6 +116,7 @@ @import "./views/auth/_Welcome.pcss"; @import "./views/avatars/_BaseAvatar.pcss"; @import "./views/avatars/_DecoratedRoomAvatar.pcss"; +@import "./views/avatars/_RoomAvatarView.pcss"; @import "./views/avatars/_WidgetAvatar.pcss"; @import "./views/avatars/_WithPresenceIndicator.pcss"; @import "./views/beta/_BetaCard.pcss"; diff --git a/res/css/views/avatars/_RoomAvatarView.pcss b/res/css/views/avatars/_RoomAvatarView.pcss new file mode 100644 index 0000000000..0d5523a9f1 --- /dev/null +++ b/res/css/views/avatars/_RoomAvatarView.pcss @@ -0,0 +1,48 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +.mx_RoomAvatarView { + --room-avatar-size: 32px; + + position: relative; + + /* Keep the container to the same size than the avatar */ + inline-size: var(--room-avatar-size); + block-size: var(--room-avatar-size); + + .mx_RoomAvatarView_RoomAvatar { + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + } + + .mx_RoomAvatarView_RoomAvatar_icon { + mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-icon-mask.svg"); + } + + .mx_RoomAvatarView_RoomAvatar_presence { + mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-presence-mask.svg"); + } + + .mx_RoomAvatarView_icon { + position: absolute; + + /* Place half the icon inside the avatar */ + /* Avatar size - (icon size (16px) / 2) */ + left: calc((var(--room-avatar-size) - 8px)); + bottom: var(--cpd-space-0-5x); + } + + .mx_RoomAvatarView_PresenceDecoration { + position: absolute; + + /* Place half the icon inside the avatar */ + /* Avatar size - (icon size (8px) / 2) */ + left: calc((var(--room-avatar-size) - 4px)); + bottom: var(--cpd-space-0-5x); + } +} diff --git a/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg new file mode 100644 index 0000000000..8eaf2f1831 --- /dev/null +++ b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg new file mode 100644 index 0000000000..b40f382d4b --- /dev/null +++ b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx new file mode 100644 index 0000000000..784184e159 --- /dev/null +++ b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx @@ -0,0 +1,139 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { + EventType, + JoinRule, + type MatrixEvent, + type Room, + RoomEvent, + type User, + UserEvent, +} from "matrix-js-sdk/src/matrix"; +import { useState } from "react"; + +import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers"; +import { BUSY_PRESENCE_NAME } from "../../views/rooms/PresenceLabel"; +import { isPresenceEnabled } from "../../../utils/presence"; + +/** + * The presence of a user in a DM room. + * - "online": The user is online. + * - "offline": The user is offline. + * - "busy": The user is busy. + * - "unavailable": the presence is unavailable. + * - null: the user is not in a DM room or presence is not enabled. + */ +export type Presence = "online" | "offline" | "busy" | "unavailable" | null; + +export interface RoomAvatarViewState { + /** + * Whether the room avatar has a decoration. + * A decoration can be a public or a video call icon or an indicator of presence. + */ + hasDecoration: boolean; + /** + * Whether the room is public. + */ + isPublic: boolean; + /** + * Whether the room is a video room. + */ + isVideoRoom: boolean; + /** + * The presence of the user in the DM room. + * If null, the user is not in a DM room or presence is not enabled. + */ + presence: Presence; +} + +/** + * Hook to get the state of the room avatar. + * @param room + */ +export function useRoomAvatarViewModel(room: Room): RoomAvatarViewState { + const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); + const presence = useDMPresence(room); + const isPublic = useIsPublic(room); + + const hasDecoration = isPublic || isVideoRoom || presence !== null; + + return { hasDecoration, isPublic, isVideoRoom, presence }; +} + +/** + * Hook listening to the room join rules. + * Return true if the room is public. + * @param room + */ +function useIsPublic(room: Room): boolean { + const [isPublic, setIsPublic] = useState(isRoomPublic(room)); + // We don't use `useTypedEventEmitterState` because we don't want to update `isPublic` value at every `RoomEvent.Timeline` event. + useTypedEventEmitter(room, RoomEvent.Timeline, (ev: MatrixEvent, _room: Room) => { + if (room.roomId !== _room.roomId) return; + if (ev.getType() !== EventType.RoomJoinRules && ev.getType() !== EventType.RoomMember) return; + + setIsPublic(isRoomPublic(_room)); + }); + + return isPublic; +} + +/** + * Whether the room is public. + * @param room + */ +function isRoomPublic(room: Room): boolean { + return room.getJoinRule() === JoinRule.Public; +} + +/** + * Hook listening to the presence of the DM user. + * @param room + */ +function useDMPresence(room: Room): Presence { + const dmUser = getDMUser(room); + const [presence, setPresence] = useState(getPresence(dmUser)); + useTypedEventEmitter(dmUser, UserEvent.Presence, () => setPresence(getPresence(dmUser))); + useTypedEventEmitter(dmUser, UserEvent.CurrentlyActive, () => setPresence(getPresence(dmUser))); + + return presence; +} + +/** + * Get the DM user of the room. + * Return undefined if the room is not a DM room, if we can't find the user or if the presence is not enabled. + * @param room + * @returns found user + */ +function getDMUser(room: Room): User | undefined { + const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId); + if (!otherUserId) return; + if (getJoinedNonFunctionalMembers(room).length !== 2) return; + if (!isPresenceEnabled(room.client)) return; + + return room.client.getUser(otherUserId) || undefined; +} + +/** + * Get the presence of the DM user. + * @param dmUser + */ +function getPresence(dmUser: User | undefined): Presence { + if (!dmUser) return null; + if (BUSY_PRESENCE_NAME.matches(dmUser.presence)) return "busy"; + + const isOnline = dmUser.currentlyActive || dmUser.presence === "online"; + if (isOnline) return "online"; + + if (dmUser.presence === "offline") return "offline"; + if (dmUser.presence === "unavailable") return "unavailable"; + + return null; +} diff --git a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx index 8a1fdb1fe7..66b44e0086 100644 --- a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx @@ -128,8 +128,8 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { const isSpaceRoom = Boolean(activeSpace); const canCreateRoom = hasCreateRoomRights(matrixClient, activeSpace); - const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms"); - const displayComposeMenu = canCreateRoom || canCreateVideoRoom; + const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms") && canCreateRoom; + const displayComposeMenu = canCreateRoom; const displaySpaceMenu = isSpaceRoom; const canInviteInSpace = Boolean( activeSpace?.getJoinRule() === JoinRule.Public || activeSpace?.canInvite(matrixClient.getSafeUserId()), diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx index d15ad0a42b..ad6a271e57 100644 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -18,6 +18,8 @@ import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNo import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { DefaultTagID } from "../../../stores/room-list/models"; +import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; +import { type ConnectionState } from "../../../models/Call"; export interface RoomListItemViewState { /** @@ -40,6 +42,19 @@ export interface RoomListItemViewState { * Whether the room should be bolded. */ isBold: boolean; + /** + * Whether the room is a video room + */ + isVideoRoom: boolean; + /** + * The connection state of the call. + * `null` if there is no call in the room. + */ + callConnectionState: ConnectionState | null; + /** + * Whether there are participants in the call. + */ + hasParticipantInCall: boolean; } /** @@ -57,6 +72,14 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { const a11yLabel = getA11yLabel(room, notificationState); const isBold = notificationState.hasAnyNotificationOrActivity; + // Video room + const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); + // EC video call or video room + const call = useCall(room.roomId); + const connectionState = useConnectionState(call); + const hasParticipantInCall = useParticipantCount(call) > 0; + const callConnectionState = call ? connectionState : null; + // Actions const openRoom = useCallback((): void => { @@ -73,6 +96,9 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { openRoom, a11yLabel, isBold, + isVideoRoom, + callConnectionState, + hasParticipantInCall, }; } diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index c4b17aea53..321c0501dc 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -79,6 +79,9 @@ function tooltipText(variant: Icon): string | undefined { } } +/** + * @deprecated Use {@link RoomAvatarView} instead. + */ export default class DecoratedRoomAvatar extends React.PureComponent { private _dmUser: User | null = null; private isUnmounted = false; diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index e1e3e9157d..2c0e079c83 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -144,7 +144,7 @@ export default class RoomAvatar extends React.Component { } public render(): React.ReactNode { - const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props; + const { room, oobData, viewAvatarOnClick, onClick, ...otherProps } = this.props; const roomName = room?.name ?? oobData.name ?? "?"; return ( diff --git a/src/components/views/avatars/RoomAvatarView.tsx b/src/components/views/avatars/RoomAvatarView.tsx new file mode 100644 index 0000000000..96bfbaf198 --- /dev/null +++ b/src/components/views/avatars/RoomAvatarView.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React, { type JSX } from "react"; +import { type Room } from "matrix-js-sdk/src/matrix"; +import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public"; +import VideoIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; +import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8"; +import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8"; +import BusyIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8"; +import classNames from "classnames"; + +import RoomAvatar from "./RoomAvatar"; +import { useRoomAvatarViewModel, type Presence } from "../../viewmodels/avatars/RoomAvatarViewModel"; +import { _t } from "../../../languageHandler"; + +interface RoomAvatarViewProps { + /** + * The room to display the avatar for. + */ + room: Room; +} + +/** + * Component to display the avatar of a room. + * Currently only 32px size is supported. + */ +export function RoomAvatarView({ room }: RoomAvatarViewProps): JSX.Element { + const vm = useRoomAvatarViewModel(room); + // No decoration, we just show the avatar + if (!vm.hasDecoration) return ; + + return ( +
+ + + {/* If the room is a public video room, we prefer to display only the video icon */} + {vm.isPublic && !vm.isVideoRoom && ( + + )} + {vm.isVideoRoom && ( + + )} + {vm.presence && } +
+ ); +} + +type PresenceDecorationProps = { + /** + * The presence of the user in the DM room. + */ + presence: NonNullable; +}; + +/** + * Component to display the presence of a user in a DM room. + */ +function PresenceDecoration({ presence }: PresenceDecorationProps): JSX.Element { + switch (presence) { + case "online": + return ( + + ); + case "unavailable": + return ( + + ); + case "offline": + return ( + + ); + case "busy": + return ( + + ); + } +} diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index 3266445782..cfb82c461c 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -9,6 +9,7 @@ import React, { type HTMLProps, type JSX } from "react"; import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/mention"; import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid"; import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid"; +import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; import { Flex } from "../../utils/Flex"; @@ -19,6 +20,10 @@ interface NotificationDecorationProps extends HTMLProps { * The notification state of the room or thread. */ notificationState: RoomNotificationState; + /** + * Whether the room has a video call. + */ + hasVideoCall: boolean; } /** @@ -26,6 +31,7 @@ interface NotificationDecorationProps extends HTMLProps { */ export function NotificationDecoration({ notificationState, + hasVideoCall, ...props }: NotificationDecorationProps): JSX.Element | null { const { @@ -38,7 +44,7 @@ export function NotificationDecoration({ count, muted, } = notificationState; - if (!hasAnyNotificationOrActivity && !muted) return null; + if (!hasAnyNotificationOrActivity && !muted && !hasVideoCall) return null; return ( {isUnsetMessage && } + {hasVideoCall && } {invited && } {isMention && } {(isMention || isNotification) && } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index c0b204fc01..89a353afb1 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -10,10 +10,10 @@ import { type Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel"; -import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; import { Flex } from "../../../utils/Flex"; import { RoomListItemMenuView } from "./RoomListItemMenuView"; import { NotificationDecoration } from "../NotificationDecoration"; +import { RoomAvatarView } from "../../avatars/RoomAvatarView"; interface RoomListItemViewPropsProps extends React.HTMLAttributes { /** @@ -39,7 +39,8 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie const showHoverDecoration = (isMenuOpen || isHover) && vm.showHoverMenu; const isNotificationDecorationVisible = - !showHoverDecoration && (vm.notificationState.hasAnyNotificationOrActivity || vm.notificationState.muted); + !showHoverDecoration && + (vm.notificationState.hasAnyNotificationOrActivity || vm.notificationState.muted || vm.hasParticipantInCall); return (