fractal/identity_verification_view/
scan_qr_code_page.rs1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone};
4use matrix_sdk::encryption::verification::QrVerificationData;
5use tracing::error;
6
7use crate::{
8 components::{LoadingButton, QrCodeScanner},
9 gettext_f,
10 prelude::*,
11 session::{IdentityVerification, VerificationSupportedMethods},
12 spawn, toast,
13 utils::BoundConstructOnlyObject,
14};
15
16mod imp {
17 use std::cell::RefCell;
18
19 use glib::subclass::InitializingObject;
20
21 use super::*;
22
23 #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
24 #[template(resource = "/org/gnome/Fractal/ui/identity_verification_view/scan_qr_code_page.ui")]
25 #[properties(wrapper_type = super::ScanQrCodePage)]
26 pub struct ScanQrCodePage {
27 #[property(get, set = Self::set_verification, construct_only)]
29 pub verification: BoundConstructOnlyObject<IdentityVerification>,
30 #[property(get)]
32 pub qrcode_scanner: BoundConstructOnlyObject<QrCodeScanner>,
33 pub display_name_handler: RefCell<Option<glib::SignalHandlerId>>,
34 #[template_child]
35 pub title: TemplateChild<gtk::Label>,
36 #[template_child]
37 pub instructions: TemplateChild<gtk::Label>,
38 #[template_child]
39 qrcode_scanner_bin: TemplateChild<adw::Bin>,
40 #[template_child]
41 pub show_qr_code_btn: TemplateChild<gtk::Button>,
42 #[template_child]
43 pub start_sas_btn: TemplateChild<LoadingButton>,
44 #[template_child]
45 pub cancel_btn: TemplateChild<LoadingButton>,
46 }
47
48 #[glib::object_subclass]
49 impl ObjectSubclass for ScanQrCodePage {
50 const NAME: &'static str = "IdentityVerificationScanQrCodePage";
51 type Type = super::ScanQrCodePage;
52 type ParentType = adw::Bin;
53
54 fn class_init(klass: &mut Self::Class) {
55 Self::bind_template(klass);
56 Self::Type::bind_template_callbacks(klass);
57 }
58
59 fn instance_init(obj: &InitializingObject<Self>) {
60 obj.init_template();
61 }
62 }
63
64 #[glib::derived_properties]
65 impl ObjectImpl for ScanQrCodePage {
66 fn dispose(&self) {
67 if let Some(handler) = self.display_name_handler.take() {
68 self.verification.obj().user().disconnect(handler);
69 }
70 }
71 }
72
73 impl WidgetImpl for ScanQrCodePage {}
74 impl BinImpl for ScanQrCodePage {}
75
76 impl ScanQrCodePage {
77 fn set_verification(&self, verification: IdentityVerification) {
79 let obj = self.obj();
80
81 let display_name_handler = verification.user().connect_display_name_notify(clone!(
82 #[weak]
83 obj,
84 move |_| {
85 obj.update_labels();
86 }
87 ));
88 self.display_name_handler
89 .replace(Some(display_name_handler));
90
91 let supported_methods_handler = verification.connect_supported_methods_notify(clone!(
92 #[weak]
93 obj,
94 move |_| {
95 obj.update_page();
96 }
97 ));
98
99 self.verification
100 .set(verification, vec![supported_methods_handler]);
101
102 self.init_qrcode_scanner();
103 obj.update_labels();
104 obj.update_page();
105 obj.notify_verification();
106 }
107
108 fn init_qrcode_scanner(&self) {
110 let Some(qrcode_scanner) = self.verification.obj().qrcode_scanner() else {
111 error!("Could not show QR code scanner: not found");
113 return;
114 };
115
116 self.qrcode_scanner_bin.set_child(Some(&qrcode_scanner));
117
118 let obj = self.obj();
119 let qrcode_detected_handler = qrcode_scanner.connect_qrcode_detected(clone!(
120 #[weak]
121 obj,
122 move |_, data| {
123 obj.qrcode_detected(data);
124 }
125 ));
126
127 self.qrcode_scanner
128 .set(qrcode_scanner, vec![qrcode_detected_handler]);
129 }
130 }
131}
132
133glib::wrapper! {
134 pub struct ScanQrCodePage(ObjectSubclass<imp::ScanQrCodePage>)
136 @extends gtk::Widget, adw::Bin,
137 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
138}
139
140#[gtk::template_callbacks]
141impl ScanQrCodePage {
142 pub fn new(verification: IdentityVerification) -> Self {
143 glib::Object::builder()
144 .property("verification", verification)
145 .build()
146 }
147
148 fn update_labels(&self) {
150 let imp = self.imp();
151 let verification = self.verification();
152
153 if verification.is_self_verification() {
154 imp.title.set_label(&gettext("Verify Session"));
155 imp.instructions
156 .set_label(&gettext("Scan the QR code displayed by the other session."));
157 } else {
158 let name = verification.user().display_name();
159 imp.title.set_markup(&gettext("Verification Request"));
160 imp.instructions.set_markup(&gettext_f(
161 "Scan the QR code shown on the device of {user}.",
164 &[("user", &format!("<b>{name}</b>"))],
165 ));
166 }
167 }
168
169 fn update_page(&self) {
171 let verification = self.verification();
172 let supported_methods = verification.supported_methods();
173
174 let show_qr_code_visible = supported_methods
175 .contains(VerificationSupportedMethods::QR_SHOW)
176 && verification.has_qr_code();
177 let sas_visible = supported_methods.contains(VerificationSupportedMethods::SAS);
178
179 let imp = self.imp();
180 imp.show_qr_code_btn.set_visible(show_qr_code_visible);
181 imp.start_sas_btn.set_visible(sas_visible);
182 }
183
184 pub fn reset(&self) {
186 let imp = self.imp();
187
188 imp.start_sas_btn.set_is_loading(false);
189 imp.cancel_btn.set_is_loading(false);
190
191 self.set_sensitive(true);
192 }
193
194 fn qrcode_detected(&self, data: QrVerificationData) {
196 spawn!(clone!(
197 #[weak(rename_to = obj)]
198 self,
199 async move {
200 if obj.verification().qr_code_scanned(data).await.is_err() {
201 toast!(obj, gettext("Could not validate scanned QR Code"));
202 }
203 }
204 ));
205 }
206
207 #[template_callback]
209 fn show_qrcode(&self) {
210 self.verification().choose_method();
211 }
212
213 #[template_callback]
215 async fn start_sas(&self) {
216 let imp = self.imp();
217
218 imp.start_sas_btn.set_is_loading(true);
219 self.set_sensitive(false);
220
221 if self.verification().start_sas().await.is_err() {
222 toast!(self, gettext("Could not start emoji verification"));
223 self.reset();
224 }
225 }
226
227 #[template_callback]
229 async fn cancel(&self) {
230 self.imp().cancel_btn.set_is_loading(true);
231 self.set_sensitive(false);
232
233 if self.verification().cancel().await.is_err() {
234 toast!(self, gettext("Could not cancel the verification"));
235 self.reset();
236 }
237 }
238}