synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;
use matrix_sdk::ruma::api::client::room::create_room::v3::Request as CreateRoomRequest;
use matrix_sdk::ruma::api::client::room::Visibility;
use matrix_sdk::ruma::OwnedRoomId;

use crate::state::app_state::{AppState, AppView};

/// Dialog for creating a new room.
#[component]
pub fn CreateRoomDialog(on_close: EventHandler<()>) -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let mut room_name = use_signal(|| String::new());
    let mut room_topic = use_signal(|| String::new());
    let mut is_public = use_signal(|| false);
    let mut is_encrypted = use_signal(|| true);
    let mut is_direct = use_signal(|| false);
    let mut is_creating = use_signal(|| false);
    let mut create_error = use_signal(|| Option::<String>::None);

    let on_create = move |_| {
        let name = room_name.read().clone();
        if name.trim().is_empty() {
            create_error.set(Some("Room name is required".to_string()));
            return;
        }
        is_creating.set(true);
        create_error.set(None);
        let topic = room_topic.read().clone();
        let public = *is_public.read();
        let _encrypted = *is_encrypted.read();
        let direct = *is_direct.read();

        spawn(async move {
            match create_room(state, &name, &topic, public, direct).await {
                Ok(room_id) => {
                    tracing::info!("Room created: {room_id}");
                    // Navigate to the new room
                    let mut s = state.write();
                    s.active_room_id = Some(room_id.clone());
                    s.current_view = AppView::Room(room_id);
                    drop(s);
                    on_close.call(());
                }
                Err(e) => {
                    tracing::error!("Failed to create room: {e}");
                    create_error.set(Some(e));
                }
            }
            is_creating.set(false);
        });
    };

    rsx! {
        div {
            class: "modal-overlay",
            onclick: move |_| on_close.call(()),

            div {
                class: "modal-dialog create-room-dialog",
                onclick: move |evt| evt.stop_propagation(),

                // Header
                div {
                    class: "modal-dialog__header",
                    h2 { "Create Room" }
                    button {
                        class: "modal-dialog__close",
                        onclick: move |_| on_close.call(()),
                        ""
                    }
                }

                // Body
                div {
                    class: "modal-dialog__body",

                    // Error display
                    if let Some(err) = create_error.read().as_ref() {
                        div {
                            class: "create-room-dialog__error",
                            "{err}"
                        }
                    }

                    // Room name
                    div {
                        class: "create-room-dialog__field",
                        label { "Room Name" }
                        input {
                            r#type: "text",
                            placeholder: "e.g. My Room",
                            value: "{room_name}",
                            oninput: move |evt| room_name.set(evt.value()),
                            disabled: *is_creating.read(),
                            autofocus: true,
                        }
                    }

                    // Room topic
                    div {
                        class: "create-room-dialog__field",
                        label { "Topic (optional)" }
                        input {
                            r#type: "text",
                            placeholder: "What is this room about?",
                            value: "{room_topic}",
                            oninput: move |evt| room_topic.set(evt.value()),
                            disabled: *is_creating.read(),
                        }
                    }

                    // Options
                    div {
                        class: "create-room-dialog__options",

                        label {
                            class: "create-room-dialog__checkbox",
                            input {
                                r#type: "checkbox",
                                checked: *is_public.read(),
                                oninput: move |evt| is_public.set(evt.value() == "true"),
                            }
                            span { "Public room (anyone can join)" }
                        }

                        label {
                            class: "create-room-dialog__checkbox",
                            input {
                                r#type: "checkbox",
                                checked: *is_encrypted.read(),
                                oninput: move |evt| is_encrypted.set(evt.value() == "true"),
                            }
                            span { "Enable encryption" }
                        }

                        label {
                            class: "create-room-dialog__checkbox",
                            input {
                                r#type: "checkbox",
                                checked: *is_direct.read(),
                                oninput: move |evt| is_direct.set(evt.value() == "true"),
                            }
                            span { "Direct message" }
                        }
                    }
                }

                // Footer
                div {
                    class: "modal-dialog__footer",
                    button {
                        class: "btn btn--secondary",
                        onclick: move |_| on_close.call(()),
                        disabled: *is_creating.read(),
                        "Cancel"
                    }
                    button {
                        class: "btn btn--primary",
                        onclick: on_create,
                        disabled: room_name.read().trim().is_empty() || *is_creating.read(),
                        if *is_creating.read() {
                            "Creating..."
                        } else {
                            "Create Room"
                        }
                    }
                }
            }
        }
    }
}

/// Create a new room via the Matrix SDK.
async fn create_room(
    state: Signal<AppState>,
    name: &str,
    topic: &str,
    is_public: bool,
    is_direct: bool,
) -> Result<OwnedRoomId, String> {
    let client = { state.read().client.clone() };
    let client = client.ok_or_else(|| "Not logged in".to_string())?;

    let mut request = CreateRoomRequest::new();
    request.name = Some(name.to_string());
    if !topic.is_empty() {
        request.topic = Some(topic.to_string());
    }
    request.visibility = if is_public {
        Visibility::Public
    } else {
        Visibility::Private
    };
    request.is_direct = is_direct;

    let response = client
        .create_room(request)
        .await
        .map_err(|e| format!("Failed to create room: {e}"))?;

    Ok(response.room_id().to_owned())
}