mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
Compare commits
7 Commits
6ab8b3c3e7
...
simon/UICh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f22263953f | ||
|
|
2c3734153a | ||
|
|
7483aa532b | ||
|
|
a76a7ae781 | ||
|
|
f4caf1688b | ||
|
|
6442e13a1a | ||
|
|
a048d6b0d1 |
@@ -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
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
||||
@@ -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()
|
||||
|
||||
7048
deltachat-jsonrpc/openrpc/openrpc.json
Normal file
7048
deltachat-jsonrpc/openrpc/openrpc.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CoreEventType> for EventType {
|
||||
@@ -406,6 +418,10 @@ impl From<CoreEventType> for EventType {
|
||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::UIChatListItemChanged { chat_id } => UIChatListItemChanged {
|
||||
chat_id: chat_id.map(|id| id.to_u32()),
|
||||
},
|
||||
CoreEventType::UIChatListChanged => UIChatListChanged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
28
src/chat.rs
28
src/chat.rs
@@ -46,6 +46,7 @@ use crate::tools::{
|
||||
create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input,
|
||||
smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty,
|
||||
};
|
||||
use crate::ui_events;
|
||||
use crate::webxdc::WEBXDC_SUFFIX;
|
||||
|
||||
/// An chat item, such as a message or a marker.
|
||||
@@ -309,6 +310,8 @@ impl ChatId {
|
||||
}
|
||||
};
|
||||
context.emit_msgs_changed_without_ids();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -425,6 +428,7 @@ impl ChatId {
|
||||
}
|
||||
}
|
||||
}
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
|
||||
if sync.into() {
|
||||
// NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices.
|
||||
@@ -447,6 +451,8 @@ impl ChatId {
|
||||
pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
|
||||
self.set_blocked(context, Blocked::Not).await?;
|
||||
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
|
||||
if sync.into() {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
// TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices.
|
||||
@@ -457,6 +463,7 @@ impl ChatId {
|
||||
.log_err(context)
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -500,6 +507,7 @@ impl ChatId {
|
||||
|
||||
if self.set_blocked(context, Blocked::Not).await? {
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
ui_events::emit_chatlist_item_changed(context, self);
|
||||
}
|
||||
|
||||
if sync.into() {
|
||||
@@ -542,6 +550,7 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
ui_events::emit_chatlist_item_changed(context, self);
|
||||
|
||||
// make sure, the receivers will get all keys
|
||||
self.reset_gossiped_timestamp(context).await?;
|
||||
@@ -590,6 +599,7 @@ impl ChatId {
|
||||
if protection_status_modified {
|
||||
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
|
||||
.await?;
|
||||
ui_events::emit_chatlist_item_changed(context, self);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -676,6 +686,8 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
ui_events::emit_chatlist_item_changed(context, self);
|
||||
|
||||
if sync.into() {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
@@ -782,6 +794,7 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
context.scheduler.interrupt_inbox().await;
|
||||
@@ -791,6 +804,7 @@ impl ChatId {
|
||||
msg.text = stock_str::self_deleted_msg_body(context).await;
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
}
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3080,7 +3094,9 @@ 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));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id_in_archive);
|
||||
}
|
||||
ui_events::emit_chatlist_item_changed(context, DC_CHAT_ID_ARCHIVED_LINK);
|
||||
} else {
|
||||
let exists = context
|
||||
.sql
|
||||
@@ -3107,6 +3123,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
|
||||
}
|
||||
|
||||
context.emit_event(EventType::MsgsNoticed(chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3174,6 +3191,7 @@ pub(crate) async fn mark_old_messages_as_noticed(
|
||||
|
||||
for c in changed_chats {
|
||||
context.emit_event(EventType::MsgsNoticed(c));
|
||||
ui_events::emit_chatlist_item_changed(context, c);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -3336,6 +3354,8 @@ pub async fn create_group_chat(
|
||||
}
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
if protect == ProtectionStatus::Protected {
|
||||
chat_id
|
||||
@@ -3423,11 +3443,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();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
|
||||
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 +3721,7 @@ 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));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
if sync.into() {
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
chat.sync(context, SyncAction::SetMuted(duration))
|
||||
@@ -3858,6 +3882,7 @@ async fn rename_ex(
|
||||
sync = Nosync;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -3918,6 +3943,7 @@ pub async fn set_chat_profile_image(
|
||||
context.emit_msgs_changed(chat_id, msg.id);
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4063,6 +4089,8 @@ 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)
|
||||
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||
if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
|
||||
context.scheduler.interrupt_smtp().await;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::Duration;
|
||||
|
||||
use crate::chat::ChatId;
|
||||
|
||||
@@ -214,6 +215,11 @@ pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
|
||||
// `max_smtp_rcpt_to` in the provider db.
|
||||
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
||||
|
||||
|
||||
/// How often UI events should be sent out / How much they should be debounced.
|
||||
/// Defines the tick rate/delay of the debounce loop for UI events in milliseconds.
|
||||
pub(crate) const UI_EVENTS_TICK_RATE: Duration = Duration::from_millis(50); // 50ms which means 20 fps
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
@@ -38,7 +38,7 @@ use crate::tools::{
|
||||
duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time,
|
||||
EmailAddress,
|
||||
};
|
||||
use crate::{chat, stock_str};
|
||||
use crate::{chat, stock_str, ui_events};
|
||||
|
||||
/// Time during which a contact is considered as seen recently.
|
||||
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
||||
@@ -761,6 +761,7 @@ impl Contact {
|
||||
if count > 0 {
|
||||
// Chat name updated
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
ui_events::emit_chatlist_items_changed_for_contact(context, contact_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,7 +798,31 @@ impl Contact {
|
||||
Ok(row_id)
|
||||
}).await?;
|
||||
|
||||
Ok((ContactId::new(row_id), sth_modified))
|
||||
let contact_id = ContactId::new(row_id);
|
||||
|
||||
Ok((contact_id, sth_modified))
|
||||
}
|
||||
|
||||
/// Get all chats the contact is part of
|
||||
pub async fn get_chats_with_contact(
|
||||
context: &Context,
|
||||
contact_id: &ContactId,
|
||||
) -> Result<Vec<ChatId>> {
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT chat_id FROM chats_contacts WHERE contact_id=?",
|
||||
(contact_id,),
|
||||
|row| {
|
||||
let chat_id: ChatId = row.get(0)?;
|
||||
Ok(chat_id)
|
||||
},
|
||||
|rows| {
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add a number of contacts.
|
||||
@@ -1528,6 +1553,7 @@ WHERE type=? AND id IN (
|
||||
}
|
||||
}
|
||||
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1576,6 +1602,7 @@ pub(crate) async fn set_profile_image(
|
||||
if changed {
|
||||
contact.update_param(context).await?;
|
||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(context, contact_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1782,6 +1809,10 @@ impl RecentlySeenLoop {
|
||||
// Timeout, notify about contact.
|
||||
if let Some(contact_id) = contact_id {
|
||||
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
|
||||
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(
|
||||
&context,
|
||||
*contact_id,
|
||||
);
|
||||
unseen_queue.pop();
|
||||
}
|
||||
}
|
||||
@@ -1811,6 +1842,10 @@ impl RecentlySeenLoop {
|
||||
// Event is already in the past.
|
||||
if let Some(contact_id) = contact_id {
|
||||
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
|
||||
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(
|
||||
&context,
|
||||
*contact_id,
|
||||
);
|
||||
}
|
||||
unseen_queue.pop();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Context module.
|
||||
|
||||
use std::borrow::BorrowMut;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
use std::ops::Deref;
|
||||
@@ -28,6 +29,7 @@ use crate::sql::Sql;
|
||||
use crate::stock_str::StockStrings;
|
||||
use crate::timesmearing::SmearedTimestamp;
|
||||
use crate::tools::{duration_to_str, time};
|
||||
use crate::ui_events::{self, UIEvents};
|
||||
|
||||
/// Builder for the [`Context`].
|
||||
///
|
||||
@@ -203,6 +205,7 @@ pub struct InnerContext {
|
||||
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
|
||||
pub(crate) translated_stockstrings: StockStrings,
|
||||
pub(crate) events: Events,
|
||||
pub(crate) ui_events: Mutex<UIEvents>,
|
||||
|
||||
pub(crate) scheduler: SchedulerState,
|
||||
pub(crate) ratelimit: RwLock<Ratelimit>,
|
||||
@@ -367,6 +370,8 @@ impl Context {
|
||||
// without starting I/O.
|
||||
new_msgs_notify.notify_one();
|
||||
|
||||
let (ui_events, ui_events_receiver) = UIEvents::new();
|
||||
|
||||
let inner = InnerContext {
|
||||
id,
|
||||
blobdir,
|
||||
@@ -378,6 +383,7 @@ impl Context {
|
||||
wrong_pw_warning_mutex: Mutex::new(()),
|
||||
translated_stockstrings: stockstrings,
|
||||
events,
|
||||
ui_events: Mutex::new(ui_events),
|
||||
scheduler: SchedulerState::new(),
|
||||
ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow at least 1 message every 10 seconds + a burst of 6.
|
||||
quota: RwLock::new(None),
|
||||
@@ -394,6 +400,8 @@ impl Context {
|
||||
inner: Arc::new(inner),
|
||||
};
|
||||
|
||||
ctx.inner.ui_events.blocking_lock().start(&ctx, ui_events_receiver);
|
||||
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
@@ -485,11 +493,15 @@ 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 });
|
||||
ui_events::emit_chatlist_changed(self);
|
||||
ui_events::emit_chatlist_item_changed(self, 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 });
|
||||
ui_events::emit_chatlist_changed(self);
|
||||
ui_events::emit_chatlist_item_changed(self, chat_id);
|
||||
}
|
||||
|
||||
/// Returns a receiver for emitted events.
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::imap::{Imap, ImapActionResult};
|
||||
use crate::message::{Message, MsgId, Viewtype};
|
||||
use crate::mimeparser::{MimeMessage, Part};
|
||||
use crate::tools::time;
|
||||
use crate::{stock_str, EventType};
|
||||
use crate::{stock_str, ui_events, EventType};
|
||||
|
||||
/// Download limits should not be used below `MIN_DOWNLOAD_LIMIT`.
|
||||
///
|
||||
@@ -115,6 +115,7 @@ impl MsgId {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: self,
|
||||
});
|
||||
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
||||
use pin_project::pin_project;
|
||||
|
||||
mod payload;
|
||||
pub(crate) mod ui_events;
|
||||
|
||||
pub use self::payload::EventType;
|
||||
|
||||
|
||||
@@ -277,4 +277,16 @@ 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 {
|
||||
/// ID of the changed chat
|
||||
chat_id: Option<ChatId>,
|
||||
},
|
||||
}
|
||||
|
||||
253
src/events/ui_events.rs
Normal file
253
src/events/ui_events.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use crate::{
|
||||
chat::{ChatId, ChatIdBlocked},
|
||||
constants::UI_EVENTS_TICK_RATE,
|
||||
contact::{Contact, ContactId},
|
||||
context::Context,
|
||||
EventType,
|
||||
};
|
||||
use async_channel::{self as channel, Receiver, Sender};
|
||||
use tokio::{
|
||||
task,
|
||||
time::{sleep_until, Instant},
|
||||
};
|
||||
|
||||
/// order or content of chatlist changes (chat ids, not the actual chatlist item)
|
||||
pub(crate) fn emit_chatlist_changed(context: &Context) {
|
||||
context
|
||||
.ui_events
|
||||
.blocking_lock()
|
||||
.send_chat_list_event(context, InternalUIEvent::ChatListChanged)
|
||||
}
|
||||
|
||||
/// Chatlist item of a specific chat changed
|
||||
pub(crate) fn emit_chatlist_item_changed(context: &Context, chat_id: ChatId) {
|
||||
context
|
||||
.ui_events
|
||||
.blocking_lock()
|
||||
.send_chat_list_event(context, InternalUIEvent::ChatListItemChanged(chat_id))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Used when you don't know which chatlist items changed, this reloads all cached chatlist items in the UI
|
||||
/// note(treefit): This is not used right now, but I know there will be a point where someone wants it
|
||||
pub(crate) fn emit_unknown_chatlist_items_changed(context: &Context) {
|
||||
context
|
||||
.ui_events
|
||||
.blocking_lock()
|
||||
.send_chat_list_event(context, InternalUIEvent::UnknownChatListItemsChanged)
|
||||
}
|
||||
|
||||
/// update event for dm chat of contact
|
||||
/// used when recently seen changes and when profile image changes
|
||||
pub(crate) fn emit_chatlist_item_changed_for_contacts_dm_chat(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
) {
|
||||
context
|
||||
.ui_events
|
||||
.blocking_lock()
|
||||
.send_chat_list_event(context, InternalUIEvent::ContactDMChatChanged(contact_id))
|
||||
}
|
||||
|
||||
/// update dm for chats that have the contact
|
||||
/// used when contact changes their name or did AEAP for example
|
||||
pub(crate) fn emit_chatlist_items_changed_for_contact(context: &Context, contact_id: ContactId) {
|
||||
context
|
||||
.ui_events
|
||||
.blocking_lock()
|
||||
.send_chat_list_event(context, InternalUIEvent::ContactChatsChanged(contact_id));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum InternalUIEvent {
|
||||
ChatListChanged,
|
||||
ChatListItemChanged(ChatId),
|
||||
UnknownChatListItemsChanged,
|
||||
ContactDMChatChanged(ContactId),
|
||||
ContactChatsChanged(ContactId),
|
||||
}
|
||||
|
||||
struct EventLoopTickState {
|
||||
chat_list_changed: bool,
|
||||
has_unknown_items: bool,
|
||||
chat_ids: Vec<ChatId>,
|
||||
contact_ids_dm: Vec<ContactId>,
|
||||
contact_ids_chats: Vec<ContactId>,
|
||||
}
|
||||
|
||||
impl EventLoopTickState {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
chat_list_changed: false,
|
||||
has_unknown_items: false,
|
||||
chat_ids: Vec::with_capacity(capacity),
|
||||
contact_ids_dm: Vec::with_capacity(capacity),
|
||||
contact_ids_chats: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_internal_ui_event(&mut self, event: InternalUIEvent) {
|
||||
match event {
|
||||
InternalUIEvent::ChatListChanged => {
|
||||
self.chat_list_changed = true;
|
||||
}
|
||||
InternalUIEvent::ChatListItemChanged(chat_id) => {
|
||||
self.chat_ids.push(chat_id);
|
||||
}
|
||||
InternalUIEvent::UnknownChatListItemsChanged => {
|
||||
self.has_unknown_items = true;
|
||||
}
|
||||
InternalUIEvent::ContactDMChatChanged(contact_id) => {
|
||||
self.contact_ids_dm.push(contact_id);
|
||||
}
|
||||
InternalUIEvent::ContactChatsChanged(contact_id) => {
|
||||
self.contact_ids_chats.push(contact_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn emit_chatlist_ui_events(&mut self, context: &Context) {
|
||||
if self.chat_list_changed {
|
||||
context.emit_event(EventType::UIChatListChanged);
|
||||
}
|
||||
if self.has_unknown_items {
|
||||
context.emit_event(EventType::UIChatListItemChanged { chat_id: None });
|
||||
return; // since this refreshes everything no further events are needed
|
||||
}
|
||||
|
||||
for contact_id in self
|
||||
.contact_ids_dm
|
||||
.iter()
|
||||
.filter(|contact| !self.contact_ids_chats.contains(contact))
|
||||
.collect::<Vec<&ContactId>>()
|
||||
{
|
||||
if let Ok(Some(chat_id)) = ChatIdBlocked::lookup_by_contact(context, *contact_id).await
|
||||
{
|
||||
self.chat_ids.push(chat_id.id)
|
||||
}
|
||||
}
|
||||
|
||||
// note:(treefit): could make sense to only update chats where the last message is from the contact, but the db query for that is more expensive
|
||||
for contact_id in &self.contact_ids_chats {
|
||||
match Contact::get_chats_with_contact(context, contact_id).await {
|
||||
Ok(contacts_chat_ids) => {
|
||||
self.chat_ids.extend(contacts_chat_ids);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Error while getting chats for contact {} in chatlist events loop: {}",
|
||||
contact_id,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.chat_ids.sort();
|
||||
self.chat_ids.dedup();
|
||||
|
||||
// TODO change event so it accepts a list of chat ids to get rid of this loop? wouldn't work with cffi unless we give it out as json
|
||||
for chat_id in &self.chat_ids {
|
||||
context.emit_event(EventType::UIChatListItemChanged {
|
||||
chat_id: Some(*chat_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Debounces UI events
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UIEvents {
|
||||
task_handle: Option<task::JoinHandle<()>>,
|
||||
chatlist_event_queue: Sender<InternalUIEvent>,
|
||||
}
|
||||
|
||||
impl UIEvents {
|
||||
pub(crate) fn new() -> (Self, Receiver<InternalUIEvent>) {
|
||||
let (chatlist_event_queue, chatlist_event_queue_recv) = channel::unbounded();
|
||||
(
|
||||
Self {
|
||||
task_handle: None,
|
||||
chatlist_event_queue,
|
||||
},
|
||||
chatlist_event_queue_recv,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
chatlist_event_queue_recv: Receiver<InternalUIEvent>,
|
||||
) {
|
||||
if let Some(handle) = self.task_handle {
|
||||
handle.abort()
|
||||
}
|
||||
self.task_handle = Some(task::spawn(Self::run_task(
|
||||
context,
|
||||
chatlist_event_queue_recv,
|
||||
)))
|
||||
}
|
||||
|
||||
async fn run_task(context: &Context, chatlist_event_queue: Receiver<InternalUIEvent>) {
|
||||
loop {
|
||||
match chatlist_event_queue.recv().await {
|
||||
Ok(chatlist_event) => {
|
||||
let backlog_len = chatlist_event_queue.len();
|
||||
let mut tick_state = EventLoopTickState::new(backlog_len);
|
||||
|
||||
tick_state.apply_internal_ui_event(chatlist_event);
|
||||
// get all events from the queue
|
||||
while let Ok(event) = chatlist_event_queue.try_recv() {
|
||||
tick_state.apply_internal_ui_event(event);
|
||||
}
|
||||
|
||||
tick_state.emit_chatlist_ui_events(context).await;
|
||||
|
||||
// cooldown
|
||||
sleep_until(Instant::now() + UI_EVENTS_TICK_RATE).await;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Error receiving an interruption in ui chatlist events loop: {}", err
|
||||
);
|
||||
// Maybe the sender side is closed, so terminate the loop to avoid looping indefinitely.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_chat_list_event(&self, context: &Context, event: InternalUIEvent) {
|
||||
// todo check if ui events are enabled?
|
||||
if let Err(error) = self.chatlist_event_queue.try_send(event) {
|
||||
warn!(
|
||||
context,
|
||||
"Error receiving an interruption in ui chatlist events loop: {}", error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UIEvents {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = &self.task_handle {
|
||||
handle.abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
// todo tests:
|
||||
|
||||
// send ui events though the UIEventsLoop
|
||||
|
||||
// check that UIEventsLoop really ratelimits the events
|
||||
|
||||
// check that has_unknown_items does not send out any ids before or afterwards
|
||||
|
||||
// if we should make it possible to disable via config then test that as well
|
||||
}
|
||||
@@ -42,6 +42,7 @@ use crate::socks::Socks5Config;
|
||||
use crate::sql;
|
||||
use crate::stock_str;
|
||||
use crate::tools::{create_id, duration_to_str};
|
||||
use crate::ui_events;
|
||||
|
||||
pub(crate) mod capabilities;
|
||||
mod client;
|
||||
@@ -1319,6 +1320,7 @@ 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));
|
||||
ui_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -14,8 +14,8 @@ use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::message::{Message, MsgId, Viewtype};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::stock_str;
|
||||
use crate::tools::{duration_to_str, time};
|
||||
use crate::{stock_str, ui_events};
|
||||
|
||||
/// Location record.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -290,6 +290,7 @@ 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));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
if 0 != seconds {
|
||||
context.scheduler.interrupt_location().await;
|
||||
}
|
||||
@@ -787,6 +788,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
|
||||
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));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::tools::{
|
||||
buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file, time,
|
||||
timestamp_to_str, truncate,
|
||||
};
|
||||
use crate::ui_events;
|
||||
|
||||
/// Message ID, including reserved IDs.
|
||||
///
|
||||
@@ -138,6 +139,7 @@ WHERE id=?;
|
||||
chat_id,
|
||||
msg_id: self,
|
||||
});
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1518,9 +1520,12 @@ 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));
|
||||
ui_events::emit_chatlist_item_changed(context, modified_chat_id);
|
||||
}
|
||||
|
||||
if !msg_ids.is_empty() {
|
||||
context.emit_msgs_changed_without_ids();
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
// Run housekeeping to delete unused blobs.
|
||||
context.set_config(Config::LastHousekeeping, None).await?;
|
||||
}
|
||||
@@ -1653,6 +1658,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
|
||||
for updated_chat_id in updated_chat_ids {
|
||||
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1713,6 +1719,7 @@ pub(crate) async fn set_msg_failed(
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -35,13 +35,12 @@ use crate::message::{
|
||||
use crate::param::{Param, Params};
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::simplify::{simplify, SimplifiedText};
|
||||
use crate::stock_str;
|
||||
use crate::sync::SyncItems;
|
||||
use crate::tools::{
|
||||
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
|
||||
strip_rtlo_characters, truncate_by_lines,
|
||||
};
|
||||
use crate::{location, tools};
|
||||
use crate::{location, stock_str, tools, ui_events};
|
||||
|
||||
/// A parsed MIME message.
|
||||
///
|
||||
@@ -2119,6 +2118,8 @@ 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)
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
||||
use crate::message::Message;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::sql::Sql;
|
||||
use crate::stock_str;
|
||||
use crate::{stock_str, ui_events};
|
||||
|
||||
/// Type of the public key stored inside the peerstate.
|
||||
#[derive(Debug)]
|
||||
@@ -695,6 +695,9 @@ impl Peerstate {
|
||||
.await?;
|
||||
}
|
||||
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
// update the chats the contact is part of
|
||||
ui_events::emit_chatlist_items_changed_for_contact(context, contact_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
|
||||
use crate::location;
|
||||
use crate::log::LogExt;
|
||||
use crate::message::{
|
||||
self, rfc724_mid_exists, rfc724_mid_exists_and, Message, MessageState, MessengerMessage, MsgId,
|
||||
@@ -40,6 +39,7 @@ use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::{buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
|
||||
use crate::{contact, imap};
|
||||
use crate::{location, ui_events};
|
||||
|
||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
||||
///
|
||||
@@ -1778,6 +1778,8 @@ 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));
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
ui_events::emit_chatlist_item_changed(context, new_chat_id);
|
||||
}
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
@@ -2069,6 +2071,7 @@ async fn apply_group_changes(
|
||||
|
||||
if send_event_chat_modified {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
Ok((group_changes_msgs, better_msg))
|
||||
}
|
||||
@@ -2368,6 +2371,8 @@ 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));
|
||||
ui_events::emit_chatlist_changed(context);
|
||||
ui_events::emit_chatlist_item_changed(context, new_chat_id);
|
||||
|
||||
Ok(Some(new_chat_id))
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::token;
|
||||
use crate::tools::time;
|
||||
use crate::ui_events;
|
||||
|
||||
mod bob;
|
||||
mod bobstate;
|
||||
@@ -683,6 +684,7 @@ async fn secure_connection_established(
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
ui_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user