synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
use matrix_sdk::ruma::OwnedRoomId;

use crate::components::avatar::Avatar;
use crate::components::modal::Modal;
use crate::state::app_state::AppState;

/// A room entry for the forward dialog list.
#[derive(Clone, PartialEq)]
struct ForwardRoomEntry {
    room_id: String,
    display_name: String,
    avatar_url: Option<String>,
}

/// Dialog for forwarding a message to another room.
#[component]
pub fn ForwardDialog(
    message_body: String,
    on_close: EventHandler<()>,
) -> Element {
    let state = use_context::<Signal<AppState>>();
    let mut search_query = use_signal(|| String::new());
    let mut selected_room_id = use_signal(|| Option::<String>::None);
    let mut is_sending = use_signal(|| false);
    let mut send_result = use_signal(|| Option::<Result<(), String>>::None);

    let query = search_query.read().to_lowercase();

    // Get filtered room list
    let rooms: Vec<ForwardRoomEntry> = {
        let s = state.read();
        s.rooms
            .values()
            .filter(|r| {
                if query.is_empty() {
                    true
                } else {
                    r.display_name.to_lowercase().contains(&query)
                }
            })
            .map(|r| ForwardRoomEntry {
                room_id: r.room_id.to_string(),
                display_name: r.display_name.clone(),
                avatar_url: r.avatar_url.clone(),
            })
            .collect()
    };

    let body_for_send = message_body.clone();
    let on_forward = move |_| {
        let selected = selected_room_id.read().clone();
        let Some(room_id_str) = selected else { return };
        let body = body_for_send.clone();
        is_sending.set(true);
        send_result.set(None);

        spawn(async move {
            let client = { state.read().client.clone() };
            let Some(client) = client else {
                send_result.set(Some(Err("Not logged in".to_string())));
                is_sending.set(false);
                return;
            };

            let room_id: OwnedRoomId = match room_id_str.as_str().try_into() {
                Ok(id) => id,
                Err(e) => {
                    send_result.set(Some(Err(format!("Invalid room ID: {e}"))));
                    is_sending.set(false);
                    return;
                }
            };

            let Some(room) = client.get_room(&room_id) else {
                send_result.set(Some(Err("Room not found".to_string())));
                is_sending.set(false);
                return;
            };

            let content = RoomMessageEventContent::text_plain(&body);
            match room.send(content).await {
                Ok(_) => {
                    tracing::info!("Forwarded message to {room_id}");
                    send_result.set(Some(Ok(())));
                }
                Err(e) => {
                    send_result.set(Some(Err(format!("Failed to forward: {e}"))));
                }
            }
            is_sending.set(false);
        });
    };

    let has_selection = selected_room_id.read().is_some();
    let is_busy = *is_sending.read();
    let succeeded = matches!(&*send_result.read(), Some(Ok(())));
    let error_msg = match &*send_result.read() {
        Some(Err(e)) => Some(e.clone()),
        _ => None,
    };
    let send_btn_opacity = if has_selection && !is_busy { "1" } else { "0.5" };
    let send_btn_style = format!(
        "padding: 8px 16px; border-radius: 6px; border: none; background: var(--accent-color, #4a9eff); color: white; cursor: pointer; opacity: {};",
        send_btn_opacity
    );
    let send_btn_label = if is_busy { "Forwarding..." } else { "Forward" };

    rsx! {
        Modal {
            title: "Forward Message".to_string(),
            on_close: move |_| on_close.call(()),
            wide: true,

            div {
                class: "forward-dialog",

                // Message preview
                div {
                    class: "forward-dialog__preview",
                    p {
                        class: "forward-dialog__preview-label",
                        "Message:"
                    }
                    div {
                        class: "forward-dialog__preview-body",
                        style: "padding: 8px 12px; background: var(--bg-secondary, #1a1a2e); border-radius: 6px; margin-bottom: 12px; font-size: 13px; max-height: 80px; overflow: auto;",
                        "{message_body}"
                    }
                }

                // Success message
                if succeeded {
                    div {
                        class: "forward-dialog__success",
                        style: "padding: 8px 12px; background: var(--success-bg, #1a3a2a); color: var(--success-text, #4caf50); border-radius: 6px; margin-bottom: 12px; text-align: center;",
                        "Message forwarded successfully!"
                    }
                }

                // Error message
                if let Some(ref err) = error_msg {
                    div {
                        class: "forward-dialog__error",
                        style: "padding: 8px 12px; background: var(--error-bg, #3a1a1a); color: var(--error-text, #f44336); border-radius: 6px; margin-bottom: 12px;",
                        "{err}"
                    }
                }

                // Room selection (hidden after success)
                if !succeeded {
                    div {
                        class: "forward-dialog__selector",

                        // Search input
                        input {
                            class: "forward-dialog__search-input",
                            style: "width: 100%; padding: 8px 12px; border: 1px solid var(--border-color, #333); border-radius: 6px; background: var(--bg-primary, #0d0d1a); color: var(--text-primary, #e0e0e0); font-size: 14px; margin-bottom: 12px;",
                            r#type: "text",
                            placeholder: "Search rooms...",
                            value: "{search_query}",
                            oninput: move |evt| search_query.set(evt.value()),
                        }

                        // Room list
                        div {
                            class: "forward-dialog__room-list",
                            style: "max-height: 300px; overflow-y: auto; border: 1px solid var(--border-color, #333); border-radius: 6px;",

                            if rooms.is_empty() {
                                p {
                                    style: "padding: 16px; text-align: center; color: var(--text-secondary, #888);",
                                    "No rooms found."
                                }
                            }

                            for entry in rooms.iter() {
                                {
                                    let rid = entry.room_id.clone();
                                    let name = entry.display_name.clone();
                                    let avatar = entry.avatar_url.clone();
                                    let is_selected = selected_room_id.read().as_ref() == Some(&entry.room_id);
                                    let item_style = if is_selected {
                                        "display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; background: var(--accent-bg, #1a2a4a); border-left: 3px solid var(--accent-color, #4a9eff);"
                                    } else {
                                        "display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; border-left: 3px solid transparent;"
                                    };
                                    rsx! {
                                        div {
                                            class: "forward-dialog__room-item",
                                            style: "{item_style}",
                                            onclick: move |_| {
                                                selected_room_id.set(Some(rid.clone()));
                                            },
                                            Avatar {
                                                name: name.clone(),
                                                url: avatar,
                                                size: 32,
                                            }
                                            span {
                                                style: "font-size: 14px;",
                                                "{name}"
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        // Forward button row
                        div {
                            style: "margin-top: 12px; display: flex; justify-content: flex-end; gap: 8px;",
                            button {
                                style: "padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border-color, #333); background: transparent; color: var(--text-primary, #e0e0e0); cursor: pointer;",
                                onclick: move |_| on_close.call(()),
                                "Cancel"
                            }
                            button {
                                style: "{send_btn_style}",
                                disabled: !has_selection || is_busy,
                                onclick: on_forward,
                                "{send_btn_label}"
                            }
                        }
                    }
                }
            }
        }
    }
}