use bytes::Bytes;
use dioxus::prelude::*;
use freya_elements as dioxus_elements;
use freya_hooks::{
use_applied_theme,
use_asset_cacher,
use_focus,
AssetAge,
AssetConfiguration,
NetworkImageTheme,
NetworkImageThemeWith,
};
use freya_node_state::dynamic_bytes;
use reqwest::Url;
use crate::Loader;
#[derive(Props, Clone, PartialEq)]
pub struct NetworkImageProps {
pub theme: Option<NetworkImageThemeWith>,
pub url: ReadOnlySignal<Url>,
pub fallback: Option<Element>,
pub loading: Option<Element>,
pub alt: Option<String>,
}
#[doc(hidden)]
#[derive(PartialEq)]
pub enum ImageState {
Loading,
Errored,
Loaded(Signal<Bytes>),
}
#[allow(non_snake_case)]
pub fn NetworkImage(props: NetworkImageProps) -> Element {
let mut asset_cacher = use_asset_cacher();
let focus = use_focus();
let mut status = use_signal(|| ImageState::Loading);
let mut cached_assets = use_signal::<Vec<AssetConfiguration>>(Vec::new);
let mut assets_tasks = use_signal::<Vec<Task>>(Vec::new);
let focus_id = focus.attribute();
let NetworkImageTheme { width, height } = use_applied_theme!(&props.theme, network_image);
let alt = props.alt.as_deref();
use_memo(move || {
let url = props.url.read().clone();
for asset_task in assets_tasks.write().drain(..) {
asset_task.cancel();
}
for cached_asset in cached_assets.write().drain(..) {
asset_cacher.unuse_asset(cached_asset);
}
let asset_configuration = AssetConfiguration {
age: AssetAge::default(),
id: url.to_string(),
};
status.set(ImageState::Loading);
if let Some(asset) = asset_cacher.use_asset(&asset_configuration) {
status.set(ImageState::Loaded(asset));
cached_assets.write().push(asset_configuration);
} else {
let asset_task = spawn(async move {
let asset = fetch_image(url).await;
if let Ok(asset_bytes) = asset {
let asset_signal =
asset_cacher.cache(asset_configuration.clone(), asset_bytes, true);
status.set(ImageState::Loaded(asset_signal));
cached_assets.write().push(asset_configuration);
} else if let Err(_err) = asset {
status.set(ImageState::Errored);
}
});
assets_tasks.write().push(asset_task);
}
});
if let ImageState::Loaded(bytes) = &*status.read_unchecked() {
let image_data = dynamic_bytes(bytes.read().clone());
rsx!(image {
height: "{height}",
width: "{width}",
focus_id,
image_data,
role: "image",
alt,
})
} else if *status.read() == ImageState::Loading {
if let Some(loading_element) = &props.loading {
rsx!({ loading_element })
} else {
rsx!(
rect {
height: "{height}",
width: "{width}",
main_align: "center",
cross_align: "center",
Loader {}
}
)
}
} else if let Some(fallback_element) = &props.fallback {
rsx!({ fallback_element })
} else {
rsx!(
rect {
height: "{height}",
width: "{width}",
main_align: "center",
cross_align: "center",
label {
text_align: "center",
"Error"
}
}
)
}
}
async fn fetch_image(url: Url) -> reqwest::Result<Bytes> {
let res = reqwest::get(url).await?;
res.bytes().await
}