fractal/identity_verification_view/
cancelled_page.rs1use adw::subclass::prelude::*;
2use gettextrs::gettext;
3use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
4use matrix_sdk::crypto::CancelInfo;
5use ruma::events::key::verification::cancel::CancelCode;
6
7use crate::{
8 components::LoadingButton, gettext_f, prelude::*, session::model::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, 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 if let Some(handler) = self.display_name_handler.take() {
56 verification.user().disconnect(handler);
57 }
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 if let Some(handler) = self.display_name_handler.take() {
82 verification.user().disconnect(handler);
83 }
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, @implements gtk::Accessible;
120}
121
122#[gtk::template_callbacks]
123impl CancelledPage {
124 pub fn new() -> Self {
125 glib::Object::new()
126 }
127
128 fn update_message(&self) {
130 let Some(verification) = self.verification() else {
131 return;
132 };
133 let cancel_info = verification.cancel_info();
134 let imp = self.imp();
135
136 let message = match cancel_info.as_ref().map(CancelInfo::cancel_code) {
137 Some(CancelCode::User) => {
138 if verification.is_self_verification() {
139 gettext("The verification was cancelled from the other session.")
140 } else {
141 let name = verification.user().display_name();
142 gettext_f(
143 "The verification was cancelled by {user}.",
146 &[("user", &format!("<b>{name}</b>"))],
147 )
148 }
149 }
150 Some(CancelCode::Timeout) => {
151 gettext("The verification process failed because it reached a timeout.")
152 }
153 Some(CancelCode::Accepted) => gettext("You accepted the request from another session."),
154 Some(CancelCode::MismatchedSas) => {
155 if verification.sas_supports_emoji() {
156 gettext("The emoji did not match.")
157 } else {
158 gettext("The numbers did not match.")
159 }
160 }
161 _ => gettext("An unexpected error happened during the verification process."),
162 };
163 imp.message.set_markup(&message);
164
165 let title = if cancel_info.is_some() {
166 gettext("Verification Cancelled")
167 } else {
168 gettext("Verification Error")
169 };
170 imp.title.set_text(&title);
171
172 let offer_to_retry = !verification.is_self_verification() || verification.started_by_us();
175 imp.try_again_btn.set_visible(offer_to_retry);
176 }
177
178 pub fn reset(&self) {
180 self.imp().try_again_btn.set_is_loading(false);
181 self.set_sensitive(true);
182 }
183
184 #[template_callback]
186 async fn try_again(&self) {
187 let Some(verification) = self.verification() else {
188 return;
189 };
190
191 self.imp().try_again_btn.set_is_loading(true);
192 self.set_sensitive(false);
193
194 if verification.restart().await.is_err() {
195 toast!(self, gettext("Could not send a new verification request"));
196 self.reset();
197 }
198 }
199
200 #[template_callback]
202 fn dismiss(&self) {
203 let Some(verification) = self.verification() else {
204 return;
205 };
206 verification.dismiss();
207 }
208}