synpad 0.1.0

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

use crate::state::app_state::AppState;

/// Voice broadcast state.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BroadcastState {
    Idle,
    Recording,
    Paused,
}

/// Voice broadcast component for live audio streaming in a room.
#[component]
pub fn VoiceBroadcast(room_id: String, on_close: EventHandler<()>) -> Element {
    let _state = use_context::<Signal<AppState>>();
    let mut broadcast_state = use_signal(|| BroadcastState::Idle);
    let elapsed_secs = use_signal(|| 0u32);
    let _listener_count = use_signal(|| 0u32);

    let on_start = {
        let rid = room_id.clone();
        move |_| {
            broadcast_state.set(BroadcastState::Recording);
            let rid = rid.clone();
            spawn(async move {
                tracing::info!("Started voice broadcast in room {rid}");
                // In full impl:
                // 1. Send io.element.voice_broadcast_info state event (started)
                // 2. Start audio recording
                // 3. Chunk audio into segments and send as voice messages
            });
        }
    };

    let elapsed = *elapsed_secs.read();
    let mins = elapsed / 60;
    let secs = elapsed % 60;

    rsx! {
        div {
            class: "voice-broadcast",
            h4 { "Voice Broadcast" }

            if *broadcast_state.read() == BroadcastState::Idle {
                div {
                    class: "voice-broadcast__start",
                    p { "Start a voice broadcast to share audio with everyone in this room in real-time." }
                    div {
                        class: "voice-broadcast__actions",
                        button { class: "btn btn--secondary", onclick: move |_| on_close.call(()), "Cancel" }
                        button { class: "btn btn--primary", onclick: on_start, "Start Broadcast" }
                    }
                }
            } else {
                div {
                    class: "voice-broadcast__live",
                    div {
                        class: "voice-broadcast__indicator",
                        if *broadcast_state.read() == BroadcastState::Recording {
                            span { class: "voice-broadcast__live-dot" }
                            span { "LIVE" }
                        } else {
                            span { "PAUSED" }
                        }
                    }
                    div {
                        class: "voice-broadcast__timer",
                        "{mins}:{secs:02}"
                    }
                    div {
                        class: "voice-broadcast__controls",
                        if *broadcast_state.read() == BroadcastState::Recording {
                            button {
                                class: "voice-broadcast__control-btn",
                                title: "Pause",
                                onclick: move |_| broadcast_state.set(BroadcastState::Paused),
                                "\u{23F8}"
                            }
                        } else {
                            button {
                                class: "voice-broadcast__control-btn",
                                title: "Resume",
                                onclick: move |_| broadcast_state.set(BroadcastState::Recording),
                                "\u{25B6}"
                            }
                        }
                        button {
                            class: "voice-broadcast__control-btn voice-broadcast__control-btn--stop",
                            title: "Stop broadcast",
                            onclick: move |_| {
                                broadcast_state.set(BroadcastState::Idle);
                                on_close.call(());
                                tracing::info!("Voice broadcast stopped");
                            },
                            "\u{23F9}"
                        }
                    }
                }
            }
        }
    }
}