Skip to main content

fractal/identity_verification_view/
scan_qr_code_page.rs

1use 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        /// The current identity verification.
28        #[property(get, set = Self::set_verification, construct_only)]
29        pub verification: BoundConstructOnlyObject<IdentityVerification>,
30        /// The QR code scanner to use.
31        #[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        /// Set the current identity verification.
78        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        /// Initialize the QR code scanner.
109        fn init_qrcode_scanner(&self) {
110            let Some(qrcode_scanner) = self.verification.obj().qrcode_scanner() else {
111                // This is a programmer error.
112                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    /// A page to scan a QR code.
135    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    /// Update the labels for the current verification.
149    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                // Translators: Do NOT translate the content between '{' and '}', this is a
162                // variable name.
163                "Scan the QR code shown on the device of {user}.",
164                &[("user", &format!("<b>{name}</b>"))],
165            ));
166        }
167    }
168
169    /// Update the UI for the available verification methods.
170    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    /// Reset the UI to its initial state.
185    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    /// Handle a detected QR Code.
195    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    /// Switch to the screen to scan a QR Code.
208    #[template_callback]
209    fn show_qrcode(&self) {
210        self.verification().choose_method();
211    }
212
213    /// Start a SAS verification.
214    #[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    /// Cancel the verification.
228    #[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}