synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use matrix_sdk::ruma::{OwnedRoomId, OwnedUserId};

/// VoIP call type.
#[derive(Clone, Debug, PartialEq)]
pub enum CallType {
    Voice,
    Video,
}

/// VoIP call direction.
#[derive(Clone, Debug, PartialEq)]
pub enum CallDirection {
    Outgoing,
    Incoming,
}

/// VoIP call status.
#[derive(Clone, Debug, PartialEq)]
pub enum CallStatus {
    /// No active call
    Idle,
    /// Ringing (outgoing or incoming)
    Ringing(CallDirection),
    /// Call is connecting
    Connecting,
    /// Call is active
    InCall,
    /// Call ended
    Ended(CallEndReason),
}

/// Reason a call ended.
#[derive(Clone, Debug, PartialEq)]
pub enum CallEndReason {
    HungUp,
    Declined,
    Timeout,
    Error(String),
    RemoteHangup,
}

/// A participant in a group call.
#[derive(Clone, Debug, PartialEq)]
pub struct CallParticipant {
    pub user_id: OwnedUserId,
    pub display_name: String,
    pub avatar_url: Option<String>,
    pub is_muted: bool,
    pub is_video_muted: bool,
    pub is_screen_sharing: bool,
    pub is_speaking: bool,
}

/// Global call state.
#[derive(Clone, Debug)]
pub struct CallState {
    /// Current call status
    pub status: CallStatus,
    /// Room the call is in
    pub room_id: Option<OwnedRoomId>,
    /// Call type (voice or video)
    pub call_type: CallType,
    /// Whether local audio is muted
    pub is_muted: bool,
    /// Whether local video is enabled
    pub is_video_enabled: bool,
    /// Whether screen sharing is active
    pub is_screen_sharing: bool,
    /// Call duration in seconds
    pub duration_secs: u64,
    /// Call ID (from m.call.invite)
    pub call_id: Option<String>,
    /// Participants (for group calls)
    pub participants: Vec<CallParticipant>,
    /// Whether the local client has put the call on hold
    pub is_on_hold: bool,
    /// Whether this is a group call (Element Call / Jitsi)
    pub is_group_call: bool,
    /// Element Call widget URL (for group calls)
    pub widget_url: Option<String>,
}

impl Default for CallState {
    fn default() -> Self {
        Self {
            status: CallStatus::Idle,
            room_id: None,
            call_type: CallType::Voice,
            is_muted: false,
            is_video_enabled: false,
            is_screen_sharing: false,
            duration_secs: 0,
            call_id: None,
            participants: Vec::new(),
            is_on_hold: false,
            is_group_call: false,
            widget_url: None,
        }
    }
}

impl CallState {
    /// Whether there is an active call (ringing, connecting, or in call).
    pub fn is_active(&self) -> bool {
        !matches!(self.status, CallStatus::Idle | CallStatus::Ended(_))
    }

    /// Format call duration as MM:SS.
    pub fn format_duration(&self) -> String {
        let mins = self.duration_secs / 60;
        let secs = self.duration_secs % 60;
        format!("{mins:02}:{secs:02}")
    }
}