fractal/identity_verification_view/
cancelled_page.rs1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone};
4use matrix_sdk::encryption::verification::CancelInfo;
5use ruma::events::key::verification::cancel::CancelCode;
6
7use crate::{
8 components::LoadingButton, gettext_f, prelude::*, session::IdentityVerification, toast,
9 utils::BoundObjectWeakRef,
10};
11
12mod imp {
13 use std::cell::RefCell;
14
15 use glib::subclass::InitializingObject;
16
17 use super::*;
18
19 #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
20 #[template(resource = "/org/gnome/Fractal/ui/identity_verification_view/cancelled_page.ui")]
21 #[properties(wrapper_type = super::CancelledPage)]
22 pub struct CancelledPage {
23 #[property(get, set = Self::set_verification, explicit_notify, nullable)]
25 pub verification: BoundObjectWeakRef<IdentityVerification>,
26 pub display_name_handler: RefCell<Option<glib::SignalHandlerId>>,
27 #[template_child]
28 pub title: TemplateChild<gtk::Label>,
29 #[template_child]
30 pub message: TemplateChild<gtk::Label>,
31 #[template_child]
32 pub try_again_btn: TemplateChild<LoadingButton>,
33 }
34
35 #[glib::object_subclass]
36 impl ObjectSubclass for CancelledPage {
37 const NAME: &'static str = "IdentityVerificationCancelledPage";
38 type Type = super::CancelledPage;
39 type ParentType = adw::Bin;
40
41 fn class_init(klass: &mut Self::Class) {
42 Self::bind_template(klass);
43 Self::Type::bind_template_callbacks(klass);
44 }
45
46 fn instance_init(obj: &InitializingObject<Self>) {
47 obj.init_template();
48 }
49 }
50
51 #[glib::derived_properties]
52 impl ObjectImpl for CancelledPage {
53 fn dispose(&self) {
54 if let Some(verification) = self.verification.obj()
55 && let Some(handler) = self.display_name_handler.take()
56 {
57 verification.user().disconnect(handler);
58 }
59 }
60 }
61
62 impl WidgetImpl for CancelledPage {
63 fn grab_focus(&self) -> bool {
64 self.try_again_btn.grab_focus()
65 }
66 }
67
68 impl BinImpl for CancelledPage {}
69
70 impl CancelledPage {
71 fn set_verification(&self, verification: Option<IdentityVerification>) {
73 let prev_verification = self.verification.obj();
74
75 if prev_verification == verification {
76 return;
77 }
78 let obj = self.obj();
79
80 if let Some(verification) = prev_verification
81 && let Some(handler) = self.display_name_handler.take()
82 {
83 verification.user().disconnect(handler);
84 }
85 self.verification.disconnect_signals();
86
87 if let Some(verification) = verification {
88 let display_name_handler = verification.user().connect_display_name_notify(clone!(
89 #[weak]
90 obj,
91 move |_| {
92 obj.update_message();
93 }
94 ));
95 self.display_name_handler
96 .replace(Some(display_name_handler));
97
98 let cancel_info_changed_handler = verification.connect_cancel_info_changed(clone!(
99 #[weak]
100 obj,
101 move |_| {
102 obj.update_message();
103 }
104 ));
105
106 self.verification
107 .set(&verification, vec![cancel_info_changed_handler]);
108 }
109
110 obj.update_message();
111 obj.notify_verification();
112 }
113 }
114}
115
116glib::wrapper! {
117 pub struct CancelledPage(ObjectSubclass<imp::CancelledPage>)
119 @extends gtk::Widget, adw::Bin,
120 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
121}
122
123#[gtk::template_callbacks]
124impl CancelledPage {
125 pub fn new() -> Self {
126 glib::Object::new()
127 }
128
129 fn update_message(&self) {
131 let Some(verification) = self.verification() else {
132 return;
133 };
134 let cancel_info = verification.cancel_info();
135 let imp = self.imp();
136
137 let message = match cancel_info.as_ref().map(CancelInfo::cancel_code) {
138 Some(CancelCode::User) => {
139 if verification.is_self_verification() {
140 gettext("The verification was cancelled from the other session.")
141 } else {
142 let name = verification.user().display_name();
143 gettext_f(
144 "The verification was cancelled by {user}.",
147 &[("user", &format!("<b>{name}</b>"))],
148 )
149 }
150 }
151 Some(CancelCode::Timeout) => {
152 gettext("The verification process failed because it reached a timeout.")
153 }
154 Some(CancelCode::Accepted) => gettext("You accepted the request from another session."),
155 Some(CancelCode::MismatchedSas) => {
156 if verification.sas_supports_emoji() {
157 gettext("The emoji did not match.")
158 } else {
159 gettext("The numbers did not match.")
160 }
161 }
162 _ => gettext("An unexpected error happened during the verification process."),
163 };
164 imp.message.set_markup(&message);
165
166 let title = if cancel_info.is_some() {
167 gettext("Verification Cancelled")
168 } else {
169 gettext("Verification Error")
170 };
171 imp.title.set_text(&title);
172
173 let offer_to_retry = !verification.is_self_verification() || verification.started_by_us();
176 imp.try_again_btn.set_visible(offer_to_retry);
177 }
178
179 pub fn reset(&self) {
181 self.imp().try_again_btn.set_is_loading(false);
182 self.set_sensitive(true);
183 }
184
185 #[template_callback]
187 async fn try_again(&self) {
188 let Some(verification) = self.verification() else {
189 return;
190 };
191
192 self.imp().try_again_btn.set_is_loading(true);
193 self.set_sensitive(false);
194
195 if verification.restart().await.is_err() {
196 toast!(self, gettext("Could not send a new verification request"));
197 self.reset();
198 }
199 }
200
201 #[template_callback]
203 fn dismiss(&self) {
204 let Some(verification) = self.verification() else {
205 return;
206 };
207 verification.dismiss();
208 }
209}