use crate::{SyncState, UnifiedContact, UnifiedEntityType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThreadSummary {
pub thread_id: String,
pub entity_id: Option<String>,
pub entity_type: Option<UnifiedEntityType>,
pub contact_id: Option<String>,
pub display_name: String,
pub last_message_preview: String,
pub last_message_timestamp: u64,
pub unread_count: u32,
pub is_muted: bool,
pub is_dm: bool,
pub typing_users: Vec<String>,
pub is_pinned: bool,
pub contact_presence: Option<PresenceStatus>,
pub sync_state: SyncState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub id: String,
pub thread_id: String,
pub sender_id: String,
pub sender_name: String,
pub text: String,
pub timestamp: u64,
pub edited: bool,
pub reply_to_id: Option<String>,
pub reactions: Vec<MessageReaction>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessageReaction {
pub emoji: String,
pub count: u32,
pub reacted_by_me: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PresenceStatus {
#[default]
Unknown,
Online,
Away,
Busy,
Offline,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContactWithPresence {
pub contact: UnifiedContact,
pub presence: PresenceStatus,
pub last_seen: Option<u64>,
pub is_in_call: bool,
pub call_entity_name: Option<String>,
pub is_screen_sharing: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchResult {
pub message: Message,
pub thread_id: String,
pub thread_name: String,
pub match_count: usize,
pub match_excerpt: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MessageSendStatus {
Sending,
Pending,
Failed(String),
}
impl MessageSendStatus {
pub fn is_sending(&self) -> bool {
matches!(self, Self::Sending)
}
pub fn is_pending(&self) -> bool {
matches!(self, Self::Pending)
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Failed(_))
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PendingMessage {
pub id: String,
pub thread_id: String,
pub text: String,
pub reply_to_id: Option<String>,
pub queued_at: u64,
pub retry_count: u32,
pub status: MessageSendStatus,
pub last_error: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thread_summary_equality() {
let t1 = ThreadSummary {
thread_id: "t1".to_string(),
entity_id: Some("e1".to_string()),
entity_type: Some(UnifiedEntityType::Channel),
contact_id: None,
display_name: "General".to_string(),
last_message_preview: "Hello".to_string(),
last_message_timestamp: 1234567890,
unread_count: 5,
is_muted: false,
is_dm: false,
typing_users: vec![],
is_pinned: false,
contact_presence: None,
sync_state: SyncState::default(),
};
let t2 = t1.clone();
assert_eq!(t1, t2);
}
#[test]
fn thread_summary_dm_thread() {
let dm = ThreadSummary {
thread_id: "dm:alice-bob-cat-dog".to_string(),
entity_id: None,
entity_type: None,
contact_id: Some("alice-bob-cat-dog".to_string()),
display_name: "Alice".to_string(),
last_message_preview: "Hey!".to_string(),
last_message_timestamp: 1234567890,
unread_count: 1,
is_muted: false,
is_dm: true,
typing_users: vec![],
is_pinned: false,
contact_presence: Some(PresenceStatus::Online),
sync_state: SyncState::Synced,
};
assert!(dm.is_dm);
assert!(dm.contact_id.is_some());
assert!(dm.entity_id.is_none());
assert_eq!(dm.contact_presence, Some(PresenceStatus::Online));
}
#[test]
fn message_with_reactions() {
let msg = Message {
id: "m1".to_string(),
thread_id: "t1".to_string(),
sender_id: "u1".to_string(),
sender_name: "Alice".to_string(),
text: "Hello world".to_string(),
timestamp: 1234567890,
edited: false,
reply_to_id: None,
reactions: vec![MessageReaction {
emoji: "👍".to_string(),
count: 3,
reacted_by_me: true,
}],
};
assert_eq!(msg.reactions.len(), 1);
assert!(msg.reactions[0].reacted_by_me);
}
#[test]
fn presence_status_default() {
let status = PresenceStatus::default();
assert_eq!(status, PresenceStatus::Unknown);
}
#[test]
fn contact_with_presence_construction() {
let contact = UnifiedContact {
id: "alice".to_string(),
display_name: "Alice".to_string(),
status: "available".to_string(),
presence: PresenceStatus::Online,
};
let cwp = ContactWithPresence {
contact: contact.clone(),
presence: PresenceStatus::Online,
last_seen: None,
is_in_call: false,
call_entity_name: None,
is_screen_sharing: false,
};
assert_eq!(cwp.contact.id, "alice");
assert_eq!(cwp.presence, PresenceStatus::Online);
assert!(!cwp.is_in_call);
assert!(cwp.call_entity_name.is_none());
assert!(!cwp.is_screen_sharing);
}
#[test]
fn contact_with_presence_in_call() {
let contact = UnifiedContact {
id: "bob".to_string(),
display_name: "Bob".to_string(),
status: "in_call".to_string(),
presence: PresenceStatus::Busy,
};
let cwp = ContactWithPresence {
contact,
presence: PresenceStatus::Busy,
last_seen: None,
is_in_call: true,
call_entity_name: Some("Team Standup".to_string()),
is_screen_sharing: false,
};
assert!(cwp.is_in_call);
assert_eq!(cwp.call_entity_name, Some("Team Standup".to_string()));
assert!(!cwp.is_screen_sharing);
}
#[test]
fn message_send_status_predicates() {
assert!(MessageSendStatus::Sending.is_sending());
assert!(!MessageSendStatus::Sending.is_pending());
assert!(!MessageSendStatus::Sending.is_failed());
assert!(!MessageSendStatus::Pending.is_sending());
assert!(MessageSendStatus::Pending.is_pending());
assert!(!MessageSendStatus::Pending.is_failed());
let failed = MessageSendStatus::Failed("Network error".to_string());
assert!(!failed.is_sending());
assert!(!failed.is_pending());
assert!(failed.is_failed());
}
#[test]
fn pending_message_construction() {
let pending = PendingMessage {
id: "pending-1".to_string(),
thread_id: "thread-1".to_string(),
text: "Hello offline".to_string(),
reply_to_id: None,
queued_at: 1234567890,
retry_count: 0,
status: MessageSendStatus::Pending,
last_error: None,
};
assert_eq!(pending.id, "pending-1");
assert_eq!(pending.thread_id, "thread-1");
assert!(pending.status.is_pending());
assert_eq!(pending.retry_count, 0);
}
}