1use crate::{SyncState, UnifiedContact, UnifiedEntityType};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ThreadSummary {
10 pub thread_id: String,
12 pub entity_id: Option<String>,
14 pub entity_type: Option<UnifiedEntityType>,
16 pub contact_id: Option<String>,
18 pub display_name: String,
20 pub last_message_preview: String,
22 pub last_message_timestamp: u64,
24 pub unread_count: u32,
26 pub is_muted: bool,
28 pub is_dm: bool,
30 pub typing_users: Vec<String>,
32 pub is_pinned: bool,
34 pub contact_presence: Option<PresenceStatus>,
36 pub sync_state: SyncState,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Message {
43 pub id: String,
45 pub thread_id: String,
47 pub sender_id: String,
49 pub sender_name: String,
51 pub text: String,
53 pub timestamp: u64,
55 pub edited: bool,
57 pub reply_to_id: Option<String>,
59 pub reactions: Vec<MessageReaction>,
61 pub is_pinned: bool,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct MessageReaction {
68 pub emoji: String,
70 pub count: u32,
72 pub reacted_by_me: bool,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub enum PresenceStatus {
79 #[default]
81 Unknown,
82 Online,
84 Away,
86 Busy,
88 Offline,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct ContactWithPresence {
95 pub contact: UnifiedContact,
97 pub presence: PresenceStatus,
99 pub last_seen: Option<u64>,
101 pub is_in_call: bool,
103 pub call_entity_name: Option<String>,
105 pub is_screen_sharing: bool,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct SearchResult {
112 pub message: Message,
114 pub thread_id: String,
116 pub thread_name: String,
118 pub match_count: usize,
120 pub match_excerpt: String,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
126pub enum MessageSendStatus {
127 Sending,
129 Pending,
131 Failed(String),
133}
134
135impl MessageSendStatus {
136 pub fn is_sending(&self) -> bool {
138 matches!(self, Self::Sending)
139 }
140
141 pub fn is_pending(&self) -> bool {
143 matches!(self, Self::Pending)
144 }
145
146 pub fn is_failed(&self) -> bool {
148 matches!(self, Self::Failed(_))
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
154pub struct PendingMessage {
155 pub id: String,
157 pub thread_id: String,
159 pub text: String,
161 pub reply_to_id: Option<String>,
163 pub queued_at: u64,
165 pub retry_count: u32,
167 pub status: MessageSendStatus,
169 pub last_error: Option<String>,
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn thread_summary_equality() {
179 let t1 = ThreadSummary {
180 thread_id: "t1".to_string(),
181 entity_id: Some("e1".to_string()),
182 entity_type: Some(UnifiedEntityType::Channel),
183 contact_id: None,
184 display_name: "General".to_string(),
185 last_message_preview: "Hello".to_string(),
186 last_message_timestamp: 1234567890,
187 unread_count: 5,
188 is_muted: false,
189 is_dm: false,
190 typing_users: vec![],
191 is_pinned: false,
192 contact_presence: None,
193 sync_state: SyncState::default(),
194 };
195 let t2 = t1.clone();
196 assert_eq!(t1, t2);
197 }
198
199 #[test]
200 fn thread_summary_dm_thread() {
201 let dm = ThreadSummary {
202 thread_id: "dm:alice-bob-cat-dog".to_string(),
203 entity_id: None,
204 entity_type: None,
205 contact_id: Some("alice-bob-cat-dog".to_string()),
206 display_name: "Alice".to_string(),
207 last_message_preview: "Hey!".to_string(),
208 last_message_timestamp: 1234567890,
209 unread_count: 1,
210 is_muted: false,
211 is_dm: true,
212 typing_users: vec![],
213 is_pinned: false,
214 contact_presence: Some(PresenceStatus::Online),
215 sync_state: SyncState::Synced,
216 };
217 assert!(dm.is_dm);
218 assert!(dm.contact_id.is_some());
219 assert!(dm.entity_id.is_none());
220 assert_eq!(dm.contact_presence, Some(PresenceStatus::Online));
221 }
222
223 #[test]
224 fn message_with_reactions() {
225 let msg = Message {
226 id: "m1".to_string(),
227 thread_id: "t1".to_string(),
228 sender_id: "u1".to_string(),
229 sender_name: "Alice".to_string(),
230 text: "Hello world".to_string(),
231 timestamp: 1234567890,
232 edited: false,
233 reply_to_id: None,
234 reactions: vec![MessageReaction {
235 emoji: "👍".to_string(),
236 count: 3,
237 reacted_by_me: true,
238 }],
239 is_pinned: false,
240 };
241 assert_eq!(msg.reactions.len(), 1);
242 assert!(msg.reactions[0].reacted_by_me);
243 }
244
245 #[test]
246 fn presence_status_default() {
247 let status = PresenceStatus::default();
248 assert_eq!(status, PresenceStatus::Unknown);
249 }
250
251 #[test]
252 fn contact_with_presence_construction() {
253 let contact = UnifiedContact {
254 id: "alice".to_string(),
255 display_name: "Alice".to_string(),
256 status: "available".to_string(),
257 presence: PresenceStatus::Online,
258 };
259 let cwp = ContactWithPresence {
260 contact: contact.clone(),
261 presence: PresenceStatus::Online,
262 last_seen: None,
263 is_in_call: false,
264 call_entity_name: None,
265 is_screen_sharing: false,
266 };
267 assert_eq!(cwp.contact.id, "alice");
268 assert_eq!(cwp.presence, PresenceStatus::Online);
269 assert!(!cwp.is_in_call);
270 assert!(cwp.call_entity_name.is_none());
271 assert!(!cwp.is_screen_sharing);
272 }
273
274 #[test]
275 fn contact_with_presence_in_call() {
276 let contact = UnifiedContact {
277 id: "bob".to_string(),
278 display_name: "Bob".to_string(),
279 status: "in_call".to_string(),
280 presence: PresenceStatus::Busy,
281 };
282 let cwp = ContactWithPresence {
283 contact,
284 presence: PresenceStatus::Busy,
285 last_seen: None,
286 is_in_call: true,
287 call_entity_name: Some("Team Standup".to_string()),
288 is_screen_sharing: false,
289 };
290 assert!(cwp.is_in_call);
291 assert_eq!(cwp.call_entity_name, Some("Team Standup".to_string()));
292 assert!(!cwp.is_screen_sharing);
293 }
294
295 #[test]
296 fn message_send_status_predicates() {
297 assert!(MessageSendStatus::Sending.is_sending());
298 assert!(!MessageSendStatus::Sending.is_pending());
299 assert!(!MessageSendStatus::Sending.is_failed());
300
301 assert!(!MessageSendStatus::Pending.is_sending());
302 assert!(MessageSendStatus::Pending.is_pending());
303 assert!(!MessageSendStatus::Pending.is_failed());
304
305 let failed = MessageSendStatus::Failed("Network error".to_string());
306 assert!(!failed.is_sending());
307 assert!(!failed.is_pending());
308 assert!(failed.is_failed());
309 }
310
311 #[test]
312 fn pending_message_construction() {
313 let pending = PendingMessage {
314 id: "pending-1".to_string(),
315 thread_id: "thread-1".to_string(),
316 text: "Hello offline".to_string(),
317 reply_to_id: None,
318 queued_at: 1234567890,
319 retry_count: 0,
320 status: MessageSendStatus::Pending,
321 last_error: None,
322 };
323 assert_eq!(pending.id, "pending-1");
324 assert_eq!(pending.thread_id, "thread-1");
325 assert!(pending.status.is_pending());
326 assert_eq!(pending.retry_count, 0);
327 }
328}