mirror of
https://github.com/chatmail/core.git
synced 2026-04-24 08:56:29 +03:00
api: add ChatListChanged and ChatListItemChanged events (#4476)
This commit is contained in:
28
src/chat.rs
28
src/chat.rs
@@ -15,6 +15,7 @@ use strum_macros::EnumIter;
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::chatlist_events;
|
||||
use crate::color::str_to_color;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
@@ -295,6 +296,8 @@ impl ChatId {
|
||||
}
|
||||
};
|
||||
context.emit_msgs_changed_without_ids();
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
@@ -411,6 +414,7 @@ impl ChatId {
|
||||
}
|
||||
}
|
||||
}
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
|
||||
if sync.into() {
|
||||
// NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices.
|
||||
@@ -433,6 +437,8 @@ impl ChatId {
|
||||
pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
|
||||
self.set_blocked(context, Blocked::Not).await?;
|
||||
|
||||
chatlist_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.
|
||||
@@ -443,6 +449,7 @@ impl ChatId {
|
||||
.log_err(context)
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -486,6 +493,7 @@ impl ChatId {
|
||||
|
||||
if self.set_blocked(context, Blocked::Not).await? {
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
}
|
||||
|
||||
if sync.into() {
|
||||
@@ -528,6 +536,7 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::ChatModified(self));
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
|
||||
// make sure, the receivers will get all keys
|
||||
self.reset_gossiped_timestamp(context).await?;
|
||||
@@ -576,6 +585,7 @@ impl ChatId {
|
||||
if protection_status_modified {
|
||||
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
|
||||
.await?;
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -662,6 +672,8 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||
|
||||
if sync.into() {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
@@ -768,6 +780,7 @@ impl ChatId {
|
||||
.await?;
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
|
||||
context
|
||||
.set_config_internal(Config::LastHousekeeping, None)
|
||||
@@ -779,6 +792,7 @@ impl ChatId {
|
||||
msg.text = stock_str::self_deleted_msg_body(context).await;
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
}
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3103,7 +3117,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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
|
||||
}
|
||||
chatlist_events::emit_chatlist_item_changed(context, DC_CHAT_ID_ARCHIVED_LINK);
|
||||
} else {
|
||||
let exists = context
|
||||
.sql
|
||||
@@ -3130,6 +3146,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
|
||||
}
|
||||
|
||||
context.emit_event(EventType::MsgsNoticed(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3197,6 +3214,7 @@ pub(crate) async fn mark_old_messages_as_noticed(
|
||||
|
||||
for c in changed_chats {
|
||||
context.emit_event(EventType::MsgsNoticed(c));
|
||||
chatlist_events::emit_chatlist_item_changed(context, c);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -3359,6 +3377,8 @@ pub async fn create_group_chat(
|
||||
}
|
||||
|
||||
context.emit_msgs_changed_without_ids();
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
if protect == ProtectionStatus::Protected {
|
||||
chat_id
|
||||
@@ -3446,11 +3466,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();
|
||||
chatlist_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)
|
||||
}
|
||||
|
||||
@@ -3721,6 +3744,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));
|
||||
chatlist_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))
|
||||
@@ -3881,6 +3905,7 @@ async fn rename_ex(
|
||||
sync = Nosync;
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -3941,6 +3966,7 @@ pub async fn set_chat_profile_image(
|
||||
context.emit_msgs_changed(chat_id, msg.id);
|
||||
}
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4087,6 +4113,8 @@ pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
msg_id: msg.id,
|
||||
});
|
||||
msg.timestamp_sort = create_smeared_timestamp(context);
|
||||
// note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it)
|
||||
chatlist_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;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::tools::{
|
||||
duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time,
|
||||
EmailAddress, SystemTime,
|
||||
};
|
||||
use crate::{chat, stock_str};
|
||||
use crate::{chat, chatlist_events, stock_str};
|
||||
|
||||
/// Time during which a contact is considered as seen recently.
|
||||
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
||||
@@ -760,6 +760,7 @@ impl Contact {
|
||||
if count > 0 {
|
||||
// Chat name updated
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -796,7 +797,9 @@ 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))
|
||||
}
|
||||
|
||||
/// Add a number of contacts.
|
||||
@@ -1524,6 +1527,7 @@ WHERE type=? AND id IN (
|
||||
}
|
||||
}
|
||||
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1574,6 +1578,7 @@ pub(crate) async fn set_profile_image(
|
||||
if changed {
|
||||
contact.update_param(context).await?;
|
||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||
chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1786,6 +1791,11 @@ impl RecentlySeenLoop {
|
||||
// Timeout, notify about contact.
|
||||
if let Some(contact_id) = contact_id {
|
||||
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
|
||||
chatlist_events::emit_chatlist_item_changed_for_contact_chat(
|
||||
&context,
|
||||
*contact_id,
|
||||
)
|
||||
.await;
|
||||
unseen_queue.pop();
|
||||
}
|
||||
}
|
||||
@@ -1818,6 +1828,11 @@ impl RecentlySeenLoop {
|
||||
// Event is already in the past.
|
||||
if let Some(contact_id) = contact_id {
|
||||
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
|
||||
chatlist_events::emit_chatlist_item_changed_for_contact_chat(
|
||||
&context,
|
||||
*contact_id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
unseen_queue.pop();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use tokio::sync::{Mutex, Notify, RwLock};
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
|
||||
@@ -593,11 +594,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 });
|
||||
chatlist_events::emit_chatlist_changed(self);
|
||||
chatlist_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 });
|
||||
chatlist_events::emit_chatlist_changed(self);
|
||||
chatlist_events::emit_chatlist_item_changed(self, chat_id);
|
||||
}
|
||||
|
||||
/// Returns a receiver for emitted events.
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::imap::session::Session;
|
||||
use crate::message::{Message, MsgId, Viewtype};
|
||||
use crate::mimeparser::{MimeMessage, Part};
|
||||
use crate::tools::time;
|
||||
use crate::{stock_str, EventType};
|
||||
use crate::{chatlist_events, stock_str, 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,
|
||||
});
|
||||
chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
||||
use pin_project::pin_project;
|
||||
|
||||
pub(crate) mod chatlist_events;
|
||||
mod payload;
|
||||
|
||||
pub use self::payload::EventType;
|
||||
|
||||
635
src/events/chatlist_events.rs
Normal file
635
src/events/chatlist_events.rs
Normal file
@@ -0,0 +1,635 @@
|
||||
use crate::{chat::ChatId, contact::ContactId, context::Context, EventType};
|
||||
|
||||
/// order or content of chatlist changes (chat ids, not the actual chatlist item)
|
||||
pub(crate) fn emit_chatlist_changed(context: &Context) {
|
||||
context.emit_event(EventType::ChatlistChanged);
|
||||
}
|
||||
|
||||
/// Chatlist item of a specific chat changed
|
||||
pub(crate) fn emit_chatlist_item_changed(context: &Context, chat_id: ChatId) {
|
||||
context.emit_event(EventType::ChatlistItemChanged {
|
||||
chat_id: Some(chat_id),
|
||||
});
|
||||
}
|
||||
|
||||
/// Used when you don't know which chatlist items changed, this reloads all cached chatlist items in the UI
|
||||
///
|
||||
/// Avoid calling this when you can find out the affected chat ids easialy (without extra expensive db queries).
|
||||
///
|
||||
/// This method is not public, so you have to define and document your new case here in this file.
|
||||
fn emit_unknown_chatlist_items_changed(context: &Context) {
|
||||
context.emit_event(EventType::ChatlistItemChanged { chat_id: None });
|
||||
}
|
||||
|
||||
/// update event for the 1:1 chat with the contact
|
||||
/// used when recently seen changes and when profile image changes
|
||||
pub(crate) async fn emit_chatlist_item_changed_for_contact_chat(
|
||||
context: &Context,
|
||||
contact_id: ContactId,
|
||||
) {
|
||||
match ChatId::lookup_by_contact(context, contact_id).await {
|
||||
Ok(Some(chat_id)) => self::emit_chatlist_item_changed(context, chat_id),
|
||||
Ok(None) => {}
|
||||
Err(error) => context.emit_event(EventType::Error(format!(
|
||||
"failed to find chat id for contact for chatlist event: {error:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// update items for chats that have the contact
|
||||
/// used when contact changes their name or did AEAP for example
|
||||
///
|
||||
/// The most common case is that the contact changed their name
|
||||
/// and their name should be updated in the chatlistitems for the chats
|
||||
/// where they sent the last message as there their name is shown in the summary on those
|
||||
pub(crate) fn emit_chatlist_items_changed_for_contact(context: &Context, _contact_id: ContactId) {
|
||||
// note:(treefit): it is too expensive to find the right chats
|
||||
// so we'll just tell ui to reload every loaded item
|
||||
emit_unknown_chatlist_items_changed(context)
|
||||
// note:(treefit): in the future we could instead emit an extra event for this and also store contact id in the chatlistitems
|
||||
// (contact id for dm chats and contact id of contact that wrote the message in the summary)
|
||||
// the ui could then look for this info in the cache and only reload the needed chats.
|
||||
}
|
||||
|
||||
/// Tests for chatlist events
|
||||
///
|
||||
/// Only checks if the events are emitted,
|
||||
/// does not check for excess/too-many events
|
||||
#[cfg(test)]
|
||||
mod test_chatlist_events {
|
||||
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
chat::{
|
||||
self, create_broadcast_list, create_group_chat, set_muted, ChatId, ChatVisibility,
|
||||
MuteDuration, ProtectionStatus,
|
||||
},
|
||||
config::Config,
|
||||
constants::*,
|
||||
contact::Contact,
|
||||
message::{self, Message, MessageState},
|
||||
reaction,
|
||||
receive_imf::receive_imf,
|
||||
securejoin::{get_securejoin_qr, join_securejoin},
|
||||
test_utils::{TestContext, TestContextManager},
|
||||
EventType,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
async fn wait_for_chatlist_and_specific_item(context: &TestContext, chat_id: ChatId) {
|
||||
let first_event_is_item = AtomicBool::new(false);
|
||||
context
|
||||
.evtracker
|
||||
.get_matching(|evt| match evt {
|
||||
EventType::ChatlistItemChanged {
|
||||
chat_id: Some(ev_chat_id),
|
||||
} => {
|
||||
if ev_chat_id == &chat_id {
|
||||
first_event_is_item.store(true, Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
EventType::ChatlistChanged => true,
|
||||
_ => false,
|
||||
})
|
||||
.await;
|
||||
if first_event_is_item.load(Ordering::Relaxed) {
|
||||
wait_for_chatlist(context).await;
|
||||
} else {
|
||||
wait_for_chatlist_specific_item(context, chat_id).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_chatlist_specific_item(context: &TestContext, chat_id: ChatId) {
|
||||
context
|
||||
.evtracker
|
||||
.get_matching(|evt| match evt {
|
||||
EventType::ChatlistItemChanged {
|
||||
chat_id: Some(ev_chat_id),
|
||||
} => ev_chat_id == &chat_id,
|
||||
_ => false,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn wait_for_chatlist_all_items(context: &TestContext) {
|
||||
context
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::ChatlistItemChanged { chat_id: None }))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn wait_for_chatlist(context: &TestContext) {
|
||||
context
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::ChatlistChanged))
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_change_chat_visibility() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat_id = create_group_chat(
|
||||
&alice,
|
||||
crate::chat::ProtectionStatus::Unprotected,
|
||||
"my_group",
|
||||
)
|
||||
.await?;
|
||||
|
||||
chat_id
|
||||
.set_visibility(&alice, ChatVisibility::Pinned)
|
||||
.await?;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat_id).await;
|
||||
|
||||
chat_id
|
||||
.set_visibility(&alice, ChatVisibility::Archived)
|
||||
.await?;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat_id).await;
|
||||
|
||||
chat_id
|
||||
.set_visibility(&alice, ChatVisibility::Normal)
|
||||
.await?;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat_id).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// mute a chat, archive it, then use another account to send a message to it, the counter on the archived chatlist item should change
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_archived_counter_increases_for_muted_chats() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
let sent_msg = alice.send_text(chat.id, "moin").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
bob_chat
|
||||
.id
|
||||
.set_visibility(&bob, ChatVisibility::Archived)
|
||||
.await?;
|
||||
set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
|
||||
let sent_msg = alice.send_text(chat.id, "moin2").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
|
||||
bob.evtracker
|
||||
.get_matching(|evt| match evt {
|
||||
EventType::ChatlistItemChanged {
|
||||
chat_id: Some(chat_id),
|
||||
} => chat_id.is_archived_link(),
|
||||
_ => false,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark noticed on archive-link chatlistitem should update the unread counter on it
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_archived_counter_update_on_mark_noticed() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
let sent_msg = alice.send_text(chat.id, "moin").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let bob_chat = bob.create_chat(&alice).await;
|
||||
bob_chat
|
||||
.id
|
||||
.set_visibility(&bob, ChatVisibility::Archived)
|
||||
.await?;
|
||||
set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?;
|
||||
let sent_msg = alice.send_text(chat.id, "moin2").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
chat::marknoticed_chat(&bob, DC_CHAT_ID_ARCHIVED_LINK).await?;
|
||||
wait_for_chatlist_specific_item(&bob, DC_CHAT_ID_ARCHIVED_LINK).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Contact name update - expect all chats to update
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_contact_name_update() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let alice_to_bob_chat = alice.create_chat(&bob).await;
|
||||
let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
// set alice name then receive messagefrom her with bob
|
||||
alice.set_config(Config::Displayname, Some("Alice")).await?;
|
||||
let sent_msg = alice
|
||||
.send_text(alice_to_bob_chat.id, "hello, I set a displayname")
|
||||
.await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let alice_on_bob = bob.add_or_lookup_contact(&alice).await;
|
||||
assert!(alice_on_bob.get_display_name() == "Alice");
|
||||
|
||||
wait_for_chatlist_all_items(&bob).await;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
// set name
|
||||
let addr = alice_on_bob.get_addr();
|
||||
Contact::create(&bob, "Alice2", addr).await?;
|
||||
assert!(bob.add_or_lookup_contact(&alice).await.get_display_name() == "Alice2");
|
||||
|
||||
wait_for_chatlist_all_items(&bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Contact changed avatar
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_contact_changed_avatar() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let alice_to_bob_chat = alice.create_chat(&bob).await;
|
||||
let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
// set alice avatar then receive messagefrom her with bob
|
||||
let file = alice.dir.path().join("avatar.png");
|
||||
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
|
||||
tokio::fs::write(&file, bytes).await?;
|
||||
alice
|
||||
.set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
|
||||
.await?;
|
||||
let sent_msg = alice
|
||||
.send_text(alice_to_bob_chat.id, "hello, I have a new avatar")
|
||||
.await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let alice_on_bob = bob.add_or_lookup_contact(&alice).await;
|
||||
assert!(alice_on_bob.get_profile_image(&bob).await?.is_some());
|
||||
|
||||
wait_for_chatlist_specific_item(&bob, bob.create_chat(&alice).await.id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete chat
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat.delete(&alice).await?;
|
||||
wait_for_chatlist(&alice).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create group chat
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_group_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
alice.evtracker.clear_events();
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create broadcastlist
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_broadcastlist() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
alice.evtracker.clear_events();
|
||||
create_broadcast_list(&alice).await?;
|
||||
wait_for_chatlist(&alice).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mute chat
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mute_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::set_muted(&alice, chat, MuteDuration::Forever).await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::set_muted(&alice, chat, MuteDuration::NotMuted).await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Expiry of mute should also trigger an event
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "does not work yet"]
|
||||
async fn test_mute_chat_expired() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
let mute_duration = MuteDuration::Until(
|
||||
std::time::SystemTime::now()
|
||||
.checked_add(Duration::from_secs(2))
|
||||
.unwrap(),
|
||||
);
|
||||
chat::set_muted(&alice, chat, mute_duration).await?;
|
||||
alice.evtracker.clear_events();
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change chat name
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_change_chat_name() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::set_chat_name(&alice, chat, "New Name").await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change chat profile image
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_change_chat_profile_image() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
let file = alice.dir.path().join("avatar.png");
|
||||
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
|
||||
tokio::fs::write(&file, bytes).await?;
|
||||
chat::set_chat_profile_image(&alice, chat, file.to_str().unwrap()).await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive group and receive name change
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_receiving_group_and_group_changes() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
|
||||
chat_id_for_bob.accept(&bob).await?;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
chat::set_chat_name(&alice, chat, "New Name").await?;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Accept contact request
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_accept_contact_request() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
chat_id_for_bob.accept(&bob).await?;
|
||||
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Block contact request
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_block_contact_request() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
chat_id_for_bob.block(&bob).await?;
|
||||
wait_for_chatlist(&bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete message
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_delete_message() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
message::delete_msgs(&alice, &[message]).await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Click on chat should remove the unread count (on msgs noticed)
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_msgs_noticed_on_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let chat = alice
|
||||
.create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
|
||||
.await;
|
||||
let sent_msg = alice.send_text(chat, "Hello").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
chat_id_for_bob.accept(&bob).await?;
|
||||
|
||||
let sent_msg = alice.send_text(chat, "New Message").await;
|
||||
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
|
||||
assert!(chat_id_for_bob.get_fresh_msg_cnt(&bob).await? >= 1);
|
||||
|
||||
bob.evtracker.clear_events();
|
||||
chat::marknoticed_chat(&bob, chat_id_for_bob).await?;
|
||||
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Block and Unblock contact
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_unblock_contact() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let contact_id = Contact::create(&alice, "example", "example@example.com").await?;
|
||||
let _ = ChatId::create_for_contact(&alice, contact_id).await;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
Contact::block(&alice, contact_id).await?;
|
||||
wait_for_chatlist(&alice).await;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
Contact::unblock(&alice, contact_id).await?;
|
||||
wait_for_chatlist(&alice).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ephemeral / disappearing messages
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_update_after_ephemeral_messages() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
chat.set_ephemeral_timer(&alice, crate::ephemeral::Timer::Enabled { duration: 1 })
|
||||
.await?;
|
||||
let _ = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
wait_for_chatlist_and_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// AdHoc (Groups without a group ID.) group receiving
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_adhoc_group() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let mime = br#"Subject: First thread
|
||||
Message-ID: first@example.org
|
||||
To: Alice <alice@example.org>, Bob <bob@example.net>
|
||||
From: Claire <claire@example.org>
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
First thread."#;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
receive_imf(&alice, mime, false).await?;
|
||||
wait_for_chatlist(&alice).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test both direction of securejoin
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_secure_join_group() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let alice_chatid =
|
||||
chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
|
||||
|
||||
// Step 1: Generate QR-code, secure-join implied by chatid
|
||||
let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?;
|
||||
|
||||
// Step 2: Bob scans QR-code, sends vg-request
|
||||
bob.evtracker.clear_events();
|
||||
let bob_chatid = join_securejoin(&bob.ctx, &qr).await?;
|
||||
wait_for_chatlist(&bob).await;
|
||||
|
||||
let sent = bob.pop_sent_msg().await;
|
||||
|
||||
// Step 3: Alice receives vg-request, sends vg-auth-required
|
||||
alice.evtracker.clear_events();
|
||||
alice.recv_msg(&sent).await;
|
||||
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
|
||||
// Step 4: Bob receives vg-auth-required, sends vg-request-with-auth
|
||||
bob.evtracker.clear_events();
|
||||
bob.recv_msg(&sent).await;
|
||||
wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
|
||||
|
||||
let sent = bob.pop_sent_msg().await;
|
||||
|
||||
// Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
|
||||
alice.evtracker.clear_events();
|
||||
alice.recv_msg(&sent).await;
|
||||
wait_for_chatlist_and_specific_item(&alice, alice_chatid).await;
|
||||
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
|
||||
// Step 7: Bob receives vg-member-added
|
||||
bob.evtracker.clear_events();
|
||||
bob.recv_msg(&sent).await;
|
||||
wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call Resend on message
|
||||
///
|
||||
/// (the event is technically only needed if it is the last message in the chat, but checking that would be too expensive so the event is always emitted)
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_resend_message() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
|
||||
let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
|
||||
let _ = alice.pop_sent_msg().await;
|
||||
|
||||
let message = Message::load_from_db(&alice, msg_id).await?;
|
||||
assert_eq!(message.get_state(), MessageState::OutDelivered);
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
chat::resend_msgs(&alice, &[msg_id]).await?;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// test that setting a reaction emits chatlistitem update event
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
|
||||
let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
|
||||
let _ = alice.pop_sent_msg().await;
|
||||
|
||||
alice.evtracker.clear_events();
|
||||
reaction::send_reaction(&alice, msg_id, "👍").await?;
|
||||
let _ = alice.pop_sent_msg().await;
|
||||
wait_for_chatlist_specific_item(&alice, chat).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -291,4 +291,15 @@ pub enum EventType {
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsBackgroundFetchDone,
|
||||
/// Inform that set of chats or the order of the chats in the chatlist has changed.
|
||||
///
|
||||
/// Sometimes this is emitted together with `UIChatlistItemChanged`.
|
||||
ChatlistChanged,
|
||||
|
||||
/// Inform 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.
|
||||
ChatlistItemChanged {
|
||||
/// ID of the changed chat
|
||||
chat_id: Option<ChatId>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use ratelimit::Ratelimit;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::chat::{self, ChatId, ChatIdBlocked};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{self, Blocked, Chattype, ShowEmails};
|
||||
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
|
||||
@@ -1170,6 +1171,7 @@ impl Session {
|
||||
.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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -13,8 +13,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::{chatlist_events, stock_str};
|
||||
|
||||
/// 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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
if 0 != seconds {
|
||||
context.scheduler.interrupt_location().await;
|
||||
}
|
||||
@@ -802,6 +803,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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{Chat, ChatId};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
use crate::constants::{
|
||||
Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
||||
@@ -138,6 +139,7 @@ WHERE id=?;
|
||||
chat_id,
|
||||
msg_id: self,
|
||||
});
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1527,9 +1529,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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
|
||||
}
|
||||
|
||||
if !msg_ids.is_empty() {
|
||||
context.emit_msgs_changed_without_ids();
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
// Run housekeeping to delete unused blobs.
|
||||
context
|
||||
.set_config_internal(Config::LastHousekeeping, None)
|
||||
@@ -1664,6 +1669,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));
|
||||
chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1724,6 +1730,7 @@ pub(crate) async fn set_msg_failed(
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -32,13 +32,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::{chatlist_events, location, stock_str, tools};
|
||||
|
||||
/// A parsed MIME message.
|
||||
///
|
||||
@@ -2153,6 +2152,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 too expensive to check, debounce also solves it)
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,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::{chatlist_events, stock_str};
|
||||
|
||||
/// Type of the public key stored inside the peerstate.
|
||||
#[derive(Debug)]
|
||||
@@ -722,6 +722,9 @@ impl Peerstate {
|
||||
.await?;
|
||||
}
|
||||
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
// update the chats the contact is part of
|
||||
chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use std::fmt;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::chat::{send_msg, Chat, ChatId};
|
||||
use crate::chatlist_events;
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -214,6 +215,7 @@ async fn set_msg_id_reaction(
|
||||
msg_id,
|
||||
contact_id,
|
||||
});
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,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::sync::Sync::*;
|
||||
use crate::tools::{
|
||||
self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters, validate_id,
|
||||
};
|
||||
use crate::{chatlist_events, location};
|
||||
use crate::{contact, imap};
|
||||
|
||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
||||
@@ -1903,6 +1903,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));
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
|
||||
}
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
@@ -2216,6 +2218,7 @@ async fn apply_group_changes(
|
||||
|
||||
if send_event_chat_modified {
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
}
|
||||
Ok((group_changes_msgs, better_msg))
|
||||
}
|
||||
@@ -2518,6 +2521,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));
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, new_chat_id);
|
||||
|
||||
Ok(Some(new_chat_id))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
|
||||
use crate::chatlist_events;
|
||||
use crate::config::Config;
|
||||
use crate::constants::Blocked;
|
||||
use crate::contact::{Contact, ContactId, Origin};
|
||||
@@ -680,6 +681,7 @@ async fn secure_connection_established(
|
||||
)
|
||||
.await?;
|
||||
context.emit_event(EventType::ChatModified(chat_id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,6 +1022,11 @@ impl EventTracker {
|
||||
self.get_matching(|evt| matches!(evt, EventType::IncomingMsg { .. }))
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Clears event queue
|
||||
pub fn clear_events(&self) {
|
||||
while self.try_recv().is_ok() {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a specific message from a chat and asserts that the chat has a specific length.
|
||||
|
||||
Reference in New Issue
Block a user