diff options
| author | 2025-04-30 02:51:02 +0100 | |
|---|---|---|
| committer | 2025-04-30 02:51:02 +0100 | |
| commit | 8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f (patch) | |
| tree | 09797c1d2da7f4840141639e154cae2b6901b314 /src | |
| parent | 4ea506818e85de5453e661552a0fd7dffda38d6e (diff) | |
| download | macaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.tar.gz macaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.tar.bz2 macaw-web-8012b20aefa1b2e52cccbc132e8e96ce6bf2b81f.zip  | |
feat: chat list is more pretty
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 111 | 
1 files changed, 99 insertions, 12 deletions
@@ -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> +    } +}  | 
