synpad 0.1.0

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

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

/// User search result from the user directory.
#[derive(Clone, Debug, PartialEq)]
struct UserSearchResult {
    user_id: String,
    display_name: Option<String>,
    avatar_url: Option<String>,
}

/// User directory search dialog.
/// Allows searching for users on the homeserver.
#[component]
pub fn UserDirectoryDialog(on_close: EventHandler<()>, on_select: EventHandler<String>) -> Element {
    let state = use_context::<Signal<AppState>>();
    let mut query = use_signal(|| String::new());
    let mut results = use_signal(Vec::<UserSearchResult>::new);
    let mut searching = use_signal(|| false);

    let mut do_search = move || {
        let q = query.read().trim().to_string();
        if q.is_empty() {
            return;
        }
        searching.set(true);
        spawn(async move {
            let client = { state.read().client.clone() };
            if let Some(client) = client {
                use matrix_sdk::ruma::api::client::user_directory::search_users::v3::Request;
                let mut request = Request::new(q);
                request.limit = matrix_sdk::ruma::uint!(20);
                match client.send(request).await {
                    Ok(response) => {
                        let users: Vec<UserSearchResult> = response
                            .results
                            .into_iter()
                            .map(|u| UserSearchResult {
                                user_id: u.user_id.to_string(),
                                display_name: u.display_name,
                                avatar_url: u.avatar_url.map(|u| u.to_string()),
                            })
                            .collect();
                        results.set(users);
                    }
                    Err(e) => {
                        tracing::error!("User directory search failed: {e}");
                        results.set(Vec::new());
                    }
                }
            }
            searching.set(false);
        });
    };

    rsx! {
        Modal {
            title: "Find People".to_string(),
            on_close: move |_| on_close.call(()),

            div {
                class: "user-directory",

                div {
                    class: "user-directory__search-bar",
                    input {
                        r#type: "text",
                        class: "settings-input",
                        placeholder: "Search users by name or ID...",
                        value: "{query}",
                        autofocus: true,
                        oninput: move |evt| query.set(evt.value()),
                        onkeydown: move |evt: Event<KeyboardData>| {
                            if evt.key() == Key::Enter {
                                evt.prevent_default();
                                do_search();
                            }
                        },
                    }
                    button {
                        class: "btn btn--primary",
                        disabled: *searching.read(),
                        onclick: move |_| do_search(),
                        if *searching.read() { "Searching..." } else { "Search" }
                    }
                }

                div {
                    class: "user-directory__results",
                    if *searching.read() {
                        div {
                            class: "user-directory__loading",
                            div { class: "spinner" }
                        }
                    }
                    if results.read().is_empty() && !*searching.read() && !query.read().is_empty() {
                        div { class: "user-directory__empty", "No users found." }
                    }
                    for user in results.read().iter() {
                        {
                            let uid = user.user_id.clone();
                            let name = user.display_name.clone().unwrap_or_else(|| uid.clone());
                            let avatar = user.avatar_url.clone();
                            let uid_for_click = uid.clone();
                            rsx! {
                                button {
                                    class: "user-directory__result",
                                    onclick: move |_| on_select.call(uid_for_click.clone()),
                                    Avatar {
                                        name: name.clone(),
                                        url: avatar,
                                        size: 40,
                                    }
                                    div {
                                        class: "user-directory__result-info",
                                        span { class: "user-directory__result-name", "{name}" }
                                        span { class: "user-directory__result-id", "{uid}" }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}