MVVM userinfo: split header and verification components (#30214)
* feat: mvvm userinfo split header and verification * test: add userinfoheader tests * fix: userHeaderVerificationView verification method
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixClient, type RoomMember, type User } from "matrix-js-sdk/src/matrix";
|
||||
import { useContext } from "react";
|
||||
import { type UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||
import { type IDevice } from "../../../views/right_panel/UserInfo";
|
||||
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
|
||||
import { verifyUser } from "../../../../verification";
|
||||
|
||||
export interface UserInfoVerificationSectionState {
|
||||
/**
|
||||
* variables used to check if we can verify the user and display the verify button
|
||||
*/
|
||||
canVerify: boolean;
|
||||
hasCrossSigningKeys: boolean | undefined;
|
||||
/**
|
||||
* used to display correct badge value
|
||||
*/
|
||||
isUserVerified: boolean;
|
||||
/**
|
||||
* callback function when verifyUser button is clicked
|
||||
*/
|
||||
verifySelectedUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
const useHomeserverSupportsCrossSigning = (cli: MatrixClient): boolean => {
|
||||
return useAsyncMemo<boolean>(
|
||||
async () => {
|
||||
return cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
||||
},
|
||||
[cli],
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
const useHasCrossSigningKeys = (cli: MatrixClient, member: User, canVerify: boolean): boolean | undefined => {
|
||||
return useAsyncMemo(async () => {
|
||||
if (!canVerify) return undefined;
|
||||
return cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
|
||||
}, [cli, member, canVerify]);
|
||||
};
|
||||
|
||||
/**
|
||||
* View model for the userInfoVerificationHeaderView
|
||||
* @see {@link UserInfoVerificationSectionState} for more information about what this view model returns.
|
||||
*/
|
||||
export const useUserInfoVerificationViewModel = (
|
||||
member: User | RoomMember,
|
||||
devices: IDevice[],
|
||||
): UserInfoVerificationSectionState => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
|
||||
|
||||
const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
|
||||
async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
|
||||
[member.userId],
|
||||
// the user verification status is not initialized
|
||||
undefined,
|
||||
);
|
||||
const hasUserVerificationStatus = Boolean(userTrust);
|
||||
const isUserVerified = Boolean(userTrust?.isVerified());
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const canVerify =
|
||||
hasUserVerificationStatus &&
|
||||
homeserverSupportsCrossSigning &&
|
||||
!isUserVerified &&
|
||||
!isMe &&
|
||||
devices &&
|
||||
devices.length > 0;
|
||||
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify);
|
||||
const verifySelectedUser = (): Promise<void> => verifyUser(cli, member as User);
|
||||
|
||||
return {
|
||||
canVerify,
|
||||
hasCrossSigningKeys,
|
||||
isUserVerified,
|
||||
verifySelectedUser,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { RoomMember, type User } from "matrix-js-sdk/src/matrix";
|
||||
import { useCallback, useContext } from "react";
|
||||
|
||||
import { mediaFromMxc } from "../../../../customisations/Media";
|
||||
import Modal from "../../../../Modal";
|
||||
import ImageView from "../../../views/elements/ImageView";
|
||||
import SdkConfig from "../../../../SdkConfig";
|
||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||
import { type Member } from "../../../views/right_panel/UserInfo";
|
||||
import { useUserTimezone } from "../../../../hooks/useUserTimezone";
|
||||
import UserIdentifierCustomisations from "../../../../customisations/UserIdentifier";
|
||||
|
||||
export interface PresenceInfo {
|
||||
lastActiveAgo: number | undefined;
|
||||
currentlyActive: boolean | undefined;
|
||||
state: string | undefined;
|
||||
}
|
||||
|
||||
export interface TimezoneInfo {
|
||||
timezone: string;
|
||||
friendly: string;
|
||||
}
|
||||
|
||||
export interface UserInfoHeaderState {
|
||||
/**
|
||||
* callback function when selected user avatar is clicked in user info
|
||||
*/
|
||||
onMemberAvatarClick: () => void;
|
||||
/**
|
||||
* Object containing information about the precense of the selected user
|
||||
*/
|
||||
precenseInfo: PresenceInfo;
|
||||
/**
|
||||
* Boolean that show or hide the precense information
|
||||
*/
|
||||
showPresence: boolean;
|
||||
/**
|
||||
* Timezone object
|
||||
*/
|
||||
timezoneInfo: TimezoneInfo | null;
|
||||
/**
|
||||
* Displayed identifier for the selected user
|
||||
*/
|
||||
userIdentifier: string | null;
|
||||
}
|
||||
interface UserInfoHeaderViewModelProps {
|
||||
member: Member;
|
||||
roomId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* View model for the userInfoHeaderView
|
||||
* props
|
||||
* @see {@link UserInfoHeaderState} for more information about what this view model returns.
|
||||
*/
|
||||
export function useUserfoHeaderViewModel({ member, roomId }: UserInfoHeaderViewModelProps): UserInfoHeaderState {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
let showPresence = true;
|
||||
|
||||
const precenseInfo: PresenceInfo = {
|
||||
lastActiveAgo: undefined,
|
||||
currentlyActive: undefined,
|
||||
state: undefined,
|
||||
};
|
||||
|
||||
const enablePresenceByHsUrl = SdkConfig.get("enable_presence_by_hs_url");
|
||||
|
||||
const timezoneInfo = useUserTimezone(cli, member.userId);
|
||||
|
||||
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, {
|
||||
roomId,
|
||||
withDisplayName: true,
|
||||
});
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
const avatarUrl = (member as RoomMember).getMxcAvatarUrl
|
||||
? (member as RoomMember).getMxcAvatarUrl()
|
||||
: (member as User).avatarUrl;
|
||||
|
||||
const httpUrl = mediaFromMxc(avatarUrl).srcHttp;
|
||||
if (!httpUrl) return;
|
||||
|
||||
const params = {
|
||||
src: httpUrl,
|
||||
name: (member as RoomMember).name || (member as User).displayName,
|
||||
};
|
||||
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
|
||||
}, [member]);
|
||||
|
||||
if (member instanceof RoomMember && member.user) {
|
||||
precenseInfo.state = member.user.presence;
|
||||
precenseInfo.lastActiveAgo = member.user.lastActiveAgo;
|
||||
precenseInfo.currentlyActive = member.user.currentlyActive;
|
||||
}
|
||||
|
||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[cli.baseUrl] !== undefined) {
|
||||
showPresence = enablePresenceByHsUrl[cli.baseUrl];
|
||||
}
|
||||
|
||||
return {
|
||||
onMemberAvatarClick,
|
||||
showPresence,
|
||||
precenseInfo,
|
||||
timezoneInfo,
|
||||
userIdentifier,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user