This commit is contained in:
Bruno Windels 2023-01-23 14:53:08 +01:00
parent f4582f19f5
commit 3bb889ed9c
6 changed files with 83 additions and 16 deletions

View File

@ -15,7 +15,11 @@ limitations under the License.
*/ */
import {SimpleTile} from "./SimpleTile.js"; import {SimpleTile} from "./SimpleTile.js";
import {ViewModel} from "../../../../ViewModel";
import {LocalMedia} from "../../../../../matrix/calls/LocalMedia"; import {LocalMedia} from "../../../../../matrix/calls/LocalMedia";
import {CallType} from "../../../../../matrix/calls/callEventTypes";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../../../avatar";
// TODO: timeline entries for state events with the same state key and type // TODO: timeline entries for state events with the same state key and type
// should also update previous entries in the timeline, so we can update the name of the call, whether it is terminated, etc ... // should also update previous entries in the timeline, so we can update the name of the call, whether it is terminated, etc ...
@ -28,17 +32,28 @@ export class CallTile extends SimpleTile {
this._call = calls.get(this._entry.stateKey); this._call = calls.get(this._entry.stateKey);
this._callSubscription = undefined; this._callSubscription = undefined;
if (this._call) { if (this._call) {
this._callSubscription = this._call.disposableOn("change", () => { this._callSubscription = this.track(this._call.disposableOn("change", () => {
// unsubscribe when terminated // unsubscribe when terminated
if (this._call.isTerminated) { if (this._call.isTerminated) {
this._callSubscription = this._callSubscription(); this._callSubscription = this._callSubscription();
this._call = undefined; this._call = undefined;
} }
this.emitChange(); this.emitChange();
}); }));
this.memberViewModels = this._setupMembersList(this._call);
} }
} }
_setupMembersList(call) {
return call.members.mapValues(
(member, emitChange) => new MemberAvatarViewModel(this.childOptions({
member,
emitChange,
mediaRepository: this.getOption("room").mediaRepository
})),
).sortValues((a, b) => a.avatarTitle < b.avatarTitle ? -1 : 1);
}
get confId() { get confId() {
return this._entry.stateKey; return this._entry.stateKey;
} }
@ -61,15 +76,23 @@ export class CallTile extends SimpleTile {
get label() { get label() {
if (this._call) { if (this._call) {
if (this._call.hasJoined) { if (this._type === CallType.Video) {
return `Ongoing call (${this.name}, ${this.confId})`; return `${this.displayName} started a video call`;
} else { } else {
return `${this.displayName} started a call (${this.name}, ${this.confId})`; return `${this.displayName} started a voice call`;
} }
} else { } else {
return `Call finished, started by ${this.displayName} (${this.name}, ${this.confId})`; if (this._type === CallType.Video) {
return `Video call ended`;
} else {
return `Voice call ended`;
} }
} }
}
get _type() {
return this._entry.event.content["m.type"];
}
async join() { async join() {
await this.logAndCatch("CallTile.join", async log => { await this.logAndCatch("CallTile.join", async log => {
@ -88,10 +111,28 @@ export class CallTile extends SimpleTile {
} }
}); });
} }
}
dispose() { class MemberAvatarViewModel extends ViewModel {
if (this._callSubscription) { get _member() {
this._callSubscription = this._callSubscription(); return this.getOption("member");
} }
get avatarLetter() {
return avatarInitials(this._member.member.name);
}
get avatarColorNumber() {
return getIdentifierColorNumber(this._member.userId);
}
avatarUrl(size) {
const {avatarUrl} = this._member.member;
const mediaRepository = this.getOption("mediaRepository");
return getAvatarHttpUrl(avatarUrl, size, this.platform, mediaRepository);
}
get avatarTitle() {
return this._member.member.name;
} }
} }

View File

@ -18,7 +18,7 @@ import {ObservableMap} from "../../observable/map";
import {WebRTC, PeerConnection} from "../../platform/types/WebRTC"; import {WebRTC, PeerConnection} from "../../platform/types/WebRTC";
import {MediaDevices, Track} from "../../platform/types/MediaDevices"; import {MediaDevices, Track} from "../../platform/types/MediaDevices";
import {handlesEventType} from "./PeerCall"; import {handlesEventType} from "./PeerCall";
import {EventType, CallIntent} from "./callEventTypes"; import {EventType, CallIntent, CallType} from "./callEventTypes";
import {GroupCall} from "./group/GroupCall"; import {GroupCall} from "./group/GroupCall";
import {makeId} from "../common"; import {makeId} from "../common";
import {CALL_LOG_TYPE} from "./common"; import {CALL_LOG_TYPE} from "./common";
@ -130,7 +130,7 @@ export class CallHandler implements RoomStateHandler {
log.set("newSize", this._calls.size); log.set("newSize", this._calls.size);
} }
createCall(roomId: string, type: "m.video" | "m.voice", name: string, intent?: CallIntent, log?: ILogItem): Promise<GroupCall> { createCall(roomId: string, type: CallType, name: string, intent?: CallIntent, log?: ILogItem): Promise<GroupCall> {
return this.options.logger.wrapOrRun(log, "CallHandler.createCall", async log => { return this.options.logger.wrapOrRun(log, "CallHandler.createCall", async log => {
if (!intent) { if (!intent) {
intent = CallIntent.Ring; intent = CallIntent.Ring;

View File

@ -227,3 +227,8 @@ export enum CallIntent {
Prompt = "m.prompt", Prompt = "m.prompt",
Room = "m.room", Room = "m.room",
}; };
export enum CallType {
Video = "m.video",
Voice = "m.voice",
}

View File

@ -20,7 +20,7 @@ import {LocalMedia} from "../LocalMedia";
import {MuteSettings, CALL_LOG_TYPE, CALL_MEMBER_VALIDITY_PERIOD_MS, mute} from "../common"; import {MuteSettings, CALL_LOG_TYPE, CALL_MEMBER_VALIDITY_PERIOD_MS, mute} from "../common";
import {MemberChange, RoomMember} from "../../room/members/RoomMember"; import {MemberChange, RoomMember} from "../../room/members/RoomMember";
import {EventEmitter} from "../../../utils/EventEmitter"; import {EventEmitter} from "../../../utils/EventEmitter";
import {EventType, CallIntent} from "../callEventTypes"; import {EventType, CallIntent, CallType} from "../callEventTypes";
import { ErrorBoundary } from "../../../utils/ErrorBoundary"; import { ErrorBoundary } from "../../../utils/ErrorBoundary";
import type {Options as MemberOptions} from "./Member"; import type {Options as MemberOptions} from "./Member";
@ -155,6 +155,10 @@ export class GroupCall extends EventEmitter<{change: never}> {
return this.callContent?.["m.intent"]; return this.callContent?.["m.intent"];
} }
get type(): CallType {
return this.callContent?.["m.type"];
}
/** /**
* Gives access the log item for this call while joined. * Gives access the log item for this call while joined.
* Can be used for call diagnostics while in the call. * Can be used for call diagnostics while in the call.
@ -318,7 +322,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
} }
/** @internal */ /** @internal */
create(type: "m.video" | "m.voice", log: ILogItem): Promise<void> { create(type: CallType, log: ILogItem): Promise<void> {
return log.wrap({l: "create call", t: CALL_LOG_TYPE}, async log => { return log.wrap({l: "create call", t: CALL_LOG_TYPE}, async log => {
if (this._state !== GroupCallState.Fledgling) { if (this._state !== GroupCallState.Fledgling) {
return; return;

View File

@ -440,4 +440,11 @@ only loads when the top comes into view*/
background-color: var(--background-color-primary); background-color: var(--background-color-primary);
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
} }
.CallTileView_members > * {
margin-left: -16px;
}
.CallTileView_members {
display: flex;
}

View File

@ -17,7 +17,16 @@ limitations under the License.
import {Builder, TemplateView} from "../../../general/TemplateView"; import {Builder, TemplateView} from "../../../general/TemplateView";
import type {CallTile} from "../../../../../../domain/session/room/timeline/tiles/CallTile"; import type {CallTile} from "../../../../../../domain/session/room/timeline/tiles/CallTile";
import {ErrorView} from "../../../general/ErrorView"; import {ErrorView} from "../../../general/ErrorView";
import {ListView} from "../../../general/ListView";
import {AvatarView} from "../../../AvatarView";
/*
.CallTileView_members > * {
margin-left: -16px;
}
.CallTileView_members {
display: flex;
}
*/
export class CallTileView extends TemplateView<CallTile> { export class CallTileView extends TemplateView<CallTile> {
render(t: Builder<CallTile>, vm: CallTile) { render(t: Builder<CallTile>, vm: CallTile) {
return t.li( return t.li(
@ -29,6 +38,7 @@ export class CallTileView extends TemplateView<CallTile> {
}), }),
t.div([ t.div([
vm => vm.label, vm => vm.label,
t.view(new ListView({className: "CallTileView_members", list: vm.memberViewModels}, vm => new AvatarView(vm, 24))),
t.button({className: "CallTileView_join", hidden: vm => !vm.canJoin}, "Join"), t.button({className: "CallTileView_join", hidden: vm => !vm.canJoin}, "Join"),
t.button({className: "CallTileView_leave", hidden: vm => !vm.canLeave}, "Leave") t.button({className: "CallTileView_leave", hidden: vm => !vm.canLeave}, "Leave")
]) ])