Reset Cargo.lock.

Added ability to leave rooms, join rooms.
General restyling.
This commit is contained in:
Werner Kroneman 2023-12-08 20:54:07 +01:00
parent 4da80010ff
commit fd56718960
3 changed files with 1108 additions and 115 deletions

961
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,10 @@ edition = "2021"
tokio = "1.34.0" tokio = "1.34.0"
log = "0.4.20" log = "0.4.20"
env_logger = "0.10.1" env_logger = "0.10.1"
dioxus = "0.4.0" dioxus = "0.4.3"
dioxus-desktop = "0.4.0" dioxus-desktop = "0.4.3"
dioxus-hot-reload = { version="0.4.3", features=["file_watcher"] }
futures-util = "0.3.29" futures-util = "0.3.29"
xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", rev="05a0b6cd8f870ff2fe9b773d7eefb8205b45f4b9"} xmpp = { git = "https://gitlab.com/werner.kroneman/xmpp-rs.git", rev="ecd0be4aad985e9812626d6c4499c2586c158aba"}
keyring = "2.1.0"
jid = "0.10.0"

View File

@ -5,15 +5,18 @@ use std::future::Future;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use dioxus::html::textarea; use dioxus::html::{hr, input, textarea};
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types // import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::Config; use dioxus_desktop::Config;
use dioxus_elements::{h1, li, ul}; use dioxus_elements::{h1, li, ul};
use keyring::Entry;
use log::info; use log::info;
use tokio::select; use tokio::select;
use xmpp::{BareJid, ClientBuilder, ClientType, RoomNick}; use xmpp::{BareJid, ClientBuilder, ClientType, FullJid, RoomNick};
use xmpp::parsers::message::MessageType; use xmpp::parsers::message::MessageType;
use env_logger::init;
use jid::ResourcePart;
const STYLESHEET: &str = r#" const STYLESHEET: &str = r#"
body { body {
@ -37,28 +40,57 @@ const STYLESHEET: &str = r#"
fn main() { fn main() {
env_logger::init(); env_logger::init();
let entry = keyring::Entry::new("dergchat", "dergchat").expect("Failed to create keyring entry");
entry.set_password("topS3cr3tP4$$w0rd").expect("Failed to set password");
let password = entry.get_password().expect("Failed to get password");
println!("My password is '{}'", password);
entry.delete_password().expect("Failed to delete password");
hot_reload_init!();
// launch the dioxus app in a webview // launch the dioxus app in a webview
dioxus_desktop::launch_cfg(App, dioxus_desktop::launch_cfg(App,
Config::default() Config::default()
.with_custom_head(format!(r#"<style>{}</style>"#, STYLESHEET)) .with_custom_head(format!(r#"<style>{}</style>"#, STYLESHEET))
.with_background_color((32,64,32,255))); .with_background_color((32, 64, 32, 255)));
} }
#[derive(Props)] #[derive(Props)]
struct RoomListProps<'a> { struct RoomListProps<'a> {
rooms: Vec<BareJid>, rooms: Vec<BareJid>,
on_room_picked: EventHandler<'a, BareJid>, on_room_picked: EventHandler<'a, BareJid>,
on_room_left: EventHandler<'a, BareJid>,
} }
/// A Dioxus component that renders a list of rooms /// A Dioxus component that renders a list of rooms
fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> {
render! { render! {
ul { ul {
list_style: "none",
flex_grow: 1,
margin: 0,
padding: 0,
for room in cx.props.rooms.iter() { for room in cx.props.rooms.iter() {
rsx! { li { rsx! { li {
// onclick: move |_| cx.props.room_selected.call(room.to_owned()), display: "flex",
flex_direction: "row",
onclick: |evt| cx.props.on_room_picked.call(room.to_owned()), onclick: |evt| cx.props.on_room_picked.call(room.to_owned()),
div {
flex_grow: 1,
"{room}" "{room}"
}
button {
onclick: |evt| {
evt.stop_propagation();
cx.props.on_room_left.call(room.to_owned());
},
"X"
}
} } } }
} }
} }
@ -78,21 +110,54 @@ struct RoomViewProps<'a> {
on_message_sent: EventHandler<'a, String>, on_message_sent: EventHandler<'a, String>,
} }
#[derive(Props)]
struct SendMessageProps<'a> {
on_message_sent: EventHandler<'a, String>,
}
fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> { fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> {
let message = use_state(cx, || "".to_owned()); let message = use_state(cx, || "".to_owned());
render! { render! {
div {
padding: "5mm",
flex_grow: 1,
display: "flex",
background_color: "#166322",
flex_direction: "column",
h2 {
margin: 0,
padding: 0,
border_bottom: "1px solid lightgray",
"{cx.props.room}"
}
ul { ul {
flex_grow: 1,
overflow_y: "scroll",
for message in cx.props.messages.iter() { for message in cx.props.messages.iter() {
rsx! { li { rsx! { li {
"{message.sender}: {message.body}" "{message.sender}: {message.body}"
} } } }
} }
} }
SendMessage {
on_message_sent: |x:String| cx.props.on_message_sent.call(x),
}
}
}
}
fn SendMessage<'a>(cx: Scope<'a, SendMessageProps>) -> Element<'a> {
let message = use_state(cx, || "".to_owned());
render! {
div { div {
display: "flex", display: "flex",
flex_direction: "row", flex_direction: "row",
textarea { textarea {
resize: "none",
flex_grow: 1, flex_grow: 1,
oninput: |evt| { oninput: |evt| {
message.set(evt.value.clone()); message.set(evt.value.clone());
@ -109,23 +174,33 @@ fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> {
} }
} }
async fn handle_event(event: xmpp::Event, async fn handle_event(event: xmpp::Event,
agent: &mut xmpp::Agent, agent: &mut xmpp::Agent,
rooms: &mut UseState<Vec<BareJid>>, rooms: &mut UseState<Vec<BareJid>>,
messages: &mut UseState<HashMap<BareJid, Vec<Message>>>) { messages: &mut UseState<HashMap<BareJid, Vec<Message>>>) {
match event { match event {
xmpp::Event::JoinRoom(jid, conference) => { xmpp::Event::JoinRoom(jid, conference) => {
println!("Joining room: {}", &jid); println!("Will auto-join room: {}", &jid);
rooms.make_mut().push(jid.clone());
agent.join_room(jid, None, None, "en/us", "online").await; agent.join_room(jid, None, None, "en/us", "online").await;
}, }
xmpp::Event::RoomJoined(room_jid) => {
println!("Joined room: {}", &room_jid);
messages.make_mut().entry(room_jid.clone()).or_insert(vec![]);
rooms.make_mut().push(room_jid);
}
xmpp::Event::RoomLeft(room_jid) => {
println!("Left room: {}", &room_jid);
messages.make_mut().remove(&room_jid);
rooms.make_mut().retain(|x| x != &room_jid);
}
xmpp::Event::ChatMessage(id, sender, body) => { xmpp::Event::ChatMessage(id, sender, body) => {
println!("Message from {}: {}", &sender, &body.0); println!("Message from {}: {}", &sender, &body.0);
messages.make_mut().entry(sender.clone()).or_insert(vec![]).push(Message { messages.make_mut().entry(sender.clone()).or_insert(vec![]).push(Message {
sender: sender.to_string(), sender: sender.to_string(),
body: body.0, body: body.0,
}); });
}, }
xmpp::Event::RoomMessage(id, room_jid, sender_nick, body) => { xmpp::Event::RoomMessage(id, room_jid, sender_nick, body) => {
println!("Message in {} from {}: {}", &room_jid, &sender_nick, &body.0); println!("Message in {} from {}: {}", &room_jid, &sender_nick, &body.0);
messages.make_mut().entry(room_jid).or_insert(vec![]).push(Message { messages.make_mut().entry(room_jid).or_insert(vec![]).push(Message {
@ -139,18 +214,46 @@ async fn handle_event(event: xmpp::Event,
sender: sender_nick, sender: sender_nick,
body: body.0, body: body.0,
}); });
}, }
_ => { _ => {
log::debug!("Received unsupported event {:?}", event); log::debug!("Received unsupported event {:?}", event);
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct SendMessage { enum NetworkCommand {
recipient: BareJid, JoinRoom { room: BareJid },
message: String, LeaveRoom { room: BareJid },
SendMessage { recipient: BareJid, message: String },
}
#[derive(Props)]
struct RoomJoinProps<'a> {
on_join_room: EventHandler<'a, String>,
}
fn RoomJoinWidget<'a>(cx: Scope<'a, RoomJoinProps>) -> Element<'a> {
let room = use_state(cx, || "".to_owned());
render! {
div {
display: "flex",
flex_direction: "row",
input {
flex_grow: 1,
display: "block",
oninput: |evt| {
room.set(evt.value.clone());
}
}
button {
onclick: move |_| cx.props.on_join_room.call(room.current().to_string()),
"Join"
}
}
}
} }
// define a component that renders a div with the text "Hello, world!" // define a component that renders a div with the text "Hello, world!"
@ -161,7 +264,7 @@ fn App(cx: Scope) -> Element {
let current_room = use_state(cx, || None::<BareJid>); let current_room = use_state(cx, || None::<BareJid>);
let cr = use_coroutine(cx, let cr = use_coroutine(cx,
|rx: UnboundedReceiver<SendMessage>| run_xmpp(rooms.clone(), messages.clone(), rx) |rx: UnboundedReceiver<NetworkCommand>| run_xmpp(rooms.clone(), messages.clone(), rx),
); );
render! { render! {
@ -173,18 +276,33 @@ fn App(cx: Scope) -> Element {
height: "100%", height: "100%",
div { div {
padding: "5mm",
background_color: "#1d852d", background_color: "#1d852d",
display: "flex",
flex_direction: "column",
div {
border_bottom: "1px solid lightgray",
"Mizah"
}
RoomList { RoomList {
rooms: rooms.to_vec(), rooms: rooms.to_vec(),
on_room_picked: move |x:BareJid| { on_room_picked: move |x:BareJid| {
println!("Room selected: {:?}", x); println!("Room selected: {:?}", x);
current_room.set(Some(x)); current_room.set(Some(x));
}, },
on_room_left: move |x:BareJid| {
println!("Leaving room: {:?}", x);
cr.send(NetworkCommand::LeaveRoom { room: x });
},
}
RoomJoinWidget {
on_join_room: move |x:String| {
println!("Joining room: {:?}", x);
cr.send(NetworkCommand::JoinRoom { room: BareJid::from_str(&x).unwrap() });
},
} }
} }
div {
flex_grow: 1,
background_color: "#166322",
if let Some(room) = current_room.get() { if let Some(room) = current_room.get() {
let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec(); let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec();
@ -194,20 +312,23 @@ fn App(cx: Scope) -> Element {
messages: messages, messages: messages,
on_message_sent: move |x:String| { on_message_sent: move |x:String| {
println!("Message sent: {:?}", x); println!("Message sent: {:?}", x);
cr.send(SendMessage { cr.send(NetworkCommand::SendMessage { recipient: room.clone(), message: x });
recipient: room.clone(),
message: x,
});
}, },
} }
} }
} else { } else {
rsx! { rsx! {
div { div {
"No room selected" padding: "5mm",
} flex_grow: 1,
display: "flex",
background_color: "#166322",
"No room selected. Pick one from the list on the left."
} }
} }
} }
} }
} }
@ -215,12 +336,13 @@ fn App(cx: Scope) -> Element {
fn run_xmpp(mut rooms: UseState<Vec<BareJid>>, fn run_xmpp(mut rooms: UseState<Vec<BareJid>>,
mut messages: UseState<HashMap<BareJid, Vec<Message>>>, mut messages: UseState<HashMap<BareJid, Vec<Message>>>,
mut commands: UnboundedReceiver<SendMessage>) -> impl Future<Output=()> + Sized { mut commands: UnboundedReceiver<NetworkCommand>) -> impl Future<Output=()> + Sized {
async move { async move {
let jid = BareJid::from_str("bot@mizah.xyz").unwrap(); let jid = BareJid::from_str("bot@mizah.xyz").unwrap();
let password = "G0cEiYp98G8nBzOdkKMF"; let password = "TLOwnPNPDGN9nfRRqPwh";
let mut agent = ClientBuilder::new(jid, &password) let mut agent = ClientBuilder::new(jid, &password)
.set_client(ClientType::Pc, "dergchat") .set_client(ClientType::Pc, "dergchat")
@ -240,8 +362,21 @@ fn run_xmpp(mut rooms: UseState<Vec<BareJid>>,
}, },
command = commands.next() => { command = commands.next() => {
if let Some(command) = command { if let Some(command) = command {
println!("Sending command: {:?}", command); match command {
agent.send_message(command.recipient.into(), MessageType::Groupchat, "en-us", &command.message).await; NetworkCommand::JoinRoom { room } => {
agent.join_room(room.clone(), None, None, "en-us", "online").await;
},
NetworkCommand::LeaveRoom { room } => {
agent.leave_room(
room,
"dergchat".to_string(),
"en-us",
"User left the room.").await;
},
NetworkCommand::SendMessage { recipient, message } => {
agent.send_message(recipient.into(), MessageType::Groupchat, "en-us", &message).await;
},
}
} else { } else {
info!("Command channel closed"); info!("Command channel closed");
break; break;