Implement a login page

That way we connect only after the UI is created.
This commit is contained in:
Emmanuel Gil Peyrot 2024-06-04 18:00:38 +02:00
parent 690e7113ab
commit 5824275f06
5 changed files with 213 additions and 86 deletions

View File

@ -40,38 +40,44 @@ fn xep_0392(input: &str) -> String {
format!("#{:02x}{:02x}{:02x}", r, g, b)
}
fn on_login_pressed(win: &window::MainWindow) {
let jid = win.jid().text();
let password = win.password().text();
println!("jid={jid:?} password={password:?}");
if !jid.is_empty() && !password.is_empty() {
win.stack().set_visible_child(win.spinner());
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&jid, &password);
let win2 = win.clone();
glib::spawn_future_local(async move {
while let Ok(event) = xmpp_receiver.recv().await {
match event {
xmpp::Event::Online => {
win2.stack().set_visible_child(win2.split_view());
}
xmpp::Event::ContactAdded(jid) => {
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
win2.tabs_store().append(&tab);
}
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
// TODO: Insert message into tab history
continue;
}
_ => continue,
}
}
});
}
}
fn main() {
env_logger::init();
let mut args = std::env::args();
let _ = args.next().unwrap();
let username = args.next().expect("Please give username argument 1");
let password = args.next().expect("Please give password argument 2");
let app = adw::Application::builder()
.application_id("fr.linkmauve.XmppClient")
.build();
let tabs_store = gio::ListStore::new::<Tab>();
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
let tabs_store_copy = tabs_store.clone();
glib::spawn_future_local(async move {
while let Ok(event) = xmpp_receiver.recv().await {
match event {
xmpp::Event::ContactAdded(jid) => {
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
tabs_store_copy.append(&tab);
}
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
// TODO: Insert message into tab history
continue;
}
_ => continue,
}
}
});
app.connect_activate(move |app| {
let win = window::MainWindow::new(app);
@ -83,6 +89,11 @@ fn main() {
win.add_action_entries([action_close]);
app.set_accels_for_action("win.close", &["<Ctrl>Q"]);
let win2 = win.clone();
win.login().connect_clicked(move |_| {
on_login_pressed(&win2);
});
assert!(Tab::static_type().is_valid());
assert!(Message::static_type().is_valid());
@ -135,7 +146,7 @@ fn main() {
});
win.tabs().set_factory(Some(&tabs_factory));
win.tabs_selection().set_model(Some(&tabs_store));
win.tabs_selection().set_model(Some(win.tabs_store()));
let win2 = win.clone();
win.tabs_selection()
@ -156,7 +167,7 @@ fn main() {
});
let win2 = win.clone();
let cmd_sender2 = cmd_sender.clone();
//let cmd_sender2 = cmd_sender.clone();
win.entry().connect_activate(move |entry| {
let text = entry.text();
entry.set_text("");
@ -166,12 +177,14 @@ fn main() {
.selected_item()
.and_downcast()
.unwrap();
/*
cmd_sender2
.send_blocking(xmpp_client::XMPPCommand::SendPM(
xmpp::BareJid::new(&current_tab.jid()).unwrap(),
text.as_str().to_string(),
))
.unwrap();
*/
let message = Message::now(&get_own_nick(), &text);
let selection = win2.selection();
let store = selection

View File

@ -15,12 +15,33 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use adw::subclass::prelude::*;
use gtk::glib;
use gtk::{gio, glib};
/// The private struct, which can hold widgets and other data.
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "window.ui")]
pub struct MainWindow {
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
// For the login page
#[template_child]
pub jid: TemplateChild<adw::EntryRow>,
#[template_child]
pub password: TemplateChild<adw::PasswordEntryRow>,
#[template_child]
pub login: TemplateChild<gtk::Button>,
// For the spinner
#[template_child]
pub spinner: TemplateChild<gtk::Box>,
// For the chats page
#[template_child]
pub tabs_store: TemplateChild<gio::ListStore>,
#[template_child]
pub split_view: TemplateChild<adw::NavigationSplitView>,
#[template_child]

View File

@ -30,6 +30,30 @@ impl MainWindow {
glib::Object::builder().property("application", app).build()
}
pub fn stack(&self) -> &gtk::Stack {
&self.imp().stack
}
pub fn jid(&self) -> &adw::EntryRow {
&self.imp().jid
}
pub fn password(&self) -> &adw::PasswordEntryRow {
&self.imp().password
}
pub fn login(&self) -> &gtk::Button {
&self.imp().login
}
pub fn spinner(&self) -> &gtk::Box {
&self.imp().spinner
}
pub fn tabs_store(&self) -> &gio::ListStore {
&self.imp().tabs_store
}
pub fn split_view(&self) -> &adw::NavigationSplitView {
&self.imp().split_view
}

View File

@ -13,81 +13,147 @@
</object>
</child>
<property name="content">
<object class="AdwNavigationSplitView" id="split_view">
<property name="sidebar">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Open Chats</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="vexpand">yes</property>
<child>
<object class="GtkListView" id="tabs_list">
<property name="model">tabs_selection</property>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
<property name="content">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Messages</property>
<property name="tag">chat</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkStack" id="stack">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar"/>
</child>
<child>
<object class="AdwClamp">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">yes</property>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Enter your XMPP credentials</property>
<child>
<object class="GtkListView" id="message_list_view">
<property name="model">selection</property>
<object class="AdwEntryRow" id="jid">
<property name="title" translatable="yes">JID</property>
<property name="input-purpose">email</property>
</object>
</child>
<child>
<object class="AdwPasswordEntryRow" id="password">
<property name="title" translatable="yes">Password</property>
<property name="input-purpose">password</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="margin-start">10</property>
<property name="margin-top">10</property>
<property name="margin-end">10</property>
<property name="margin-bottom">10</property>
<child>
<object class="GtkEntry" id="entry">
<property name="hexpand">yes</property>
<property name="placeholder-text" translatable="yes">Send a message</property>
</object>
</child>
<!--
<child>
<object class="GtkButton" id="send_input">
<property name="icon-name">go-next</property>
</object>
</child>
-->
<object class="GtkButton" id="login">
<property name="label" translatable="yes">Login</property>
<property name="css-classes">suggested-action</property>
<property name="margin-top">8</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="spinner">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar"/>
</child>
<child>
<object class="GtkLabel">
<property name="label">Connecting…</property>
</object>
</child>
<child>
<object class="GtkSpinner">
<property name="spinning">yes</property>
<property name="vexpand">no</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwNavigationSplitView" id="split_view">
<property name="sidebar">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Open Chats</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="vexpand">yes</property>
<child>
<object class="GtkListView" id="tabs_list">
<property name="model">tabs_selection</property>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
<property name="content">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Messages</property>
<property name="tag">chat</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">yes</property>
<child>
<object class="GtkListView" id="message_list_view">
<property name="model">selection</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="margin-start">10</property>
<property name="margin-top">10</property>
<property name="margin-end">10</property>
<property name="margin-bottom">10</property>
<child>
<object class="GtkEntry" id="entry">
<property name="hexpand">yes</property>
<property name="placeholder-text" translatable="yes">Send a message</property>
<property name="input-purpose">free-form</property>
</object>
</child>
<!--
<child>
<object class="GtkButton" id="send_input">
<property name="icon-name">go-next</property>
</object>
</child>
-->
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</child>
</object>
</property>
</template>
<object class="GListStore" id="tabs_store"/>
<object class="GtkSingleSelection" id="tabs_selection"/>
<object class="GtkNoSelection" id="selection"/>
</interface>

View File

@ -32,6 +32,9 @@ pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sende
Some(events) = client.wait_for_events() => {
for event in events {
match event {
Event::Online => {
event_sender.send(event).await.expect("BOOOOOOHOOOO");
}
Event::ContactAdded(_) => {
event_sender.send(event).await.expect("BOOOOOOHOOOO");
}
@ -45,8 +48,8 @@ pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sende
}
}
command = cmd_receiver.recv() => {
match command.unwrap() {
Ok(command) = cmd_receiver.recv() => {
match command {
XMPPCommand::SendPM(jid, content) => {
client.send_message(jid.into(), xmpp::parsers::message::MessageType::Chat, "en", &content).await;
},