diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index a3f8f785a..311106525 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -918,7 +918,7 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha * or "Not now". * The UI can also offer a "Close" button that calls dc_marknoticed_contact() then. * - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has - * archived _any_ chat using dc_archive_chat(). The UI should show a link as + * archived _any_ chat using dc_set_chat_visibility(). The UI should show a link as * "Show archived chats", if the user clicks this item, the UI should show a * list of all archived chats that can be created by this function hen using * the DC_GCL_ARCHIVED_ONLY flag. @@ -1372,25 +1372,18 @@ uint32_t dc_get_next_media (dc_context_t* context, uint32_t ms /** - * Archive or unarchive a chat. + * Set chat visibility to pinned, archived or normal. * - * Archived chats are not included in the default chatlist returned - * by dc_get_chatlist(). Instead, if there are _any_ archived chats, - * the pseudo-chat with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added the the - * end of the chatlist. - * - * - To get a list of archived chats, use dc_get_chatlist() with the flag DC_GCL_ARCHIVED_ONLY. - * - To find out the archived state of a given chat, use dc_chat_get_archived() - * - Messages in archived chats are marked as being noticed, so they do not count as "fresh" - * - Calling this function usually results in the event #DC_EVENT_MSGS_CHANGED + * Calling this function usually results in the event #DC_EVENT_MSGS_CHANGED + * See @ref DC_CHAT_VISIBILITY for detailed information about the visibilities. * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). - * @param chat_id The ID of the chat to archive or unarchive. - * @param archive 1=archive chat, 0=unarchive chat, all other values are reserved for future use + * @param chat_id The ID of the chat to change the visibility for. + * @param visibility one of @ref DC_CHAT_VISIBILITY * @return None. */ -void dc_archive_chat (dc_context_t* context, uint32_t chat_id, int archive); +void dc_set_chat_visibility (dc_context_t* context, uint32_t chat_id, int visibility); /** @@ -2846,21 +2839,14 @@ uint32_t dc_chat_get_color (const dc_chat_t* chat); /** - * Get archived state. - * - * - 0 = normal chat, not archived, not sticky. - * - 1 = chat archived - * - 2 = chat sticky (reserved for future use, if you do not support this value, just treat the chat as a normal one) - * - * To archive or unarchive chats, use dc_archive_chat(). - * If chats are archived, this should be shown in the UI by a little icon or text, - * eg. the search will also return archived chats. + * Get visibility of chat. + * See @ref DC_CHAT_VISIBILITY for detailed information about the visibilities. * * @memberof dc_chat_t * @param chat The chat object. - * @return Archived state. + * @return One of @ref DC_CHAT_VISIBILITY */ -int dc_chat_get_archived (const dc_chat_t* chat); +int dc_chat_get_visibility (const dc_chat_t* chat); /** @@ -3773,7 +3759,7 @@ int dc_contact_is_verified (dc_contact_t* contact); * accessor functions. If no provider info is found, NULL will be * returned. */ -dc_provider_t* dc_provider_new_from_email (const dc_context_t*, const char* email); +dc_provider_t* dc_provider_new_from_email (const dc_context_t* context, const char* email); /** @@ -4532,6 +4518,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); #define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore char* dc_get_version_str (void); // deprecated void dc_array_add_id (dc_array_t*, uint32_t); // deprecated +#define dc_archive_chat(a,b,c) dc_set_chat_visibility((a), (b), (c)? 1 : 0) // not used anymore +#define dc_chat_get_archived(a) (dc_chat_get_visibility((a))==1? 1 : 0) // not used anymore /* @@ -4597,6 +4585,48 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca */ +/** + * @defgroup DC_CHAT_VISIBILITY DC_CHAT_VISIBILITY + * + * These constants describe the visibility of a chat. + * The chat visibiliry can be get using dc_chat_get_visibility() + * and set using dc_set_chat_visibility(). + * + * @addtogroup DC_CHAT_VISIBILITY + * @{ + */ + +/** + * Chats with normal visibility are not archived and are shown below all pinned chats. + * Archived chats, that receive new messages automatically become normal chats. + */ +#define DC_CHAT_VISIBILITY_NORMAL 0 + +/** + * Archived chats are not included in the default chatlist returned by dc_get_chatlist(). + * Instead, if there are _any_ archived chats, the pseudo-chat + * with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added the the end of the chatlist. + * + * The UI typically shows a little icon or chats beside archived chats in the chatlist, + * this is needed as eg. the search will also return archived chats. + * + * If archived chats receive new messages, they become normal chats again. + * + * To get a list of archived chats, use dc_get_chatlist() with the flag DC_GCL_ARCHIVED_ONLY. + */ +#define DC_CHAT_VISIBILITY_ARCHIVED 1 + +/** + * Pinned chats are included in the default chatlist. moreover, + * they are always the first items, whether they have fresh messages or not. + */ +#define DC_CHAT_VISIBILITY_PINNED 2 + +/** + * @} + */ + + /* * TODO: Strings need some doumentation about used placeholders. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 0a7b9e832..da6913433 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,8 +25,7 @@ use std::time::{Duration, SystemTime}; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; -use deltachat::chat::ChatId; -use deltachat::chat::MuteDuration; +use deltachat::chat::{ChatId, ChatVisibility, MuteDuration}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; @@ -1189,28 +1188,32 @@ pub unsafe extern "C" fn dc_get_next_media( } #[no_mangle] -pub unsafe extern "C" fn dc_archive_chat( +pub unsafe extern "C" fn dc_set_chat_visibility( context: *mut dc_context_t, chat_id: u32, archive: libc::c_int, ) { if context.is_null() { - eprintln!("ignoring careless call to dc_archive_chat()"); + eprintln!("ignoring careless call to dc_set_chat_visibility()"); return; } let ffi_context = &*context; - let archive = if archive == 0 { - false - } else if archive == 1 { - true - } else { - return; + let visibility = match archive { + 0 => ChatVisibility::Normal, + 1 => ChatVisibility::Archived, + 2 => ChatVisibility::Pinned, + _ => { + ffi_context.warning( + "ignoring careless call to dc_set_chat_visibility(): unknown archived state", + ); + return; + } }; ffi_context .with_inner(|ctx| { ChatId::new(chat_id) - .set_archived(ctx, archive) - .log_err(ffi_context, "Failed archive chat") + .set_visibility(ctx, visibility) + .log_err(ffi_context, "Failed setting chat visibility") .unwrap_or(()) }) .unwrap_or(()) @@ -2454,13 +2457,17 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { } #[no_mangle] -pub unsafe extern "C" fn dc_chat_get_archived(chat: *mut dc_chat_t) -> libc::c_int { +pub unsafe extern "C" fn dc_chat_get_visibility(chat: *mut dc_chat_t) -> libc::c_int { if chat.is_null() { - eprintln!("ignoring careless call to dc_chat_get_archived()"); + eprintln!("ignoring careless call to dc_chat_get_visibility()"); return 0; } let ffi_chat = &*chat; - ffi_chat.chat.is_archived() as libc::c_int + match ffi_chat.chat.visibility { + ChatVisibility::Normal => 0, + ChatVisibility::Archived => 1, + ChatVisibility::Pinned => 2, + } } #[no_mangle] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index a788fda01..b065f5845 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,7 +1,7 @@ use std::path::Path; use std::str::FromStr; -use deltachat::chat::{self, Chat, ChatId}; +use deltachat::chat::{self, Chat, ChatId, ChatVisibility}; use deltachat::chatlist::*; use deltachat::constants::*; use deltachat::contact::*; @@ -371,6 +371,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { listmedia\n\ archive \n\ unarchive \n\ + pin \n\ + unpin \n\ delchat \n\ ===========================Message commands==\n\ listmsgs \n\ @@ -511,14 +513,19 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { for i in (0..cnt).rev() { let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; println!( - "{}#{}: {} [{} fresh]", + "{}#{}: {} [{} fresh] {}", chat_prefix(&chat), chat.get_id(), chat.get_name(), chat.get_id().get_fresh_msg_cnt(context), + match chat.visibility { + ChatVisibility::Normal => "", + ChatVisibility::Archived => "📦", + ChatVisibility::Pinned => "📌", + }, ); let lot = chatlist.get_summary(context, i, Some(&chat)); - let statestr = if chat.is_archived() { + let statestr = if chat.visibility == ChatVisibility::Archived { " [Archived]" } else { match lot.get_state() { @@ -842,10 +849,18 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } print!("\n"); } - "archive" | "unarchive" => { + "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.set_archived(context, arg0 == "archive")?; + chat_id.set_visibility( + context, + match arg0 { + "archive" => ChatVisibility::Archived, + "unarchive" | "unpin" => ChatVisibility::Normal, + "pin" => ChatVisibility::Pinned, + _ => panic!("Unexpected command (This should never happen)"), + }, + )?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -969,7 +984,10 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "setqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - set_config_from_qr(context, arg1); + match set_config_from_qr(context, arg1) { + Ok(()) => println!("Config set from QR code, you can now call 'configure'"), + Err(err) => println!("Cannot set config from QR code: {:?}", err), + } } "providerinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 25a2ab166..87a0a9397 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -263,7 +263,7 @@ const DB_COMMANDS: [&str; 11] = [ "housekeeping", ]; -const CHAT_COMMANDS: [&str; 24] = [ +const CHAT_COMMANDS: [&str; 26] = [ "listchats", "listarchived", "chat", @@ -287,6 +287,8 @@ const CHAT_COMMANDS: [&str; 24] = [ "listmedia", "archive", "unarchive", + "pin", + "unpin", "delchat", ]; const MESSAGE_COMMANDS: [&str; 8] = [ diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index c2517f156..09883f452 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -423,7 +423,7 @@ class Chat(object): """return True if this chat is archived. :returns: True if archived. """ - return lib.dc_chat_get_archived(self._dc_chat) + return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED def enable_sending_locations(self, seconds): """enable sending locations for this chat. diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index baeaa2c11..189943268 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -33,6 +33,9 @@ DC_CHAT_TYPE_UNDEFINED = 0 DC_CHAT_TYPE_SINGLE = 100 DC_CHAT_TYPE_GROUP = 120 DC_CHAT_TYPE_VERIFIED_GROUP = 130 +DC_CHAT_VISIBILITY_NORMAL = 0 +DC_CHAT_VISIBILITY_ARCHIVED = 1 +DC_CHAT_VISIBILITY_PINNED = 2 DC_MSG_ID_MARKER1 = 1 DC_MSG_ID_DAYMARKER = 9 DC_MSG_ID_LAST_SPECIAL = 9 diff --git a/src/chat.rs b/src/chat.rs index b4e7c81d8..745739a34 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -137,14 +137,18 @@ impl ChatId { } /// Archives or unarchives a chat. - pub fn set_archived(self, context: &Context, new_archived: bool) -> Result<(), Error> { + pub fn set_visibility( + self, + context: &Context, + visibility: ChatVisibility, + ) -> Result<(), Error> { ensure!( !self.is_special(), "bad chat_id, can not be special chat: {}", self ); - if new_archived { + if visibility == ChatVisibility::Archived { sql::execute( context, &context.sql, @@ -157,7 +161,7 @@ impl ChatId { context, &context.sql, "UPDATE chats SET archived=? WHERE id=?;", - params![new_archived, self], + params![visibility, self], )?; context.call_cb(Event::MsgsChanged { @@ -168,13 +172,13 @@ impl ChatId { Ok(()) } - // note that unarchive() is not the same as set_archived(false) - - // eg. unarchive() does not send events as done for set_archived(false). + // note that unarchive() is not the same as set_visibility(Normal) - + // eg. unarchive() does not modify pinned chats and does not send events. pub fn unarchive(self, context: &Context) -> Result<(), Error> { sql::execute( context, &context.sql, - "UPDATE chats SET archived=0 WHERE id=?", + "UPDATE chats SET archived=0 WHERE id=? and archived=1", params![self], )?; Ok(()) @@ -421,7 +425,7 @@ pub struct Chat { pub id: ChatId, pub typ: Chattype, pub name: String, - archived: bool, + pub visibility: ChatVisibility, pub grpid: String, blocked: Blocked, pub param: Params, @@ -445,7 +449,7 @@ impl Chat { name: row.get::<_, String>(1)?, grpid: row.get::<_, String>(2)?, param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - archived: row.get(4)?, + visibility: row.get(4)?, blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, mute_duration: row.get(7)?, @@ -656,7 +660,7 @@ impl Chat { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: self.archived, + archived: self.visibility == ChatVisibility::Archived, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -668,9 +672,8 @@ impl Chat { }) } - /// Returns true if the chat is archived. - pub fn is_archived(&self) -> bool { - self.archived + pub fn get_visibility(&self) -> ChatVisibility { + self.visibility } pub fn is_unpromoted(&self) -> bool { @@ -928,6 +931,40 @@ impl Chat { } } +#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)] +pub enum ChatVisibility { + Normal, + Archived, + Pinned, +} + +impl rusqlite::types::ToSql for ChatVisibility { + fn to_sql(&self) -> rusqlite::Result { + let visibility = match &self { + ChatVisibility::Normal => 0, + ChatVisibility::Archived => 1, + ChatVisibility::Pinned => 2, + }; + let val = rusqlite::types::Value::Integer(visibility); + let out = rusqlite::types::ToSqlOutput::Owned(val); + Ok(out) + } +} + +impl rusqlite::types::FromSql for ChatVisibility { + fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { + i64::column_result(value).and_then(|val| { + match val { + 2 => Ok(ChatVisibility::Pinned), + 1 => Ok(ChatVisibility::Archived), + 0 => Ok(ChatVisibility::Normal), + // fallback to to Normal for unknown values, may happen eg. on imports created by a newer version. + _ => Ok(ChatVisibility::Normal), + } + }) + } +} + /// The current state of a chat. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[non_exhaustive] @@ -2560,7 +2597,7 @@ mod tests { let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); - assert!(!chat.archived); + assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); @@ -2574,7 +2611,7 @@ mod tests { assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); - assert!(!chat.archived); + assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); @@ -2780,35 +2817,136 @@ mod tests { assert_eq!(DC_GCL_NO_SPECIALS, 0x02); // archive first chat - assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id1 + .set_visibility(&t.ctx, ChatVisibility::Archived) + .is_ok()); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ChatVisibility::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ChatVisibility::Normal + ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); // archive second chat - assert!(chat_id2.set_archived(&t.ctx, true).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id2 + .set_visibility(&t.ctx, ChatVisibility::Archived) + .is_ok()); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ChatVisibility::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ChatVisibility::Archived + ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2); // archive already archived first chat, unarchive second chat two times - assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); - assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); - assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id1 + .set_visibility(&t.ctx, ChatVisibility::Archived) + .is_ok()); + assert!(chat_id2 + .set_visibility(&t.ctx, ChatVisibility::Normal) + .is_ok()); + assert!(chat_id2 + .set_visibility(&t.ctx, ChatVisibility::Normal) + .is_ok()); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ChatVisibility::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ChatVisibility::Normal + ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); } + fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { + let chatlist = Chatlist::try_load(ctx, listflags, None, None).unwrap(); + let mut result = Vec::new(); + for chatlist_index in 0..chatlist.len() { + result.push(chatlist.get_chat_id(chatlist_index)) + } + result + } + + #[test] + fn test_pinned() { + let t = dummy_context(); + let mut msg = Message::new(Viewtype::Text); + msg.text = Some("foo".to_string()); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .unwrap() + .chat_id; + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id3, chat_id1, chat_id2]); + + // pin + assert!( + chat_id1 + .set_visibility(&t.ctx, ChatVisibility::Pinned) + .is_ok() + == true + ); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility(), + ChatVisibility::Pinned + ); + + // check if chat order changed + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id1, chat_id3, chat_id2]); + + // unpin + assert!( + chat_id1 + .set_visibility(&t.ctx, ChatVisibility::Normal) + .is_ok() + == true + ); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility(), + ChatVisibility::Normal + ); + + // check if chat order changed back + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id3, chat_id1, chat_id2]); + } + #[test] fn test_set_chat_name() { let t = dummy_context(); diff --git a/src/chatlist.rs b/src/chatlist.rs index 3c1b3df90..0d2967dbf 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -60,7 +60,7 @@ impl Chatlist { /// or "Not now". /// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then. /// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has - /// archived *any* chat using dc_archive_chat(). The UI should show a link as + /// archived *any* chat using dc_set_chat_visibility(). The UI should show a link as /// "Show archived chats", if the user clicks this item, the UI should show a /// list of all archived chats that can be created by this function hen using /// the DC_GCL_ARCHIVED_ONLY flag. @@ -199,13 +199,13 @@ impl Chatlist { SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id - AND (hidden=0 OR state=?)) + AND (hidden=0 OR state=?1)) WHERE c.id>9 AND c.blocked=0 - AND c.archived=0 + AND NOT c.archived=?2 GROUP BY c.id - ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft], + ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", + params![MessageState::OutDraft, ChatVisibility::Archived, ChatVisibility::Pinned], process_row, process_rows, )?; @@ -392,7 +392,9 @@ mod tests { let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); assert_eq!(chats.len(), 0); - chat_id1.set_archived(&t.ctx, true).ok(); + chat_id1 + .set_visibility(&t.ctx, ChatVisibility::Archived) + .ok(); let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); assert_eq!(chats.len(), 1); }