synpad 0.1.0

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

/// Summary information about a room for the room list.
#[derive(Clone, Debug)]
pub struct RoomSummary {
    pub room_id: OwnedRoomId,
    pub display_name: String,
    pub avatar_url: Option<String>,
    pub topic: Option<String>,
    pub is_direct: bool,
    pub is_favorite: bool,
    pub is_encrypted: bool,
    pub is_tombstoned: bool,
    pub tombstone_successor: Option<OwnedRoomId>,
    pub tombstone_body: Option<String>,
    pub unread_count: u64,
    pub highlight_count: u64,
    pub last_activity_ts: Option<u64>,
    pub last_event_preview: Option<String>,
    pub last_event_sender: Option<String>,
    pub member_count: u64,
    pub typing_members: Vec<String>,
    pub notification_level: NotificationLevel,
    pub parent_spaces: Vec<OwnedRoomId>,
    pub membership: RoomMembership,
}

/// Room membership state.
#[derive(Clone, Debug, PartialEq)]
pub enum RoomMembership {
    Joined,
    Invited,
    Left,
    Knocked,
}

/// Per-room notification level.
#[derive(Clone, Debug, PartialEq)]
pub enum NotificationLevel {
    Default,
    AllMessages,
    MentionsAndKeywords,
    Mute,
}

impl Default for NotificationLevel {
    fn default() -> Self {
        Self::Default
    }
}

/// Room filter categories.
#[derive(Clone, Debug, PartialEq)]
pub enum RoomFilter {
    All,
    DirectMessages,
    Rooms,
    Favorites,
    Space(OwnedRoomId),
}

/// Room sort order.
#[derive(Clone, Debug, PartialEq)]
pub enum RoomSortOrder {
    Activity,
    Alphabetical,
    Unread,
}

/// A room member's information.
#[derive(Clone, Debug)]
pub struct RoomMember {
    pub user_id: OwnedUserId,
    pub display_name: Option<String>,
    pub avatar_url: Option<String>,
    pub power_level: i64,
    pub membership: MemberMembership,
    pub is_ignored: bool,
}

/// Member's membership state.
#[derive(Clone, Debug, PartialEq)]
pub enum MemberMembership {
    Join,
    Invite,
    Leave,
    Ban,
    Knock,
}

/// Timeline item for display.
#[derive(Clone, Debug)]
pub enum TimelineItemKind {
    /// A message or state event
    Event(EventTimelineData),
    /// A date separator
    DaySeparator(String),
    /// Read marker indicator
    ReadMarker,
    /// Loading indicator for pagination
    Loading,
}

/// Data for an event in the timeline.
#[derive(Clone, Debug)]
pub struct EventTimelineData {
    pub event_id: Option<OwnedEventId>,
    pub sender: OwnedUserId,
    pub sender_display_name: String,
    pub sender_avatar_url: Option<String>,
    pub timestamp: u64,
    pub content: TimelineContent,
    pub reactions: Vec<ReactionGroup>,
    pub is_edited: bool,
    pub reply_to: Option<Box<ReplyPreview>>,
    pub read_receipts: Vec<ReadReceipt>,
    pub send_state: SendState,
    pub is_own_message: bool,
}

/// The content of a timeline event.
#[derive(Clone, Debug, PartialEq)]
pub enum TimelineContent {
    /// Text message (plain or formatted)
    Text {
        body: String,
        formatted_body: Option<String>,
    },
    /// Image message
    Image {
        body: String,
        url: Option<String>,
        thumbnail_url: Option<String>,
        blurhash: Option<String>,
        width: Option<u32>,
        height: Option<u32>,
    },
    /// File message
    File {
        body: String,
        url: Option<String>,
        size: Option<u64>,
        mimetype: Option<String>,
    },
    /// Audio message
    Audio {
        body: String,
        url: Option<String>,
        duration_ms: Option<u64>,
    },
    /// Video message
    Video {
        body: String,
        url: Option<String>,
        thumbnail_url: Option<String>,
        width: Option<u32>,
        height: Option<u32>,
        duration_ms: Option<u64>,
    },
    /// Emote (/me) message
    Emote {
        body: String,
        formatted_body: Option<String>,
    },
    /// Notice (bot/server) message
    Notice {
        body: String,
        formatted_body: Option<String>,
    },
    /// State event (membership changes, room name changes, etc.)
    StateEvent { description: String },
    /// Redacted event
    Redacted { reason: Option<String> },
    /// Encrypted but unable to decrypt
    EncryptionError { message: String },
    /// Sticker message
    Sticker { body: String, url: Option<String> },
    /// Poll message
    Poll {
        question: String,
        answers: Vec<PollAnswer>,
        kind: PollKind,
        is_ended: bool,
    },
    /// Location message
    Location {
        body: String,
        geo_uri: String,
        description: Option<String>,
    },
    /// Voice message (audio with waveform)
    VoiceMessage {
        body: String,
        url: Option<String>,
        duration_ms: Option<u64>,
        waveform: Vec<u16>,
    },
}

/// Poll answer option.
#[derive(Clone, Debug, PartialEq)]
pub struct PollAnswer {
    pub id: String,
    pub text: String,
    pub vote_count: u32,
    pub voted_by_me: bool,
}

/// Poll kind (disclosed = results visible, undisclosed = results hidden until ended).
#[derive(Clone, Debug, PartialEq)]
pub enum PollKind {
    Disclosed,
    Undisclosed,
}

/// A group of reactions with the same key.
#[derive(Clone, Debug, PartialEq)]
pub struct ReactionGroup {
    pub key: String,
    pub count: u64,
    pub user_reacted: bool,
    pub senders: Vec<OwnedUserId>,
}

/// Reply preview data.
#[derive(Clone, Debug, PartialEq)]
pub struct ReplyPreview {
    pub event_id: OwnedEventId,
    pub sender_name: String,
    pub body: String,
}

/// Read receipt data.
#[derive(Clone, Debug, PartialEq)]
pub struct ReadReceipt {
    pub user_id: OwnedUserId,
    pub display_name: Option<String>,
    pub avatar_url: Option<String>,
    pub timestamp: Option<u64>,
}

/// Message send state.
#[derive(Clone, Debug, PartialEq)]
pub enum SendState {
    /// Not a local message
    None,
    /// Waiting to be sent
    Pending,
    /// Successfully sent
    Sent,
    /// Failed to send
    Failed(String),
}

/// Information about the message being edited (shared between timeline and composer).
#[derive(Clone, Debug, PartialEq)]
pub struct EditingMessage {
    pub event_id: OwnedEventId,
    pub room_id: OwnedRoomId,
    pub original_body: String,
}

/// Information about the message being replied to (shared between timeline and composer).
#[derive(Clone, Debug, PartialEq)]
pub struct ReplyingTo {
    pub event_id: OwnedEventId,
    pub sender_name: String,
    pub body: String,
    pub room_id: OwnedRoomId,
}

impl TimelineContent {
    /// Extract the plain-text body from any content variant.
    pub fn body_text(&self) -> String {
        match self {
            TimelineContent::Text { body, .. } => body.clone(),
            TimelineContent::Image { body, .. } => body.clone(),
            TimelineContent::File { body, .. } => body.clone(),
            TimelineContent::Audio { body, .. } => body.clone(),
            TimelineContent::Video { body, .. } => body.clone(),
            TimelineContent::Emote { body, .. } => body.clone(),
            TimelineContent::Notice { body, .. } => body.clone(),
            TimelineContent::StateEvent { description } => description.clone(),
            TimelineContent::Redacted { .. } => "Deleted message".to_string(),
            TimelineContent::EncryptionError { .. } => "Encrypted message".to_string(),
            TimelineContent::Sticker { body, .. } => body.clone(),
            TimelineContent::Poll { question, .. } => format!("Poll: {question}"),
            TimelineContent::Location { body, .. } => body.clone(),
            TimelineContent::VoiceMessage { body, .. } => body.clone(),
        }
    }
}

/// User presence status.
#[derive(Clone, Debug, PartialEq)]
pub enum PresenceStatus {
    Online,
    Unavailable,
    Offline,
}

/// Space information.
#[derive(Clone, Debug)]
pub struct SpaceSummary {
    pub room_id: OwnedRoomId,
    pub display_name: String,
    pub avatar_url: Option<String>,
    pub child_room_ids: Vec<OwnedRoomId>,
}