Skip to main content

fractal/login/
method_page.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::glib;
4use ruma::OwnedServerName;
5use tracing::warn;
6use url::Url;
7
8use super::Login;
9use crate::{components::LoadingButton, gettext_f, prelude::*, spawn_tokio, toast};
10
11mod imp {
12    use glib::subclass::InitializingObject;
13
14    use super::*;
15
16    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
17    #[template(resource = "/org/gnome/Fractal/ui/login/method_page.ui")]
18    #[properties(wrapper_type = super::LoginMethodPage)]
19    pub struct LoginMethodPage {
20        #[template_child]
21        title: TemplateChild<gtk::Label>,
22        #[template_child]
23        homeserver_url: TemplateChild<gtk::Label>,
24        #[template_child]
25        username_entry: TemplateChild<adw::EntryRow>,
26        #[template_child]
27        password_entry: TemplateChild<adw::PasswordEntryRow>,
28        #[template_child]
29        sso_button: TemplateChild<gtk::Button>,
30        #[template_child]
31        next_button: TemplateChild<LoadingButton>,
32        /// The parent `Login` object.
33        #[property(get, set, nullable)]
34        login: glib::WeakRef<Login>,
35    }
36
37    #[glib::object_subclass]
38    impl ObjectSubclass for LoginMethodPage {
39        const NAME: &'static str = "LoginMethodPage";
40        type Type = super::LoginMethodPage;
41        type ParentType = adw::NavigationPage;
42
43        fn class_init(klass: &mut Self::Class) {
44            Self::bind_template(klass);
45            Self::bind_template_callbacks(klass);
46        }
47
48        fn instance_init(obj: &InitializingObject<Self>) {
49            obj.init_template();
50        }
51    }
52
53    #[glib::derived_properties]
54    impl ObjectImpl for LoginMethodPage {}
55
56    impl WidgetImpl for LoginMethodPage {
57        fn grab_focus(&self) -> bool {
58            self.username_entry.grab_focus()
59        }
60    }
61
62    impl NavigationPageImpl for LoginMethodPage {
63        fn shown(&self) {
64            self.grab_focus();
65        }
66    }
67
68    #[gtk::template_callbacks]
69    impl LoginMethodPage {
70        /// The username entered by the user.
71        fn username(&self) -> glib::GString {
72            self.username_entry.text()
73        }
74
75        /// The password entered by the user.
76        fn password(&self) -> glib::GString {
77            self.password_entry.text()
78        }
79
80        /// Update the domain name and URL displayed in the title.
81        pub(super) fn update_title(
82            &self,
83            homeserver_url: &Url,
84            server_name: Option<&OwnedServerName>,
85        ) {
86            let title = if let Some(server_name) = server_name {
87                gettext_f(
88                    // Translators: Do NOT translate the content between '{' and '}', this is a
89                    // variable name.
90                    "Log in to {domain_name}",
91                    &[(
92                        "domain_name",
93                        &format!("<span segment=\"word\">{server_name}</span>"),
94                    )],
95                )
96            } else {
97                gettext("Log in")
98            };
99            self.title.set_markup(&title);
100
101            let homeserver_url = homeserver_url.as_str().trim_end_matches('/');
102            self.homeserver_url.set_label(homeserver_url);
103        }
104
105        /// Update the SSO group.
106        pub(super) fn update_sso(&self, supports_sso: bool) {
107            self.sso_button.set_visible(supports_sso);
108        }
109
110        /// Whether the current state allows to login with a password.
111        fn can_login_with_password(&self) -> bool {
112            let username_length = self.username().len();
113            let password_length = self.password().len();
114            username_length != 0 && password_length != 0
115        }
116
117        /// Update the state of the "Next" button.
118        #[template_callback]
119        pub(super) fn update_next_state(&self) {
120            self.next_button
121                .set_sensitive(self.can_login_with_password());
122        }
123
124        /// Login with the password login type.
125        #[template_callback]
126        async fn login_with_password(&self) {
127            if !self.can_login_with_password() {
128                return;
129            }
130
131            let Some(login) = self.login.upgrade() else {
132                return;
133            };
134
135            self.next_button.set_is_loading(true);
136            login.freeze();
137
138            let username = self.username();
139            let password = self.password();
140
141            let client = login.client().await.unwrap();
142            let handle = spawn_tokio!(async move {
143                client
144                    .matrix_auth()
145                    .login_username(&username, &password)
146                    .initial_device_display_name("Fractal")
147                    .send()
148                    .await
149            });
150
151            match handle.await.expect("task was not aborted") {
152                Ok(_) => {
153                    login.create_session().await;
154                }
155                Err(error) => {
156                    warn!("Could not log in: {error}");
157                    toast!(self.obj(), error.to_user_facing());
158                }
159            }
160
161            self.next_button.set_is_loading(false);
162            login.unfreeze();
163        }
164
165        /// Reset this page.
166        pub(super) fn clean(&self) {
167            self.username_entry.set_text("");
168            self.password_entry.set_text("");
169            self.next_button.set_is_loading(false);
170            self.update_next_state();
171        }
172    }
173}
174
175glib::wrapper! {
176    /// The login page allowing to login via password or to choose a SSO provider.
177    pub struct LoginMethodPage(ObjectSubclass<imp::LoginMethodPage>)
178        @extends gtk::Widget, adw::NavigationPage,
179        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
180}
181
182impl LoginMethodPage {
183    /// The tag for this page.
184    pub(super) const TAG: &str = "method";
185
186    pub fn new() -> Self {
187        glib::Object::new()
188    }
189
190    /// Update this page with the given data.
191    pub(crate) fn update(
192        &self,
193        homeserver_url: &Url,
194        domain_name: Option<&OwnedServerName>,
195        supports_sso: bool,
196    ) {
197        let imp = self.imp();
198        imp.update_title(homeserver_url, domain_name);
199        imp.update_sso(supports_sso);
200        imp.update_next_state();
201    }
202
203    /// Reset this page.
204    pub(crate) fn clean(&self) {
205        self.imp().clean();
206    }
207}