fractal/session/view/content/room_history/
verification_info_bar.rs

1use adw::subclass::prelude::*;
2use gettextrs::gettext;
3use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
4
5use crate::{
6    components::LoadingButton,
7    gettext_f,
8    prelude::*,
9    session::model::{IdentityVerification, VerificationState},
10    toast,
11    utils::BoundObjectWeakRef,
12    Window,
13};
14
15mod imp {
16    use std::cell::RefCell;
17
18    use glib::subclass::InitializingObject;
19
20    use super::*;
21
22    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
23    #[template(
24        resource = "/org/gnome/Fractal/ui/session/view/content/room_history/verification_info_bar.ui"
25    )]
26    #[properties(wrapper_type = super::VerificationInfoBar)]
27    pub struct VerificationInfoBar {
28        #[template_child]
29        revealer: TemplateChild<gtk::Revealer>,
30        #[template_child]
31        label: TemplateChild<gtk::Label>,
32        #[template_child]
33        accept_btn: TemplateChild<LoadingButton>,
34        #[template_child]
35        cancel_btn: TemplateChild<LoadingButton>,
36        /// The identity verification presented by this info bar.
37        #[property(get, set = Self::set_verification, explicit_notify)]
38        verification: BoundObjectWeakRef<IdentityVerification>,
39        user_handler: RefCell<Option<glib::SignalHandlerId>>,
40    }
41
42    #[glib::object_subclass]
43    impl ObjectSubclass for VerificationInfoBar {
44        const NAME: &'static str = "ContentVerificationInfoBar";
45        type Type = super::VerificationInfoBar;
46        type ParentType = adw::Bin;
47
48        fn class_init(klass: &mut Self::Class) {
49            Self::bind_template(klass);
50
51            klass.set_css_name("infobar");
52            klass.set_accessible_role(gtk::AccessibleRole::Group);
53
54            klass.install_action_async("verification.accept", None, |obj, _, _| async move {
55                let Some(window) = obj.root().and_downcast::<Window>() else {
56                    return;
57                };
58                let Some(verification) = obj.verification() else {
59                    return;
60                };
61                let imp = obj.imp();
62
63                if verification.state() == VerificationState::Requested {
64                    imp.accept_btn.set_is_loading(true);
65
66                    if verification.accept().await.is_err() {
67                        toast!(obj, gettext("Could not accept verification"));
68                        imp.accept_btn.set_is_loading(false);
69                        return;
70                    }
71                }
72
73                window
74                    .session_view()
75                    .select_identity_verification(verification);
76                imp.accept_btn.set_is_loading(false);
77            });
78
79            klass.install_action_async("verification.decline", None, |obj, _, _| async move {
80                let Some(verification) = obj.verification() else {
81                    return;
82                };
83                let imp = obj.imp();
84
85                imp.cancel_btn.set_is_loading(true);
86
87                if verification.cancel().await.is_err() {
88                    toast!(obj, gettext("Could not decline verification"));
89                }
90
91                imp.cancel_btn.set_is_loading(false);
92            });
93        }
94
95        fn instance_init(obj: &InitializingObject<Self>) {
96            obj.init_template();
97        }
98    }
99
100    #[glib::derived_properties]
101    impl ObjectImpl for VerificationInfoBar {
102        fn dispose(&self) {
103            if let Some(verification) = self.verification.obj() {
104                if let Some(handler) = self.user_handler.take() {
105                    verification.user().disconnect(handler);
106                }
107            }
108        }
109    }
110
111    impl WidgetImpl for VerificationInfoBar {}
112    impl BinImpl for VerificationInfoBar {}
113
114    impl VerificationInfoBar {
115        /// Set the identity verification presented by this info bar.
116        fn set_verification(&self, verification: Option<&IdentityVerification>) {
117            let prev_verification = self.verification.obj();
118
119            if prev_verification.as_ref() == verification {
120                return;
121            }
122
123            if let Some(verification) = prev_verification {
124                if let Some(handler) = self.user_handler.take() {
125                    verification.user().disconnect(handler);
126                }
127            }
128            self.verification.disconnect_signals();
129
130            if let Some(verification) = verification {
131                let user_handler = verification.user().connect_display_name_notify(clone!(
132                    #[weak(rename_to = imp)]
133                    self,
134                    move |_| {
135                        imp.update_bar();
136                    }
137                ));
138                self.user_handler.replace(Some(user_handler));
139
140                let state_handler = verification.connect_state_notify(clone!(
141                    #[weak(rename_to = imp)]
142                    self,
143                    move |_| {
144                        imp.update_bar();
145                    }
146                ));
147
148                self.verification.set(verification, vec![state_handler]);
149            }
150
151            self.update_bar();
152            self.obj().notify_verification();
153        }
154
155        /// Update the bar for the current verification state.
156        fn update_bar(&self) {
157            let Some(verification) = self.verification.obj().filter(|v| !v.is_finished()) else {
158                self.revealer.set_reveal_child(false);
159                return;
160            };
161
162            if matches!(verification.state(), VerificationState::Requested) {
163                self.label.set_markup(&gettext_f(
164                    // Translators: Do NOT translate the content between '{' and '}', this is a
165                    // variable name.
166                    "{user_name} wants to be verified",
167                    &[(
168                        "user_name",
169                        &format!("<b>{}</b>", verification.user().display_name()),
170                    )],
171                ));
172                self.accept_btn.set_label(&gettext("Verify"));
173                self.cancel_btn.set_label(&gettext("Decline"));
174            } else {
175                self.label.set_label(&gettext("Verification in progress"));
176                self.accept_btn.set_label(&gettext("Continue"));
177                self.cancel_btn.set_label(&gettext("Cancel"));
178            }
179
180            self.revealer.set_reveal_child(true);
181        }
182    }
183}
184
185glib::wrapper! {
186    /// An info bar presenting an ongoing identity verification.
187    pub struct VerificationInfoBar(ObjectSubclass<imp::VerificationInfoBar>)
188        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
189}
190
191impl VerificationInfoBar {
192    pub fn new() -> Self {
193        glib::Object::new()
194    }
195}