mirror of
https://gitea.mizah.xyz/mizah/dergchat
synced 2024-11-27 01:22:16 -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
|
// 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/>.
|
// 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::LoginAttempt;
|
||||||
use crate::widgets::login_screen::{LoginScreen, LoginStatus};
|
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::widgets::room_view::RoomView;
|
||||||
use crate::xmpp_interface::xmpp_mainloop;
|
use crate::widgets::sidebar::SideBar;
|
||||||
use crate::xmpp_interface::NetworkCommand;
|
use crate::xmpp_interface::NetworkCommand;
|
||||||
use dioxus::core::{Element, Scope};
|
use dioxus::core::{Element, Scope};
|
||||||
|
|
||||||
@ -32,71 +30,16 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use xmpp::{Agent, ClientBuilder, ClientType};
|
|
||||||
|
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use crate::xmpp_interface;
|
||||||
|
|
||||||
mod login_screen;
|
pub mod login_screen;
|
||||||
mod room_join_widget;
|
pub mod room_join_widget;
|
||||||
mod room_list;
|
pub mod room_list;
|
||||||
mod room_view;
|
pub mod room_view;
|
||||||
mod send_message;
|
pub mod send_message;
|
||||||
|
pub mod sidebar;
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
@ -110,7 +53,7 @@ pub fn App(cx: Scope) -> Element {
|
|||||||
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
|
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
|
||||||
|
|
||||||
let coroutine = use_coroutine(cx, |rx: UnboundedReceiver<NetworkCommand>| {
|
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) {
|
let config = use_ref(cx, || match confy::load("dergchat", None) {
|
||||||
@ -204,45 +147,24 @@ pub fn App(cx: Scope) -> Element {
|
|||||||
rsx!{
|
rsx!{
|
||||||
|
|
||||||
// Sidebar.
|
// Sidebar.
|
||||||
div {
|
SideBar {
|
||||||
padding: "5mm",
|
|
||||||
background_color: "#1d852d",
|
|
||||||
display: "flex",
|
|
||||||
flex_direction: "column",
|
|
||||||
|
|
||||||
div {
|
|
||||||
border_bottom: "1px solid lightgray",
|
|
||||||
"Mizah"
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomList {
|
|
||||||
rooms: messages.read().keys().cloned().collect(),
|
rooms: messages.read().keys().cloned().collect(),
|
||||||
on_room_picked: move |x: BareJid| {
|
on_room_picked: move |x: BareJid| {
|
||||||
current_room.set(Some(x));
|
current_room.set(Some(x.clone()));
|
||||||
|
coroutine.send(NetworkCommand::JoinRoom { room: x });
|
||||||
},
|
},
|
||||||
on_room_left: move |x: BareJid| {
|
on_room_left: move |x: BareJid| {
|
||||||
|
current_room.set(None);
|
||||||
coroutine.send(NetworkCommand::LeaveRoom { room: x });
|
coroutine.send(NetworkCommand::LeaveRoom { room: x });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomJoinWidget {
|
// The current room.
|
||||||
on_join_room: move |x: String| {
|
|
||||||
coroutine.send(NetworkCommand::JoinRoom {
|
|
||||||
room: BareJid::from_str(&x).expect("Invalid JID"),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Room view.
|
|
||||||
|
|
||||||
if let Some(room) = current_room.get() {
|
if let Some(room) = current_room.get() {
|
||||||
let messages = messages.read().get(&room).expect("Selected non-existant room").to_vec();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
RoomView {
|
RoomView {
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
messages: messages,
|
messages: messages.read().get(&room).expect("Selected non-existant room").to_vec(),
|
||||||
on_message_sent: move |x:String| {
|
on_message_sent: move |x:String| {
|
||||||
println!("Message sent: {:?}", x);
|
println!("Message sent: {:?}", x);
|
||||||
coroutine.send(NetworkCommand::SendMessage { recipient: room.clone(), message: x });
|
coroutine.send(NetworkCommand::SendMessage { recipient: room.clone(), message: x });
|
||||||
@ -251,8 +173,15 @@ pub fn App(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No room selected
|
// No room selected
|
||||||
|
NoRoomPlaceholder {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rsx! {
|
fn NoRoomPlaceholder(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
div {
|
div {
|
||||||
padding: "5mm",
|
padding: "5mm",
|
||||||
flex_grow: 1,
|
flex_grow: 1,
|
||||||
@ -261,13 +190,4 @@ pub fn App(cx: Scope) -> Element {
|
|||||||
"No room selected. Pick one from the list on the left."
|
"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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::types::{LoginCredentials, Message};
|
use crate::types::{LoginCredentials, Message};
|
||||||
use dioxus::hooks::{UnboundedReceiver, UseRef};
|
use dioxus::hooks::{UnboundedReceiver, UseRef, UseState};
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use jid::BareJid;
|
use jid::BareJid;
|
||||||
use log::info;
|
use log::{error, info};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use xmpp::parsers::message::MessageType;
|
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(
|
async fn handle_event(
|
||||||
event: xmpp::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>(
|
pub fn xmpp_mainloop<'a>(
|
||||||
agent: &'a mut Agent,
|
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