Add low priority avatar decoration to room tile (#30065)

* Add avatar decoration for low priority rooms

* Write tests

* Remove unnecesasry step in test

* Make the vm expose which decoration to render

* Fix jest test

* Fix broken e2e test
This commit is contained in:
R Midhun Suresh
2025-06-10 13:45:38 +05:30
committed by GitHub
parent 2b24232f14
commit 6103f7e3b4
8 changed files with 207 additions and 142 deletions

View File

@@ -10,26 +10,26 @@ import { useEffect, useState } from "react";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { useDmMember, usePresence, type Presence } from "../../views/avatars/WithPresenceIndicator";
import { DefaultTagID } from "../../../stores/room-list/models";
export enum AvatarBadgeDecoration {
LowPriority = "LowPriority",
VideoRoom = "VideoRoom",
PublicRoom = "PublicRoom",
Presence = "Presence",
}
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 | null;
/**
* The decoration that should be rendered.
*/
badgeDecoration?: AvatarBadgeDecoration;
}
/**
@@ -41,10 +41,20 @@ export function useRoomAvatarViewModel(room: Room): RoomAvatarViewState {
const roomMember = useDmMember(room);
const presence = usePresence(room, roomMember);
const isPublic = useIsPublic(room);
const isLowPriority = !!room.tags[DefaultTagID.LowPriority];
const hasDecoration = isPublic || isVideoRoom || presence !== null;
let badgeDecoration: AvatarBadgeDecoration | undefined;
if (isLowPriority) {
badgeDecoration = AvatarBadgeDecoration.LowPriority;
} else if (isVideoRoom) {
badgeDecoration = AvatarBadgeDecoration.VideoRoom;
} else if (isPublic) {
badgeDecoration = AvatarBadgeDecoration.PublicRoom;
} else if (presence) {
badgeDecoration = AvatarBadgeDecoration.Presence;
}
return { hasDecoration, isPublic, isVideoRoom, presence };
return { badgeDecoration, presence };
}
/**

View File

@@ -9,13 +9,14 @@ 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 ArrowDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/arrow-down";
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 } from "../../viewmodels/avatars/RoomAvatarViewModel";
import { AvatarBadgeDecoration, useRoomAvatarViewModel } from "../../viewmodels/avatars/RoomAvatarViewModel";
import { _t } from "../../../languageHandler";
import { Presence } from "./WithPresenceIndicator";
@@ -33,41 +34,21 @@ interface RoomAvatarViewProps {
export function RoomAvatarView({ room }: RoomAvatarViewProps): JSX.Element {
const vm = useRoomAvatarViewModel(room);
// No decoration, we just show the avatar
if (!vm.hasDecoration) return <RoomAvatar size="32px" room={room} />;
if (!vm.badgeDecoration) return <RoomAvatar size="32px" room={room} />;
const icon = getAvatarDecoration(vm.badgeDecoration, vm.presence);
// Presence indicator and video/public icons don't have the same size
// We use different masks
const maskClass =
vm.badgeDecoration === AvatarBadgeDecoration.Presence
? "mx_RoomAvatarView_RoomAvatar_presence"
: "mx_RoomAvatarView_RoomAvatar_icon";
return (
<div className="mx_RoomAvatarView">
<RoomAvatar
className={classNames("mx_RoomAvatarView_RoomAvatar", {
// Presence indicator and video/public icons don't have the same size
// We use different masks
mx_RoomAvatarView_RoomAvatar_icon: vm.isVideoRoom || vm.isPublic,
mx_RoomAvatarView_RoomAvatar_presence: Boolean(vm.presence),
})}
size="32px"
room={room}
/>
{/* If the room is a public video room, we prefer to display only the video icon */}
{vm.isPublic && !vm.isVideoRoom && (
<PublicIcon
width="16px"
height="16px"
className="mx_RoomAvatarView_icon"
color="var(--cpd-color-icon-tertiary)"
aria-label={_t("room|header|room_is_public")}
/>
)}
{vm.isVideoRoom && (
<VideoIcon
width="16px"
height="16px"
className="mx_RoomAvatarView_icon"
color="var(--cpd-color-icon-tertiary)"
aria-label={_t("room|video_room")}
/>
)}
{vm.presence && <PresenceDecoration presence={vm.presence} />}
<RoomAvatar className={classNames("mx_RoomAvatarView_RoomAvatar", maskClass)} size="32px" room={room} />
{icon}
</div>
);
}
@@ -126,3 +107,39 @@ function PresenceDecoration({ presence }: PresenceDecorationProps): JSX.Element
);
}
}
function getAvatarDecoration(decoration: AvatarBadgeDecoration, presence: Presence | null): React.ReactNode {
if (decoration === AvatarBadgeDecoration.LowPriority) {
return (
<ArrowDownIcon
width="16px"
height="16px"
className="mx_RoomAvatarView_icon"
color="var(--cpd-color-icon-tertiary)"
aria-label={_t("room|room_is_low_priority")}
/>
);
} else if (decoration === AvatarBadgeDecoration.VideoRoom) {
return (
<VideoIcon
width="16px"
height="16px"
className="mx_RoomAvatarView_icon"
color="var(--cpd-color-icon-tertiary)"
aria-label={_t("room|video_room")}
/>
);
} else if (decoration === AvatarBadgeDecoration.PublicRoom) {
return (
<PublicIcon
width="16px"
height="16px"
className="mx_RoomAvatarView_icon"
color="var(--cpd-color-icon-tertiary)"
aria-label={_t("room|header|room_is_public")}
/>
);
} else if (decoration === AvatarBadgeDecoration.Presence) {
return <PresenceDecoration presence={presence!} />;
}
}

View File

@@ -2047,6 +2047,7 @@
"read_topic": "Click to read topic",
"rejecting": "Rejecting invite…",
"rejoin_button": "Re-join",
"room_is_low_priority": "This is a low priority room",
"search": {
"all_rooms_button": "Search all rooms",
"placeholder": "Search messages…",