move ononone layout into CallViewModel

This commit is contained in:
Timo K
2025-11-17 15:09:58 +01:00
parent 49ec4b4298
commit f1bccb896e
6 changed files with 31 additions and 31 deletions

View File

@@ -262,6 +262,8 @@ export const InCallView: FC<InCallViewProps> = ({
const earpieceMode = useBehavior(vm.earpieceMode$); const earpieceMode = useBehavior(vm.earpieceMode$);
const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$); const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$);
const sharingScreen = useBehavior(vm.sharingScreen$); const sharingScreen = useBehavior(vm.sharingScreen$);
const localUserIsAlone = useBehavior(vm.localUserIsAlone$);
const oneOnOneMember = useBehavior(vm.isOneOnOneWith$);
const fatalCallError = useBehavior(vm.configError$); const fatalCallError = useBehavior(vm.configError$);
// Stop the rendering and throw for the error boundary // Stop the rendering and throw for the error boundary
@@ -300,24 +302,15 @@ export const InCallView: FC<InCallViewProps> = ({
// Waiting UI overlay // Waiting UI overlay
const waitingOverlay: JSX.Element | null = useMemo(() => { const waitingOverlay: JSX.Element | null = useMemo(() => {
// No overlay if not in ringing state // No overlay if not in ringing state
if (callPickupState !== "ringing") return null; if (callPickupState !== "ringing" || localUserIsAlone) return null;
// Use room state for other participants data (the one that we likely want to reach) const name = oneOnOneMember ? oneOnOneMember.userId : matrixRoom.roomId;
// TODO: this screams it wants to be a behavior in the vm. const id = oneOnOneMember ? oneOnOneMember.userId : matrixRoom.roomId;
const roomOthers = [ const text = oneOnOneMember
...matrixRoom.getMembersWithMembership("join"), ? `Waiting for ${name ?? oneOnOneMember.userId} to join…`
...matrixRoom.getMembersWithMembership("invite"),
].filter((m) => m.userId !== client.getUserId());
// Yield if there are not other members in the room.
if (roomOthers.length === 0) return null;
const otherMember = roomOthers.length > 0 ? roomOthers[0] : undefined;
const isOneOnOne = roomOthers.length === 1 && otherMember;
const text = isOneOnOne
? `Waiting for ${otherMember.name ?? otherMember.userId} to join…`
: "Waiting for other participants…"; : "Waiting for other participants…";
const avatarMxc = isOneOnOne const avatarMxc = oneOnOneMember
? (otherMember.getMxcAvatarUrl?.() ?? undefined) ? (oneOnOneMember.getMxcAvatarUrl?.() ?? undefined)
: (matrixRoom.getMxcAvatarUrl() ?? undefined); : (matrixRoom.getMxcAvatarUrl() ?? undefined);
return ( return (
@@ -326,12 +319,7 @@ export const InCallView: FC<InCallViewProps> = ({
className={classNames(overlayStyles.content, waitingStyles.content)} className={classNames(overlayStyles.content, waitingStyles.content)}
> >
<div className={waitingStyles.pulse}> <div className={waitingStyles.pulse}>
<Avatar <Avatar id={id} name={name} src={avatarMxc} size={AvatarSize.XL} />
id={isOneOnOne ? otherMember.userId : matrixRoom.roomId}
name={isOneOnOne ? otherMember.name : matrixRoom.name}
src={avatarMxc}
size={AvatarSize.XL}
/>
</div> </div>
<Text size="md" className={waitingStyles.text}> <Text size="md" className={waitingStyles.text}>
{text} {text}
@@ -339,7 +327,7 @@ export const InCallView: FC<InCallViewProps> = ({
</div> </div>
</div> </div>
); );
}, [callPickupState, client, matrixRoom]); }, [callPickupState, localUserIsAlone, matrixRoom, oneOnOneMember]);
// Ideally we could detect taps by listening for click events and checking // Ideally we could detect taps by listening for click events and checking
// that the pointerType of the event is "touch", but this isn't yet supported // that the pointerType of the event is "touch", but this isn't yet supported

View File

@@ -122,6 +122,7 @@ export function withCallViewModel(
} }
})() as Partial<MatrixClient> as MatrixClient, })() as Partial<MatrixClient> as MatrixClient,
getMembers: () => Array.from(roomMembers.values()), getMembers: () => Array.from(roomMembers.values()),
getMembersWithMembership: () => Array.from(roomMembers.values()),
}); });
const rtcSession = new MockRTCSession(room, []).withMemberships(rtcMembers$); const rtcSession = new MockRTCSession(room, []).withMemberships(rtcMembers$);
const participantsSpy = vi const participantsSpy = vi

View File

@@ -58,6 +58,10 @@ describe("MatrixMemberMetadata", () => {
const members = Array.from(fakeMembersMap.values()); const members = Array.from(fakeMembersMap.values());
return members; return members;
}), }),
getMembersWithMembership: vi.fn().mockImplementation(() => {
const members = Array.from(fakeMembersMap.values());
return members;
}),
} as unknown as MatrixRoom; } as unknown as MatrixRoom;
}); });

View File

@@ -9,7 +9,10 @@ import { type RoomMember, RoomStateEvent } from "matrix-js-sdk";
import { combineLatest, fromEvent, map } from "rxjs"; import { combineLatest, fromEvent, map } from "rxjs";
import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc"; import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { type Room as MatrixRoom } from "matrix-js-sdk/lib/matrix"; import {
KnownMembership,
type Room as MatrixRoom,
} from "matrix-js-sdk/lib/matrix";
// eslint-disable-next-line rxjs/no-internal // eslint-disable-next-line rxjs/no-internal
import { type ObservableScope } from "../../ObservableScope"; import { type ObservableScope } from "../../ObservableScope";
@@ -26,7 +29,10 @@ export type RoomMemberMap = Map<
Pick<RoomMember, "userId" | "getMxcAvatarUrl" | "rawDisplayName"> Pick<RoomMember, "userId" | "getMxcAvatarUrl" | "rawDisplayName">
>; >;
export function roomToMembersMap(matrixRoom: MatrixRoom): RoomMemberMap { export function roomToMembersMap(matrixRoom: MatrixRoom): RoomMemberMap {
return matrixRoom.getMembers().reduce((acc, member) => { const members = matrixRoom
.getMembersWithMembership(KnownMembership.Join)
.concat(matrixRoom.getMembersWithMembership(KnownMembership.Invite));
return members.reduce((acc, member) => {
acc.set(member.userId, { acc.set(member.userId, {
userId: member.userId, userId: member.userId,
getMxcAvatarUrl: member.getMxcAvatarUrl.bind(member), getMxcAvatarUrl: member.getMxcAvatarUrl.bind(member),

View File

@@ -25,7 +25,7 @@ import { roomToMembersMap } from "../state/CallViewModel/remoteMembers/MatrixMem
describe("shouldDisambiguate", () => { describe("shouldDisambiguate", () => {
test("should not disambiguate a solo member", () => { test("should not disambiguate a solo member", () => {
const room = mockMatrixRoom({ const room = mockMatrixRoom({
getMembers: () => [], getMembersWithMembership: () => [],
}); });
expect(shouldDisambiguate(alice, [], roomToMembersMap(room))).toEqual( expect(shouldDisambiguate(alice, [], roomToMembersMap(room))).toEqual(
false, false,
@@ -33,7 +33,7 @@ describe("shouldDisambiguate", () => {
}); });
test("should not disambiguate a member with an empty displayname", () => { test("should not disambiguate a member with an empty displayname", () => {
const room = mockMatrixRoom({ const room = mockMatrixRoom({
getMembers: () => [alice, aliceDoppelganger], getMembersWithMembership: () => [alice, aliceDoppelganger],
}); });
expect( expect(
shouldDisambiguate( shouldDisambiguate(
@@ -44,14 +44,14 @@ describe("shouldDisambiguate", () => {
).toEqual(false); ).toEqual(false);
}); });
test("should disambiguate a member with RTL characters", () => { test("should disambiguate a member with RTL characters", () => {
const room = mockMatrixRoom({ getMembers: () => [] }); const room = mockMatrixRoom({ getMembersWithMembership: () => [] });
expect(shouldDisambiguate(daveRTL, [], roomToMembersMap(room))).toEqual( expect(shouldDisambiguate(daveRTL, [], roomToMembersMap(room))).toEqual(
true, true,
); );
}); });
test("should disambiguate a member with a matching displayname", () => { test("should disambiguate a member with a matching displayname", () => {
const room = mockMatrixRoom({ const room = mockMatrixRoom({
getMembers: () => [alice, aliceDoppelganger], getMembersWithMembership: () => [alice, aliceDoppelganger],
}); });
expect( expect(
shouldDisambiguate( shouldDisambiguate(
@@ -70,7 +70,7 @@ describe("shouldDisambiguate", () => {
}); });
test("should disambiguate a member with a matching displayname with hidden spaces", () => { test("should disambiguate a member with a matching displayname with hidden spaces", () => {
const room = mockMatrixRoom({ const room = mockMatrixRoom({
getMembers: () => [bob, bobZeroWidthSpace], getMembersWithMembership: () => [bob, bobZeroWidthSpace],
}); });
expect( expect(
shouldDisambiguate( shouldDisambiguate(
@@ -91,7 +91,7 @@ describe("shouldDisambiguate", () => {
"should disambiguate a member with a displayname containing a mxid-like string '%s'", "should disambiguate a member with a displayname containing a mxid-like string '%s'",
(rawDisplayName) => { (rawDisplayName) => {
const room = mockMatrixRoom({ const room = mockMatrixRoom({
getMembers: () => [alice, aliceDoppelganger], getMembersWithMembership: () => [alice, aliceDoppelganger],
}); });
expect( expect(
shouldDisambiguate( shouldDisambiguate(

View File

@@ -83,6 +83,7 @@ export function getBasicRTCSession(
} as Partial<MatrixClient> as MatrixClient, } as Partial<MatrixClient> as MatrixClient,
getMember: (userId) => matrixRoomMembers.get(userId) ?? null, getMember: (userId) => matrixRoomMembers.get(userId) ?? null,
getMembers: () => Array.from(matrixRoomMembers.values()), getMembers: () => Array.from(matrixRoomMembers.values()),
getMembersWithMembership: () => Array.from(matrixRoomMembers.values()),
roomId: matrixRoomId, roomId: matrixRoomId,
on: vitest on: vitest
.fn() .fn()