diff options
| author | 2025-06-07 17:36:22 +0100 | |
|---|---|---|
| committer | 2025-06-07 17:36:22 +0100 | |
| commit | 36e87b708ed3a2b0d6f2e932509b0fdf10fe0089 (patch) | |
| tree | 5f3fc5a3d863f52a82a204a7746532202103d7cc /src | |
| parent | 4a5aa6579f5184c443ffbe80ce93e0daa0926826 (diff) | |
| download | macaw-web-36e87b708ed3a2b0d6f2e932509b0fdf10fe0089.tar.gz macaw-web-36e87b708ed3a2b0d6f2e932509b0fdf10fe0089.tar.bz2 macaw-web-36e87b708ed3a2b0d6f2e932509b0fdf10fe0089.zip  | |
fix: avatar flashing from avatar load
Diffstat (limited to 'src')
| -rw-r--r-- | src/chat.rs | 4 | ||||
| -rw-r--r-- | src/components/avatar.rs | 9 | ||||
| -rw-r--r-- | src/components/chat_header.rs | 3 | ||||
| -rw-r--r-- | src/components/chats_list.rs | 22 | ||||
| -rw-r--r-- | src/components/chats_list/chats_list_item.rs | 3 | ||||
| -rw-r--r-- | src/components/message.rs | 3 | ||||
| -rw-r--r-- | src/components/message_history_buffer.rs | 13 | ||||
| -rw-r--r-- | src/components/new_chat.rs | 15 | ||||
| -rw-r--r-- | src/components/personal_status.rs | 12 | ||||
| -rw-r--r-- | src/components/roster_list.rs | 7 | ||||
| -rw-r--r-- | src/components/roster_list/roster_list_item.rs | 62 | ||||
| -rw-r--r-- | src/contact.rs | 4 | ||||
| -rw-r--r-- | src/message.rs | 4 | ||||
| -rw-r--r-- | src/state_store.rs | 68 | ||||
| -rw-r--r-- | src/user.rs | 45 | ||||
| -rw-r--r-- | src/views/macaw.rs | 39 | 
16 files changed, 193 insertions, 120 deletions
diff --git a/src/chat.rs b/src/chat.rs index 29e2641..f4202f6 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -59,11 +59,11 @@ pub struct ArcMacawChat {  }  impl ArcMacawChat { -    pub fn got_chat_and_user(chat: Chat, user: User) -> Self { +    pub async fn got_chat_and_user(chat: Chat, user: User) -> Self {          let chat_state_store: StateStore<BareJID, ArcStore<Chat>> =              use_context().expect("no chat state store");          let chat = chat_state_store.store(chat.correspondent.clone(), ArcStore::new(chat)); -        let user = ArcMacawUser::got_user(user); +        let user = ArcMacawUser::got_user(user).await;          Self { chat, user }      }  } diff --git a/src/components/avatar.rs b/src/components/avatar.rs index 9265ef7..292173e 100644 --- a/src/components/avatar.rs +++ b/src/components/avatar.rs @@ -2,13 +2,12 @@ use filamento::{presence::PresenceType, user::User};  use leptos::prelude::*;  use reactive_stores::Store; -use crate::{components::icon::{show_to_icon, IconComponent}, icon::Icon, user::get_avatar, user_presences::UserPresences}; +use crate::{components::icon::{show_to_icon, IconComponent}, icon::Icon, user::{get_avatar, MacawUser}, user_presences::UserPresences};  #[component] -pub fn AvatarWithPresence(user: Store<User>) -> impl IntoView { -    let avatar = LocalResource::new(move || get_avatar(user)); +pub fn AvatarWithPresence(user: MacawUser) -> impl IntoView {      let user_presences: Store<UserPresences> = use_context().expect("no user presences in context"); -    let presence = move || user_presences.write().get_user_presences(&user.read().jid).read().presence(); +    let presence = move || user_presences.write().get_user_presences(&user.get().read().jid).read().presence();      let show_icon = move || presence().map(|(_, presence)| {          match presence.presence {              PresenceType::Online(online) => if let Some(show) = online.show { @@ -22,7 +21,7 @@ pub fn AvatarWithPresence(user: Store<User>) -> impl IntoView {      view! {          <div class="avatar-with-presence"> -        <img class="avatar" src=move || avatar.get() /> +        <img class="avatar" src=move || user.avatar().get() />          {move || if let Some(icon) = show_icon() {              view!{                  <IconComponent icon=icon class:presence-show-icon=true /> diff --git a/src/components/chat_header.rs b/src/components/chat_header.rs index 51906aa..fe4e8d9 100644 --- a/src/components/chat_header.rs +++ b/src/components/chat_header.rs @@ -12,9 +12,8 @@ pub fn ChatViewHeader(chat: MacawChat) -> impl IntoView {      view! {          <div class="chat-view-header panel">              {move || { -                let user = chat.user.get().into();                  view! { -                    <AvatarWithPresence user /> +                    <AvatarWithPresence user=chat.user />                  }              }}              <div class="user-info"> diff --git a/src/components/chats_list.rs b/src/components/chats_list.rs index a5ecc9b..f958ebe 100644 --- a/src/components/chats_list.rs +++ b/src/components/chats_list.rs @@ -20,18 +20,14 @@ pub fn ChatsList() -> impl IntoView {              .map_err(|e| e.to_string());          match chats {              Ok(c) => { -                let chats = c -                    .into_iter() -                    .map(|((chat, chat_user), (message, message_user))| { -                        ( -                            chat.correspondent.clone(), -                            ( -                                ArcMacawChat::got_chat_and_user(chat, chat_user), -                                ArcMacawMessage::got_message_and_user(message, message_user), -                            ), -                        ) -                    }) -                    .collect::<IndexMap<BareJID, _>>(); +                let mut chats = IndexMap::new(); +                for ((chat, chat_user), (message, message_user)) in c { +                    chats.insert(chat.correspondent.clone(), ( +                            ArcMacawChat::got_chat_and_user(chat, chat_user).await, +                            ArcMacawMessage::got_message_and_user(message, message_user).await, +                         +                    )); +                }                  set_chats.set(chats);              }              Err(_) => { @@ -64,7 +60,7 @@ pub fn ChatsList() -> impl IntoView {                  let chat = client.get_chat(to.clone()).await.unwrap();                  let user = client.get_user(to.clone()).await.unwrap();                  debug!("before got chat"); -                let chat = ArcMacawChat::got_chat_and_user(chat, user); +                let chat = ArcMacawChat::got_chat_and_user(chat, user).await;                  debug!("after got chat");                  chats.insert_before(0, to, (chat, new_message));                  debug!("done setting"); diff --git a/src/components/chats_list/chats_list_item.rs b/src/components/chats_list/chats_list_item.rs index 98e014e..ae01288 100644 --- a/src/components/chats_list/chats_list_item.rs +++ b/src/components/chats_list/chats_list_item.rs @@ -54,9 +54,8 @@ pub fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView {      view! {          <div class="chats-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat>              {move || { -                let user = chat.user.get().into();                  view! { -                    <AvatarWithPresence user /> +                    <AvatarWithPresence user=chat.user />                  }              }}              <div class="item-info"> diff --git a/src/components/message.rs b/src/components/message.rs index f55c5f7..78ebeb6 100644 --- a/src/components/message.rs +++ b/src/components/message.rs @@ -8,7 +8,6 @@ use super::icon::Delivery;  #[component]  pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoView { -    let avatar = LocalResource::new(move || get_avatar(message.user.get().into()));      let name = move || get_name(message.user.get().into(), false);      // TODO: chrono-humanize? @@ -19,7 +18,7 @@ pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoVi              <div class:final=r#final class="chat-message major">                      <div class="left">                      <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> } > -                        <img class="avatar" src=move || avatar.get() /> +                        <img class="avatar" src=move || message.user.avatar().get() />                      </Transition>                      </div>                  <div class="middle"> diff --git a/src/components/message_history_buffer.rs b/src/components/message_history_buffer.rs index 4f4561c..dc93054 100644 --- a/src/components/message_history_buffer.rs +++ b/src/components/message_history_buffer.rs @@ -24,15 +24,10 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView {                  .map_err(|e| e.to_string());              match messages {                  Ok(m) => { -                    let messages = m -                        .into_iter() -                        .map(|(message, message_user)| { -                            ( -                                message.id, -                                ArcMacawMessage::got_message_and_user(message, message_user), -                            ) -                        }) -                        .collect::<IndexMap<Uuid, _>>(); +                    let mut messages = IndexMap::new(); +                    for (message, message_user) in m { +                        messages.insert(message.id, ArcMacawMessage::got_message_and_user(message, message_user).await); +                    }                      load_set_messages.set(messages);                  }                  Err(err) => { diff --git a/src/components/new_chat.rs b/src/components/new_chat.rs index b706ca9..2f9b943 100644 --- a/src/components/new_chat.rs +++ b/src/components/new_chat.rs @@ -6,7 +6,7 @@ use leptos::{html::Input, prelude::*};  use reactive_stores::{ArcStore, Store};  use thiserror::Error; -use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, open_chats::OpenChatsPanel, state_store::StateStore, user::{ArcMacawUser, MacawUser}}; +use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, open_chats::OpenChatsPanel, state_store::StateStore, user::{fetch_avatar, ArcMacawUser, MacawUser}};  #[derive(Clone, Debug, Error)]  pub enum NewChatError { @@ -41,7 +41,7 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView {      let chat_state_store: StateStore<BareJID, ArcStore<Chat>> =          use_context().expect("no chat state store"); -    let user_state_store: StateStore<BareJID, ArcStore<User>> = +    let user_state_store: StateStore<BareJID, (ArcStore<User>, ArcRwSignal<String>)> =          use_context().expect("no user state store");      let open_chat = Action::new_local(move |_| { @@ -77,7 +77,16 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView {              let chat = {                  // let user = MacawUser::got_user(user); -                let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); +                // let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); +                let old_user = user_state_store.get_listener(user.jid.clone()); +                let user = if let Some(old_user) = old_user { +                    old_user.update(|(old_user, _avatar)| { old_user.set(user); }); +                    old_user +                } else { +                    let avatar = fetch_avatar(user.avatar.as_deref()).await; +                    let avatar = ArcRwSignal::new(avatar); +                    user_state_store.store(user.jid.clone(), (ArcStore::new(user), avatar)) +                };                  let user = ArcMacawUser { user };                  let chat = chat_state_store.store(chat.correspondent.clone(), ArcStore::new(chat));                  ArcMacawChat { chat, user } diff --git a/src/components/personal_status.rs b/src/components/personal_status.rs index 8439756..eb7f6e1 100644 --- a/src/components/personal_status.rs +++ b/src/components/personal_status.rs @@ -16,7 +16,7 @@ pub fn PersonalStatus() -> impl IntoView {                  debug!("set open to true");                  set_open.update(|state| *state = !*state)              }> -                <AvatarWithPresence user=user.get().into() /> +                <AvatarWithPresence user />                  <div class="dock-pill"></div>              </div>              {move || { @@ -25,7 +25,7 @@ pub fn PersonalStatus() -> impl IntoView {                  if open {                  view! {                      <Overlay set_open> -                        <PersonalStatusMenu user=user.get().into() set_open/> +                        <PersonalStatusMenu user set_open/>                      </Overlay>                  }.into_any()              } else { @@ -38,7 +38,7 @@ pub fn PersonalStatus() -> impl IntoView {  }  #[component] -pub fn PersonalStatusMenu(user: Store<User>, set_open: WriteSignal<bool>) -> impl IntoView { +pub fn PersonalStatusMenu(user: MacawUser, set_open: WriteSignal<bool>) -> impl IntoView {      let set_app: WriteSignal<AppState> = use_context().unwrap();      let show_settings: RwSignal<Option<SettingsPage>> = use_context().unwrap();      let user_presences: Store<UserPresences> = use_context().expect("no user presence store"); @@ -46,7 +46,7 @@ pub fn PersonalStatusMenu(user: Store<User>, set_open: WriteSignal<bool>) -> imp      let client = use_context::<Client>().expect("client not in context");      let client1 = client.clone();      let (show_value, set_show_value) = signal({ -        let show = match user_presences.write().get_user_presences(&user.jid().read()).write().resource_presence(client.resource.read().clone().unwrap_or_default()).presence { +        let show = match user_presences.write().get_user_presences(&user.get().jid().read()).write().resource_presence(client.resource.read().clone().unwrap_or_default()).presence {          PresenceType::Online(online) => match online.show {              Some(s) => match s {                  Show::Away => 3, @@ -129,8 +129,8 @@ pub fn PersonalStatusMenu(user: Store<User>, set_open: WriteSignal<bool>) -> imp              <div class="user">                  <AvatarWithPresence user=user />                  <div class="user-info"> -                    <div class="nick">{move || get_name(user, false)}</div> -                    <div class="jid">{move || user.jid().with(|jid| jid.to_string())}</div> +                    <div class="nick">{move || get_name(user.get().into(), false)}</div> +                    <div class="jid">{move || user.get().jid().with(|jid| jid.to_string())}</div>                  </div>              </div>              <div class="status-edit"> diff --git a/src/components/roster_list.rs b/src/components/roster_list.rs index a398ffe..310b703 100644 --- a/src/components/roster_list.rs +++ b/src/components/roster_list.rs @@ -6,7 +6,7 @@ use leptos::prelude::*;  use reactive_stores::Store;  use roster_list_item::RosterListItem; -use crate::{components::icon::IconComponent, icon::Icon, roster::{Roster, RosterStoreFields}}; +use crate::{components::icon::IconComponent, icon::Icon, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, roster::{Roster, RosterStoreFields}};  mod contact_request_manager;  mod roster_list_item; @@ -15,8 +15,13 @@ mod roster_list_item;  pub fn RosterList() -> impl IntoView {      let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context"); +    let open_chats: Store<OpenChatsPanel> = +        use_context().expect("no open chats panel store in context"); +      let roster: Store<Roster> = use_context().expect("no roster in context");      let (open_add_contact, set_open_add_contact) = signal(false); +    let open_chat = Memo::new(move |_| open_chats.chat_view().get()); +    provide_context(open_chat);      // TODO: filter new messages signal      view! { diff --git a/src/components/roster_list/roster_list_item.rs b/src/components/roster_list/roster_list_item.rs index 842f66c..538e664 100644 --- a/src/components/roster_list/roster_list_item.rs +++ b/src/components/roster_list/roster_list_item.rs @@ -1,11 +1,12 @@  use std::ops::Deref;  use filamento::{chat::Chat, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}}; +use jid::BareJID;  use leptos::prelude::*;  use reactive_stores::{ArcStore, Store};  use tracing::debug; -use crate::{chat::{ArcMacawChat, MacawChat}, components::{avatar::AvatarWithPresence, sidebar::Open}, contact::MacawContact, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, user::get_name}; +use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, components::{avatar::AvatarWithPresence, sidebar::Open}, contact::MacawContact, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, state_store::StateStore, user::{fetch_avatar, get_name, ArcMacawUser}};  #[component]  pub fn RosterListItem(contact: MacawContact) -> impl IntoView { @@ -14,21 +15,51 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView {      let open_chats: Store<OpenChatsPanel> =          use_context().expect("no open chats panel store in context"); -    // TODO: why can this not be in the closure????? -    // TODO: not good, as overwrites preexisting chat state with possibly incorrect one... -    let chat = Chat { -        correspondent: contact.user.get().jid().get(), -        have_chatted: false, -    }; -    let chat = ArcMacawChat::got_chat_and_user(chat, contact.user.get().get()); +    let client = use_context::<Client>().expect("client not in context"); -    let open_chat = move |_| { -        debug!("opening chat"); -        open_chats.update(|open_chats| open_chats.open(Clone::clone(&chat))); -    }; +    let chat_state_store: StateStore<BareJID, ArcStore<Chat>> = +        use_context().expect("no chat state store"); +    let user_state_store: StateStore<BareJID, (ArcStore<User>, ArcRwSignal<String>)> = +        use_context().expect("no user state store"); + +    let open_chat = Action::new_local(move |_| { +        let client= client.clone(); +        async move { +            let to = contact.user.get().jid().get(); +            let (chat, user) = match client.get_chat_and_user(to).await { +                Ok(c) => c, +                Err(e) => { +                    // TODO: error +                    // set_error.set(Some(e.into())); +                    // set_new_chat_pending.set(false); +                    return; +                }, +            }; + +            let chat = { +                // let user = MacawUser::got_user(user); +                // let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); +                let old_user = user_state_store.get_listener(user.jid.clone()); +                let user = if let Some(old_user) = old_user { +                    old_user.update(|(old_user, _avatar)| { old_user.set(user); }); +                    old_user +                } else { +                    let avatar = fetch_avatar(user.avatar.as_deref()).await; +                    let avatar = ArcRwSignal::new(avatar); +                    user_state_store.store(user.jid.clone(), (ArcStore::new(user), avatar)) +                }; +                let user = ArcMacawUser { user }; +                let chat = chat_state_store.store(chat.correspondent.clone(), ArcStore::new(chat)); +                ArcMacawChat { chat, user } +            }; +            open_chats.update(|open_chats| open_chats.open(chat.clone())); +        } +    }); + +    let current_open_chat: Memo<Option<BareJID>> = use_context().expect("no open chat memo in context");      let open = move || { -        if let Some(open_chat) = &*open_chats.chat_view().read() { +        if let Some(open_chat) = &*current_open_chat.read() {              debug!("got open chat: {:?}", open_chat);              if *open_chat == *contact.user.get().jid().read() {                  return Open::Focused; @@ -47,11 +78,10 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView {      let open = move || open().is_open();      view! { -        <div class="roster-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat> +        <div class="roster-list-item" class:open=move || open() class:focused=move || focused() on:click=move |_| { open_chat.dispatch(()); }>              {move || { -                let user = contact.user.get().into();                  view! { -                    <AvatarWithPresence user /> +                    <AvatarWithPresence user=contact.user />                  }              }}              <div class="item-info"> diff --git a/src/contact.rs b/src/contact.rs index 9adec16..e0e0d78 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -41,9 +41,9 @@ pub struct ArcMacawContact {  }  impl ArcMacawContact { -    pub fn got_contact_and_user(contact: Contact, user: User) -> Self { +    pub async fn got_contact_and_user(contact: Contact, user: User) -> Self {          let contact = Store::new(contact); -        let user = ArcMacawUser::got_user(user); +        let user = ArcMacawUser::got_user(user).await;          Self { contact, user }      }  } diff --git a/src/message.rs b/src/message.rs index 878085e..23ca6be 100644 --- a/src/message.rs +++ b/src/message.rs @@ -49,11 +49,11 @@ pub struct ArcMacawMessage {  }  impl ArcMacawMessage { -    pub fn got_message_and_user(message: Message, user: User) -> Self { +    pub async fn got_message_and_user(message: Message, user: User) -> Self {          let message_state_store: StateStore<Uuid, ArcStore<Message>> =              use_context().expect("no message state store");          let message = message_state_store.store(message.id, ArcStore::new(message)); -        let user = ArcMacawUser::got_user(user); +        let user = ArcMacawUser::got_user(user).await;          Self { message, user }      }  } diff --git a/src/state_store.rs b/src/state_store.rs index ac90e40..7e6e6a1 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -112,35 +112,51 @@ where      V: Send + Sync + 'static,  {      pub fn store(&self, key: K, value: V) -> StateListener<K, V> { -        { -            let store = self.inner.try_get_value().unwrap(); -            let mut store = store.store.write().unwrap(); -            debug!("store state: {:?}", store); -            if let Some((v, count)) = store.get_mut(&key) { -                debug!("updating old value already in store"); -                v.set(value); -                *count += 1; -                StateListener { -                    value: v.clone(), -                    cleaner: StateCleaner { -                        key, -                        state_store: self.clone(), -                    }, -                } -            } else { -                let v = ArcRwSignal::new(value); -                store.insert(key.clone(), (v.clone(), 1)); -                debug!("inserting new value: {:?}", store); -                StateListener { -                    value: v.into(), -                    cleaner: StateCleaner { -                        key, -                        state_store: self.clone(), -                    }, -                } +        let store = self.inner.try_get_value().unwrap(); +        let mut store = store.store.write().unwrap(); +        debug!("store state: {:?}", store); +        if let Some((v, count)) = store.get_mut(&key) { +            debug!("updating old value already in store"); +            v.set(value); +            *count += 1; +            StateListener { +                value: v.clone(), +                cleaner: StateCleaner { +                    key, +                    state_store: self.clone(), +                }, +            } +        } else { +            let v = ArcRwSignal::new(value); +            store.insert(key.clone(), (v.clone(), 1)); +            debug!("inserting new value: {:?}", store); +            StateListener { +                value: v.into(), +                cleaner: StateCleaner { +                    key, +                    state_store: self.clone(), +                },              }          }      } + +    pub fn get_listener(&self, key: K) -> Option<StateListener<K, V>> { +        let store = self.inner.try_get_value().unwrap(); +        let mut store = store.store.write().unwrap(); +        debug!("store state: {:?}", store); +        if let Some((v, count)) = store.get_mut(&key) { +            *count += 1; +            Some(StateListener { +                value: v.clone(), +                cleaner: StateCleaner { +                    key, +                    state_store: self.clone(), +                }, +            }) +        } else { +            None +        } +    }  }  impl<K, V> StateStore<K, V> diff --git a/src/user.rs b/src/user.rs index e62ebea..fb722c6 100644 --- a/src/user.rs +++ b/src/user.rs @@ -12,11 +12,16 @@ pub struct MacawUser {      pub user: ArenaItem<ArcMacawUser>,      // TODO: just store avatar src in user      // pub avatar: String, +    // pub avatar: RwSignal<String>,  }  impl MacawUser {      pub fn get(&self) -> ArcStore<User> { -        self.try_get_value().unwrap().get() +        self.try_get_value().unwrap().get().0 +    } + +    pub fn avatar(&self) -> ArcRwSignal<String> { +        self.try_get_value().unwrap().get().1      }  } @@ -38,6 +43,7 @@ impl From<ArcMacawUser> for MacawUser {      fn from(value: ArcMacawUser) -> Self {          Self {              user: ArenaItem::new_with_storage(value), +            // avatar: value.avatar.into(),          }      }  } @@ -50,21 +56,29 @@ impl From<MacawUser> for ArcMacawUser {  #[derive(Clone)]  pub struct ArcMacawUser { -    pub user: StateListener<BareJID, ArcStore<User>>, +    pub user: StateListener<BareJID, (ArcStore<User>, ArcRwSignal<String>)>,  }  impl ArcMacawUser { -    pub fn got_user(user: User) -> Self { -         -        let user_state_store: StateStore<BareJID, ArcStore<User>> = +    pub async fn got_user(user: User) -> Self { +        let user_state_store: StateStore<BareJID, (ArcStore<User>, ArcRwSignal<String>)> =              use_context().expect("no user state store"); -        let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); -        Self { user } +        let old_user = user_state_store.get_listener(user.jid.clone()); +        let user = if let Some(old_user) = old_user { +            old_user.update(|(old_user, _avatar)| { old_user.set(user); }); +            old_user +        } else { +            let avatar = fetch_avatar(user.avatar.as_deref()).await; +            let avatar = ArcRwSignal::new(avatar); +            user_state_store.store(user.jid.clone(), (ArcStore::new(user), avatar)) +        }; +        let user = ArcMacawUser { user }; +        user      }  }  impl Deref for ArcMacawUser { -    type Target = StateListener<BareJID, ArcStore<User>>; +    type Target = StateListener<BareJID, (ArcStore<User>, ArcRwSignal<String>)>;      fn deref(&self) -> &Self::Target {          &self.user @@ -79,6 +93,19 @@ impl DerefMut for ArcMacawUser {  pub const NO_AVATAR: &str = "/assets/no-avatar.png"; +pub async fn fetch_avatar(id: Option<&str>) -> String { +    if let Some(avatar) = id { +        let client = use_context::<Client>().expect("client not in context"); +        if let Some(data) = client.file_store.get_src(avatar).await { +            data +        } else { +            NO_AVATAR.to_string() +        } +    } else { +        NO_AVATAR.to_string() +    } +} +  pub async fn get_avatar(user: Store<User>) -> String {      if let Some(avatar) = &user.read().avatar {          let client = use_context::<Client>().expect("client not in context"); @@ -87,8 +114,6 @@ pub async fn get_avatar(user: Store<User>) -> String {          } else {              NO_AVATAR.to_string()          } -        // TODO: enable avatar fetching -        // format!("/files/{}", avatar)      } else {          NO_AVATAR.to_string()      } diff --git a/src/views/macaw.rs b/src/views/macaw.rs index 328e3d2..4a5b794 100644 --- a/src/views/macaw.rs +++ b/src/views/macaw.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet};  use filamento::{chat::{Chat, Message, MessageStoreFields}, user::User, UpdateMessage};  use jid::BareJID; @@ -10,7 +10,7 @@ use tokio::sync::mpsc::Receiver;  use tracing::debug;  use uuid::Uuid; -use crate::{client::Client, components::sidebar::Sidebar, contact::{ArcMacawContact, MacawContact}, message::{ArcMacawMessage, MacawMessage}, message_subscriptions::MessageSubscriptions, open_chats::OpenChatsPanel, roster::{Roster, RosterStoreFields}, state_store::StateStore, user::{ArcMacawUser, MacawUser}, user_presences::{Presences, UserPresences}}; +use crate::{client::Client, components::sidebar::Sidebar, contact::{ArcMacawContact, MacawContact}, message::{ArcMacawMessage, MacawMessage}, message_subscriptions::MessageSubscriptions, open_chats::OpenChatsPanel, roster::{Roster, RosterStoreFields}, state_store::StateStore, user::{fetch_avatar, ArcMacawUser, MacawUser}, user_presences::{Presences, UserPresences}};  use super::AppState; @@ -25,6 +25,7 @@ pub fn Macaw(      mut updates: Receiver<UpdateMessage>,      set_app: WriteSignal<AppState>,  ) -> impl IntoView { +    let (updates, set_updates) = signal(Some(updates));      provide_context(set_app);      provide_context(client); @@ -38,7 +39,7 @@ pub fn Macaw(      provide_context(messages_store);      let chats_store: StateStore<BareJID, ArcStore<Chat>> = StateStore::new();      provide_context(chats_store); -    let users_store: StateStore<BareJID, ArcStore<User>> = StateStore::new(); +    let users_store: StateStore<BareJID, (ArcStore<User>, ArcRwSignal<String>)> = StateStore::new();      provide_context(users_store);      let open_chats = Store::new(OpenChatsPanel::default()); @@ -53,7 +54,7 @@ pub fn Macaw(          async move {              let client = use_context::<Client>().expect("client not in context");              let user = client.get_user((*client.jid).clone()).await.unwrap(); -            ArcMacawUser::got_user(user).into() +            ArcMacawUser::got_user(user).await.into()          }      });      provide_context(client_user); @@ -65,19 +66,15 @@ pub fn Macaw(      // TODO: get cached contacts on login before getting the updated contacts -    OnceResource::new(async move { +    LocalResource::new(move || async move { +        let mut updates = set_updates.write().take().expect("main loop ran twice");          while let Some(update) = updates.recv().await {              match update {                  UpdateMessage::Online(online, items) => { -                    let contacts = items -                        .into_iter() -                        .map(|(contact, user)| { -                            ( -                                contact.user_jid.clone(), -                                ArcMacawContact::got_contact_and_user(contact, user).into(), -                            ) -                        }) -                        .collect(); +                    let mut contacts = HashMap::new(); +                    for (contact, user) in items { +                        contacts.insert(contact.user_jid.clone(), ArcMacawContact::got_contact_and_user(contact, user).await.into()); +                    }                      roster.contacts().set(contacts);                  }                  UpdateMessage::Offline(offline) => { @@ -85,13 +82,13 @@ pub fn Macaw(                      user_presences.write().clear();                  }                  UpdateMessage::RosterUpdate(contact, user) => { +                    let new_contact = ArcMacawContact::got_contact_and_user(contact.clone(), user).await.into();                      roster.contacts().update(|roster| {                          if let Some(macaw_contact) = roster.get_mut(&contact.user_jid) {                              macaw_contact.set(contact);                          } else {                              let jid = contact.user_jid.clone(); -                            let contact = ArcMacawContact::got_contact_and_user(contact, user).into(); -                            roster.insert(jid, contact); +                            roster.insert(jid, new_contact);                          }                      });                  } @@ -116,7 +113,7 @@ pub fn Macaw(                  }                  UpdateMessage::Message { to, from, message } => {                      debug!("before got message"); -                    let new_message = ArcMacawMessage::got_message_and_user(message, from); +                    let new_message = ArcMacawMessage::got_message_and_user(message, from).await;                      debug!("after got message");                      spawn_local(async move {                          message_subscriptions @@ -136,12 +133,16 @@ pub fn Macaw(                      set_subscription_requests.update(|req| { req.insert(jid); });                  }                  UpdateMessage::NickChanged { jid, nick } => { -                    users_store.modify(&jid, |user| { +                    users_store.modify(&jid, |(user, _avatar)| {                          user.update(|user| *&mut user.nick = nick.clone())                      });                  }                  UpdateMessage::AvatarChanged { jid, id } => { -                    users_store.modify(&jid, |user| *&mut user.write().avatar = id.clone()); +                    let new_avatar = fetch_avatar(id.as_deref()).await; +                    users_store.modify(&jid, |(user, avatar)| { +                        *&mut user.write().avatar = id.clone(); +                        *&mut avatar.set(new_avatar.clone()) +                    });                  }              }          }  | 
