fractal/identity_verification_view/
cancelled_page.rs

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