synpad 0.1.0

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

use matrix_sdk::ruma::OwnedRoomId;

use crate::room::composer::message_input::MessageInput;
use crate::room::invite_preview::InvitePreviewBar;
use crate::room::room_header::RoomHeader;
use crate::room::timeline::timeline_panel::TimelinePanel;
use crate::state::app_state::{AppState, AppView};
use crate::state::room_state::RoomMembership;

/// Main room screen container.
#[component]
pub fn RoomScreen(room_id: String) -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let mut show_search = use_signal(|| false);
    let mut search_query = use_signal(|| String::new());

    // Update breadcrumbs (recent rooms)
    {
        if let Ok(rid) = OwnedRoomId::try_from(room_id.as_str()) {
            let mut s = state.write();
            s.breadcrumbs.retain(|r| r != &rid);
            s.breadcrumbs.insert(0, rid);
            if s.breadcrumbs.len() > 12 {
                s.breadcrumbs.truncate(12);
            }
        }
    }

    // Clear reply/edit state if it belongs to a different room
    {
        let s = state.read();
        let replying_room = s.replying_to.as_ref().map(|r| r.room_id.to_string());
        let editing_room = s.editing_message.as_ref().map(|e| e.room_id.to_string());
        drop(s);

        let needs_clear = replying_room.map_or(false, |r| r != room_id)
            || editing_room.map_or(false, |r| r != room_id);
        if needs_clear {
            let mut s = state.write();
            s.replying_to = None;
            s.editing_message = None;
        }
    }

    let state_read = state.read();

    // Get room info
    let room = state_read
        .rooms
        .values()
        .find(|r| r.room_id.to_string() == room_id);

    let (room_name, room_topic, member_count, is_encrypted, membership, avatar_url,
         is_tombstoned, tombstone_successor, tombstone_body) = match room {
        Some(r) => (
            r.display_name.clone(),
            r.topic.clone(),
            r.member_count,
            r.is_encrypted,
            r.membership.clone(),
            r.avatar_url.clone(),
            r.is_tombstoned,
            r.tombstone_successor.clone(),
            r.tombstone_body.clone(),
        ),
        None => ("Loading...".to_string(), None, 0, false, RoomMembership::Joined, None, false, None, None),
    };

    let is_invited = matches!(membership, RoomMembership::Invited);
    let is_knocked = matches!(membership, RoomMembership::Knocked);
    let room_not_found = room.is_none() && state_read.client.is_some();

    let search_text = search_query.read().clone();

    rsx! {
        div {
            class: "room-screen",

            RoomHeader {
                room_id: room_id.clone(),
                room_name: room_name.clone(),
                room_topic: room_topic,
                member_count: member_count,
                is_encrypted: is_encrypted,
                on_search_toggle: move |_| {
                    let current = *show_search.read();
                    if current {
                        search_query.set(String::new());
                    }
                    show_search.set(!current);
                },
            }

            // Search bar
            if *show_search.read() {
                div {
                    class: "room-screen__search-bar",
                    span { class: "room-screen__search-icon", "🔍" }
                    input {
                        class: "room-screen__search-input",
                        r#type: "text",
                        placeholder: "Search messages in this room...",
                        value: "{search_query}",
                        oninput: move |evt| search_query.set(evt.value()),
                        autofocus: true,
                    }
                    if !search_query.read().is_empty() {
                        span {
                            class: "room-screen__search-count",
                            // Show count of matching messages
                        }
                    }
                    button {
                        class: "room-screen__search-close",
                        title: "Close search",
                        onclick: move |_| {
                            search_query.set(String::new());
                            show_search.set(false);
                        },
                        ""
                    }
                }
            }

            // Tombstoned room banner (#45)
            if is_tombstoned {
                {
                    let successor = tombstone_successor.clone();
                    let body = tombstone_body.clone().unwrap_or_else(|| "This room has been replaced and will no longer receive messages.".to_string());
                    rsx! {
                        div {
                            class: "room-screen__tombstone-bar",
                            div {
                                class: "room-screen__tombstone-icon",
                                ""
                            }
                            div {
                                class: "room-screen__tombstone-info",
                                p { class: "room-screen__tombstone-text", "{body}" }
                                if let Some(ref successor_id) = successor {
                                    {
                                        let sid = successor_id.to_string();
                                        rsx! {
                                            button {
                                                class: "btn btn--primary btn--sm",
                                                onclick: move |_| {
                                                    if let Ok(new_room_id) = OwnedRoomId::try_from(sid.as_str()) {
                                                        let mut s = state.write();
                                                        s.active_room_id = Some(new_room_id.clone());
                                                        s.current_view = AppView::Room(new_room_id);
                                                    }
                                                },
                                                "Go to new room"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Room loading indicator (#61)
            if room_not_found {
                div {
                    class: "room-screen__loading",
                    div { class: "spinner" }
                    span { "Loading room..." }
                }
            }

            // Invite preview bar (for invited rooms)
            if is_invited {
                InvitePreviewBar {
                    room_id: room_id.clone(),
                    room_name: room_name.clone(),
                    avatar_url: avatar_url,
                    inviter: None,
                }
            }

            // Knocked room banner (#55)
            if is_knocked {
                div {
                    class: "room-screen__knock-bar",
                    div { class: "room-screen__knock-icon", "🚪" }
                    div {
                        class: "room-screen__knock-info",
                        p { "You've requested to join this room." }
                        p { class: "room-screen__knock-hint", "Waiting for a room member to approve your request..." }
                    }
                }
            }

            if !is_invited && !is_knocked && !room_not_found {
                TimelinePanel {
                    room_id: room_id.clone(),
                    search_filter: search_text,
                }

                // Hide composer for tombstoned rooms
                if !is_tombstoned {
                    MessageInput {
                        room_id: room_id,
                    }
                }
            }
        }
    }
}