synpad 0.1.0

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

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

/// Right panel showing messages that have been pinned in the current room.
#[component]
pub fn PinnedMessagesPanel() -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let mut pinned_messages = use_signal(Vec::<PinnedMessageData>::new);
    let mut loaded_for = use_signal(|| Option::<String>::None);

    let active_room_id = state.read().active_room_id.clone();
    let room_id_str = active_room_id.as_ref().map(|id| id.to_string());

    // Load pinned events when room changes
    if room_id_str != *loaded_for.read() {
        loaded_for.set(room_id_str.clone());
        if let Some(rid) = room_id_str {
            spawn(async move {
                let client = { state.read().client.clone() };
                if let Some(client) = client {
                    if let Ok(room_id) = OwnedRoomId::try_from(rid.as_str()) {
                        if let Some(room) = client.get_room(&room_id) {
                            // Get pinned event IDs from room state
                            match room.pinned_event_ids() {
                                Some(pinned_ids) => {
                                    let mut list = Vec::new();
                                    for eid in pinned_ids.iter() {
                                        // Try to fetch each event's content
                                        match room.event(eid, None).await {
                                            Ok(timeline_event) => {
                                                let raw = timeline_event.raw();
                                                // Extract sender and body from the raw event
                                                let mut sender = String::new();
                                                let mut body = format!("Pinned event: {eid}");
                                                let mut ts = 0u64;

                                                if let Ok(val) = raw.deserialize_as::<serde_json::Value>() {
                                                    if let Some(s) = val.get("sender").and_then(|v| v.as_str()) {
                                                        sender = s.to_string();
                                                    }
                                                    if let Some(content) = val.get("content") {
                                                        if let Some(b) = content.get("body").and_then(|v| v.as_str()) {
                                                            body = b.to_string();
                                                        }
                                                    }
                                                    if let Some(origin_ts) = val.get("origin_server_ts").and_then(|v| v.as_u64()) {
                                                        ts = origin_ts / 1000; // ms to seconds
                                                    }
                                                }

                                                list.push(PinnedMessageData {
                                                    event_id: eid.to_string(),
                                                    sender_name: sender,
                                                    body,
                                                    timestamp: ts,
                                                });
                                            }
                                            Err(e) => {
                                                tracing::debug!("Could not fetch pinned event {eid}: {e}");
                                                list.push(PinnedMessageData {
                                                    event_id: eid.to_string(),
                                                    sender_name: String::new(),
                                                    body: format!("Pinned event: {eid}"),
                                                    timestamp: 0,
                                                });
                                            }
                                        }
                                    }
                                    pinned_messages.set(list);
                                }
                                None => {
                                    pinned_messages.set(Vec::new());
                                }
                            }
                        }
                    }
                }
            });
        }
    }

    let close_panel = move |_| {
        state.write().right_panel = RightPanelView::Closed;
    };

    let back_to_info = move |_| {
        state.write().right_panel = RightPanelView::RoomInfo;
    };

    let pinned_read = pinned_messages.read();

    rsx! {
        div {
            class: "pinned-messages-panel",

            // Header
            header {
                class: "pinned-messages-panel__header",
                button {
                    class: "pinned-messages-panel__back-btn",
                    title: "Back to room info",
                    onclick: back_to_info,
                    ""
                }
                h3 {
                    class: "pinned-messages-panel__title",
                    "Pinned Messages ({pinned_read.len()})"
                }
                button {
                    class: "pinned-messages-panel__close-btn",
                    title: "Close panel",
                    onclick: close_panel,
                    ""
                }
            }

            // Pinned messages list
            div {
                class: "pinned-messages-panel__list",

                if pinned_read.is_empty() {
                    div {
                        class: "pinned-messages-panel__empty",
                        p { "No pinned messages in this room." }
                    }
                }

                for msg in pinned_read.iter() {
                    PinnedMessageItem {
                        key: "{msg.event_id}",
                        event_id: msg.event_id.clone(),
                        sender_name: msg.sender_name.clone(),
                        body: msg.body.clone(),
                        timestamp: msg.timestamp,
                    }
                }
            }
        }
    }
}

/// Data for a single pinned message.
#[derive(Clone, Debug)]
struct PinnedMessageData {
    pub event_id: String,
    pub sender_name: String,
    pub body: String,
    pub timestamp: u64,
}

/// Renders a single pinned message entry.
#[component]
fn PinnedMessageItem(
    event_id: String,
    sender_name: String,
    body: String,
    timestamp: u64,
) -> Element {
    rsx! {
        div {
            class: "pinned-message-item",

            div {
                class: "pinned-message-item__header",
                if !sender_name.is_empty() {
                    span {
                        class: "pinned-message-item__sender",
                        "{sender_name}"
                    }
                }
                span {
                    class: "pinned-message-item__pin-icon",
                    "📌"
                }
            }

            p {
                class: "pinned-message-item__body",
                "{body}"
            }
        }
    }
}