diff --git a/src/chat.rs b/src/chat.rs index 43a6dd865..d9c6c4a56 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -422,6 +422,7 @@ pub struct Chat { blocked: Blocked, pub param: Params, is_sending_locations: bool, + pub mute_duration: MuteDuration, } impl Chat { @@ -429,7 +430,7 @@ impl Chat { pub fn load_from_db(context: &Context, chat_id: ChatId) -> Result { let res = context.sql.query_row( "SELECT c.type, c.name, c.grpid, c.param, c.archived, - c.blocked, c.locations_send_until + c.blocked, c.locations_send_until, c.muted_until FROM chats c WHERE c.id=?;", params![chat_id], @@ -443,6 +444,7 @@ impl Chat { archived: row.get(4)?, blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, + mute_duration: MuteDuration::deserialize(row.get(7)?), }; Ok(c) }, @@ -658,6 +660,7 @@ impl Chat { profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new), subtitle: self.get_subtitle(context), draft, + is_muted: self.is_muted(), }) } @@ -684,6 +687,14 @@ impl Chat { self.is_sending_locations } + pub fn is_muted(&self) -> bool { + match self.mute_duration { + MuteDuration::NotMuted => false, + MuteDuration::Forever => true, + MuteDuration::MutedUntilTimestamp(timestamp) => timestamp > time(), + } + } + fn prepare_msg_raw( &mut self, context: &Context, @@ -968,6 +979,11 @@ pub struct ChatInfo { /// which contain non-text parts. Perhaps it should be a /// simple `has_draft` bool instead. pub draft: String, + + /// Wether the chat is muted + /// + /// The exact time its muted can be found out via the `chat.mute_duration` property + pub is_muted:bool, // ToDo: // - [ ] deaddrop, // - [ ] summary, @@ -1901,6 +1917,56 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result i64 { + match &self { + MuteDuration::NotMuted => 0, + MuteDuration::Forever => 1, + MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, // TODO make this pretier? + } + } + + fn deserialize (value: i64) -> MuteDuration { + match value { + 0 => MuteDuration::NotMuted, + 1 => MuteDuration::Forever, + _ => MuteDuration::MutedUntilTimestamp(value), + } + } +} + +pub fn set_muted ( + context: &Context, + chat_id: ChatId, + duration: MuteDuration +) -> Result<(), Error>{ + let mut success = false; + ensure!(!chat_id.is_special(), "Invalid chat ID"); + if real_group_exists(context, chat_id) && sql::execute( + context, + &context.sql, + "UPDATE chats SET muted_until=? WHERE id=?;", + params![duration.serialize(), chat_id], + ).is_ok() { + context.call_cb(Event::ChatModified(chat_id)); + success = true; + } + + if !success { + bail!("Failed to set name"); + } + + Ok(()) +} + pub fn remove_contact_from_chat( context: &Context, chat_id: ChatId, @@ -2386,6 +2452,7 @@ pub fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { #[cfg(test)] mod tests { use super::*; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::contact::Contact; use crate::test_utils::*; @@ -2413,7 +2480,8 @@ mod tests { "color": 15895624, "profile_image": "", "subtitle": "bob@example.com", - "draft": "" + "draft": "", + "is_muted": false } "#; @@ -2777,4 +2845,41 @@ mod tests { assert!(chat_id.set_selfavatar_timestamp(&t.ctx, time()).is_ok()); assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); } + + #[test] + fn test_set_mute_duration() { + let t = dummy_context(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + // Initial + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + // Forever + set_muted(&t.ctx, chat_id, MuteDuration::Forever).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + true + ); + // unMute + set_muted(&t.ctx, chat_id, MuteDuration::NotMuted).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + // Timed in the future + let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + Duration::from_secs(3600); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(current_timestamp.as_secs() as i64)).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + true + ); + // Time in the past + let past_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() - Duration::from_secs(3600); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(past_timestamp.as_secs() as i64)).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + } } diff --git a/src/sql.rs b/src/sql.rs index e722d539c..181be75df 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -885,6 +885,14 @@ fn open( update_icons = true; sql.set_raw_config_int(context, "dbversion", 61)?; } + if dbversion < 62 { + info!(context, "[migration] v62"); + sql.execute( + "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", + NO_PARAMS, + )?; + sql.set_raw_config_int(context, "dbversion", 62)?; + } // (2) updates that require high-level objects // (the structure is complete now and all objects are usable)