summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-04-30 02:51:02 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-04-30 02:51:02 +0100
commit8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f (patch)
tree09797c1d2da7f4840141639e154cae2b6901b314
parent4ea506818e85de5453e661552a0fd7dffda38d6e (diff)
downloadmacaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.tar.gz
macaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.tar.bz2
macaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.zip
feat: chat list is more pretty
-rw-r--r--src/lib.rs111
1 files changed, 99 insertions, 12 deletions
diff --git a/src/lib.rs b/src/lib.rs
index b0cada7..2b64970 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,12 +12,7 @@ use std::{
};
use filamento::{
- chat::{Chat, Message},
- db::Db,
- error::{CommandError, ConnectionError, DatabaseError},
- files::FilesMem,
- user::User,
- UpdateMessage,
+ chat::{Chat, Message}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, roster::Contact, user::User, UpdateMessage
};
use futures::stream::StreamExt;
use indexmap::IndexMap;
@@ -34,6 +29,8 @@ use tokio::sync::{mpsc::{self, Receiver}, Mutex};
use tracing::debug;
use uuid::Uuid;
+const NO_AVATAR: &str = "/assets/no-avatar.png";
+
pub enum AppState {
LoggedOut,
LoggedIn,
@@ -305,6 +302,10 @@ fn Macaw(
) -> impl IntoView {
provide_context(client);
+ // TODO: roster as store
+ let (roster, set_roster) = signal(HashMap::<JID, MacawContact>::new());
+ provide_context(roster);
+
let message_subscriptions = RwSignal::new(MessageSubscriptions::new());
provide_context(message_subscriptions);
@@ -315,10 +316,14 @@ fn Macaw(
let users_store: StateStore<JID, Store<User>> = StateStore::new();
provide_context(users_store);
+ // TODO: get cached contacts on login before getting the updated contacts
+
OnceResource::new(async move {
while let Some(update) = updates.recv().await {
match update {
- UpdateMessage::Online(online, items) => {},
+ UpdateMessage::Online(online, items) => {
+
+ },
UpdateMessage::Offline(offline) => {},
UpdateMessage::RosterUpdate(contact, user) => {},
UpdateMessage::RosterDelete(jid) => {},
@@ -332,8 +337,12 @@ fn Macaw(
},
UpdateMessage::MessageDelivery { id, chat, delivery } => {},
UpdateMessage::SubscriptionRequest(jid) => {},
- UpdateMessage::NickChanged { jid, nick } => {},
- UpdateMessage::AvatarChanged { jid, id } => {},
+ UpdateMessage::NickChanged { jid, nick } => {
+ users_store.modify(&jid, |user| user.update(|user| *&mut user.nick = nick.clone()));
+ },
+ UpdateMessage::AvatarChanged { jid, id } => {
+ users_store.modify(&jid, |user| *&mut user.write().avatar = id.clone());
+ },
}
}
});
@@ -613,6 +622,37 @@ impl DerefMut for MacawUser {
}
}
+struct MacawContact {
+ contact: Store<Contact>,
+ user: StateListener<JID, Store<User>>,
+}
+
+impl MacawContact {
+ fn got_contact_and_user(contact: Contact, user: User) -> Self {
+ let contact = Store::new(contact);
+ let user_state_store: StateStore<JID, Store<User>> = use_context().expect("no user state store");
+ let user = user_state_store.store(user.jid.clone(), Store::new(user));
+ Self {
+ contact,
+ user,
+ }
+ }
+}
+
+impl Deref for MacawContact {
+ type Target = Store<Contact>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.contact
+ }
+}
+
+impl DerefMut for MacawContact {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.contact
+ }
+}
+
#[component]
fn ChatsList() -> impl IntoView {
let (chats, set_chats) = signal(IndexMap::new());
@@ -643,6 +683,7 @@ fn ChatsList() -> impl IntoView {
let mut chats = set_chats.write();
if let Some((chat, _latest_message)) = chats.shift_remove(&to) {
debug!("chat existed");
+ debug!("new message: {}", new_message.read().body.body);
chats.insert_before(0, to, (chat.clone(), new_message));
debug!("done setting");
} else {
@@ -663,11 +704,57 @@ fn ChatsList() -> impl IntoView {
view! {
<div class="chats-list panel">
<h2>Chats</h2>
- <div>
- <For each=move || chats.get() key=|chat| chat.0.clone() let(chat)>
- <p>{chat.0.to_string()}</p>
+ <div class="chats-list-chats">
+ <For each=move || chats.get() key=|chat| chat.1.1.message.read().id let(chat)>
+ <ChatsListItem chat=chat.1.0 message=chat.1.1 />
</For>
</div>
</div>
}
}
+
+pub fn get_avatar(user: Store<User>) -> String {
+ if let Some(avatar) = &user.read().avatar {
+ NO_AVATAR.to_string()
+ // TODO: enable avatar fetching
+ // format!("/files/{}", avatar)
+ } else {
+ NO_AVATAR.to_string()
+ }
+}
+
+pub fn get_name(user: Store<User>) -> String {
+ let roster: ReadSignal<HashMap<JID, MacawContact>> = use_context().expect("no roster in context");
+ if let Some(name) = roster
+ .read()
+ .get(&user.read().jid)
+ .map(|contact| contact.read().name.clone())
+ .unwrap_or_default()
+ {
+ name.to_string()
+ } else if let Some(nick) = &user.read().nick {
+ nick.to_string()
+ } else {
+ user.read().jid.to_string()
+ }
+}
+
+#[component]
+fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView {
+ let chat_user = *chat.user;
+ let avatar = move || get_avatar(chat_user);
+ let name = move || get_name(chat_user);
+
+ // TODO: store fine-grained reactivity
+ let latest_message_body = move || message.get().body.body;
+
+ view! {
+ <div class="chats-list-item">
+ <img class="avatar" src=avatar />
+ <div class="item-info">
+ <h3>{name}</h3>
+ <p>{latest_message_body}</p>
+ </div>
+ </div>
+ }
+}