From a048d6b0d1fd4a79255d576278f70a7b381f4441 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Thu, 15 Jun 2023 15:05:41 +0200 Subject: [PATCH] feat: add `UIChatListChanged` and `UIChatListItemChanged` events --- deltachat-ffi/deltachat.h | 16 +++++++ deltachat-ffi/src/lib.rs | 16 +++++-- deltachat-jsonrpc/src/api/types/events.rs | 16 +++++++ node/constants.js | 2 + node/events.js | 4 +- node/lib/constants.ts | 4 ++ src/chat.rs | 52 +++++++++++++++++++++++ src/contact.rs | 4 ++ src/context.rs | 8 ++++ src/download.rs | 3 ++ src/events/payload.rs | 9 ++++ src/imap.rs | 3 ++ src/location.rs | 6 +++ src/message.rs | 14 ++++++ src/mimeparser.rs | 4 ++ src/peerstate.rs | 2 + src/receive_imf.rs | 11 +++++ src/securejoin.rs | 3 ++ 18 files changed, 173 insertions(+), 4 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 427b846a9..d6e3d0f7c 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -6230,6 +6230,22 @@ void dc_event_unref(dc_event_t* event); #define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121 +/** + * Inform UI that Order (and content as in chat ids) of the chatlist changed. + * + * Sometimes this is emitted together with `DC_EVENT_UI_CHATLIST_ITEM_CHANGED` such as on `DC_EVENT_INCOMING_MSG`. + */ + +#define DC_EVENT_UI_CHATLIST_CHANGED 2200 + +/** + * Inform UI that all or a single chat list item changed and needs to be rerendered + * If `chat_id` is set to 0, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache. + * + * @param data1 (int) chat_id chat id of chatlist item to be rerendered, if chat_id = 0 all (cached & visible) items need to be rerendered + */ + +#define DC_EVENT_UI_CHATLIST_ITEM_CHANGED 2201 /** * @} diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 06c55bc84..94a39a80a 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -558,6 +558,8 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int EventType::SelfavatarChanged => 2110, EventType::WebxdcStatusUpdate { .. } => 2120, EventType::WebxdcInstanceDeleted { .. } => 2121, + EventType::UIChatListChanged => 2200, + EventType::UIChatListItemChanged { .. } => 2201, } } @@ -584,7 +586,8 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: | EventType::ConnectivityChanged | EventType::SelfavatarChanged | EventType::IncomingMsgBunch { .. } - | EventType::ErrorSelfNotInGroup(_) => 0, + | EventType::ErrorSelfNotInGroup(_) + | EventType::UIChatListChanged => 0, EventType::MsgsChanged { chat_id, .. } | EventType::ReactionsChanged { chat_id, .. } | EventType::IncomingMsg { chat_id, .. } @@ -609,6 +612,9 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: } EventType::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int, EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int, + EventType::UIChatListItemChanged { chat_id } => { + chat_id.unwrap_or_default().to_u32() as libc::c_int + } } } @@ -643,7 +649,9 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: | EventType::ConnectivityChanged | EventType::WebxdcInstanceDeleted { .. } | EventType::IncomingMsgBunch { .. } - | EventType::SelfavatarChanged => 0, + | EventType::SelfavatarChanged + | EventType::UIChatListChanged + | EventType::UIChatListItemChanged { .. } => 0, EventType::ChatModified(_) => 0, EventType::MsgsChanged { msg_id, .. } | EventType::ReactionsChanged { msg_id, .. } @@ -705,7 +713,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut | EventType::SelfavatarChanged | EventType::WebxdcStatusUpdate { .. } | EventType::WebxdcInstanceDeleted { .. } - | EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(), + | EventType::ChatEphemeralTimerModified { .. } + | EventType::UIChatListItemChanged { .. } + | EventType::UIChatListChanged => ptr::null_mut(), EventType::ConfigureProgress { comment, .. } => { if let Some(comment) = comment { comment.to_c_string().unwrap_or_default().into_raw() diff --git a/deltachat-jsonrpc/src/api/types/events.rs b/deltachat-jsonrpc/src/api/types/events.rs index dd03358bc..c239e4796 100644 --- a/deltachat-jsonrpc/src/api/types/events.rs +++ b/deltachat-jsonrpc/src/api/types/events.rs @@ -301,6 +301,18 @@ pub enum EventType { WebxdcInstanceDeleted { msg_id: u32, }, + + /// Inform UI that Order (and content as in chat ids) of the chatlist changed. + /// + /// Sometimes this is emitted together with `UIChatListItemChanged` such as on IncomingMessage. + UIChatListChanged, + + /// Inform UI that a single chat list item changed and needs to be rerendered + /// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache. + #[serde(rename_all = "camelCase")] + UIChatListItemChanged { + chat_id: Option, + }, } impl From for EventType { @@ -406,6 +418,10 @@ impl From for EventType { CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted { msg_id: msg_id.to_u32(), }, + EventType::UIChatListItemChanged { chat_id } => UIChatListItemChanged { + chat_id: chat_id.map(|id| id.to_u32()), + }, + EventType::UIChatListChanged => UIChatListChanged, } } } diff --git a/node/constants.js b/node/constants.js index f6efbe290..a2d99eb30 100644 --- a/node/constants.js +++ b/node/constants.js @@ -60,6 +60,8 @@ module.exports = { DC_EVENT_SELFAVATAR_CHANGED: 2110, DC_EVENT_SMTP_CONNECTED: 101, DC_EVENT_SMTP_MESSAGE_SENT: 103, + DC_EVENT_UI_CHATLIST_CHANGED: 2200, + DC_EVENT_UI_CHATLIST_ITEM_CHANGED: 2201, DC_EVENT_WARNING: 300, DC_EVENT_WEBXDC_INSTANCE_DELETED: 2121, DC_EVENT_WEBXDC_STATUS_UPDATE: 2120, diff --git a/node/events.js b/node/events.js index 5a24586a2..98c4c0ce1 100644 --- a/node/events.js +++ b/node/events.js @@ -35,5 +35,7 @@ module.exports = { 2100: 'DC_EVENT_CONNECTIVITY_CHANGED', 2110: 'DC_EVENT_SELFAVATAR_CHANGED', 2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE', - 2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED' + 2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED', + 2200: 'DC_EVENT_UI_CHATLIST_CHANGED', + 2201: 'DC_EVENT_UI_CHATLIST_ITEM_CHANGED' } diff --git a/node/lib/constants.ts b/node/lib/constants.ts index d4df49102..b1a958b7c 100644 --- a/node/lib/constants.ts +++ b/node/lib/constants.ts @@ -60,6 +60,8 @@ export enum C { DC_EVENT_SELFAVATAR_CHANGED = 2110, DC_EVENT_SMTP_CONNECTED = 101, DC_EVENT_SMTP_MESSAGE_SENT = 103, + DC_EVENT_UI_CHATLIST_CHANGED = 2200, + DC_EVENT_UI_CHATLIST_ITEM_CHANGED = 2201, DC_EVENT_WARNING = 300, DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121, DC_EVENT_WEBXDC_STATUS_UPDATE = 2120, @@ -321,4 +323,6 @@ export const EventId2EventName: { [key: number]: string } = { 2110: 'DC_EVENT_SELFAVATAR_CHANGED', 2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE', 2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED', + 2200: 'DC_EVENT_UI_CHATLIST_CHANGED', + 2201: 'DC_EVENT_UI_CHATLIST_ITEM_CHANGED', } diff --git a/src/chat.rs b/src/chat.rs index 88f3b4b57..882590a63 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -309,6 +309,10 @@ impl ChatId { } }; context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); Ok(chat_id) } @@ -425,6 +429,7 @@ impl ChatId { } } } + context.emit_event(EventType::UIChatListChanged); if sync.into() { // NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices. @@ -456,6 +461,8 @@ impl ChatId { .await .log_err(context) .ok(); + + context.emit_event(EventType::UIChatListChanged); } Ok(()) } @@ -500,6 +507,9 @@ impl ChatId { if self.set_blocked(context, Blocked::Not).await? { context.emit_event(EventType::ChatModified(self)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(self), + }); } if sync.into() { @@ -542,6 +552,9 @@ impl ChatId { .await?; context.emit_event(EventType::ChatModified(self)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(self), + }); // make sure, the receivers will get all keys self.reset_gossiped_timestamp(context).await?; @@ -590,6 +603,9 @@ impl ChatId { if protection_status_modified { self.add_protection_msg(context, protect, contact_id, timestamp_sort) .await?; + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(self), + }); } Ok(()) } @@ -676,6 +692,10 @@ impl ChatId { .await?; context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(self), + }); if sync.into() { let chat = Chat::load_from_db(context, self).await?; @@ -782,6 +802,7 @@ impl ChatId { .await?; context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); context.set_config(Config::LastHousekeeping, None).await?; context.scheduler.interrupt_inbox().await; @@ -791,6 +812,7 @@ impl ChatId { msg.text = stock_str::self_deleted_msg_body(context).await; add_device_msg(context, None, Some(&mut msg)).await?; } + context.emit_event(EventType::UIChatListChanged); Ok(()) } @@ -3080,7 +3102,13 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> .await?; for chat_id_in_archive in chat_ids_in_archive { context.emit_event(EventType::MsgsNoticed(chat_id_in_archive)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id_in_archive), + }); } + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(DC_CHAT_ID_ARCHIVED_LINK), + }); } else { let exists = context .sql @@ -3107,6 +3135,9 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> } context.emit_event(EventType::MsgsNoticed(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); Ok(()) } @@ -3174,6 +3205,7 @@ pub(crate) async fn mark_old_messages_as_noticed( for c in changed_chats { context.emit_event(EventType::MsgsNoticed(c)); + context.emit_event(EventType::UIChatListItemChanged { chat_id: Some(c) }); } Ok(()) @@ -3336,6 +3368,10 @@ pub async fn create_group_chat( } context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); if protect == ProtectionStatus::Protected { chat_id @@ -3423,11 +3459,14 @@ pub(crate) async fn create_broadcast_list_ex( let chat_id = ChatId::new(u32::try_from(row_id)?); context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); + if sync.into() { let id = SyncId::Grpid(grpid); let action = SyncAction::CreateBroadcast(chat_name); self::sync(context, id, action).await.log_err(context).ok(); } + Ok(chat_id) } @@ -3698,6 +3737,9 @@ pub(crate) async fn set_muted_ex( .await .context(format!("Failed to set mute duration for {chat_id}"))?; context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); if sync.into() { let chat = Chat::load_from_db(context, chat_id).await?; chat.sync(context, SyncAction::SetMuted(duration)) @@ -3858,6 +3900,9 @@ async fn rename_ex( sync = Nosync; } context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); success = true; } } @@ -3918,6 +3963,9 @@ pub async fn set_chat_profile_image( context.emit_msgs_changed(chat_id, msg.id); } context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); Ok(()) } @@ -4063,6 +4111,10 @@ pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> { chat_id: msg.chat_id, msg_id: msg.id, }); + // note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it) + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(msg.chat_id), + }); if !create_send_msg_jobs(context, &mut msg).await?.is_empty() { context.scheduler.interrupt_smtp().await; } diff --git a/src/contact.rs b/src/contact.rs index 836a46003..0d9669de7 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -761,6 +761,9 @@ impl Contact { if count > 0 { // Chat name updated context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } } } @@ -1528,6 +1531,7 @@ WHERE type=? AND id IN ( } } + context.emit_event(EventType::UIChatListChanged); Ok(()) } diff --git a/src/context.rs b/src/context.rs index ebadb60bf..a57689d67 100644 --- a/src/context.rs +++ b/src/context.rs @@ -485,11 +485,19 @@ impl Context { /// Emits a MsgsChanged event with specified chat and message ids pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) { self.emit_event(EventType::MsgsChanged { chat_id, msg_id }); + self.emit_event(EventType::UIChatListChanged); + self.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } /// Emits an IncomingMsg event with specified chat and message ids pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) { self.emit_event(EventType::IncomingMsg { chat_id, msg_id }); + self.emit_event(EventType::UIChatListChanged); + self.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } /// Returns a receiver for emitted events. diff --git a/src/download.rs b/src/download.rs index 5c626fc71..08569f539 100644 --- a/src/download.rs +++ b/src/download.rs @@ -115,6 +115,9 @@ impl MsgId { chat_id: msg.chat_id, msg_id: self, }); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(msg.chat_id), + }); Ok(()) } } diff --git a/src/events/payload.rs b/src/events/payload.rs index ea9e43698..f1aa41aaa 100644 --- a/src/events/payload.rs +++ b/src/events/payload.rs @@ -277,4 +277,13 @@ pub enum EventType { /// ID of the deleted message. msg_id: MsgId, }, + + /// Inform UI that Order (and content as in chat ids) of the chatlist changed. + /// + /// Sometimes this is emitted together with `UIChatListItemChanged` such as on IncomingMessage. + UIChatListChanged, + + /// Inform UI that a single chat list item changed and needs to be rerendered + /// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache. + UIChatListItemChanged { chat_id: Option }, } diff --git a/src/imap.rs b/src/imap.rs index 81a31d3d4..f7cdcf6cd 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1319,6 +1319,9 @@ impl Imap { .with_context(|| format!("failed to set MODSEQ for folder {folder}"))?; for updated_chat_id in updated_chat_ids { context.emit_event(EventType::MsgsNoticed(updated_chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(updated_chat_id), + }); } Ok(()) diff --git a/src/location.rs b/src/location.rs index 79c96958f..afe44a565 100644 --- a/src/location.rs +++ b/src/location.rs @@ -290,6 +290,9 @@ pub async fn send_locations_to_chat( chat::add_info_msg(context, chat_id, &stock_str, now).await?; } context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); if 0 != seconds { context.scheduler.interrupt_location().await; } @@ -787,6 +790,9 @@ async fn maybe_send_locations(context: &Context) -> Result> { let stock_str = stock_str::msg_location_disabled(context).await; chat::add_info_msg(context, chat_id, &stock_str, now).await?; context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } } diff --git a/src/message.rs b/src/message.rs index 2711c5bf5..64ce99db8 100644 --- a/src/message.rs +++ b/src/message.rs @@ -138,6 +138,9 @@ WHERE id=?; chat_id, msg_id: self, }); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); Ok(()) } @@ -1518,9 +1521,14 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> { for modified_chat_id in modified_chat_ids { context.emit_msgs_changed(modified_chat_id, MsgId::new(0)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(modified_chat_id), + }); } if !msg_ids.is_empty() { + context.emit_msgs_changed_without_ids(); + context.emit_event(EventType::UIChatListChanged); // Run housekeeping to delete unused blobs. context.set_config(Config::LastHousekeeping, None).await?; } @@ -1653,6 +1661,9 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> Result<()> for updated_chat_id in updated_chat_ids { context.emit_event(EventType::MsgsNoticed(updated_chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(updated_chat_id), + }); } Ok(()) @@ -1713,6 +1724,9 @@ pub(crate) async fn set_msg_failed( chat_id: msg.chat_id, msg_id: msg.id, }); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(msg.chat_id), + }); Ok(()) } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 90c2384c9..a2307281a 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -2119,6 +2119,10 @@ async fn handle_mdn( { update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await?; context.emit_event(EventType::MsgRead { chat_id, msg_id }); + // note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it) + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } Ok(()) } diff --git a/src/peerstate.rs b/src/peerstate.rs index 916b6852a..0b4f7f1a5 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -695,6 +695,8 @@ impl Peerstate { .await?; } + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { chat_id: None }); Ok(()) } diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 07b1ac483..20446d5bc 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1778,6 +1778,10 @@ async fn create_or_lookup_group( chat::add_to_chat_contacts_table(context, new_chat_id, &members).await?; context.emit_event(EventType::ChatModified(new_chat_id)); + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(new_chat_id), + }); } if let Some(chat_id) = chat_id { @@ -2069,6 +2073,9 @@ async fn apply_group_changes( if send_event_chat_modified { context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); } Ok((group_changes_msgs, better_msg)) } @@ -2368,6 +2375,10 @@ async fn create_adhoc_group( chat::add_to_chat_contacts_table(context, new_chat_id, member_ids).await?; context.emit_event(EventType::ChatModified(new_chat_id)); + context.emit_event(EventType::UIChatListChanged); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(new_chat_id), + }); Ok(Some(new_chat_id)) } diff --git a/src/securejoin.rs b/src/securejoin.rs index 26260a63d..b31a7c5b7 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -683,6 +683,9 @@ async fn secure_connection_established( ) .await?; context.emit_event(EventType::ChatModified(chat_id)); + context.emit_event(EventType::UIChatListItemChanged { + chat_id: Some(chat_id), + }); Ok(()) }