mirror of
https://gitea.mizah.xyz/mizah/dergchat
synced 2024-11-23 07:32:15 -05:00
Refactored the mod.rs, specifically focusing on getting the Sidebar out and some xmpp bits.
This commit is contained in:
parent
1ddf72d8a4
commit
4c857f2fba
@ -14,13 +14,11 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::{LoginCredentials, Message};
|
||||
use crate::types::LoginCredentials;
|
||||
use crate::widgets::login_screen::LoginAttempt;
|
||||
use crate::widgets::login_screen::{LoginScreen, LoginStatus};
|
||||
use crate::widgets::room_join_widget::RoomJoinWidget;
|
||||
use crate::widgets::room_list::RoomList;
|
||||
use crate::widgets::room_view::RoomView;
|
||||
use crate::xmpp_interface::xmpp_mainloop;
|
||||
use crate::widgets::sidebar::SideBar;
|
||||
use crate::xmpp_interface::NetworkCommand;
|
||||
use dioxus::core::{Element, Scope};
|
||||
|
||||
@ -32,71 +30,16 @@ use std::collections::HashMap;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::string::String;
|
||||
use xmpp::{Agent, ClientBuilder, ClientType};
|
||||
|
||||
use keyring::Entry;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::xmpp_interface;
|
||||
|
||||
mod login_screen;
|
||||
mod room_join_widget;
|
||||
mod room_list;
|
||||
mod room_view;
|
||||
mod send_message;
|
||||
|
||||
async fn await_online(agent: &mut Agent) -> Result<(), String> {
|
||||
let events = agent
|
||||
.wait_for_events()
|
||||
.await
|
||||
.ok_or("Stream closed unexpectedly".to_string())?;
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
|
||||
match events.into_iter().next().expect("No events") {
|
||||
xmpp::Event::Online => {
|
||||
info!("Online");
|
||||
Ok(())
|
||||
}
|
||||
xmpp::Event::Disconnected => {
|
||||
error!("Disconnected");
|
||||
Err("Disconnected".to_string())
|
||||
}
|
||||
e => return Err(format!("Unexpected event: {:?}", e)),
|
||||
}
|
||||
}
|
||||
pub async fn run_xmpp_toplevel(
|
||||
connection_status: UseState<LoginStatus>,
|
||||
mut messages: UseRef<HashMap<BareJid, Vec<Message>>>,
|
||||
mut commands: UnboundedReceiver<NetworkCommand>,
|
||||
) {
|
||||
// Await a login attempt:
|
||||
|
||||
let cmd = commands.next().await;
|
||||
|
||||
if let Some(NetworkCommand::TryLogin { credentials }) = cmd {
|
||||
println!("Received credentials: {:?}", credentials);
|
||||
|
||||
connection_status.set(LoginStatus::LoggingIn);
|
||||
|
||||
let mut agent = ClientBuilder::new(credentials.username, &*credentials.password)
|
||||
.set_client(ClientType::Pc, "dergchat")
|
||||
.build();
|
||||
|
||||
match await_online(&mut agent).await {
|
||||
Ok(_) => {
|
||||
connection_status.set(LoginStatus::LoggedIn);
|
||||
info!("Connected");
|
||||
|
||||
xmpp_mainloop(&mut agent, &mut messages, &mut commands).await;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to connect: {}", e);
|
||||
connection_status.set(LoginStatus::Error("Failed to connect".to_string()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Expected TryLogin command");
|
||||
}
|
||||
}
|
||||
pub mod login_screen;
|
||||
pub mod room_join_widget;
|
||||
pub mod room_list;
|
||||
pub mod room_view;
|
||||
pub mod send_message;
|
||||
pub mod sidebar;
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct Configuration {
|
||||
@ -110,7 +53,7 @@ pub fn App(cx: Scope) -> Element {
|
||||
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
|
||||
|
||||
let coroutine = use_coroutine(cx, |rx: UnboundedReceiver<NetworkCommand>| {
|
||||
run_xmpp_toplevel(connection_status.to_owned(), messages.to_owned(), rx)
|
||||
xmpp_interface::run_xmpp_toplevel(connection_status.to_owned(), messages.to_owned(), rx)
|
||||
});
|
||||
|
||||
let config = use_ref(cx, || match confy::load("dergchat", None) {
|
||||
@ -204,45 +147,24 @@ pub fn App(cx: Scope) -> Element {
|
||||
rsx!{
|
||||
|
||||
// Sidebar.
|
||||
div {
|
||||
padding: "5mm",
|
||||
background_color: "#1d852d",
|
||||
display: "flex",
|
||||
flex_direction: "column",
|
||||
|
||||
div {
|
||||
border_bottom: "1px solid lightgray",
|
||||
"Mizah"
|
||||
}
|
||||
|
||||
RoomList {
|
||||
rooms: messages.read().keys().cloned().collect(),
|
||||
on_room_picked: move |x: BareJid| {
|
||||
current_room.set(Some(x));
|
||||
},
|
||||
on_room_left: move |x: BareJid| {
|
||||
coroutine.send(NetworkCommand::LeaveRoom { room: x });
|
||||
},
|
||||
}
|
||||
|
||||
RoomJoinWidget {
|
||||
on_join_room: move |x: String| {
|
||||
coroutine.send(NetworkCommand::JoinRoom {
|
||||
room: BareJid::from_str(&x).expect("Invalid JID"),
|
||||
});
|
||||
},
|
||||
}
|
||||
SideBar {
|
||||
rooms: messages.read().keys().cloned().collect(),
|
||||
on_room_picked: move |x: BareJid| {
|
||||
current_room.set(Some(x.clone()));
|
||||
coroutine.send(NetworkCommand::JoinRoom { room: x });
|
||||
},
|
||||
on_room_left: move |x: BareJid| {
|
||||
current_room.set(None);
|
||||
coroutine.send(NetworkCommand::LeaveRoom { room: x });
|
||||
},
|
||||
}
|
||||
|
||||
// Room view.
|
||||
|
||||
// The current room.
|
||||
if let Some(room) = current_room.get() {
|
||||
let messages = messages.read().get(&room).expect("Selected non-existant room").to_vec();
|
||||
|
||||
rsx! {
|
||||
RoomView {
|
||||
room: room.clone(),
|
||||
messages: messages,
|
||||
messages: messages.read().get(&room).expect("Selected non-existant room").to_vec(),
|
||||
on_message_sent: move |x:String| {
|
||||
println!("Message sent: {:?}", x);
|
||||
coroutine.send(NetworkCommand::SendMessage { recipient: room.clone(), message: x });
|
||||
@ -251,23 +173,21 @@ pub fn App(cx: Scope) -> Element {
|
||||
}
|
||||
} else {
|
||||
// No room selected
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
padding: "5mm",
|
||||
flex_grow: 1,
|
||||
display: "flex",
|
||||
background_color: "#166322",
|
||||
"No room selected. Pick one from the list on the left."
|
||||
}
|
||||
}
|
||||
|
||||
NoRoomPlaceholder {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn NoRoomPlaceholder(cx: Scope) -> Element {
|
||||
render! {
|
||||
div {
|
||||
padding: "5mm",
|
||||
flex_grow: 1,
|
||||
display: "flex",
|
||||
background_color: "#166322",
|
||||
"No room selected. Pick one from the list on the left."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
src/widgets/sidebar.rs
Normal file
61
src/widgets/sidebar.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// Dergchat, a free XMPP client.
|
||||
// Copyright (C) 2023 Werner Kroneman
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use dioxus::core::{Element, Scope};
|
||||
use dioxus::core_macro::Props;
|
||||
use jid::BareJid;
|
||||
use dioxus::prelude::*;
|
||||
use crate::widgets::room_list::RoomList;
|
||||
use crate::widgets::room_join_widget::RoomJoinWidget;
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct SideBarProps {
|
||||
pub rooms: Vec<BareJid>,
|
||||
pub on_room_picked: EventHandler<'static, BareJid>,
|
||||
pub on_room_left: EventHandler<'static, BareJid>,
|
||||
}
|
||||
|
||||
/// A widget that combines the RoomList, RoomJoinWidget, and current user info into a sidebar.
|
||||
pub fn SideBar(cx: Scope<SideBarProps>) -> Element {
|
||||
render! {
|
||||
div {
|
||||
padding: "5mm",
|
||||
background_color: "#1d852d",
|
||||
display: "flex",
|
||||
flex_direction: "column",
|
||||
|
||||
// The name of the current user (TODO: make this actually reflect the current user).
|
||||
div {
|
||||
border_bottom: "1px solid lightgray",
|
||||
"Mizah"
|
||||
}
|
||||
|
||||
// The list of rooms.
|
||||
RoomList {
|
||||
rooms: cx.props.rooms.clone(),
|
||||
on_room_picked: cx.props.on_room_picked.clone(),
|
||||
on_room_left: cx.props.on_room_left.clone(),
|
||||
}
|
||||
|
||||
// The widget to join a room.
|
||||
RoomJoinWidget {
|
||||
on_join_room: |x: String| {
|
||||
println!("Joining room: {:?}", x);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,16 +15,37 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::{LoginCredentials, Message};
|
||||
use dioxus::hooks::{UnboundedReceiver, UseRef};
|
||||
use dioxus::hooks::{UnboundedReceiver, UseRef, UseState};
|
||||
use futures_util::stream::StreamExt;
|
||||
use jid::BareJid;
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
|
||||
use tokio::select;
|
||||
use xmpp::parsers::message::MessageType;
|
||||
use xmpp::Agent;
|
||||
use xmpp::{Agent, ClientBuilder, ClientType};
|
||||
use crate::widgets::login_screen::LoginStatus;
|
||||
|
||||
/// An enum of commands that can be sent to the XMPP interface.
|
||||
///
|
||||
/// These very loosely correspond to XMPP stanzas, but are more high level.
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkCommand {
|
||||
|
||||
/// Start a new login attempt, resulting in either a successful login or an error.
|
||||
TryLogin { credentials: LoginCredentials },
|
||||
|
||||
/// Join a room.
|
||||
JoinRoom { room: BareJid },
|
||||
|
||||
/// Leave a room.
|
||||
LeaveRoom { room: BareJid },
|
||||
|
||||
/// Send a message to a recipient.
|
||||
SendMessage { recipient: BareJid, message: String },
|
||||
|
||||
}
|
||||
|
||||
async fn handle_event(
|
||||
event: xmpp::Event,
|
||||
@ -87,13 +108,7 @@ async fn handle_event(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkCommand {
|
||||
TryLogin { credentials: LoginCredentials },
|
||||
JoinRoom { room: BareJid },
|
||||
LeaveRoom { room: BareJid },
|
||||
SendMessage { recipient: BareJid, message: String },
|
||||
}
|
||||
|
||||
|
||||
pub fn xmpp_mainloop<'a>(
|
||||
agent: &'a mut Agent,
|
||||
@ -141,3 +156,75 @@ pub fn xmpp_mainloop<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for the agent to go online.
|
||||
///
|
||||
/// This function mutably borrows an Agent and waits for either an Online or Disconnected event.
|
||||
///
|
||||
/// The future resolves to Ok(()) if the agent went online, or Err(String) if the agent disconnected.
|
||||
async fn await_online(agent: &mut Agent) -> Result<(), String> {
|
||||
|
||||
// Wait for the next batch of events.
|
||||
let events = agent
|
||||
.wait_for_events()
|
||||
.await
|
||||
.ok_or("Stream closed unexpectedly".to_string())?;
|
||||
|
||||
// We expect exactly one event; we operate under the assumption that xmpp-rs emits
|
||||
// exactly one events when it receives a connect/disconnect stanza from the server,
|
||||
// and that that is the first stanza it receives.
|
||||
// TODO: This is brittle; need to talk to the xmpp-rs to get stronger guarantees.
|
||||
assert_eq!(events.len(), 1);
|
||||
|
||||
// We expect the first event to be either Online or Disconnected.
|
||||
match events.into_iter().next().expect("No events") {
|
||||
xmpp::Event::Online => {
|
||||
info!("Login attempt successful; connected.");
|
||||
Ok(())
|
||||
}
|
||||
xmpp::Event::Disconnected => {
|
||||
error!("Login attempt resulted in disconnect.");
|
||||
Err("Disconnected".to_string())
|
||||
}
|
||||
// We don't expect any other events; if we get one, we panic. (The assumption we made must be wrong.)
|
||||
// TODO: This is ugly.
|
||||
e => {
|
||||
panic!("Unexpected event: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_xmpp_toplevel(
|
||||
connection_status: UseState<LoginStatus>,
|
||||
mut messages: UseRef<HashMap<BareJid, Vec<Message>>>,
|
||||
mut commands: UnboundedReceiver<NetworkCommand>,
|
||||
) {
|
||||
// Await a login attempt:
|
||||
|
||||
let cmd = commands.next().await;
|
||||
|
||||
if let Some(NetworkCommand::TryLogin { credentials }) = cmd {
|
||||
println!("Received credentials: {:?}", credentials);
|
||||
|
||||
connection_status.set(LoginStatus::LoggingIn);
|
||||
|
||||
let mut agent = ClientBuilder::new(credentials.username, &*credentials.password)
|
||||
.set_client(ClientType::Pc, "dergchat")
|
||||
.build();
|
||||
|
||||
match await_online(&mut agent).await {
|
||||
Ok(_) => {
|
||||
connection_status.set(LoginStatus::LoggedIn);
|
||||
info!("Connected");
|
||||
|
||||
xmpp_mainloop(&mut agent, &mut messages, &mut commands).await;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to connect: {}", e);
|
||||
connection_status.set(LoginStatus::Error("Failed to connect".to_string()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Expected TryLogin command");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user