From 88fffdbc4d3b398b817299340e1ebb69d428eb4f Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 6 Apr 2026 03:00:32 +0300 Subject: [PATCH 1/6] added statuses to dms --- src/app/components/room-avatar/RoomAvatar.css.ts | 1 - src/app/features/room-nav/RoomNavItem.tsx | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/components/room-avatar/RoomAvatar.css.ts b/src/app/components/room-avatar/RoomAvatar.css.ts index c9745a9aa..7cf6cad0d 100644 --- a/src/app/components/room-avatar/RoomAvatar.css.ts +++ b/src/app/components/room-avatar/RoomAvatar.css.ts @@ -5,7 +5,6 @@ export const RoomAvatar = style({ backgroundColor: color.Secondary.Container, color: color.Secondary.OnContainer, textTransform: 'capitalize', - selectors: { '&[data-image-loaded="true"]': { backgroundColor: 'transparent', diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index f9d279062..09f3e83b1 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -68,6 +68,7 @@ import { useCallPreferencesAtom } from '$state/hooks/callPreferences'; import { CallControlState } from '$plugins/call/CallControlState'; import { useAutoDiscoveryInfo } from '$hooks/useAutoDiscoveryInfo'; import { livekitSupport } from '$hooks/useLivekitSupport'; +import { useUserPresence } from '$hooks/useUserPresence'; import { RoomNavUser } from './RoomNavUser'; /** @@ -282,6 +283,8 @@ export function RoomNavItem({ const dmUserId = direct ? room.getAvatarFallbackMember()?.userId : undefined; const matrixRoomName = useRoomName(room); const roomName = (dmUserId && nicknames[dmUserId]) || matrixRoomName; + const presence = useUserPresence(dmUserId ?? ''); + const roomDescription = direct ? presence?.status : 'aaa'; const { navigateRoom } = useRoomNavigate(); const navigate = useNavigate(); @@ -413,15 +416,20 @@ export function RoomNavItem({ /> )} - + {roomName} + {roomDescription && ( + + {presence?.status} + + )} {!optionsVisible && !unread && !selected && typingMember.length > 0 && ( From 31fec891f7524c00213f1da4dcd63e475a951c4c Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 6 Apr 2026 04:54:14 +0300 Subject: [PATCH 2/6] added presence and setting --- src/app/features/room-nav/RoomNavItem.tsx | 106 ++++++++++++------ .../features/settings/cosmetics/Themes.tsx | 10 ++ src/app/pages/client/direct/Direct.tsx | 2 + src/app/state/settings.ts | 2 + 4 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 09f3e83b1..15e615720 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -26,7 +26,12 @@ import { useNavigate } from 'react-router-dom'; import { NavButton, NavItem, NavItemContent, NavItemOptions } from '$components/nav'; import { UnreadBadge, UnreadBadgeCenter } from '$components/unread-badge'; import { RoomAvatar, RoomIcon } from '$components/room-avatar'; -import { getDirectRoomAvatarUrl, getRoomAvatarUrl, roomHaveUnread } from '$utils/room'; +import { + getDirectRoomAvatarUrl, + getRoomAvatarUrl, + getStateEvent, + roomHaveUnread, +} from '$utils/room'; import { nameInitials } from '$utils/common'; import { useMatrixClient } from '$hooks/useMatrixClient'; import { useRoomUnread } from '$state/hooks/unread'; @@ -68,7 +73,9 @@ import { useCallPreferencesAtom } from '$state/hooks/callPreferences'; import { CallControlState } from '$plugins/call/CallControlState'; import { useAutoDiscoveryInfo } from '$hooks/useAutoDiscoveryInfo'; import { livekitSupport } from '$hooks/useLivekitSupport'; -import { useUserPresence } from '$hooks/useUserPresence'; +import { Presence, useUserPresence } from '$hooks/useUserPresence'; +import { StateEvent } from '$types/matrix/room'; +import { AvatarPresence, PresenceBadge } from '$components/presence'; import { RoomNavUser } from './RoomNavUser'; /** @@ -256,6 +263,7 @@ type RoomNavItemProps = { notificationMode?: RoomNotificationMode; showAvatar?: boolean; direct?: boolean; + customDMCards?: boolean; }; export function RoomNavItem({ @@ -263,6 +271,7 @@ export function RoomNavItem({ selected, showAvatar, direct, + customDMCards, notificationMode, linkPath, }: RoomNavItemProps) { @@ -284,7 +293,13 @@ export function RoomNavItem({ const matrixRoomName = useRoomName(room); const roomName = (dmUserId && nicknames[dmUserId]) || matrixRoomName; const presence = useUserPresence(dmUserId ?? ''); - const roomDescription = direct ? presence?.status : 'aaa'; + const [topicEvent, setTopicEvent] = useState(getStateEvent(room, StateEvent.RoomTopic)); + + // Ensures that the description does not stick to the position the room is in the row + useEffect(() => setTopicEvent(getStateEvent(room, StateEvent.RoomTopic)), [room, setTopicEvent]); + const roomDescription = direct + ? (customDMCards && (topicEvent?.getContent().topic as string)) || presence?.status + : undefined; const { navigateRoom } = useRoomNavigate(); const navigate = useNavigate(); @@ -384,38 +399,47 @@ export function RoomNavItem({ - - {showAvatar ? ( - ( - - {nameInitials(roomName)} - - )} - /> - ) : ( - - )} - + + ) + } + > + + {showAvatar ? ( + ( + + {nameInitials(roomName)} + + )} + /> + ) : ( + + )} + + {roomDescription && ( - - {presence?.status} + + {roomDescription} )} diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index 3e5c8cac1..f543a19ea 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -481,6 +481,7 @@ function PageZoomInput() { export function Appearance() { const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); + const [customDMCards, setCustomDMCards] = useSetting(settingsAtom, 'customDMCards'); const [showEasterEggs, setShowEasterEggs] = useSetting(settingsAtom, 'showEasterEggs'); const [closeFoldersByDefault, setCloseFoldersByDefault] = useSetting( settingsAtom, @@ -519,6 +520,15 @@ export function Appearance() { /> + + } + /> + + Date: Mon, 6 Apr 2026 05:26:27 +0300 Subject: [PATCH 3/6] update all pages to show the right room image --- src/app/components/room-avatar/RoomAvatar.css.ts | 1 + src/app/features/common-settings/general/RoomProfile.tsx | 5 ++++- src/app/features/room-settings/RoomSettings.tsx | 5 ++++- src/app/features/room/RoomViewHeader.tsx | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/components/room-avatar/RoomAvatar.css.ts b/src/app/components/room-avatar/RoomAvatar.css.ts index 7cf6cad0d..c9745a9aa 100644 --- a/src/app/components/room-avatar/RoomAvatar.css.ts +++ b/src/app/components/room-avatar/RoomAvatar.css.ts @@ -5,6 +5,7 @@ export const RoomAvatar = style({ backgroundColor: color.Secondary.Container, color: color.Secondary.OnContainer, textTransform: 'capitalize', + selectors: { '&[data-image-loaded="true"]': { backgroundColor: 'transparent', diff --git a/src/app/features/common-settings/general/RoomProfile.tsx b/src/app/features/common-settings/general/RoomProfile.tsx index cd523f32b..6425d4555 100644 --- a/src/app/features/common-settings/general/RoomProfile.tsx +++ b/src/app/features/common-settings/general/RoomProfile.tsx @@ -35,6 +35,8 @@ import { useFilePicker } from '$hooks/useFilePicker'; import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback'; import { useAlive } from '$hooks/useAlive'; import { RoomPermissionsAPI } from '$hooks/useRoomPermissions'; +import { useSetting } from '$state/hooks/settings'; +import { settingsAtom } from '$state/settings'; type RoomProfileEditProps = { canEditAvatar: boolean; @@ -294,8 +296,9 @@ export function RoomProfile({ permissions }: RoomProfileProps) { const room = useRoom(); const directs = useAtomValue(mDirectAtom); const isDm = directs.has(room.roomId); + const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); - const avatar = useRoomAvatar(room, directs.has(room.roomId)); + const avatar = useRoomAvatar(room, directs.has(room.roomId) && !customDMCards); const name = useRoomName(room); const topic = useRoomTopic(room); const joinRule = useRoomJoinRule(room); diff --git a/src/app/features/room-settings/RoomSettings.tsx b/src/app/features/room-settings/RoomSettings.tsx index 02fb0e849..def0d4483 100644 --- a/src/app/features/room-settings/RoomSettings.tsx +++ b/src/app/features/room-settings/RoomSettings.tsx @@ -17,6 +17,8 @@ import { Members } from '$features/common-settings/members'; import { EmojisStickers } from '$features/common-settings/emojis-stickers'; import { DeveloperTools } from '$features/common-settings/developer-tools'; import { Cosmetics } from '$features/common-settings/cosmetics/Cosmetics'; +import { settingsAtom } from '$state/settings'; +import { useSetting } from '$state/hooks/settings'; import { Permissions } from './permissions'; import { General } from './general'; import { RoomAbbreviations } from './abbreviations/RoomAbbreviations'; @@ -81,8 +83,9 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const mDirects = useAtomValue(mDirectAtom); + const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); - const roomAvatar = useRoomAvatar(room, mDirects.has(room.roomId)); + const roomAvatar = useRoomAvatar(room, mDirects.has(room.roomId) && !customDMCards); const roomName = useRoomName(room); const joinRuleContent = useRoomJoinRule(room); diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index 8552332cf..921b517f9 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -350,6 +350,7 @@ export function RoomViewHeader({ callView }: Readonly<{ callView?: boolean }>) { const [menuAnchor, setMenuAnchor] = useState(); const [pinMenuAnchor, setPinMenuAnchor] = useState(); const direct = useIsDirectRoom(); + const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); const [chat, setChat] = useAtom(callChatAtom); const [threadBrowserOpen, setThreadBrowserOpen] = useAtom( @@ -366,7 +367,7 @@ export function RoomViewHeader({ callView }: Readonly<{ callView?: boolean }>) { const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption); const encryptedRoom = !!encryptionEvent; - const avatarMxc = useRoomAvatar(room, direct); + const avatarMxc = useRoomAvatar(room, direct && !customDMCards); const name = useRoomName(room); const topic = useRoomTopic(room); const avatarUrl = avatarMxc From d92f47a96f6574d8d0ce686cdf44ff988977c956 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 6 Apr 2026 06:02:23 +0300 Subject: [PATCH 4/6] add changeset and statuses in member tile --- .changeset/feat-add-DM-statuses.md | 5 +++ .changeset/feat-add-custom-DM-looks.md | 5 +++ .changeset/feat-add-statuses-in-Members.md | 5 +++ src/app/components/member-tile/MemberTile.tsx | 40 ++++++++++++------- .../common-settings/members/Members.tsx | 8 +++- 5 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 .changeset/feat-add-DM-statuses.md create mode 100644 .changeset/feat-add-custom-DM-looks.md create mode 100644 .changeset/feat-add-statuses-in-Members.md diff --git a/.changeset/feat-add-DM-statuses.md b/.changeset/feat-add-DM-statuses.md new file mode 100644 index 000000000..55e0aa3be --- /dev/null +++ b/.changeset/feat-add-DM-statuses.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add statuses to DMs diff --git a/.changeset/feat-add-custom-DM-looks.md b/.changeset/feat-add-custom-DM-looks.md new file mode 100644 index 000000000..a2df236d5 --- /dev/null +++ b/.changeset/feat-add-custom-DM-looks.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add custom DM images and descriptions diff --git a/.changeset/feat-add-statuses-in-Members.md b/.changeset/feat-add-statuses-in-Members.md new file mode 100644 index 000000000..9ea6f93ce --- /dev/null +++ b/.changeset/feat-add-statuses-in-Members.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add statuses to Member Tile diff --git a/src/app/components/member-tile/MemberTile.tsx b/src/app/components/member-tile/MemberTile.tsx index 9af782eef..40321c06d 100644 --- a/src/app/components/member-tile/MemberTile.tsx +++ b/src/app/components/member-tile/MemberTile.tsx @@ -7,6 +7,8 @@ import { useSableCosmetics } from '$hooks/useSableCosmetics'; import { useAtomValue } from 'jotai'; import { nicknamesAtom } from '$state/nicknames'; import { UserAvatar } from '$components/user-avatar'; +import { useUserPresence } from '$hooks/useUserPresence'; +import { AvatarPresence, PresenceBadge } from '$components/presence'; import * as css from './style.css'; const getName = (room: Room, member: RoomMember, nicknames: Record) => @@ -25,7 +27,7 @@ export const MemberTile = as<'button', MemberTileProps>( ({ as: AsMemberTile = 'button', mx, room, member, useAuthentication, after, ...props }, ref) => { const nicknames = useAtomValue(nicknamesAtom); const name = getName(room, member, nicknames); - const username = getMxIdLocalPart(member.userId); + const presence = useUserPresence(member.userId ?? ''); const avatarMxcUrl = member.getMxcAvatarUrl(); const avatarUrl = avatarMxcUrl @@ -37,23 +39,33 @@ export const MemberTile = as<'button', MemberTileProps>( return ( - - } - /> - + + ) : undefined + } + > + + } + /> + + {name} - - - {username} - - + {presence && presence.status && ( + + + {presence.status} + + + )} {after} diff --git a/src/app/features/common-settings/members/Members.tsx b/src/app/features/common-settings/members/Members.tsx index 19b8ffc46..678d29248 100644 --- a/src/app/features/common-settings/members/Members.tsx +++ b/src/app/features/common-settings/members/Members.tsx @@ -305,6 +305,7 @@ export function Members({ requestClose }: MembersProps) { if ('userId' in tagOrMember) { const server = getMxIdServer(tagOrMember.userId); + const username = getMxIdLocalPart(tagOrMember.userId); return ( + + ) From 3b2a7957235c287b8e55d3194d3d4f1964407e7a Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 6 Apr 2026 07:06:27 +0300 Subject: [PATCH 5/6] move presence pill next to status in member drawer to avoid z-fighting --- src/app/components/member-tile/MemberTile.tsx | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/app/components/member-tile/MemberTile.tsx b/src/app/components/member-tile/MemberTile.tsx index 40321c06d..b017890c4 100644 --- a/src/app/components/member-tile/MemberTile.tsx +++ b/src/app/components/member-tile/MemberTile.tsx @@ -39,28 +39,21 @@ export const MemberTile = as<'button', MemberTileProps>( return ( - - ) : undefined - } - > - - } - /> - - + + } + /> + {name} {presence && presence.status && ( - + + {presence.status} From 83f931148a416467bf8261edf22f08ac54f97b19 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 6 Apr 2026 07:15:18 +0300 Subject: [PATCH 6/6] making linter happi :smile: --- src/app/components/member-tile/MemberTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/member-tile/MemberTile.tsx b/src/app/components/member-tile/MemberTile.tsx index b017890c4..e30c226c7 100644 --- a/src/app/components/member-tile/MemberTile.tsx +++ b/src/app/components/member-tile/MemberTile.tsx @@ -8,7 +8,7 @@ import { useAtomValue } from 'jotai'; import { nicknamesAtom } from '$state/nicknames'; import { UserAvatar } from '$components/user-avatar'; import { useUserPresence } from '$hooks/useUserPresence'; -import { AvatarPresence, PresenceBadge } from '$components/presence'; +import { PresenceBadge } from '$components/presence'; import * as css from './style.css'; const getName = (room: Room, member: RoomMember, nicknames: Record) =>