synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;

use crate::utils::media_helpers::file_icon_for_mime;
use crate::utils::room_helpers::format_file_size;

/// Possible states of a file download.
#[derive(Clone, Debug, PartialEq)]
pub enum DownloadState {
    /// Not started yet.
    Idle,
    /// Download in progress with bytes received so far.
    Downloading { bytes_received: u64 },
    /// Download completed successfully.
    Completed,
    /// Download failed with an error message.
    Failed { error: String },
}

/// Render the download action area based on download state.
fn render_download_action(
    state: &DownloadState,
    file_size: Option<u64>,
    on_start: EventHandler<()>,
) -> Element {
    match state {
        DownloadState::Idle => rsx! {
            button {
                class: "file-download__btn",
                title: "Download file",
                onclick: move |_| on_start.call(()),
                "Download"
            }
        },
        DownloadState::Downloading { bytes_received } => {
            let progress_text = if let Some(total) = file_size {
                let pct = if total > 0 {
                    (*bytes_received as f64 / total as f64 * 100.0) as u32
                } else {
                    0
                };
                format!("{pct}%")
            } else {
                format_file_size(*bytes_received)
            };
            rsx! {
                span {
                    class: "file-download__progress-text",
                    "{progress_text}"
                }
            }
        },
        DownloadState::Completed => rsx! {
            span {
                class: "file-download__done",
                "Done"
            }
        },
        DownloadState::Failed { error } => {
            let err = error.clone();
            rsx! {
                span {
                    class: "file-download__error",
                    title: "{err}",
                    "Failed"
                }
                button {
                    class: "file-download__btn file-download__btn--retry",
                    onclick: move |_| on_start.call(()),
                    "Retry"
                }
            }
        },
    }
}

/// File download handler component showing file info and download controls.
#[component]
pub fn FileDownload(
    file_name: String,
    file_url: String,
    file_size: Option<u64>,
    mimetype: Option<String>,
    on_download: EventHandler<String>,
) -> Element {
    let mut download_state = use_signal(|| DownloadState::Idle);

    let mime = mimetype.unwrap_or_else(|| "application/octet-stream".to_string());
    let icon = file_icon_for_mime(&mime);
    let size_text = file_size.map(format_file_size).unwrap_or_default();
    let icon_label = match icon {
        "image" => "img",
        "video" => "vid",
        "audio" => "aud",
        "pdf" => "pdf",
        "archive" => "zip",
        "text" => "txt",
        _ => "file",
    };

    let on_start_download = {
        let file_url = file_url.clone();
        move |_: ()| {
            download_state.set(DownloadState::Downloading { bytes_received: 0 });
            on_download.call(file_url.clone());
        }
    };

    let state_val = download_state.read().clone();

    rsx! {
        div {
            class: "file-download",

            div {
                class: "file-download__icon file-download__icon--{icon}",
                span { "{icon_label}" }
            }

            div {
                class: "file-download__info",
                span {
                    class: "file-download__name",
                    title: "{file_name}",
                    "{file_name}"
                }
                div {
                    class: "file-download__meta",
                    if !size_text.is_empty() {
                        span {
                            class: "file-download__size",
                            "{size_text}"
                        }
                    }
                    span {
                        class: "file-download__mime",
                        "{mime}"
                    }
                }
            }

            div {
                class: "file-download__action",
                {render_download_action(&state_val, file_size, EventHandler::new(on_start_download))}
            }
        }
    }
}