fractal/identity_verification_view/
cancelled_page.rs

1use 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        /// The current identity verification.
24        #[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        /// Set the current identity verification.
72        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    /// A page to show when the verification was cancelled.
118    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    /// Update the labels for the current verification.
130    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                        // Translators: Do NOT translate the content between '{' and '}', this is a
145                        // variable name.
146                        "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        // If the verification was started by one of our other devices, let it offer to
174        // try again.
175        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    /// Reset the UI to its initial state.
180    pub fn reset(&self) {
181        self.imp().try_again_btn.set_is_loading(false);
182        self.set_sensitive(true);
183    }
184
185    /// Send a new request to replace the verification.
186    #[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    /// Dismiss the verification.
202    #[template_callback]
203    fn dismiss(&self) {
204        let Some(verification) = self.verification() else {
205            return;
206        };
207        verification.dismiss();
208    }
209}