synpad 0.1.0

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

use crate::components::avatar::Avatar;
use crate::room::call::call_state::{CallDirection, CallEndReason, CallStatus, CallType};
use crate::room::call::element_call::ElementCallWidget;
use crate::state::app_state::AppState;

/// Full-screen call overlay shown during active calls.
#[component]
pub fn CallView() -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let call = state.read().call_state.clone();

    if !call.is_active() {
        return rsx! {};
    }

    if call.is_group_call || call.widget_url.is_some() {
        if let Some(room_id) = call.room_id.as_ref() {
            return rsx! {
                ElementCallWidget {
                    room_id: room_id.to_string(),
                }
            };
        }
    }

    let room_name = call
        .room_id
        .as_ref()
        .and_then(|rid| {
            state
                .read()
                .rooms
                .get(rid)
                .map(|r| r.display_name.clone())
        })
        .unwrap_or_else(|| "Unknown".to_string());

    let is_video = matches!(call.call_type, CallType::Video);
    let is_ringing = matches!(call.status, CallStatus::Ringing(_));
    let is_connecting = matches!(call.status, CallStatus::Connecting);
    let is_incoming = matches!(call.status, CallStatus::Ringing(CallDirection::Incoming));
    let duration = call.format_duration();
    let is_muted = call.is_muted;
    let is_video_enabled = call.is_video_enabled;
    let is_screen_sharing = call.is_screen_sharing;
    let is_on_hold = call.is_on_hold;

    let status_text = match &call.status {
        CallStatus::Ringing(CallDirection::Outgoing) => "Ringing...".to_string(),
        CallStatus::Ringing(CallDirection::Incoming) => "Incoming call...".to_string(),
        CallStatus::Connecting => "Connecting...".to_string(),
        CallStatus::InCall if is_on_hold => "On hold".to_string(),
        CallStatus::InCall => duration.clone(),
        CallStatus::Ended(reason) => match reason {
            CallEndReason::HungUp => "Call ended".to_string(),
            CallEndReason::Declined => "Call declined".to_string(),
            CallEndReason::Timeout => "No answer".to_string(),
            CallEndReason::RemoteHangup => "Call ended by remote".to_string(),
            CallEndReason::Error(e) => format!("Call error: {e}"),
        },
        CallStatus::Idle => String::new(),
    };

    rsx! {
        div {
            class: if is_video { "call-view call-view--video" } else { "call-view call-view--voice" },

            // Call info header
            div {
                class: "call-view__header",
                h3 { class: "call-view__room-name", "{room_name}" }
                span {
                    class: "call-view__status",
                    "{status_text}"
                }
            }

            // Video area (placeholder)
            if is_video {
                div {
                    class: "call-view__video-area",
                    // Remote video (placeholder)
                    div {
                        class: "call-view__remote-video",
                        if is_ringing || is_connecting {
                            div {
                                class: "call-view__connecting-spinner",
                                div { class: "spinner spinner--lg" }
                            }
                        } else {
                            div {
                                class: "call-view__video-placeholder",
                                Avatar {
                                    name: room_name.clone(),
                                    url: None::<String>,
                                    size: 120,
                                }
                            }
                        }
                    }
                    // Local video (small overlay)
                    if is_video_enabled {
                        div {
                            class: "call-view__local-video",
                            div {
                                class: "call-view__video-placeholder call-view__video-placeholder--small",
                                "You"
                            }
                        }
                    }
                }
            } else {
                // Voice call - avatar display
                div {
                    class: "call-view__voice-area",
                    Avatar {
                        name: room_name.clone(),
                        url: None::<String>,
                        size: 120,
                    }
                }
            }

            // Participants list (for group calls)
            if !call.participants.is_empty() {
                div {
                    class: "call-view__participants",
                    {
                        let pcount = call.participants.len();
                        rsx! { h4 { "Participants ({pcount})" } }
                    }
                    for participant in call.participants.iter() {
                        {
                            let pname = participant.display_name.clone();
                            let is_muted = participant.is_muted;
                            let is_speaking = participant.is_speaking;
                            rsx! {
                                div {
                                    class: if is_speaking {
                                        "call-view__participant call-view__participant--speaking"
                                    } else {
                                        "call-view__participant"
                                    },
                                    Avatar {
                                        name: pname.clone(),
                                        url: participant.avatar_url.clone(),
                                        size: 32,
                                    }
                                    span { class: "call-view__participant-name", "{pname}" }
                                    if is_muted {
                                        span { class: "call-view__participant-muted", "🔇" }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Call controls
            div {
                class: "call-view__controls",

                // Incoming call: accept/decline
                if is_incoming {
                    button {
                        class: "call-view__btn call-view__btn--accept",
                        title: "Accept call",
                        onclick: move |_| {
                            let mut s = state.write();
                            s.call_state.status = CallStatus::InCall;
                        },
                        "Accept"
                    }
                    button {
                        class: "call-view__btn call-view__btn--decline",
                        title: "Decline call",
                        onclick: move |_| {
                            let mut s = state.write();
                            s.call_state.status = CallStatus::Ended(CallEndReason::Declined);
                        },
                        "Decline"
                    }
                }

                // Active call controls
                if !is_incoming {
                    // Mute toggle
                    button {
                        class: if is_muted { "call-view__btn call-view__btn--active" } else { "call-view__btn" },
                        title: if is_muted { "Unmute" } else { "Mute" },
                        onclick: move |_| {
                            let mut s = state.write();
                            s.call_state.is_muted = !s.call_state.is_muted;
                        },
                        if is_muted { "🔇" } else { "🎤" }
                    }

                    // Video toggle (if video call)
                    if is_video {
                        button {
                            class: if !is_video_enabled { "call-view__btn call-view__btn--active" } else { "call-view__btn" },
                            title: if is_video_enabled { "Turn off camera" } else { "Turn on camera" },
                            onclick: move |_| {
                                let mut s = state.write();
                                s.call_state.is_video_enabled = !s.call_state.is_video_enabled;
                            },
                            if is_video_enabled { "📹" } else { "📷" }
                        }
                    }

                    // Screen share toggle
                    button {
                        class: if is_screen_sharing { "call-view__btn call-view__btn--active" } else { "call-view__btn" },
                        title: if is_screen_sharing { "Stop sharing" } else { "Share screen" },
                        onclick: move |_| {
                            let mut s = state.write();
                            s.call_state.is_screen_sharing = !s.call_state.is_screen_sharing;
                        },
                        "🖥"
                    }

                    // Hang up
                    button {
                        class: "call-view__btn call-view__btn--hangup",
                        title: "Hang up",
                        onclick: move |_| {
                            let mut s = state.write();
                            s.call_state.status = CallStatus::Ended(CallEndReason::HungUp);
                        },
                        "End"
                    }
                }
            }
        }
    }
}