fractal/login/
method_page.rs1use 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 #[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 fn username(&self) -> glib::GString {
72 self.username_entry.text()
73 }
74
75 fn password(&self) -> glib::GString {
77 self.password_entry.text()
78 }
79
80 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 "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 pub(super) fn update_sso(&self, supports_sso: bool) {
107 self.sso_button.set_visible(supports_sso);
108 }
109
110 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 #[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 #[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 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 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 pub(super) const TAG: &str = "method";
185
186 pub fn new() -> Self {
187 glib::Object::new()
188 }
189
190 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 pub(crate) fn clean(&self) {
205 self.imp().clean();
206 }
207}