fractal/identity_verification_view/
choose_method_page.rs1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone};
4
5use crate::{
6 components::LoadingButton,
7 contrib::QRCode,
8 gettext_f,
9 prelude::*,
10 session::{IdentityVerification, VerificationSupportedMethods},
11 toast,
12 utils::BoundObjectWeakRef,
13};
14
15mod imp {
16 use std::cell::RefCell;
17
18 use glib::subclass::InitializingObject;
19
20 use super::*;
21
22 #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
23 #[template(resource = "/org/gnome/Fractal/ui/identity_verification_view/choose_method_page.ui")]
24 #[properties(wrapper_type = super::ChooseMethodPage)]
25 pub struct ChooseMethodPage {
26 #[property(get, set = Self::set_verification, explicit_notify, nullable)]
28 pub verification: BoundObjectWeakRef<IdentityVerification>,
29 pub display_name_handler: RefCell<Option<glib::SignalHandlerId>>,
30 #[template_child]
31 pub title: TemplateChild<gtk::Label>,
32 #[template_child]
33 pub instructions: TemplateChild<gtk::Label>,
34 #[template_child]
35 pub qrcode: TemplateChild<QRCode>,
36 #[template_child]
37 pub cannot_scan_label: TemplateChild<gtk::Label>,
38 #[template_child]
39 pub scan_qr_code_btn: TemplateChild<LoadingButton>,
40 #[template_child]
41 pub start_sas_btn: TemplateChild<LoadingButton>,
42 #[template_child]
43 pub cancel_btn: TemplateChild<LoadingButton>,
44 }
45
46 #[glib::object_subclass]
47 impl ObjectSubclass for ChooseMethodPage {
48 const NAME: &'static str = "IdentityVerificationChooseMethodPage";
49 type Type = super::ChooseMethodPage;
50 type ParentType = adw::Bin;
51
52 fn class_init(klass: &mut Self::Class) {
53 Self::bind_template(klass);
54 Self::Type::bind_template_callbacks(klass);
55 }
56
57 fn instance_init(obj: &InitializingObject<Self>) {
58 obj.init_template();
59 }
60 }
61
62 #[glib::derived_properties]
63 impl ObjectImpl for ChooseMethodPage {
64 fn dispose(&self) {
65 if let Some(verification) = self.verification.obj()
66 && let Some(handler) = self.display_name_handler.take()
67 {
68 verification.user().disconnect(handler);
69 }
70 }
71 }
72
73 impl WidgetImpl for ChooseMethodPage {
74 fn grab_focus(&self) -> bool {
75 if self.scan_qr_code_btn.is_visible() {
76 self.scan_qr_code_btn.grab_focus()
77 } else if self.start_sas_btn.is_visible() {
78 self.start_sas_btn.grab_focus()
79 } else {
80 self.cancel_btn.grab_focus()
81 }
82 }
83 }
84
85 impl BinImpl for ChooseMethodPage {}
86
87 impl ChooseMethodPage {
88 fn set_verification(&self, verification: Option<&IdentityVerification>) {
90 let prev_verification = self.verification.obj();
91
92 if prev_verification.as_ref() == verification {
93 return;
94 }
95 let obj = self.obj();
96
97 obj.reset();
98
99 if let Some(verification) = prev_verification
100 && let Some(handler) = self.display_name_handler.take()
101 {
102 verification.user().disconnect(handler);
103 }
104 self.verification.disconnect_signals();
105
106 if let Some(verification) = verification {
107 let display_name_handler = verification.user().connect_display_name_notify(clone!(
108 #[weak]
109 obj,
110 move |_| {
111 obj.update_page();
112 }
113 ));
114 self.display_name_handler
115 .replace(Some(display_name_handler));
116
117 let supported_methods_handler =
118 verification.connect_supported_methods_notify(clone!(
119 #[weak]
120 obj,
121 move |_| {
122 obj.update_page();
123 }
124 ));
125
126 self.verification
127 .set(verification, vec![supported_methods_handler]);
128 }
129
130 obj.update_page();
131 obj.notify_verification();
132 }
133 }
134}
135
136glib::wrapper! {
137 pub struct ChooseMethodPage(ObjectSubclass<imp::ChooseMethodPage>)
139 @extends gtk::Widget, adw::Bin,
140 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
141}
142
143#[gtk::template_callbacks]
144impl ChooseMethodPage {
145 pub fn new() -> Self {
146 glib::Object::new()
147 }
148
149 fn update_page(&self) {
151 let Some(verification) = self.verification() else {
152 return;
153 };
154 let imp = self.imp();
155 let supported_methods = verification.supported_methods();
156
157 let qr_code_visible = if supported_methods.contains(VerificationSupportedMethods::QR_SHOW) {
158 if let Some(qr_code) = verification.qr_code() {
159 imp.qrcode.set_qrcode(qr_code);
160 true
161 } else {
162 false
163 }
164 } else {
165 false
166 };
167 let scan_qr_code_visible =
168 supported_methods.contains(VerificationSupportedMethods::QR_SCAN);
169 let sas_visible = supported_methods.contains(VerificationSupportedMethods::SAS);
170
171 let options_nb =
172 u8::from(qr_code_visible) + u8::from(scan_qr_code_visible) + u8::from(sas_visible);
173 let has_several_options = options_nb > 1;
174
175 if verification.is_self_verification() {
176 imp.title.set_label(&gettext("Verify Session"));
177
178 if qr_code_visible {
179 imp.instructions
180 .set_label(&gettext("Scan this QR code from the other session."));
181 }
182 } else {
183 let name = verification.user().display_name();
184 imp.title.set_label(&gettext("Verification Request"));
185
186 if qr_code_visible {
187 imp.instructions.set_markup(&gettext_f(
188 "Ask {user} to scan this QR code from their session.",
191 &[("user", &format!("<b>{name}</b>"))],
192 ));
193 }
194 }
195
196 if !qr_code_visible {
197 if has_several_options {
198 imp.instructions
199 .set_label(&gettext("Select a verification method to proceed."));
200 } else {
201 imp.instructions
202 .set_label(&gettext("Click on the verification method to proceed."));
203 }
204 }
205
206 imp.qrcode.set_visible(qr_code_visible);
207 imp.scan_qr_code_btn.set_visible(scan_qr_code_visible);
208 imp.start_sas_btn.set_visible(sas_visible);
209 }
210
211 pub fn reset(&self) {
213 let imp = self.imp();
214
215 imp.scan_qr_code_btn.set_is_loading(false);
216 imp.start_sas_btn.set_is_loading(false);
217 imp.cancel_btn.set_is_loading(false);
218
219 self.set_sensitive(true);
220 }
221
222 #[template_callback]
224 async fn start_qr_code_scan(&self) {
225 let Some(verification) = self.verification() else {
226 return;
227 };
228 let imp = self.imp();
229
230 imp.scan_qr_code_btn.set_is_loading(true);
231 self.set_sensitive(false);
232
233 if verification.start_qr_code_scan().await.is_err() {
234 toast!(self, gettext("Could not access camera"));
235 self.reset();
236 }
237 }
238
239 #[template_callback]
241 async fn start_sas(&self) {
242 let Some(verification) = self.verification() else {
243 return;
244 };
245 let imp = self.imp();
246
247 imp.start_sas_btn.set_is_loading(true);
248 self.set_sensitive(false);
249
250 if verification.start_sas().await.is_err() {
251 toast!(self, gettext("Could not start emoji verification"));
252 self.reset();
253 }
254 }
255
256 #[template_callback]
258 async fn cancel(&self) {
259 let Some(verification) = self.verification() else {
260 return;
261 };
262
263 self.imp().cancel_btn.set_is_loading(true);
264 self.set_sensitive(false);
265
266 if verification.cancel().await.is_err() {
267 toast!(self, gettext("Could not cancel the verification"));
268 self.reset();
269 }
270 }
271}