diff --git a/common/src/lib.rs b/common/src/lib.rs index a1d7be64b2b..660cfe8bb68 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,5 +1,6 @@ pub mod language; pub mod notifications; +pub mod profile_update_channel; pub mod sounds; pub mod state; pub mod testing; diff --git a/common/src/profile_update_channel.rs b/common/src/profile_update_channel.rs new file mode 100644 index 00000000000..4d793b9844a --- /dev/null +++ b/common/src/profile_update_channel.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use futures::channel::oneshot; +use once_cell::sync::Lazy; +use tokio::sync::Mutex; +use tracing::log; +use warp::crypto::DID; + +use crate::{ + state::Identity, + warp_runner::{MultiPassCmd, WarpCmd}, + WARP_CMD_CH, +}; + +pub struct ProfileUpdateChannel { + pub tx: tokio::sync::mpsc::UnboundedSender, + pub rx: Arc>>, +} + +pub static PROFILE_CHANNEL_LISTENER: Lazy = Lazy::new(|| { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + ProfileUpdateChannel { + tx, + rx: Arc::new(Mutex::new(rx)), + } +}); + +pub struct ProfileDataUpdate { + pub did: DID, + pub picture: Option, + pub banner: Option, +} + +pub fn fetch_identity_data(identities: &[Identity]) { + let identities: Vec<_> = identities.iter().map(|id| id.did_key()).collect(); + tokio::spawn(async move { + let warp_cmd_tx = WARP_CMD_CH.tx.clone(); + + for identity in identities { + let (tx, rx) = oneshot::channel(); + let _ = warp_cmd_tx.send(WarpCmd::MultiPass(MultiPassCmd::GetProfilePicture { + did: identity.clone(), + rsp: tx, + })); + + let profile_picture = match rx.await { + Ok(res) => res.ok(), + Err(e) => { + log::error!("error fetching profile pic {e}"); + return; + } + }; + let (tx, rx) = oneshot::channel(); + let _ = warp_cmd_tx.send(WarpCmd::MultiPass(MultiPassCmd::GetProfileBanner { + did: identity.clone(), + rsp: tx, + })); + let profile_banner = match rx.await { + Ok(res) => res.ok(), + Err(e) => { + log::error!("error fetching profile banner {e}"); + return; + } + }; + if profile_picture.is_some() || profile_banner.is_some() { + let _ = PROFILE_CHANNEL_LISTENER.tx.send(ProfileDataUpdate { + did: identity, + picture: profile_picture, + banner: profile_banner, + }); + } + } + }); +} diff --git a/common/src/state/mod.rs b/common/src/state/mod.rs index 7bf80699102..71e7e467634 100644 --- a/common/src/state/mod.rs +++ b/common/src/state/mod.rs @@ -1689,6 +1689,12 @@ impl State { *friend = ident; } } + + pub fn update_identity_with(&mut self, id: DID, mut ident: impl FnMut(&mut Identity)) { + if let Some(friend) = self.identities.get_mut(&id) { + ident(friend); + } + } // identities are updated once a minute for friends. but if someone sends you a message, they should be seen as online. // this function checks if the friend is offline and if so, sets them to online. This may be incorrect, but should // be corrected when the identity list is periodically updated diff --git a/common/src/warp_runner/manager/commands/mod.rs b/common/src/warp_runner/manager/commands/mod.rs index bda0a532cd9..2f61734104b 100644 --- a/common/src/warp_runner/manager/commands/mod.rs +++ b/common/src/warp_runner/manager/commands/mod.rs @@ -8,7 +8,7 @@ mod tesseract_commands; // this shortens the path required to use the functions and structs pub use blink_commands::{handle_blink_cmd, BlinkCmd}; pub use constellation_commands::{handle_constellation_cmd, thumbnail_to_base64, ConstellationCmd}; -pub use multipass_commands::{handle_multipass_cmd, identity_image_to_base64, MultiPassCmd}; +pub use multipass_commands::{handle_multipass_cmd, MultiPassCmd}; pub use other_commands::*; pub use raygun_commands::{handle_raygun_cmd, RayGunCmd}; pub use tesseract_commands::{handle_tesseract_cmd, TesseractCmd}; diff --git a/common/src/warp_runner/manager/commands/multipass_commands.rs b/common/src/warp_runner/manager/commands/multipass_commands.rs index 53ea07ea488..5ffb062136a 100644 --- a/common/src/warp_runner/manager/commands/multipass_commands.rs +++ b/common/src/warp_runner/manager/commands/multipass_commands.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, slice, str::FromStr}; use derive_more::Display; @@ -16,6 +16,7 @@ use warp::{ use tracing::log; use crate::{ + profile_update_channel::fetch_identity_data, state::{self, Identity}, warp_runner::{ui_adapter::dids_to_identity, Account}, }; @@ -93,12 +94,12 @@ pub enum MultiPassCmd { #[display(fmt = "UpdateProfilePicture")] GetProfilePicture { did: DID, - rsp: oneshot::Sender>, + rsp: oneshot::Sender>, }, #[display(fmt = "UpdateProfilePicture")] GetProfileBanner { did: DID, - rsp: oneshot::Sender>, + rsp: oneshot::Sender>, }, #[display(fmt = "UpdateProfilePicture")] UpdateProfilePicture { @@ -250,11 +251,19 @@ pub async fn handle_multipass_cmd(cmd: MultiPassCmd, warp: &mut super::super::Wa let _ = rsp.send(r); } MultiPassCmd::GetProfilePicture { did, rsp } => { - let pfp = warp.multipass.identity_picture(&did).await; + let pfp = warp + .multipass + .identity_picture(&did) + .await + .map(|img| identity_image_to_base64(&img)); let _ = rsp.send(pfp); } MultiPassCmd::GetProfileBanner { did, rsp } => { - let pfb = warp.multipass.identity_banner(&did).await; + let pfb = warp + .multipass + .identity_banner(&did) + .await + .map(|img| identity_image_to_base64(&img)); let _ = rsp.send(pfb); } MultiPassCmd::ClearProfilePicture { rsp } => { @@ -438,12 +447,7 @@ pub async fn handle_multipass_cmd(cmd: MultiPassCmd, warp: &mut super::super::Wa } async fn update_identity(id: &mut Identity, warp: &mut crate::warp_runner::manager::Warp) { - if let Ok(picture) = warp.multipass.identity_picture(&id.did_key()).await { - id.set_profile_picture(&identity_image_to_base64(&picture)); - } - if let Ok(banner) = warp.multipass.identity_banner(&id.did_key()).await { - id.set_profile_banner(&identity_image_to_base64(&banner)); - } + fetch_identity_data(slice::from_ref(id)); if let Ok(status) = warp.multipass.identity_status(&id.did_key()).await { id.set_identity_status(status); } diff --git a/common/src/warp_runner/ui_adapter/mod.rs b/common/src/warp_runner/ui_adapter/mod.rs index 9f823604e26..95a14148620 100644 --- a/common/src/warp_runner/ui_adapter/mod.rs +++ b/common/src/warp_runner/ui_adapter/mod.rs @@ -12,12 +12,16 @@ pub use multipass_event::{convert_multipass_event, MultiPassEvent}; pub use raygun_event::{convert_raygun_event, RayGunEvent}; use uuid::Uuid; -use crate::state::{self, chats, utils::mention_regex_epattern, Identity, MAX_PINNED_MESSAGES}; +use crate::{ + profile_update_channel::fetch_identity_data, + state::{self, chats, utils::mention_regex_epattern, Identity, MAX_PINNED_MESSAGES}, +}; use futures::{stream::FuturesOrdered, FutureExt, StreamExt}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashSet, VecDeque}, ops::Range, + slice, }; use warp::{ constellation::file::File, @@ -29,9 +33,7 @@ use warp::{ use tracing::log; -use super::{ - manager::commands::identity_image_to_base64, FetchMessagesConfig, FetchMessagesResponse, -}; +use super::{FetchMessagesConfig, FetchMessagesResponse}; /// the UI needs additional information for message replies, namely the text of the message being replied to. /// fetch that before sending the message to the UI. @@ -149,16 +151,9 @@ pub async fn did_to_identity( .identity_platform(&id.did_key()) .await .unwrap_or(Platform::Unknown); - let mut id = state::Identity::new(id, status, platform); - - if let Ok(picture) = account.identity_picture(&id.did_key()).await { - id.set_profile_picture(&identity_image_to_base64(&picture)); - } - - if let Ok(banner) = account.identity_banner(&id.did_key()).await { - id.set_profile_banner(&identity_image_to_base64(&banner)); - } + let id = state::Identity::new(id, status, platform); + fetch_identity_data(slice::from_ref(&id)); id } None => get_uninitialized_identity(did)?, @@ -180,19 +175,10 @@ pub async fn dids_to_identity( .identity_platform(&id.did_key()) .await .unwrap_or(Platform::Unknown); - let mut id = state::Identity::new(id, status, platform); - - if let Ok(picture) = account.identity_picture(&id.did_key()).await { - id.set_profile_picture(&identity_image_to_base64(&picture)); - } - - if let Ok(banner) = account.identity_banner(&id.did_key()).await { - id.set_profile_banner(&identity_image_to_base64(&banner)); - } - - id + state::Identity::new(id, status, platform) }); - let converted_ids = FuturesOrdered::from_iter(ids).collect().await; + let converted_ids: Vec = FuturesOrdered::from_iter(ids).collect().await; + fetch_identity_data(&converted_ids); Ok(converted_ids) } diff --git a/ui/src/components/settings/sub_pages/profile/mod.rs b/ui/src/components/settings/sub_pages/profile/mod.rs index ea8a297cfc7..4a05bbc5591 100644 --- a/ui/src/components/settings/sub_pages/profile/mod.rs +++ b/ui/src/components/settings/sub_pages/profile/mod.rs @@ -188,7 +188,11 @@ pub fn ProfileSettings(cx: Scope) -> Element { if let Some(ident) = should_update.get() { log::trace!("Updating ProfileSettings"); - state.write().set_own_identity(ident.clone()); + let mut ident = ident.clone(); + let current = state.read().get_own_identity(); + ident.set_profile_banner(¤t.profile_banner()); + ident.set_profile_picture(¤t.profile_picture()); + state.write().set_own_identity(ident); state .write() .mutate(common::state::Action::AddToastNotification( diff --git a/ui/src/lib.rs b/ui/src/lib.rs index e6794f8220d..addd399d702 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -10,6 +10,7 @@ use common::icons::outline::Shape as Icon; use common::icons::Icon as IconElement; use common::language::{get_local_text, get_local_text_with_args}; use common::notifications::{NotificationAction, NOTIFICATION_LISTENER}; +use common::profile_update_channel::PROFILE_CHANNEL_LISTENER; use common::state::settings::GlobalShortcut; use common::state::ToastNotification; use common::warp_runner::ui_adapter::MessageEvent; @@ -537,6 +538,40 @@ fn use_app_coroutines(cx: &ScopeState) -> Option<()> { } }); + // Listen to profile updates + use_future(cx, (), |_| { + to_owned![state]; + async move { + while !state.read().initialized { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + let channel = PROFILE_CHANNEL_LISTENER.rx.clone(); + let mut ch = channel.lock().await; + while let Some(action) = ch.recv().await { + let mut id = state.read().get_own_identity(); + let did = action.did; + if did.eq(&id.did_key()) { + if let Some(picture) = action.picture.as_ref() { + id.set_profile_picture(picture); + } + if let Some(banner) = action.banner.as_ref() { + id.set_profile_banner(banner); + } + state.write().set_own_identity(id); + } else { + state.write().update_identity_with(did, |id| { + if let Some(picture) = action.picture.as_ref() { + id.set_profile_picture(picture); + } + if let Some(banner) = action.banner.as_ref() { + id.set_profile_banner(banner); + } + }); + } + } + } + }); + // Listen to async tasks actions that should be handled on main thread use_future(cx, (), |_| { to_owned![state];