Skip to main content

fractal/identity_verification_view/
mod.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone};
4
5mod accept_request_page;
6mod cancelled_page;
7mod choose_method_page;
8mod completed_page;
9mod confirm_qr_code_page;
10mod no_supported_methods_page;
11mod qr_code_scanned_page;
12mod room_left_page;
13mod sas_emoji;
14mod sas_page;
15mod scan_qr_code_page;
16mod wait_for_other_page;
17
18use self::{
19    accept_request_page::AcceptRequestPage, cancelled_page::CancelledPage,
20    choose_method_page::ChooseMethodPage, completed_page::CompletedPage,
21    confirm_qr_code_page::ConfirmQrCodePage, no_supported_methods_page::NoSupportedMethodsPage,
22    qr_code_scanned_page::QrCodeScannedPage, room_left_page::RoomLeftPage, sas_page::SasPage,
23    scan_qr_code_page::ScanQrCodePage, wait_for_other_page::WaitForOtherPage,
24};
25use crate::{
26    session::{IdentityVerification, VerificationState},
27    utils::BoundObject,
28};
29
30mod imp {
31    use glib::subclass::InitializingObject;
32
33    use super::*;
34
35    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
36    #[template(resource = "/org/gnome/Fractal/ui/identity_verification_view/mod.ui")]
37    #[properties(wrapper_type = super::IdentityVerificationView)]
38    pub struct IdentityVerificationView {
39        /// The current identity verification.
40        #[property(get, set = Self::set_verification, explicit_notify, nullable)]
41        pub verification: BoundObject<IdentityVerification>,
42        #[template_child]
43        pub main_stack: TemplateChild<gtk::Stack>,
44        #[template_child]
45        pub accept_request_page: TemplateChild<AcceptRequestPage>,
46        #[template_child]
47        pub wait_for_other_page: TemplateChild<WaitForOtherPage>,
48        #[template_child]
49        pub no_supported_methods_page: TemplateChild<NoSupportedMethodsPage>,
50        #[template_child]
51        pub choose_method_page: TemplateChild<ChooseMethodPage>,
52        #[template_child]
53        pub qr_code_scanned_page: TemplateChild<QrCodeScannedPage>,
54        #[template_child]
55        pub confirm_qr_code_page: TemplateChild<ConfirmQrCodePage>,
56        #[template_child]
57        pub sas_page: TemplateChild<SasPage>,
58        #[template_child]
59        pub completed_page: TemplateChild<CompletedPage>,
60        #[template_child]
61        pub cancelled_page: TemplateChild<CancelledPage>,
62        #[template_child]
63        pub room_left_page: TemplateChild<RoomLeftPage>,
64    }
65
66    #[glib::object_subclass]
67    impl ObjectSubclass for IdentityVerificationView {
68        const NAME: &'static str = "IdentityVerificationView";
69        type Type = super::IdentityVerificationView;
70        type ParentType = adw::Bin;
71
72        fn class_init(klass: &mut Self::Class) {
73            Self::bind_template(klass);
74            Self::bind_template_callbacks(klass);
75        }
76
77        fn instance_init(obj: &InitializingObject<Self>) {
78            obj.init_template();
79        }
80    }
81
82    #[glib::derived_properties]
83    impl ObjectImpl for IdentityVerificationView {}
84
85    impl WidgetImpl for IdentityVerificationView {
86        fn grab_focus(&self) -> bool {
87            let Some(name) = self.main_stack.visible_child_name() else {
88                return false;
89            };
90
91            match name.as_str() {
92                "accept-request" => self.accept_request_page.grab_focus(),
93                "no-supported-methods" => self.no_supported_methods_page.grab_focus(),
94                "choose-method" => self.choose_method_page.grab_focus(),
95                "confirm-qr-code" => self.confirm_qr_code_page.grab_focus(),
96                "sas" => self.sas_page.grab_focus(),
97                "completed" => self.completed_page.grab_focus(),
98                "cancelled" => self.cancelled_page.grab_focus(),
99                "room-left" => self.room_left_page.grab_focus(),
100                _ => false,
101            }
102        }
103    }
104
105    impl BinImpl for IdentityVerificationView {}
106
107    #[gtk::template_callbacks]
108    impl IdentityVerificationView {
109        /// Set the current identity verification.
110        fn set_verification(&self, verification: Option<IdentityVerification>) {
111            let prev_verification = self.verification.obj();
112
113            if prev_verification == verification {
114                return;
115            }
116            let obj = self.obj();
117
118            self.verification.disconnect_signals();
119
120            if let Some(verification) = verification {
121                let state_handler = verification.connect_state_notify(clone!(
122                    #[weak]
123                    obj,
124                    move |_| {
125                        obj.update_view();
126                    }
127                ));
128
129                verification.set_was_viewed(true);
130                self.verification.set(verification, vec![state_handler]);
131            }
132
133            obj.update_view();
134            obj.notify_verification();
135        }
136
137        #[template_callback]
138        fn handle_transition_running(&self) {
139            if !self.main_stack.is_transition_running() {
140                // Focus the default widget when the transition has ended.
141                self.grab_focus();
142
143                // Drop the page to scan QR codes if it is not the current page, to free the
144                // camera.
145                if let Some(scan_qrcode_page) = self.main_stack.child_by_name("scan-qrcode")
146                    && self.main_stack.visible_child_name().as_deref() != Some("scan-qrcode")
147                {
148                    self.main_stack.remove(&scan_qrcode_page);
149                }
150            }
151        }
152    }
153}
154
155glib::wrapper! {
156    /// A view to show the different stages of an identity verification.
157    pub struct IdentityVerificationView(ObjectSubclass<imp::IdentityVerificationView>)
158        @extends gtk::Widget, adw::Bin,
159        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
160}
161
162impl IdentityVerificationView {
163    pub fn new(verification: &IdentityVerification) -> Self {
164        glib::Object::builder()
165            .property("verification", verification)
166            .build()
167    }
168
169    /// Update this view for the current state of the verification.
170    fn update_view(&self) {
171        let Some(verification) = self.verification() else {
172            return;
173        };
174        let imp = self.imp();
175
176        match verification.state() {
177            VerificationState::Created => {
178                imp.wait_for_other_page.reset();
179                imp.main_stack
180                    .set_visible_child_name("wait-for-other-party");
181            }
182            VerificationState::Requested => {
183                imp.accept_request_page.reset();
184                imp.main_stack.set_visible_child_name("accept-request");
185            }
186            VerificationState::NoSupportedMethods => {
187                imp.no_supported_methods_page.reset();
188                imp.main_stack
189                    .set_visible_child_name("no-supported-methods");
190            }
191            VerificationState::Ready => {
192                imp.choose_method_page.reset();
193                imp.main_stack.set_visible_child_name("choose-method");
194            }
195            VerificationState::QrScan => {
196                let scan_qrcode_page = ScanQrCodePage::new(verification);
197                imp.main_stack.add_titled(
198                    &scan_qrcode_page,
199                    Some("scan-qrcode"),
200                    &gettext("Scan QR Code"),
201                );
202                imp.main_stack.set_visible_child_name("scan-qrcode");
203            }
204            VerificationState::QrScanned => {
205                imp.qr_code_scanned_page.reset();
206                imp.main_stack.set_visible_child_name("qr-code-scanned");
207            }
208            VerificationState::QrConfirm => {
209                imp.confirm_qr_code_page.reset();
210                imp.main_stack.set_visible_child_name("confirm-qr-code");
211            }
212            VerificationState::SasConfirm => {
213                imp.sas_page.reset();
214                imp.main_stack.set_visible_child_name("sas");
215            }
216            VerificationState::Done => {
217                imp.main_stack.set_visible_child_name("completed");
218            }
219            VerificationState::Cancelled | VerificationState::Error => {
220                imp.cancelled_page.reset();
221                imp.main_stack.set_visible_child_name("cancelled");
222            }
223            VerificationState::RoomLeft => {
224                imp.main_stack.set_visible_child_name("room-left");
225            }
226            // Nothing to do, this view should be closed.
227            VerificationState::Dismissed => {}
228        }
229    }
230}