Refactor E2eMessageSharedIcon to MVVM (#33544)

* Refactor E2eMessageSharedIcon to MVVM

* FIx Prettier and i18n

* Fix prettier

* Remove legacy E2eMessageSharedIcon component

* Cover E2eMessageSharedIcon MVVM updates

* Fix prettier

* Update apps/web/src/components/views/rooms/EventTile.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Skip unknown E2E shared icon visual

* Track E2E shared icon listener disposal

* Remove E2E shared icon room state guard

* Remove E2E shared icon client resync

* Use built-in disposables for shared icon VM

* Update shared icon VM without React key

---------

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
This commit is contained in:
Zack
2026-05-28 08:01:39 +00:00
committed by GitHub
co-authored by Florian Duros
parent 013413c27a
commit 34c388f760
14 changed files with 516 additions and 121 deletions
@@ -41,6 +41,7 @@ import { CircleIcon, CheckCircleIcon, ThreadsIcon } from "@vector-im/compound-de
import {
useCreateAutoDisposedViewModel,
ActionBarView,
E2eMessageSharedIconView,
MessageTimestampView,
PinnedMessageBadge,
ReactionsRowButtonView,
@@ -88,10 +89,10 @@ import { Icon as LateIcon } from "../../../../res/img/sensor.svg";
import PinningUtils from "../../../utils/PinningUtils";
import { EventPreview } from "./EventPreview";
import { E2eStandardPadlockIcon } from "./EventTile/E2eStandardPadlockIcon";
import { E2eMessageSharedIcon } from "./EventTile/E2eMessageSharedIcon";
import SettingsStore from "../../../settings/SettingsStore";
import { CardContext } from "../right_panel/context";
import { EventTileViewModel } from "../../../viewmodels/room/timeline/event-tile/EventTileViewModel";
import { E2eMessageSharedIconViewModel } from "../../../viewmodels/room/timeline/event-tile/E2eMessageSharedIconViewModel";
import {
getEventTileReceiptState,
type EventTileReceiptState,
@@ -292,6 +293,50 @@ interface IState {
thread: Thread | null;
}
interface E2eMessageSharedIconWrapperProps {
/**
* The ID of the room containing the event whose keys were shared.
*/
roomId: string;
/**
* The ID of the user who shared the keys.
*/
keyForwardingUserId: string;
}
function E2eMessageSharedIconWrapper({
roomId,
keyForwardingUserId,
}: Readonly<E2eMessageSharedIconWrapperProps>): JSX.Element {
const client = useMatrixClientContext();
const vm = useCreateAutoDisposedViewModel(
() =>
new E2eMessageSharedIconViewModel({
client,
roomId,
keyForwardingUserId,
}),
);
useEffect(() => {
vm.setRoomId(roomId);
}, [roomId, vm]);
useEffect(() => {
vm.setKeyForwardingUserId(keyForwardingUserId);
}, [keyForwardingUserId, vm]);
return (
<E2eMessageSharedIconView
vm={vm}
className={
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
"mx_EventTile_e2eIcon"
}
/>
);
}
// MUST be rendered within a RoomContext with a set timelineRenderingType
export class UnwrappedEventTile extends React.Component<EventTileProps, IState> {
private suppressReadReceiptAnimation: boolean;
@@ -685,7 +730,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
return null;
case "messageShared":
return (
<E2eMessageSharedIcon
<E2eMessageSharedIconWrapper
keyForwardingUserId={e2ePadlockViewState.keyForwardingUserId}
roomId={e2ePadlockViewState.roomId}
/>
@@ -1,54 +0,0 @@
/*
Copyright 2026 Element Creations 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 { EventTimeline } from "matrix-js-sdk/src/matrix";
import { E2ePadlock, E2ePadlockIcon } from "@element-hq/web-shared-components";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
import { _t } from "../../../../languageHandler.tsx";
/** The React properties of an {@link E2eMessageSharedIcon}. */
interface E2eMessageSharedIconParams {
/** The ID of the user who shared the keys. */
keyForwardingUserId: string;
/** The ID of the room that contains the event whose keys were shared. Used to find the displayname of the user who shared the keys. */
roomId: string;
}
/**
* A small icon with tooltip, used as part of an {@link EventTile}, which indicates that the key to this event
* was shared with us by another user.
*
* An alternative to the {@link E2ePadlock} component, which is used for UTD events and other error cases.
*/
export function E2eMessageSharedIcon(props: E2eMessageSharedIconParams): JSX.Element {
const { roomId, keyForwardingUserId } = props;
const client = useMatrixClientContext();
const roomState = client.getRoom(roomId)?.getLiveTimeline()?.getState(EventTimeline.FORWARDS);
const forwardingMember = roomState?.getMember(keyForwardingUserId);
// We always disambiguate the user, since we need to prevent users from forging a disambiguation, and
// the ToolTip component doesn't support putting styling inside a label.
const tooltip = _t("encryption|message_shared_by", {
displayName: forwardingMember?.rawDisplayName ?? keyForwardingUserId,
userId: keyForwardingUserId,
});
return (
<E2ePadlock
className={
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
"mx_EventTile_e2eIcon"
}
icon={E2ePadlockIcon.Normal}
title={tooltip}
/>
);
}