Style and add more UI

This commit is contained in:
RMidhunSuresh 2023-04-26 16:51:01 +05:30
parent 1c1a713ea2
commit ffb5eb92da
11 changed files with 212 additions and 35 deletions

View File

@ -17,15 +17,31 @@ limitations under the License.
import {SASRequest} from "../../../../../matrix/verification/SAS/SASRequest";
import {TileShape} from "./ITile";
import {SimpleTile} from "./SimpleTile";
import type {SASVerification} from "../../../../../matrix/verification/SAS/SASVerification";
import type {EventEntry} from "../../../../../matrix/room/timeline/entries/EventEntry.js";
import type {Options} from "./SimpleTile";
export const enum Status {
Ready,
InProgress,
Completed,
Cancelled,
};
export class VerificationTile extends SimpleTile {
private request: SASRequest;
public isCancelledByUs: boolean;
public status: Status = Status.Ready;
constructor(entry: EventEntry, options: Options) {
super(entry, options);
this.request = new SASRequest(this.lowerEntry);
const crossSigning = this.getOption("session").crossSigning.get();
this.track(
crossSigning.sasVerificationObservable.subscribe(sas => {
this.subscribeToSASVerification(sas);
})
)
}
get shape(): TileShape {
@ -40,6 +56,8 @@ export class VerificationTile extends SimpleTile {
const crossSigning = this.getOption("session").crossSigning.get()
crossSigning.receivedSASVerifications.set(this.eventId, this.request);
this.openVerificationPanel(this.eventId);
this.status = Status.InProgress;
this.emitChange("status");
}
async reject(): Promise<void> {
@ -47,6 +65,9 @@ export class VerificationTile extends SimpleTile {
await this.logAndCatch("VerificationTile.reject", async (log) => {
const crossSigning = this.getOption("session").crossSigning.get();
await this.request.reject(crossSigning, this._room, log);
this.isCancelledByUs = true;
this.status = Status.Cancelled;
this.emitChange("status");
});
}
@ -56,4 +77,27 @@ export class VerificationTile extends SimpleTile {
path = path.with(this.navigation.segment("verification", eventId))!;
this.navigation.applyPath(path);
}
private subscribeToSASVerification(sas: SASVerification | undefined) {
if (!sas || sas.channel.id !== this.eventId) {
return;
}
/**
* Subscribe to SAS events so that we can update the UI when each stage is
* completed.
*/
this.track(
sas.disposableOn("VerificationCancelled", (cancellation) => {
this.isCancelledByUs = cancellation?.cancelledByUs!;
this.status = Status.Cancelled;
this.emitChange("status");
})
);
this.track(
sas.disposableOn("VerificationCompleted", () => {
this.status = Status.Completed;
this.emitChange("status");
})
);
}
}

View File

@ -43,6 +43,9 @@ export class VerificationToastCollectionViewModel extends ViewModel<SegmentType,
async onAdd(_, request: SASRequest) {
if (request.sender !== this.getOption("session").userId) {
return;
}
const dismiss = () => {
const idx = this.toastViewModels.array.findIndex(vm => vm.request.id === request.id);
if (idx !== -1) {

View File

@ -99,7 +99,7 @@ export class DeviceVerificationViewModel extends ErrorReportViewModel<SegmentTyp
this.track(this.sas.disposableOn("VerificationCancelled", (cancellation) => {
this.updateCurrentStageViewModel(
new VerificationCancelledViewModel(
this.childOptions({ cancellation: cancellation! })
this.childOptions({ cancellation: cancellation!, sas: this.sas })
)
);
}));

View File

@ -0,0 +1,37 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {SegmentType} from "../../../navigation/index";
import {ErrorReportViewModel} from "../../../ErrorReportViewModel";
import type {Options as BaseOptions} from "../../../ViewModel";
import type {Session} from "../../../../matrix/Session.js";
import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification";
type Options = BaseOptions & {
sas: SASVerification;
session: Session;
};
export abstract class DismissibleVerificationViewModel<O extends Options> extends ErrorReportViewModel<SegmentType, O> {
dismiss(): void {
if (this.getOption("sas").isCrossSigningAnotherUser) {
const path = this.navigation.path.until("room");
this.navigation.applyPath(path);
} else {
this.navigation.push("settings", true);
}
}
}

View File

@ -14,16 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel, Options as BaseOptions} from "../../../ViewModel";
import {SegmentType} from "../../../navigation/index";
import {Options as BaseOptions} from "../../../ViewModel";
import type {CancelReason} from "../../../../matrix/verification/SAS/channel/types";
import type {Session} from "../../../../matrix/Session.js";
import type {IChannel} from "../../../../matrix/verification/SAS/channel/IChannel";
import {DismissibleVerificationViewModel} from "./DismissibleVerificationViewModel";
import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification";
type Options = BaseOptions & {
cancellation: IChannel["cancellation"];
session: Session;
sas: SASVerification;
};
export class VerificationCancelledViewModel extends ViewModel<SegmentType, Options> {
export class VerificationCancelledViewModel extends DismissibleVerificationViewModel<Options> {
get cancelCode(): CancelReason {
return this.options.cancellation!.code;
}
@ -32,10 +36,6 @@ export class VerificationCancelledViewModel extends ViewModel<SegmentType, Optio
return this.options.cancellation!.cancelledByUs;
}
gotoSettings() {
this.navigation.push("settings", true);
}
get kind(): string {
return "verification-cancelled";
}

View File

@ -19,6 +19,7 @@ import {ErrorReportViewModel} from "../../../ErrorReportViewModel";
import type {Options as BaseOptions} from "../../../ViewModel";
import type {Session} from "../../../../matrix/Session.js";
import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification";
import { DismissibleVerificationViewModel } from "./DismissibleVerificationViewModel";
type Options = BaseOptions & {
deviceId: string;
@ -26,7 +27,7 @@ type Options = BaseOptions & {
sas: SASVerification;
};
export class VerificationCompleteViewModel extends ErrorReportViewModel<SegmentType, Options> {
export class VerificationCompleteViewModel extends DismissibleVerificationViewModel<Options> {
get otherDeviceId(): string {
return this.options.deviceId;
}
@ -35,10 +36,6 @@ export class VerificationCompleteViewModel extends ErrorReportViewModel<SegmentT
return this.getOption("sas").otherUserId;
}
gotoSettings() {
this.navigation.push("settings", true);
}
get kind(): string {
return "verification-completed";
}

View File

@ -16,6 +16,7 @@ limitations under the License.
import {verifyEd25519Signature, SignatureVerification} from "../e2ee/common";
import {BaseObservableValue, RetainedObservableValue} from "../../observable/value";
import {ObservableValue} from "../../observable/value";
import {pkSign} from "./common";
import {SASVerification} from "./SAS/SASVerification";
import {ToDeviceChannel} from "./SAS/channel/ToDeviceChannel";
@ -100,7 +101,7 @@ export class CrossSigning {
private _isMasterKeyTrusted: boolean = false;
private readonly observedUsers: Map<string, RetainedObservableValue<UserTrust | undefined>> = new Map();
private readonly deviceId: string;
private sasVerificationInProgress?: SASVerification;
public sasVerificationObservable: ObservableValue<SASVerification | undefined> = new ObservableValue(undefined);
public receivedSASVerifications: ObservableMap<string, SASRequest> = new ObservableMap();
constructor(options: {
@ -185,7 +186,8 @@ export class CrossSigning {
startVerification(requestOrUserId: SASRequest, logOrRoom: Room, _log: ILogItem): SASVerification | undefined;
startVerification(requestOrUserId: string, logOrRoom: Room, _log: ILogItem): SASVerification | undefined;
startVerification(requestOrUserId: string | SASRequest, logOrRoom: Room | ILogItem, _log?: ILogItem): SASVerification | undefined {
if (this.sasVerificationInProgress && !this.sasVerificationInProgress.finished) {
const sasVerificationInProgress = this.sasVerificationObservable.get();
if (sasVerificationInProgress && !sasVerificationInProgress.finished) {
return;
}
const otherUserId = requestOrUserId instanceof SASRequest ? requestOrUserId.sender : requestOrUserId;
@ -213,7 +215,7 @@ export class CrossSigning {
}, startingMessage);
}
this.sasVerificationInProgress = new SASVerification({
const sas = new SASVerification({
olm: this.olm,
olmUtil: this.olmUtil,
ourUserId: this.ownUserId,
@ -226,7 +228,8 @@ export class CrossSigning {
hsApi: this.hsApi,
clock: this.platform.clock,
});
return this.sasVerificationInProgress;
this.sasVerificationObservable.set(sas);
return sas;
}
private handleSASDeviceMessage({ unencrypted: event }) {
@ -236,7 +239,8 @@ export class CrossSigning {
* SAS verification, we should ignore it because the device channel
* object (who also listens for to_device messages) will take care of it (if needed).
*/
const shouldIgnoreEvent = this.sasVerificationInProgress?.channel.id === txnId;
const sasVerificationInProgress = this.sasVerificationObservable.get();
const shouldIgnoreEvent = sasVerificationInProgress?.channel.id === txnId;
if (shouldIgnoreEvent) { return; }
/**
* 1. If we receive the cancel message, we need to update the requests map.

View File

@ -1511,7 +1511,14 @@ button.RoomDetailsView_row::after {
display: inline-block;
}
.VerificationTileView,
.VerificationInProgressTileView,
.VerificationCompletedTileView,
.VerificationCancelledTileView,
.VerificationReadyTileView {
display: flex;
gap: 20px;
}
.VerificationTileView__actions {
display: flex;
gap: 16px;
@ -1523,18 +1530,39 @@ button.RoomDetailsView_row::after {
font-weight: bold;
font-size: 1.4rem;
color: var(--text-color);
gap: 4px;
}
.VerificationTileView {
.VerificationInProgressTileView,
.VerificationCompletedTileView,
.VerificationCancelledTileView,
.VerificationReadyTileView {
background: var(--background-color-primary--darker-5);
padding: 12px;
box-sizing: border-box;
border-radius: 8px;
}
.VerificationTileContainer {
.VerificationTileView {
display: flex;
justify-content: center;
padding: 16px;
box-sizing: border-box;
}
.VerificationInProgressTileView .VerificationTileView__shield,
.VerificationReadyTileView .VerificationTileView__shield {
background: url("./icons/e2ee-normal.svg?primary=background-color-secondary--darker-40");
}
.VerificationCompletedTileView .VerificationTileView__shield {
background: url("./icons/e2ee-normal.svg?primary=accent-color");
}
.VerificationTileView__shield {
width: 20px;
height: 20px;
display: flex;
align-self: center;
}

View File

@ -14,24 +14,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Builder, TemplateView} from "../../../general/TemplateView";
import {TemplateView} from "../../../general/TemplateView";
import {Status} from "../../../../../../domain/session/room/timeline/tiles/VerificationTile";
import {spinner} from "../../../common.js";
import type {IView} from "../../../general/types";
import type {Builder} from "../../../general/TemplateView";
import type {VerificationTile} from "../../../../../../domain/session/room/timeline/tiles/VerificationTile";
type IClickableView = {
onClick: (evt) => void;
} & IView;
export class VerificationTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div( { className: "VerificationTileContainer" },
t.div({ className: "VerificationTileView" }, [
t.div({className: "VerificationTileView__shield"}),
t.div({ className: "VerificationTileView__description" }, vm.description),
t.div({ className: "VerificationTileView__actions" }, [
t.button({ className: "VerificationTileView__accept button-action primary" }, "Accept"),
t.button({ className: "VerificationTileView__reject button-action secondary" }, "Reject"),
]),
])
return t.div({ className: "VerificationTileView" },
t.mapView(vm => vm.status, (status: Status) => {
switch (status) {
case Status.Ready:
return new VerificationReadyTileView(vm);
case Status.Cancelled:
return new VerificationCancelledTileView(vm);
case Status.Completed:
return new VerificationCompletedTileView(vm);
case Status.InProgress:
return new VerificationInProgressTileView(vm);
}
})
);
}
/* This is called by the parent ListView, which just has 1 listener for the whole list */
onClick(evt) {
// Propagate click events to the sub-view
this._subViews?.forEach((s: IClickableView) => s.onClick?.(evt));
}
}
class VerificationReadyTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div({ className: "VerificationReadyTileView" }, [
t.div({ className: "VerificationTileView__description" }, [
t.div({ className: "VerificationTileView__shield" }),
t.div(vm.description)
]),
t.div({ className: "VerificationTileView__actions" }, [
t.button({ className: "VerificationTileView__accept button-action primary" }, "Accept"),
t.button({ className: "VerificationTileView__reject button-action secondary" }, "Reject"),
]),
]);
}
onClick(evt) {
if (evt.target.classList.contains("VerificationTileView__accept")) {
this.value.accept();
@ -40,3 +71,36 @@ export class VerificationTileView extends TemplateView<VerificationTile> {
}
}
}
class VerificationCancelledTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div({ className: "VerificationCancelledTileView" }, [
t.div({ className: "VerificationTileView__description" },
vm.i18n`${vm.isCancelledByUs? "You": vm.sender} cancelled the verification!`),
]);
}
}
class VerificationCompletedTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div({ className: "VerificationCompletedTileView" }, [
t.div({ className: "VerificationTileView__description" }, [
t.div({ className: "VerificationTileView__shield" }),
t.div(vm.i18n`You verified ${vm.sender}`),
]),
]);
}
}
class VerificationInProgressTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div({ className: "VerificationInProgressTileView" }, [
t.div({ className: "VerificationTileView__description" },
vm.i18n`Verification in progress`),
t.div({ className: "VerificationTileView__actions" }, [
spinner(t)
]),
]);
}
}

View File

@ -41,7 +41,7 @@ export class VerificationCancelledView extends TemplateView<VerificationCancelle
"button-action": true,
"primary": true,
},
onclick: () => vm.gotoSettings(),
onclick: () => vm.dismiss(),
}, "Got it")
]),
]

View File

@ -35,7 +35,7 @@ export class VerificationCompleteView extends TemplateView<VerificationCompleteV
"button-action": true,
"primary": true,
},
onclick: () => vm.gotoSettings(),
onclick: () => vm.dismiss(),
}, "Got it")
]),
]);