mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-11-20 03:25:52 +01:00
Style and add more UI
This commit is contained in:
parent
1c1a713ea2
commit
ffb5eb92da
@ -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");
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 })
|
||||
)
|
||||
);
|
||||
}));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export class VerificationCancelledView extends TemplateView<VerificationCancelle
|
||||
"button-action": true,
|
||||
"primary": true,
|
||||
},
|
||||
onclick: () => vm.gotoSettings(),
|
||||
onclick: () => vm.dismiss(),
|
||||
}, "Got it")
|
||||
]),
|
||||
]
|
||||
|
@ -35,7 +35,7 @@ export class VerificationCompleteView extends TemplateView<VerificationCompleteV
|
||||
"button-action": true,
|
||||
"primary": true,
|
||||
},
|
||||
onclick: () => vm.gotoSettings(),
|
||||
onclick: () => vm.dismiss(),
|
||||
}, "Got it")
|
||||
]),
|
||||
]);
|
||||
|
Loading…
Reference in New Issue
Block a user