Add message timestamps and room sorting capability

This commit is contained in:
Werner Kroneman 2023-12-11 14:15:01 +01:00
parent d11daee5df
commit 53ad01ed41
7 changed files with 126 additions and 22 deletions

56
Cargo.lock generated
View File

@ -38,6 +38,21 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.75" version = "1.0.75"
@ -455,6 +470,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "chatclient" name = "chatclient"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"confy", "confy",
"dioxus", "dioxus",
"dioxus-desktop", "dioxus-desktop",
@ -475,7 +491,12 @@ version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [ dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"wasm-bindgen",
"windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -1748,6 +1769,29 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -4301,6 +4345,15 @@ dependencies = [
"windows-tokens", "windows-tokens",
] ]
[[package]]
name = "windows-core"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-implement" name = "windows-implement"
version = "0.44.0" version = "0.44.0"
@ -4625,8 +4678,9 @@ dependencies = [
[[package]] [[package]]
name = "xmpp" name = "xmpp"
version = "0.5.0" version = "0.5.0"
source = "git+https://gitlab.com/xmpp-rs/xmpp-rs.git?rev=2b433d7036209f35ff7daf033a7b7692e58ab6c3#2b433d7036209f35ff7daf033a7b7692e58ab6c3" source = "git+https://gitlab.com/werner.kroneman/xmpp-rs.git?rev=b75e0047837c33eb0cd9c405c859e46be4f4bc09#b75e0047837c33eb0cd9c405c859e46be4f4bc09"
dependencies = [ dependencies = [
"chrono",
"futures", "futures",
"log", "log",
"reqwest", "reqwest",

View File

@ -28,9 +28,10 @@ env_logger = "0.10.1"
dioxus="0.4.3" dioxus="0.4.3"
dioxus-desktop="0.4.3" dioxus-desktop="0.4.3"
futures-util = "0.3.29" futures-util = "0.3.29"
xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", rev="2b433d7036209f35ff7daf033a7b7692e58ab6c3" } xmpp = { git = "https://gitlab.com/werner.kroneman/xmpp-rs.git", rev="b75e0047837c33eb0cd9c405c859e46be4f4bc09" }
keyring = "2.1.0" keyring = "2.1.0"
jid = { version = "0.10.0", features = ["serde"] } jid = { version = "0.10.0", features = ["serde"] }
confy = "0.5.1" confy = "0.5.1"
serde = "1.0.193" serde = "1.0.193"
serde_derive = "1.0" serde_derive = "1.0"
chrono="0.4.31"

View File

@ -17,6 +17,7 @@
use crate::passwords::Password; use crate::passwords::Password;
use jid::BareJid; use jid::BareJid;
use std::fmt::Debug; use std::fmt::Debug;
use chrono::{DateTime, FixedOffset};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Room { pub struct Room {
@ -27,6 +28,7 @@ pub struct Room {
pub struct Message { pub struct Message {
pub sender: String, pub sender: String,
pub body: String, pub body: String,
pub timestamp: DateTime<FixedOffset>
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -24,6 +24,7 @@ use crate::{
no_room_open::NoRoomPlaceholder, no_room_open::NoRoomPlaceholder,
room_view::RoomView, room_view::RoomView,
sidebar::SideBar, sidebar::SideBar,
room_list::RoomMeta,
}, },
xmpp_interface, xmpp_interface,
xmpp_interface::NetworkCommand, xmpp_interface::NetworkCommand,
@ -83,7 +84,12 @@ pub fn App(cx: Scope) -> Element {
// Sidebar. // Sidebar.
SideBar { SideBar {
current_user: user.clone(), current_user: user.clone(),
rooms: messages.read().keys().cloned().collect(), rooms: messages.read().iter().map(|(k, v)| {
RoomMeta {
room: k.clone(),
last_update_time: v.last().map(|x| x.timestamp),
}
}).collect(),
on_room_picked: move |x: BareJid| { on_room_picked: move |x: BareJid| {
current_room.set(Some(x.clone())); current_room.set(Some(x.clone()));
coroutine.send(NetworkCommand::JoinRoom { room: x }); coroutine.send(NetworkCommand::JoinRoom { room: x });

View File

@ -14,20 +14,24 @@
// 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 std::time::Instant;
use chrono::{DateTime, FixedOffset};
use dioxus::core::{Element, Scope}; use dioxus::core::{Element, Scope};
use dioxus::core_macro::Props; use dioxus::core_macro::Props;
use dioxus::html::button;
use dioxus::prelude::*; use dioxus::prelude::*;
use jid::BareJid; use jid::BareJid;
/// The props for the room list widget, including: #[derive(Clone, Eq, PartialEq)]
/// * The list of rooms to show. pub struct RoomMeta {
/// * The event handler for when the user clicks a room. pub room: BareJid,
/// * The event handler for when the user clicks the "leave" button for a room. pub last_update_time: Option<DateTime<FixedOffset>>
#[derive(Props)] }
pub struct RoomListProps<'a> {
rooms: Vec<BareJid>, // TODO: Should this be a reference of some kind? #[derive(Clone, Eq, PartialEq, Copy)]
on_room_picked: EventHandler<'a, BareJid>, pub enum SortMethod {
on_room_left: EventHandler<'a, BareJid>, Alphabetical,
Recency,
} }
/// A widget that shows a list of rooms known to the client. /// A widget that shows a list of rooms known to the client.
@ -36,8 +40,41 @@ pub struct RoomListProps<'a> {
/// select a room or to leave it. /// select a room or to leave it.
/// ///
/// How "selecting" and "leaving" a room is handled is up to the context. /// How "selecting" and "leaving" a room is handled is up to the context.
pub fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { #[component]
pub fn RoomList<'a>(cx: Scope<'a>,
rooms: Vec<RoomMeta>, // TODO: Should this be a reference of some kind?
on_room_picked: EventHandler<'a, BareJid>,
on_room_left: EventHandler<'a, BareJid>) -> Element<'a> {
let sort_method = use_state(cx, || SortMethod::Recency);
let sorted_rooms = use_memo(cx, (rooms,sort_method), |(mut rooms, sort_method)| {
match *sort_method.current() {
SortMethod::Alphabetical => rooms.sort_by_key(|room| room.room.to_string()),
SortMethod::Recency => rooms.sort_by_key(|room| room.last_update_time),
}
rooms
});
render! { render! {
button {
onclick: move |_| {
match *sort_method.current() {
SortMethod::Alphabetical => sort_method.set(SortMethod::Recency),
SortMethod::Recency => sort_method.set(SortMethod::Alphabetical),
}
},
"Sort by: ",
match *sort_method.current() {
SortMethod::Alphabetical => "alphabetical",
SortMethod::Recency => "recency",
}
}
// An <ul> with a list of <li> elements, each containing a room name and a button. // An <ul> with a list of <li> elements, each containing a room name and a button.
ul { ul {
list_style: "none", list_style: "none",
@ -51,12 +88,12 @@ pub fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> {
display: "flex", display: "flex",
flex_direction: "row", flex_direction: "row",
onclick: |_evt| cx.props.on_room_picked.call(room.to_owned()), onclick: |_evt| cx.props.on_room_picked.call(room.room.clone()),
// The room name inside a <div>. // The room name inside a <div>.
div { div {
flex_grow: 1, flex_grow: 1,
"{room}" "{room.room}"
} }
// A button to leave the room. // A button to leave the room.
@ -64,7 +101,7 @@ pub fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> {
onclick: |evt| { onclick: |evt| {
evt.stop_propagation(); evt.stop_propagation();
cx.props.on_room_left.call(room.to_owned()); cx.props.on_room_left.call(room.room.clone());
}, },
"X" "X"
} }

View File

@ -15,7 +15,7 @@
// 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::widgets::room_join_widget::RoomJoinWidget; use crate::widgets::room_join_widget::RoomJoinWidget;
use crate::widgets::room_list::RoomList; use crate::widgets::room_list::{RoomList, RoomMeta};
use dioxus::core::{Element, Scope}; use dioxus::core::{Element, Scope};
use dioxus::core_macro::Props; use dioxus::core_macro::Props;
use dioxus::html::text; use dioxus::html::text;
@ -26,7 +26,7 @@ use jid::BareJid;
#[component] #[component]
pub fn SideBar<'a>(cx: Scope<'a, SideBarProps>, pub fn SideBar<'a>(cx: Scope<'a, SideBarProps>,
current_user: BareJid, current_user: BareJid,
rooms: Vec<BareJid>, rooms: Vec<RoomMeta>,
on_room_picked: EventHandler<'a, BareJid>, on_room_picked: EventHandler<'a, BareJid>,
on_room_left: EventHandler<'a, BareJid>, on_room_left: EventHandler<'a, BareJid>,
on_join_room: EventHandler<'a, BareJid>, on_join_room: EventHandler<'a, BareJid>,

View File

@ -21,6 +21,7 @@ use jid::BareJid;
use log::{error, info}; use log::{error, info};
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;
use std::time::Instant;
use crate::widgets::login_screen::LoginStatus; use crate::widgets::login_screen::LoginStatus;
use tokio::select; use tokio::select;
@ -69,16 +70,17 @@ async fn handle_event(
m.remove(&room_jid); m.remove(&room_jid);
}); });
} }
xmpp::Event::ChatMessage(_id, sender, body) => { xmpp::Event::ChatMessage(_id, sender, body, timestamp) => {
println!("Message from {}: {}", &sender, &body.0); println!("Message from {}: {}", &sender, &body.0);
messages.with_mut(move |m| { messages.with_mut(move |m| {
m.entry(sender.clone()).or_insert(vec![]).push(Message { m.entry(sender.clone()).or_insert(vec![]).push(Message {
sender: sender.to_string(), sender: sender.to_string(),
body: body.0, body: body.0,
timestamp,
}); });
}); });
} }
xmpp::Event::RoomMessage(_id, room_jid, sender_nick, body) => { xmpp::Event::RoomMessage(_id, room_jid, sender_nick, body, timestamp) => {
println!( println!(
"Message in {} from {}: {}", "Message in {} from {}: {}",
&room_jid, &sender_nick, &body.0 &room_jid, &sender_nick, &body.0
@ -87,10 +89,11 @@ async fn handle_event(
m.entry(room_jid.clone()).or_insert(vec![]).push(Message { m.entry(room_jid.clone()).or_insert(vec![]).push(Message {
sender: sender_nick, sender: sender_nick,
body: body.0, body: body.0,
timestamp,
}) })
}); });
} }
xmpp::Event::RoomPrivateMessage(_id, room_jid, sender_nick, body) => { xmpp::Event::RoomPrivateMessage(_id, room_jid, sender_nick, body, timestamp) => {
println!( println!(
"Private message in {} from {}: {}", "Private message in {} from {}: {}",
&room_jid, &sender_nick, &body.0 &room_jid, &sender_nick, &body.0
@ -99,6 +102,7 @@ async fn handle_event(
m.entry(room_jid.clone()).or_insert(vec![]).push(Message { m.entry(room_jid.clone()).or_insert(vec![]).push(Message {
sender: sender_nick, sender: sender_nick,
body: body.0, body: body.0,
timestamp,
}) })
}); });
} }