diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index 164c1a8a082..077c8ba4621 100644 Binary files a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 9e75652dcce..e1e3e9157d6 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2015, 2016 OpenMarket Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial @@ -7,7 +7,14 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ComponentProps } from "react"; -import { type Room, RoomStateEvent, type MatrixEvent, EventType, RoomType } from "matrix-js-sdk/src/matrix"; +import { + type Room, + RoomStateEvent, + type MatrixEvent, + EventType, + RoomType, + KnownMembership, +} from "matrix-js-sdk/src/matrix"; import BaseAvatar from "./BaseAvatar"; import ImageView from "../elements/ImageView"; @@ -19,6 +26,7 @@ import { mediaFromMxc } from "../../../customisations/Media"; import { type IOOBData } from "../../../stores/ThreepidInviteStore"; import { LocalRoom } from "../../../models/LocalRoom"; import { filterBoolean } from "../../../utils/arrays"; +import SettingsStore from "../../../settings/SettingsStore"; interface IProps extends Omit, "name" | "idName" | "url" | "onClick"> { // Room may be left unset here, but if it is, @@ -86,6 +94,13 @@ export default class RoomAvatar extends React.Component { }; private static getImageUrls(props: IProps): string[] { + const myMembership = props.room?.getMyMembership(); + if (myMembership === KnownMembership.Invite || !myMembership) { + if (SettingsStore.getValue("showAvatarsOnInvites") === false) { + // The user has opted out of showing avatars, so return no urls here. + return []; + } + } let oobAvatar: string | null = null; if (props.oobData.avatarUrl) { oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp( diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index cb22bb2a0cf..a7166fdb6bd 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2019-2023 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> @@ -116,7 +116,7 @@ const SpellCheckSection: React.FC = () => { }; export default class PreferencesUserSettingsTab extends React.Component { - private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs"]; + private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs", "showAvatarsOnInvites"]; private static SPACES_SETTINGS: BooleanSettingKey[] = ["Spaces.allRoomsInHome"]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 55702791159..e07aa7dad52 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2643,6 +2643,7 @@ "inline_url_previews_room": "Enable URL previews by default for participants in this room", "inline_url_previews_room_account": "Enable URL previews for this room (only affects you)", "insert_trailing_colon_mentions": "Insert a trailing colon after user mentions at the start of a message", + "invite_avatars": "Show avatars of rooms you have been invited to", "jump_to_bottom_on_send": "Jump to the bottom of the timeline when you send a message", "key_backup": { "backup_in_progress": "Your keys are being backed up (the first backup could take a few minutes).", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 3046813f4d2..3084263b2fe 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2018-2024 The Matrix.org Foundation C.I.C. Copyright 2017 Travis Ralston @@ -312,6 +312,7 @@ export interface Settings { "lowBandwidth": IBaseSetting; "fallbackICEServerAllowed": IBaseSetting; "showImages": IBaseSetting; + "showAvatarsOnInvites": IBaseSetting; "RoomList.preferredSorting": IBaseSetting; "RightPanel.phasesGlobal": IBaseSetting; "RightPanel.phases": IBaseSetting; @@ -1116,6 +1117,11 @@ export const SETTINGS: Settings = { displayName: _td("settings|image_thumbnails"), default: true, }, + "showAvatarsOnInvites": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("settings|invite_avatars"), + default: true, + }, "RoomList.preferredSorting": { supportedLevels: [SettingLevel.DEVICE], default: SortingAlgorithm.Recency, diff --git a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx index 8e7a446cbd0..615e24fa69d 100644 --- a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx +++ b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial @@ -17,6 +17,8 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import { LocalRoom } from "../../../../../src/models/LocalRoom"; import * as AvatarModule from "../../../../../src/Avatar"; import { DirectoryMember } from "../../../../../src/utils/direct-messages"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; +import { SettingLevel } from "../../../../../src/settings/SettingLevel"; describe("RoomAvatar", () => { let client: MatrixClient; @@ -41,6 +43,12 @@ describe("RoomAvatar", () => { afterEach(() => { mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset(); mocked(AvatarModule.defaultAvatarUrlForString).mockClear(); + SettingsStore.setValue( + "showAvatarsOnInvites", + null, + SettingLevel.ACCOUNT, + SettingsStore.getDefaultValue("showAvatarsOnInvites"), + ); }); it("should render as expected for a Room", () => { @@ -64,4 +72,19 @@ describe("RoomAvatar", () => { localRoom.targets.push(new DirectoryMember({ user_id: userId })); expect(render().container).toMatchSnapshot(); }); + it("should render an avatar for a room the user is invited to", () => { + SettingsStore.setValue("showAvatarsOnInvites", null, SettingLevel.ACCOUNT, true); + const room = new Room("!room:example.com", client, client.getSafeUserId()); + jest.spyOn(room, "getMxcAvatarUrl").mockImplementation(() => "mxc://example.com/foobar"); + room.name = "test room"; + room.updateMyMembership("invite"); + expect(render().container).toMatchSnapshot(); + }); + it("should not render an invite avatar if the user has disabled it", () => { + SettingsStore.setValue("showAvatarsOnInvites", null, SettingLevel.ACCOUNT, false); + const room = new Room("!room:example.com", client, client.getSafeUserId()); + room.name = "test room"; + room.updateMyMembership("invite"); + expect(render().container).toMatchSnapshot(); + }); }); diff --git a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap index 921b17baf13..f77dcc6948c 100644 --- a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap +++ b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap @@ -1,5 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`RoomAvatar should not render an invite avatar if the user has disabled it 1`] = ` +
+ + t + +
+`; + +exports[`RoomAvatar should render an avatar for a room the user is invited to 1`] = ` +
+ + + +
+`; + exports[`RoomAvatar should render as expected for a DM room 1`] = `
+
+ +
+
+
+