diff --git a/Cargo.lock b/Cargo.lock index a5b0bd9..ae3b689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,21 @@ dependencies = [ "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]] name = "anyhow" version = "1.0.75" @@ -455,6 +470,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "chatclient" version = "0.1.0" dependencies = [ + "chrono", "confy", "dioxus", "dioxus-desktop", @@ -475,7 +491,12 @@ version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-targets 0.48.5", ] [[package]] @@ -1748,6 +1769,29 @@ dependencies = [ "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]] name = "ident_case" version = "1.0.1" @@ -4301,6 +4345,15 @@ dependencies = [ "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]] name = "windows-implement" version = "0.44.0" @@ -4625,8 +4678,9 @@ dependencies = [ [[package]] name = "xmpp" 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 = [ + "chrono", "futures", "log", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index bdaf8f7..5cdb17f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,10 @@ env_logger = "0.10.1" dioxus="0.4.3" dioxus-desktop="0.4.3" 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" jid = { version = "0.10.0", features = ["serde"] } confy = "0.5.1" serde = "1.0.193" -serde_derive = "1.0" \ No newline at end of file +serde_derive = "1.0" +chrono="0.4.31" \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 779686d..7d74505 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,6 +17,7 @@ use crate::passwords::Password; use jid::BareJid; use std::fmt::Debug; +use chrono::{DateTime, FixedOffset}; #[derive(Debug, Clone, PartialEq)] pub struct Room { @@ -27,6 +28,7 @@ pub struct Room { pub struct Message { pub sender: String, pub body: String, + pub timestamp: DateTime } #[derive(Debug)] diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 4e27d00..ad938ee 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -24,6 +24,7 @@ use crate::{ no_room_open::NoRoomPlaceholder, room_view::RoomView, sidebar::SideBar, + room_list::RoomMeta, }, xmpp_interface, xmpp_interface::NetworkCommand, @@ -83,7 +84,12 @@ pub fn App(cx: Scope) -> Element { // Sidebar. SideBar { 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| { current_room.set(Some(x.clone())); coroutine.send(NetworkCommand::JoinRoom { room: x }); diff --git a/src/widgets/room_list.rs b/src/widgets/room_list.rs index d42e5a0..4aef489 100644 --- a/src/widgets/room_list.rs +++ b/src/widgets/room_list.rs @@ -14,20 +14,24 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use std::time::Instant; +use chrono::{DateTime, FixedOffset}; use dioxus::core::{Element, Scope}; use dioxus::core_macro::Props; +use dioxus::html::button; use dioxus::prelude::*; use jid::BareJid; -/// The props for the room list widget, including: -/// * The list of rooms to show. -/// * The event handler for when the user clicks a room. -/// * The event handler for when the user clicks the "leave" button for a room. -#[derive(Props)] -pub struct RoomListProps<'a> { - rooms: Vec, // TODO: Should this be a reference of some kind? - on_room_picked: EventHandler<'a, BareJid>, - on_room_left: EventHandler<'a, BareJid>, +#[derive(Clone, Eq, PartialEq)] +pub struct RoomMeta { + pub room: BareJid, + pub last_update_time: Option> +} + +#[derive(Clone, Eq, PartialEq, Copy)] +pub enum SortMethod { + Alphabetical, + Recency, } /// 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. /// /// 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, // 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! { + + 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
    with a list of
  • elements, each containing a room name and a button. ul { list_style: "none", @@ -51,12 +88,12 @@ pub fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { 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.room.clone()), // The room name inside a
    . div { flex_grow: 1, - "{room}" + "{room.room}" } // A button to leave the room. @@ -64,7 +101,7 @@ pub fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { onclick: |evt| { evt.stop_propagation(); - cx.props.on_room_left.call(room.to_owned()); + cx.props.on_room_left.call(room.room.clone()); }, "X" } diff --git a/src/widgets/sidebar.rs b/src/widgets/sidebar.rs index ee8b274..bee431d 100644 --- a/src/widgets/sidebar.rs +++ b/src/widgets/sidebar.rs @@ -15,7 +15,7 @@ // along with this program. If not, see . 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_macro::Props; use dioxus::html::text; @@ -26,7 +26,7 @@ use jid::BareJid; #[component] pub fn SideBar<'a>(cx: Scope<'a, SideBarProps>, current_user: BareJid, - rooms: Vec, + rooms: Vec, on_room_picked: EventHandler<'a, BareJid>, on_room_left: EventHandler<'a, BareJid>, on_join_room: EventHandler<'a, BareJid>, diff --git a/src/xmpp_interface.rs b/src/xmpp_interface.rs index ebfd7e9..90ffade 100644 --- a/src/xmpp_interface.rs +++ b/src/xmpp_interface.rs @@ -21,6 +21,7 @@ use jid::BareJid; use log::{error, info}; use std::collections::HashMap; use std::future::Future; +use std::time::Instant; use crate::widgets::login_screen::LoginStatus; use tokio::select; @@ -69,16 +70,17 @@ async fn handle_event( m.remove(&room_jid); }); } - xmpp::Event::ChatMessage(_id, sender, body) => { + xmpp::Event::ChatMessage(_id, sender, body, timestamp) => { println!("Message from {}: {}", &sender, &body.0); messages.with_mut(move |m| { m.entry(sender.clone()).or_insert(vec![]).push(Message { sender: sender.to_string(), body: body.0, + timestamp, }); }); } - xmpp::Event::RoomMessage(_id, room_jid, sender_nick, body) => { + xmpp::Event::RoomMessage(_id, room_jid, sender_nick, body, timestamp) => { println!( "Message in {} from {}: {}", &room_jid, &sender_nick, &body.0 @@ -87,10 +89,11 @@ async fn handle_event( m.entry(room_jid.clone()).or_insert(vec![]).push(Message { sender: sender_nick, body: body.0, + timestamp, }) }); } - xmpp::Event::RoomPrivateMessage(_id, room_jid, sender_nick, body) => { + xmpp::Event::RoomPrivateMessage(_id, room_jid, sender_nick, body, timestamp) => { println!( "Private message in {} from {}: {}", &room_jid, &sender_nick, &body.0 @@ -99,6 +102,7 @@ async fn handle_event( m.entry(room_jid.clone()).or_insert(vec![]).push(Message { sender: sender_nick, body: body.0, + timestamp, }) }); }