[fractal/fractal-next] verification: Add widget to display incoming verification request
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] verification: Add widget to display incoming verification request
- Date: Fri, 19 Nov 2021 11:37:58 +0000 (UTC)
commit 735bda23f7e6883a2d5f77281e596ef150f9f6d7
Author: Julian Sparber <julian sparber net>
Date: Tue Nov 16 17:53:08 2021 +0100
verification: Add widget to display incoming verification request
data/resources/resources.gresource.xml | 1 +
data/resources/ui/content.ui | 25 ++
data/resources/ui/incoming-verification.ui | 455 ++++++++++++++++++++++
src/meson.build | 1 +
src/session/content/mod.rs | 51 ++-
src/session/verification/incoming_verification.rs | 425 ++++++++++++++++++++
src/session/verification/mod.rs | 2 +
src/session/verification/verification_list.rs | 39 +-
8 files changed, 986 insertions(+), 13 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 7ea0ad7c..57a90645 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -44,6 +44,7 @@
<file compressed="true" preprocess="xml-stripblanks" alias="room-creation.ui">ui/room-creation.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="session-verification.ui">ui/session-verification.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="verification-emoji.ui">ui/verification-emoji.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="incoming-verification.ui">ui/incoming-verification.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="qr-code-scanner.ui">ui/qr-code-scanner.ui</file>
<file compressed="true">style.css</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index 9a56762c..e8b6156a 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -54,6 +54,31 @@
<property name="session" bind-source="Content" bind-property="session" bind-flags="sync-create"/>
</object>
</child>
+ <child>
+ <object class="GtkBox" id="verification_page">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="AdwHeaderBar">
+ <property name="show-start-title-buttons" bind-source="Content" bind-property="compact"
bind-flags="sync-create"/>
+ <child type="start">
+ <object class="GtkRevealer">
+ <property name="transition-type">crossfade</property>
+ <property name="reveal-child" bind-source="Content" bind-property="compact"
bind-flags="sync-create"/>
+ <property name="child">
+ <object class="GtkButton">
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="action-name">content.go-back</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="IncomingVerification" id="incoming_verification"/>
+ </child>
+ </object>
+ </child>
</object>
</property>
</template>
diff --git a/data/resources/ui/incoming-verification.ui b/data/resources/ui/incoming-verification.ui
new file mode 100644
index 00000000..f477b60f
--- /dev/null
+++ b/data/resources/ui/incoming-verification.ui
@@ -0,0 +1,455 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IncomingVerification" parent="AdwBin">
+ <style>
+ <class name="incoming-verification"/>
+ </style>
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="transition-type">crossfade</property>
+ <property name="vexpand">True</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">accept-request</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Login Request From Another
Session</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify the new session with the current
session.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/other-device.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">center</property>
+ <property name="spacing">24</property>
+ <child>
+ <object class="GtkButton" id="dismiss_btn">
+ <property name="label" translatable="yes">Dismiss</property>
+ <property name="halign">center</property>
+ <property name="action-name">verification.dismiss</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="accept_btn">
+ <property name="label" translatable="yes">Accept</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">scan-qr-code</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Scan the Qr code with this session from
another session logged into this account</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="QrCodeScanner" id="qr_code_scanner">
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ <property name="vexpand">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Can't scan QR code?</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="start_emoji_btn2">
+ <property name="label" translatable="yes">Compare Emoji</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="take_screenshot_btn2">
+ <property name="label" translatable="yes">Take a Screenshot of a Qr Code</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">qr-code-scanned</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Scan Complete</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/setup-complete.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">You scanned to qr code successfully. You
may need to confirm the verification in the other session.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">no-camera</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Select an option to verify the new
session.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="start_emoji_btn3">
+ <property name="label" translatable="yes">Compare Emoji</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="take_screenshot_btn3">
+ <property name="label" translatable="yes">Take a Screenshot of a Qr Code</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">qrcode</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Scan this qr code with the newly logged in
session.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="TriQRCode" id="qrcode">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Can't scan QR code?</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="scan_qr_code_btn">
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="start_emoji_btn">
+ <property name="label" translatable="yes">Compare Emoji</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">emoji</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Verify Session</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Check if the same emoji appear in the same
order on the other device</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emoji_row_1">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="spacing">30</property>
+ <property name="margin-top">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="emoji_row_2">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="spacing">30</property>
+ <property name="margin-bottom">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="emoji_not_match_btn">
+ <property name="label" translatable="yes">Do Not Match</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="destructive-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="emoji_match_btn">
+ <property name="label" translatable="yes">Match</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">completed</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">300</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Request Complete</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPicture">
+ <property
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/setup-complete.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">The new session is now ready to send and
receive secure messages.</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="done_btn">
+ <property name="label" translatable="yes">Done</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/src/meson.build b/src/meson.build
index 74d0465f..db22af8f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -98,6 +98,7 @@ sources = files(
'session/verification/mod.rs',
'session/verification/emoji.rs',
'session/verification/identity_verification.rs',
+ 'session/verification/incoming_verification.rs',
'session/verification/session_verification.rs',
'session/verification/verification_list.rs',
)
diff --git a/src/session/content/mod.rs b/src/session/content/mod.rs
index 1b0ecbd2..69b6e69b 100644
--- a/src/session/content/mod.rs
+++ b/src/session/content/mod.rs
@@ -18,7 +18,7 @@ use self::room_history::RoomHistory;
use self::state_row::StateRow;
use crate::session::sidebar::{Entry, EntryType};
-use crate::session::verification::IdentityVerification;
+use crate::session::verification::{IdentityVerification, IncomingVerification, VerificationMode};
use adw::subclass::prelude::*;
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
@@ -40,7 +40,7 @@ mod imp {
pub session: RefCell<Option<WeakRef<Session>>>,
pub item: RefCell<Option<glib::Object>>,
pub error_list: RefCell<Option<gio::ListStore>>,
- pub category_handler: RefCell<Option<SignalHandlerId>>,
+ pub signal_handler: RefCell<Option<SignalHandlerId>>,
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
#[template_child]
@@ -51,6 +51,10 @@ mod imp {
pub explore: TemplateChild<Explore>,
#[template_child]
pub empty_page: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub verification_page: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub incoming_verification: TemplateChild<IncomingVerification>,
}
#[glib::object_subclass]
@@ -144,6 +148,17 @@ mod imp {
_ => unimplemented!(),
}
}
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ self.stack
+ .connect_visible_child_notify(clone!(@weak obj => move |stack| {
+ let priv_ = imp::Content::from_instance(&obj);
+ if stack.visible_child().as_ref() !=
Some(priv_.verification_page.upcast_ref::<gtk::Widget>()) {
+ priv_.incoming_verification.set_request(None);
+ }
+ }));
+ }
}
impl WidgetImpl for Content {}
@@ -189,22 +204,32 @@ impl Content {
return;
}
- if let Some(category_handler) = priv_.category_handler.take() {
+ if let Some(signal_handler) = priv_.signal_handler.take() {
if let Some(item) = self.item() {
- item.disconnect(category_handler);
+ item.disconnect(signal_handler);
}
}
- if let Some(ref room) = item {
- if room.is::<Room>() {
- let handler_id = room.connect_notify_local(
+ if let Some(ref item) = item {
+ if item.is::<Room>() {
+ let handler_id = item.connect_notify_local(
Some("category"),
clone!(@weak self as obj => move |_, _| {
obj.set_visible_child();
}),
);
- priv_.category_handler.replace(Some(handler_id));
+ priv_.signal_handler.replace(Some(handler_id));
+ }
+
+ if item.is::<IdentityVerification>() {
+ let handler_id = item.connect_notify_local(Some("mode"), clone!(@weak self as obj => move
|request, _| {
+ let request = request.downcast_ref::<IdentityVerification>().unwrap();
+ if request.mode() == VerificationMode::Cancelled || request.mode() ==
VerificationMode::Error || request.mode() == VerificationMode::Dismissed {
+ obj.set_item(None);
+ }
+ }));
+ priv_.signal_handler.replace(Some(handler_id));
}
}
@@ -249,7 +274,15 @@ impl Content {
priv_.stack.set_visible_child(&*priv_.explore);
}
Some(o) if o.is::<IdentityVerification>() => {
- todo!("Incoming verifications arn't implemented yet");
+ if let Some(item) = priv_
+ .item
+ .borrow()
+ .as_ref()
+ .and_then(|item| item.downcast_ref::<IdentityVerification>())
+ {
+ priv_.incoming_verification.set_request(Some(item.clone()));
+ priv_.stack.set_visible_child(&*priv_.verification_page);
+ }
}
_ => {}
}
diff --git a/src/session/verification/incoming_verification.rs
b/src/session/verification/incoming_verification.rs
new file mode 100644
index 00000000..30d14b08
--- /dev/null
+++ b/src/session/verification/incoming_verification.rs
@@ -0,0 +1,425 @@
+use adw::subclass::prelude::*;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use log::warn;
+
+use crate::components::SpinnerButton;
+use crate::contrib::screenshot;
+use crate::contrib::QRCode;
+use crate::contrib::QRCodeExt;
+use crate::contrib::QrCodeScanner;
+use crate::session::verification::{Emoji, IdentityVerification, VerificationMode};
+use crate::spawn;
+use gettextrs::gettext;
+use matrix_sdk::encryption::verification::QrVerificationData;
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+ use glib::SignalHandlerId;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/incoming-verification.ui")]
+ pub struct IncomingVerification {
+ pub request: RefCell<Option<IdentityVerification>>,
+ #[template_child]
+ pub qrcode: TemplateChild<QRCode>,
+ #[template_child]
+ pub emoji_row_1: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub emoji_row_2: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub emoji_match_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub emoji_not_match_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub start_emoji_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub start_emoji_btn2: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub start_emoji_btn3: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub scan_qr_code_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub accept_btn: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub dismiss_btn: TemplateChild<gtk::Button>,
+ #[template_child]
+ pub take_screenshot_btn2: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub take_screenshot_btn3: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub main_stack: TemplateChild<gtk::Stack>,
+ #[template_child]
+ pub qr_code_scanner: TemplateChild<QrCodeScanner>,
+ #[template_child]
+ pub done_btn: TemplateChild<gtk::Button>,
+ pub mode_handler: RefCell<Option<SignalHandlerId>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for IncomingVerification {
+ const NAME: &'static str = "IncomingVerification";
+ type Type = super::IncomingVerification;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ SpinnerButton::static_type();
+ QRCode::static_type();
+ Emoji::static_type();
+ QrCodeScanner::static_type();
+
+ klass.install_action("verification.dismiss", None, move |obj, _, _| {
+ obj.dismiss();
+ });
+
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for IncomingVerification {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_object(
+ "request",
+ "Request",
+ "The Object holding the data for the verification",
+ IdentityVerification::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "request" => obj.set_request(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "request" => obj.request().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ self.accept_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.dismiss_btn.set_sensitive(false);
+ obj.accept();
+ }));
+
+ self.emoji_match_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.emoji_not_match_btn.set_sensitive(false);
+ if let Some(request) = obj.request() {
+ request.emoji_match();
+ }
+ }));
+
+ self.emoji_not_match_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.emoji_match_btn.set_sensitive(false);
+ if let Some(request) = obj.request() {
+ request.emoji_not_match();
+ }
+ }));
+
+ self.start_emoji_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.scan_qr_code_btn.set_sensitive(false);
+ if let Some(request) = obj.request() {
+ request.start_sas();
+ }
+ }));
+ self.start_emoji_btn2
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.take_screenshot_btn2.set_sensitive(false);
+ if let Some(request) = obj.request() {
+ request.start_sas();
+ }
+ }));
+ self.start_emoji_btn3
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.take_screenshot_btn3.set_sensitive(false);
+ if let Some(request) = obj.request() {
+ request.start_sas();
+ }
+ }));
+
+ self.scan_qr_code_btn
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.start_emoji_btn.set_sensitive(false);
+ if priv_.qr_code_scanner.has_camera() {
+ obj.start_scanning();
+ } else {
+ obj.take_screenshot();
+ }
+ }));
+
+ self.take_screenshot_btn2
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.start_emoji_btn2.set_sensitive(false);
+ obj.take_screenshot();
+ }));
+
+ self.take_screenshot_btn3
+ .connect_clicked(clone!(@weak obj => move |button| {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ button.set_loading(true);
+ priv_.start_emoji_btn3.set_sensitive(false);
+ obj.take_screenshot();
+ }));
+
+ self.done_btn.connect_clicked(clone!(@weak obj => move |_| {
+ obj.dismiss();
+ }));
+
+ self.qr_code_scanner
+ .connect_code_detected(clone!(@weak obj => move |_, data| {
+ obj.finish_scanning(data);
+ }));
+
+ self.qr_code_scanner.connect_notify_local(
+ Some("has-camera"),
+ clone!(@weak obj => move |_, _| {
+ obj.update_camera_state();
+ }),
+ );
+ obj.update_camera_state();
+ }
+
+ fn dispose(&self, obj: &Self::Type) {
+ if let Some(request) = obj.request() {
+ if let Some(handler) = self.mode_handler.take() {
+ request.disconnect(handler);
+ }
+ }
+ }
+ }
+
+ impl WidgetImpl for IncomingVerification {
+ fn map(&self, widget: &Self::Type) {
+ self.parent_map(widget);
+ widget.update_view();
+ }
+ }
+ impl BinImpl for IncomingVerification {}
+}
+
+glib::wrapper! {
+ pub struct IncomingVerification(ObjectSubclass<imp::IncomingVerification>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl IncomingVerification {
+ pub fn new(request: &IdentityVerification) -> Self {
+ glib::Object::new(&[("request", request)]).expect("Failed to create IncomingVerification")
+ }
+
+ pub fn request(&self) -> Option<IdentityVerification> {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ priv_.request.borrow().clone()
+ }
+
+ pub fn set_request(&self, request: Option<IdentityVerification>) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ let previous_request = self.request();
+
+ if previous_request == request {
+ return;
+ }
+
+ self.reset();
+
+ if let Some(previous_request) = previous_request {
+ if let Some(handler) = priv_.mode_handler.take() {
+ previous_request.disconnect(handler);
+ }
+ }
+
+ if let Some(ref request) = request {
+ let handler = request.connect_notify_local(
+ Some("mode"),
+ clone!(@weak self as obj => move |_, _| {
+ obj.update_view();
+ }),
+ );
+ self.update_view();
+
+ priv_.mode_handler.replace(Some(handler));
+ }
+
+ priv_.request.replace(request);
+ self.notify("request");
+ }
+
+ fn reset(&self) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ priv_.accept_btn.set_loading(false);
+ priv_.accept_btn.set_sensitive(true);
+ priv_.dismiss_btn.set_sensitive(true);
+ priv_.scan_qr_code_btn.set_loading(false);
+ priv_.scan_qr_code_btn.set_sensitive(true);
+ priv_.emoji_not_match_btn.set_loading(false);
+ priv_.emoji_not_match_btn.set_sensitive(true);
+ priv_.emoji_match_btn.set_loading(false);
+ priv_.emoji_match_btn.set_sensitive(true);
+ priv_.start_emoji_btn.set_loading(false);
+ priv_.start_emoji_btn.set_sensitive(true);
+ priv_.start_emoji_btn2.set_loading(false);
+ priv_.start_emoji_btn2.set_sensitive(true);
+ priv_.start_emoji_btn3.set_loading(false);
+ priv_.start_emoji_btn3.set_sensitive(true);
+ priv_.take_screenshot_btn2.set_loading(false);
+ priv_.take_screenshot_btn2.set_sensitive(true);
+ priv_.take_screenshot_btn3.set_loading(false);
+ priv_.take_screenshot_btn3.set_sensitive(true);
+
+ self.clean_emoji();
+ }
+
+ fn clean_emoji(&self) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+
+ while let Some(child) = priv_.emoji_row_1.first_child() {
+ priv_.emoji_row_1.remove(&child);
+ }
+
+ while let Some(child) = priv_.emoji_row_2.first_child() {
+ priv_.emoji_row_2.remove(&child);
+ }
+ }
+
+ pub fn accept(&self) {
+ if let Some(request) = self.request() {
+ request.accept_incoming();
+ }
+ }
+
+ pub fn dismiss(&self) {
+ if let Some(request) = self.request() {
+ request.dismiss();
+ }
+ }
+
+ fn update_view(&self) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ if let Some(request) = self.request() {
+ match request.mode() {
+ VerificationMode::IdentityNotFound => {
+ // TODO: what should we do if we don't find the identity
+ }
+ VerificationMode::Requested => {
+ priv_.main_stack.set_visible_child_name("accept-request");
+ }
+ VerificationMode::QrV1Show => {
+ if let Some(qrcode) = request.qr_code() {
+ priv_.qrcode.set_qrcode(qrcode);
+ priv_.main_stack.set_visible_child_name("qrcode");
+ } else {
+ warn!("Failed to get qrcode for QrVerification");
+ request.start_sas();
+ }
+ }
+ VerificationMode::QrV1Scan => {
+ self.start_scanning();
+ }
+ VerificationMode::SasV1 => {
+ self.clean_emoji();
+ // TODO: implement sas fallback when emojis arn't supported
+ if let Some(emoji) = request.emoji() {
+ for (index, emoji) in emoji.iter().enumerate() {
+ if index < 4 {
+ priv_.emoji_row_1.append(&Emoji::new(emoji));
+ } else {
+ priv_.emoji_row_2.append(&Emoji::new(emoji));
+ }
+ }
+ priv_.main_stack.set_visible_child_name("emoji");
+ }
+ }
+ VerificationMode::Completed => {
+ priv_.main_stack.set_visible_child_name("completed");
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn start_scanning(&self) {
+ spawn!(clone!(@weak self as obj => async move {
+ let priv_ = imp::IncomingVerification::from_instance(&obj);
+ if priv_.qr_code_scanner.start().await {
+ priv_.main_stack.set_visible_child_name("scan-qr-code");
+ } else {
+ priv_.main_stack.set_visible_child_name("no-camera");
+ }
+ }));
+ }
+
+ fn take_screenshot(&self) {
+ spawn!(clone!(@weak self as obj => async move {
+ let root = obj.root().unwrap();
+ if let Some(code) = screenshot::capture(&root).await {
+ obj.finish_scanning(code);
+ } else {
+ obj.reset();
+ }
+ }));
+ }
+
+ fn finish_scanning(&self, data: QrVerificationData) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ priv_.qr_code_scanner.stop();
+ if let Some(request) = self.request() {
+ request.scanned_qr_code(data);
+ }
+ priv_.main_stack.set_visible_child_name("qr-code-scanned");
+ }
+
+ fn update_camera_state(&self) {
+ let priv_ = imp::IncomingVerification::from_instance(self);
+ if priv_.qr_code_scanner.has_camera() {
+ priv_
+ .scan_qr_code_btn
+ .set_label(&gettext("Scan QR code with this session"))
+ } else {
+ priv_
+ .scan_qr_code_btn
+ .set_label(&gettext("Take a Screenshot of a Qr Code"))
+ }
+ }
+}
diff --git a/src/session/verification/mod.rs b/src/session/verification/mod.rs
index 9fc05594..489f7d8e 100644
--- a/src/session/verification/mod.rs
+++ b/src/session/verification/mod.rs
@@ -1,9 +1,11 @@
mod emoji;
mod identity_verification;
+mod incoming_verification;
mod session_verification;
mod verification_list;
pub use self::emoji::Emoji;
pub use self::identity_verification::{IdentityVerification, Mode as VerificationMode};
+pub use self::incoming_verification::IncomingVerification;
pub use self::session_verification::SessionVerification;
pub use self::verification_list::VerificationList;
diff --git a/src/session/verification/verification_list.rs b/src/session/verification/verification_list.rs
index f3d73421..fedb7a59 100644
--- a/src/session/verification/verification_list.rs
+++ b/src/session/verification/verification_list.rs
@@ -1,7 +1,10 @@
-use gtk::{gio, glib, prelude::*, subclass::prelude::*};
+use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::{api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent};
-use crate::session::{verification::IdentityVerification, Session};
+use crate::session::{
+ verification::{IdentityVerification, VerificationMode},
+ Session,
+};
mod imp {
use glib::object::WeakRef;
@@ -99,8 +102,10 @@ impl VerificationList {
let priv_ = imp::VerificationList::from_instance(self);
for event in &to_device.events {
- if let Ok(AnyToDeviceEvent::KeyVerificationRequest(_event)) = event.deserialize() {
- //TODO: implement handling of incomming requests
+ if let Ok(AnyToDeviceEvent::KeyVerificationRequest(event)) = event.deserialize() {
+ let request = IdentityVerification::new(self.session().user().unwrap());
+ request.set_flow_id(Some(event.content.transaction_id.to_owned()));
+ self.add(request);
}
}
@@ -115,9 +120,35 @@ impl VerificationList {
let length = {
let mut list = priv_.list.borrow_mut();
let length = list.len();
+ request.connect_notify_local(Some("mode"), clone!(@weak self as obj => move |request, _| {
+ if request.mode() == VerificationMode::Error || request.mode() ==
VerificationMode::Cancelled || request.mode() == VerificationMode::Dismissed || request.mode() ==
VerificationMode::Completed {
+ obj.remove(request);
+ }
+ }));
list.push(request);
length as u32
};
self.items_changed(length, 0, 1)
}
+
+ pub fn remove(&self, request: &IdentityVerification) {
+ let priv_ = imp::VerificationList::from_instance(self);
+ let position = {
+ let mut list = priv_.list.borrow_mut();
+ let mut position = None;
+ for (index, item) in list.iter().enumerate() {
+ if item == request {
+ position = Some(index);
+ break;
+ }
+ }
+ if let Some(position) = position {
+ list.remove(position);
+ }
+ position
+ };
+ if let Some(position) = position {
+ self.items_changed(position as u32, 1, 0);
+ }
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]