mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
api: add ChatListChanged and ChatListItemChanged events (#4476)
This commit is contained in:
@@ -6213,7 +6213,24 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
* This event is only emitted by the account manager
|
* This event is only emitted by the account manager
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE 2200
|
#define DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE 2200
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform that set of chats or the order of the chats in the chatlist has changed.
|
||||||
|
*
|
||||||
|
* Sometimes this is emitted together with `DC_EVENT_CHATLIST_ITEM_CHANGED`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DC_EVENT_CHATLIST_CHANGED 2300
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform 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_CHATLIST_ITEM_CHANGED 2301
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
|
|||||||
@@ -564,6 +564,8 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
|||||||
EventType::WebxdcStatusUpdate { .. } => 2120,
|
EventType::WebxdcStatusUpdate { .. } => 2120,
|
||||||
EventType::WebxdcInstanceDeleted { .. } => 2121,
|
EventType::WebxdcInstanceDeleted { .. } => 2121,
|
||||||
EventType::AccountsBackgroundFetchDone => 2200,
|
EventType::AccountsBackgroundFetchDone => 2200,
|
||||||
|
EventType::ChatlistChanged => 2300,
|
||||||
|
EventType::ChatlistItemChanged { .. } => 2301,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,6 +595,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::IncomingMsgBunch { .. }
|
| EventType::IncomingMsgBunch { .. }
|
||||||
| EventType::ErrorSelfNotInGroup(_)
|
| EventType::ErrorSelfNotInGroup(_)
|
||||||
| EventType::AccountsBackgroundFetchDone => 0,
|
| EventType::AccountsBackgroundFetchDone => 0,
|
||||||
|
EventType::ChatlistChanged => 0,
|
||||||
EventType::MsgsChanged { chat_id, .. }
|
EventType::MsgsChanged { chat_id, .. }
|
||||||
| EventType::ReactionsChanged { chat_id, .. }
|
| EventType::ReactionsChanged { chat_id, .. }
|
||||||
| EventType::IncomingMsg { chat_id, .. }
|
| EventType::IncomingMsg { chat_id, .. }
|
||||||
@@ -617,6 +620,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::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||||
EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||||
|
EventType::ChatlistItemChanged { chat_id } => {
|
||||||
|
chat_id.unwrap_or_default().to_u32() as libc::c_int
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +659,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::IncomingMsgBunch { .. }
|
| EventType::IncomingMsgBunch { .. }
|
||||||
| EventType::SelfavatarChanged
|
| EventType::SelfavatarChanged
|
||||||
| EventType::AccountsBackgroundFetchDone
|
| EventType::AccountsBackgroundFetchDone
|
||||||
|
| EventType::ChatlistChanged
|
||||||
|
| EventType::ChatlistItemChanged { .. }
|
||||||
| EventType::ConfigSynced { .. } => 0,
|
| EventType::ConfigSynced { .. } => 0,
|
||||||
EventType::ChatModified(_) => 0,
|
EventType::ChatModified(_) => 0,
|
||||||
EventType::MsgsChanged { msg_id, .. }
|
EventType::MsgsChanged { msg_id, .. }
|
||||||
@@ -717,7 +725,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
|||||||
| EventType::WebxdcInstanceDeleted { .. }
|
| EventType::WebxdcInstanceDeleted { .. }
|
||||||
| EventType::AccountsBackgroundFetchDone
|
| EventType::AccountsBackgroundFetchDone
|
||||||
| EventType::ChatEphemeralTimerModified { .. }
|
| EventType::ChatEphemeralTimerModified { .. }
|
||||||
| EventType::IncomingMsgBunch { .. } => ptr::null_mut(),
|
| EventType::IncomingMsgBunch { .. }
|
||||||
|
| EventType::ChatlistItemChanged { .. }
|
||||||
|
| EventType::ChatlistChanged => ptr::null_mut(),
|
||||||
EventType::ConfigureProgress { comment, .. } => {
|
EventType::ConfigureProgress { comment, .. } => {
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
comment.to_c_string().unwrap_or_default().into_raw()
|
comment.to_c_string().unwrap_or_default().into_raw()
|
||||||
|
|||||||
@@ -1626,6 +1626,9 @@ impl CommandApi {
|
|||||||
/// the current device.
|
/// the current device.
|
||||||
///
|
///
|
||||||
/// Can be cancelled by stopping the ongoing process.
|
/// Can be cancelled by stopping the ongoing process.
|
||||||
|
///
|
||||||
|
/// Do not forget to call start_io on the account after a successful import,
|
||||||
|
/// otherwise it will not connect to the email server.
|
||||||
async fn get_backup(&self, account_id: u32, qr_text: String) -> Result<()> {
|
async fn get_backup(&self, account_id: u32, qr_text: String) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let qr = qr::check_qr(&ctx, &qr_text).await?;
|
let qr = qr::check_qr(&ctx, &qr_text).await?;
|
||||||
|
|||||||
@@ -250,6 +250,15 @@ pub enum EventType {
|
|||||||
///
|
///
|
||||||
/// This event is only emitted by the account manager
|
/// This event is only emitted by the account manager
|
||||||
AccountsBackgroundFetchDone,
|
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.
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
ChatlistItemChanged { chat_id: Option<u32> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CoreEventType> for EventType {
|
impl From<CoreEventType> for EventType {
|
||||||
@@ -357,6 +366,10 @@ impl From<CoreEventType> for EventType {
|
|||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
|
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
|
||||||
|
CoreEventType::ChatlistItemChanged { chat_id } => ChatlistItemChanged {
|
||||||
|
chat_id: chat_id.map(|id| id.to_u32()),
|
||||||
|
},
|
||||||
|
CoreEventType::ChatlistChanged => ChatlistChanged,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ class Account:
|
|||||||
"""Wait until the next event and return it."""
|
"""Wait until the next event and return it."""
|
||||||
return AttrDict(self._rpc.wait_for_event(self.id))
|
return AttrDict(self._rpc.wait_for_event(self.id))
|
||||||
|
|
||||||
|
def clear_all_events(self):
|
||||||
|
"""Removes all queued-up events for a given account. Useful for tests."""
|
||||||
|
self._rpc.clear_all_events(self.id)
|
||||||
|
|
||||||
def remove(self) -> None:
|
def remove(self) -> None:
|
||||||
"""Remove the account."""
|
"""Remove the account."""
|
||||||
self._rpc.remove_account(self.id)
|
self._rpc.remove_account(self.id)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ class EventType(str, Enum):
|
|||||||
SELFAVATAR_CHANGED = "SelfavatarChanged"
|
SELFAVATAR_CHANGED = "SelfavatarChanged"
|
||||||
WEBXDC_STATUS_UPDATE = "WebxdcStatusUpdate"
|
WEBXDC_STATUS_UPDATE = "WebxdcStatusUpdate"
|
||||||
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
||||||
|
CHATLIST_CHANGED = "ChatlistChanged"
|
||||||
|
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
|
||||||
|
|
||||||
|
|
||||||
class ChatId(IntEnum):
|
class ChatId(IntEnum):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from queue import Queue
|
from queue import Empty, Queue
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
from typing import Any, Iterator, Optional
|
from typing import Any, Iterator, Optional
|
||||||
|
|
||||||
@@ -188,5 +188,14 @@ class Rpc:
|
|||||||
queue = self.get_queue(account_id)
|
queue = self.get_queue(account_id)
|
||||||
return queue.get()
|
return queue.get()
|
||||||
|
|
||||||
|
def clear_all_events(self, account_id: int):
|
||||||
|
"""Removes all queued-up events for a given account. Useful for tests."""
|
||||||
|
queue = self.get_queue(account_id)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
queue.get_nowait()
|
||||||
|
except Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
def __getattr__(self, attr: str):
|
def __getattr__(self, attr: str):
|
||||||
return RpcMethod(self, attr)
|
return RpcMethod(self, attr)
|
||||||
|
|||||||
218
deltachat-rpc-client/tests/test_chatlist_events.py
Normal file
218
deltachat-rpc-client/tests/test_chatlist_events.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from deltachat_rpc_client import Account, EventType, const
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_chatlist_and_specific_item(account, chat_id):
|
||||||
|
first_event = ""
|
||||||
|
while True:
|
||||||
|
event = account.wait_for_event()
|
||||||
|
if event.kind == EventType.CHATLIST_CHANGED:
|
||||||
|
first_event = "change"
|
||||||
|
break
|
||||||
|
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id:
|
||||||
|
first_event = "item_change"
|
||||||
|
break
|
||||||
|
while True:
|
||||||
|
event = account.wait_for_event()
|
||||||
|
if event.kind == EventType.CHATLIST_CHANGED and first_event == "item_change":
|
||||||
|
break
|
||||||
|
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id and first_event == "change":
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_chatlist_specific_item(account, chat_id):
|
||||||
|
while True:
|
||||||
|
event = account.wait_for_event()
|
||||||
|
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_chatlist(account):
|
||||||
|
while True:
|
||||||
|
event = account.wait_for_event()
|
||||||
|
if event.kind == EventType.CHATLIST_CHANGED:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def test_delivery_status(acfactory: ACFactory) -> None:
|
||||||
|
"""
|
||||||
|
Test change status on chatlistitem when status changes (delivered, read)
|
||||||
|
"""
|
||||||
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
bob_addr = bob.get_config("addr")
|
||||||
|
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
||||||
|
alice_chat_bob = alice_contact_bob.create_chat()
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
bob.stop_io()
|
||||||
|
alice.stop_io()
|
||||||
|
alice_chat_bob.send_text("hi")
|
||||||
|
wait_for_chatlist_and_specific_item(alice, chat_id=alice_chat_bob.id)
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
alice.start_io()
|
||||||
|
wait_for_chatlist_specific_item(alice, chat_id=alice_chat_bob.id)
|
||||||
|
|
||||||
|
bob.clear_all_events()
|
||||||
|
bob.start_io()
|
||||||
|
|
||||||
|
event = bob.wait_for_incoming_msg_event()
|
||||||
|
msg = bob.get_message_by_id(event.msg_id)
|
||||||
|
msg.get_snapshot().chat.accept()
|
||||||
|
msg.mark_seen()
|
||||||
|
|
||||||
|
chat_item = alice._rpc.get_chatlist_items_by_entries(alice.id, [alice_chat_bob.id])[str(alice_chat_bob.id)]
|
||||||
|
assert chat_item["summaryStatus"] == const.MessageState.OUT_DELIVERED
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event = alice.wait_for_event()
|
||||||
|
if event.kind == EventType.MSG_READ:
|
||||||
|
break
|
||||||
|
|
||||||
|
wait_for_chatlist_specific_item(alice, chat_id=alice_chat_bob.id)
|
||||||
|
chat_item = alice._rpc.get_chatlist_items_by_entries(alice.id, [alice_chat_bob.id])[str(alice_chat_bob.id)]
|
||||||
|
assert chat_item["summaryStatus"] == const.MessageState.OUT_MDN_RCVD
|
||||||
|
|
||||||
|
|
||||||
|
def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
||||||
|
"""
|
||||||
|
Test change status on chatlistitem when status changes failed
|
||||||
|
"""
|
||||||
|
(alice,) = acfactory.get_online_accounts(1)
|
||||||
|
|
||||||
|
invalid_contact = alice.create_contact("example@example.com", "invalid address")
|
||||||
|
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
|
||||||
|
failing_message = invalid_chat.send_text("test")
|
||||||
|
|
||||||
|
wait_for_chatlist_and_specific_item(alice, invalid_chat.id)
|
||||||
|
|
||||||
|
assert failing_message.get_snapshot().state == const.MessageState.OUT_PENDING
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event = alice.wait_for_event()
|
||||||
|
if event.kind == EventType.MSG_FAILED:
|
||||||
|
break
|
||||||
|
|
||||||
|
wait_for_chatlist_specific_item(alice, invalid_chat.id)
|
||||||
|
|
||||||
|
assert failing_message.get_snapshot().state == const.MessageState.OUT_FAILED
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||||
|
"""
|
||||||
|
Test if download on demand emits chatlist update events.
|
||||||
|
This is only needed for last message in chat, but finding that out is too expensive, so it's always emitted
|
||||||
|
"""
|
||||||
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
bob_addr = bob.get_config("addr")
|
||||||
|
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
||||||
|
alice_chat_bob = alice_contact_bob.create_chat()
|
||||||
|
alice_chat_bob.send_text("hi")
|
||||||
|
|
||||||
|
alice.set_config("download_limit", "1")
|
||||||
|
|
||||||
|
event = bob.wait_for_incoming_msg_event()
|
||||||
|
msg = bob.get_message_by_id(event.msg_id)
|
||||||
|
chat_id = msg.get_snapshot().chat_id
|
||||||
|
msg.get_snapshot().chat.accept()
|
||||||
|
bob.get_chat_by_id(chat_id).send_message(
|
||||||
|
"Hello World, this message is bigger than 5 bytes",
|
||||||
|
html=base64.b64encode(os.urandom(300000)).decode("utf-8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
msg_id = alice.wait_for_incoming_msg_event().msg_id
|
||||||
|
|
||||||
|
assert alice.get_message_by_id(msg_id).get_snapshot().download_state == const.DownloadState.AVAILABLE
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
chat_id = alice.get_message_by_id(msg_id).get_snapshot().chat_id
|
||||||
|
alice._rpc.download_full_message(alice.id, msg_id)
|
||||||
|
|
||||||
|
wait_for_chatlist_specific_item(alice, chat_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Account]:
|
||||||
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
bob_addr = bob.get_config("addr")
|
||||||
|
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
||||||
|
alice_chat_bob = alice_contact_bob.create_chat()
|
||||||
|
alice_chat_bob.send_text("hi")
|
||||||
|
|
||||||
|
bob.wait_for_incoming_msg_event()
|
||||||
|
|
||||||
|
alice_second_device: Account = acfactory.get_unconfigured_account()
|
||||||
|
|
||||||
|
alice._rpc.provide_backup.future(alice.id)
|
||||||
|
backup_code = alice._rpc.get_backup_qr(alice.id)
|
||||||
|
alice_second_device._rpc.get_backup(alice_second_device.id, backup_code)
|
||||||
|
alice_second_device.start_io()
|
||||||
|
alice.clear_all_events()
|
||||||
|
alice_second_device.clear_all_events()
|
||||||
|
bob.clear_all_events()
|
||||||
|
return [alice, alice_second_device, bob, alice_chat_bob]
|
||||||
|
|
||||||
|
|
||||||
|
def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
|
||||||
|
"""
|
||||||
|
Test that chatlist changed events are emitted for the second device
|
||||||
|
when the message is marked as read on the first device
|
||||||
|
"""
|
||||||
|
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)
|
||||||
|
|
||||||
|
alice_chat_bob.send_text("hello")
|
||||||
|
|
||||||
|
event = bob.wait_for_incoming_msg_event()
|
||||||
|
msg = bob.get_message_by_id(event.msg_id)
|
||||||
|
bob_chat_id = msg.get_snapshot().chat_id
|
||||||
|
msg.get_snapshot().chat.accept()
|
||||||
|
|
||||||
|
alice.clear_all_events()
|
||||||
|
alice_second_device.clear_all_events()
|
||||||
|
bob.get_chat_by_id(bob_chat_id).send_text("hello")
|
||||||
|
|
||||||
|
# make sure alice_second_device already received the message
|
||||||
|
alice_second_device.wait_for_incoming_msg_event()
|
||||||
|
|
||||||
|
event = alice.wait_for_incoming_msg_event()
|
||||||
|
msg = alice.get_message_by_id(event.msg_id)
|
||||||
|
alice_second_device.clear_all_events()
|
||||||
|
msg.mark_seen()
|
||||||
|
|
||||||
|
wait_for_chatlist_specific_item(bob, bob_chat_id)
|
||||||
|
wait_for_chatlist_specific_item(alice, alice_chat_bob.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multidevice_sync_chat(acfactory: ACFactory) -> None:
|
||||||
|
"""
|
||||||
|
Test multidevice sync: syncing chat visibility and muting across multiple devices
|
||||||
|
"""
|
||||||
|
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)
|
||||||
|
|
||||||
|
alice_chat_bob.archive()
|
||||||
|
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
||||||
|
assert alice_second_device.get_chat_by_id(alice_chat_bob.id).get_basic_snapshot().archived
|
||||||
|
|
||||||
|
alice_second_device.clear_all_events()
|
||||||
|
alice_chat_bob.pin()
|
||||||
|
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
||||||
|
|
||||||
|
alice_second_device.clear_all_events()
|
||||||
|
alice_chat_bob.mute()
|
||||||
|
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
||||||
|
assert alice_second_device.get_chat_by_id(alice_chat_bob.id).get_basic_snapshot().is_muted
|
||||||
@@ -30,6 +30,8 @@ module.exports = {
|
|||||||
DC_DOWNLOAD_IN_PROGRESS: 1000,
|
DC_DOWNLOAD_IN_PROGRESS: 1000,
|
||||||
DC_DOWNLOAD_UNDECIPHERABLE: 30,
|
DC_DOWNLOAD_UNDECIPHERABLE: 30,
|
||||||
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
|
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
|
||||||
|
DC_EVENT_CHATLIST_CHANGED: 2300,
|
||||||
|
DC_EVENT_CHATLIST_ITEM_CHANGED: 2301,
|
||||||
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
|
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
|
||||||
DC_EVENT_CHAT_MODIFIED: 2020,
|
DC_EVENT_CHAT_MODIFIED: 2020,
|
||||||
DC_EVENT_CONFIGURE_PROGRESS: 2041,
|
DC_EVENT_CONFIGURE_PROGRESS: 2041,
|
||||||
|
|||||||
@@ -37,5 +37,7 @@ module.exports = {
|
|||||||
2111: 'DC_EVENT_CONFIG_SYNCED',
|
2111: 'DC_EVENT_CONFIG_SYNCED',
|
||||||
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
|
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
|
||||||
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
|
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
|
||||||
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE'
|
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
|
||||||
|
2300: 'DC_EVENT_CHATLIST_CHANGED',
|
||||||
|
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export enum C {
|
|||||||
DC_DOWNLOAD_IN_PROGRESS = 1000,
|
DC_DOWNLOAD_IN_PROGRESS = 1000,
|
||||||
DC_DOWNLOAD_UNDECIPHERABLE = 30,
|
DC_DOWNLOAD_UNDECIPHERABLE = 30,
|
||||||
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
|
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
|
||||||
|
DC_EVENT_CHATLIST_CHANGED = 2300,
|
||||||
|
DC_EVENT_CHATLIST_ITEM_CHANGED = 2301,
|
||||||
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021,
|
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021,
|
||||||
DC_EVENT_CHAT_MODIFIED = 2020,
|
DC_EVENT_CHAT_MODIFIED = 2020,
|
||||||
DC_EVENT_CONFIGURE_PROGRESS = 2041,
|
DC_EVENT_CONFIGURE_PROGRESS = 2041,
|
||||||
@@ -333,4 +335,6 @@ export const EventId2EventName: { [key: number]: string } = {
|
|||||||
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
|
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
|
||||||
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
|
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
|
||||||
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
|
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
|
||||||
|
2300: 'DC_EVENT_CHATLIST_CHANGED',
|
||||||
|
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/chat.rs
28
src/chat.rs
@@ -15,6 +15,7 @@ use strum_macros::EnumIter;
|
|||||||
use crate::aheader::EncryptPreference;
|
use crate::aheader::EncryptPreference;
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chatlist::Chatlist;
|
use crate::chatlist::Chatlist;
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::color::str_to_color;
|
use crate::color::str_to_color;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
@@ -295,6 +296,8 @@ impl ChatId {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
Ok(chat_id)
|
Ok(chat_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +414,7 @@ impl ChatId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
// NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices.
|
// 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<()> {
|
pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
|
||||||
self.set_blocked(context, Blocked::Not).await?;
|
self.set_blocked(context, Blocked::Not).await?;
|
||||||
|
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
let chat = Chat::load_from_db(context, self).await?;
|
let chat = Chat::load_from_db(context, self).await?;
|
||||||
// TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices.
|
// TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices.
|
||||||
@@ -443,6 +449,7 @@ impl ChatId {
|
|||||||
.log_err(context)
|
.log_err(context)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +493,7 @@ impl ChatId {
|
|||||||
|
|
||||||
if self.set_blocked(context, Blocked::Not).await? {
|
if self.set_blocked(context, Blocked::Not).await? {
|
||||||
context.emit_event(EventType::ChatModified(self));
|
context.emit_event(EventType::ChatModified(self));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
@@ -528,6 +536,7 @@ impl ChatId {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
context.emit_event(EventType::ChatModified(self));
|
context.emit_event(EventType::ChatModified(self));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||||
|
|
||||||
// make sure, the receivers will get all keys
|
// make sure, the receivers will get all keys
|
||||||
self.reset_gossiped_timestamp(context).await?;
|
self.reset_gossiped_timestamp(context).await?;
|
||||||
@@ -576,6 +585,7 @@ impl ChatId {
|
|||||||
if protection_status_modified {
|
if protection_status_modified {
|
||||||
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
|
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
|
||||||
.await?;
|
.await?;
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -662,6 +672,8 @@ impl ChatId {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, self);
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
let chat = Chat::load_from_db(context, self).await?;
|
let chat = Chat::load_from_db(context, self).await?;
|
||||||
@@ -768,6 +780,7 @@ impl ChatId {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
|
||||||
context
|
context
|
||||||
.set_config_internal(Config::LastHousekeeping, None)
|
.set_config_internal(Config::LastHousekeeping, None)
|
||||||
@@ -779,6 +792,7 @@ impl ChatId {
|
|||||||
msg.text = stock_str::self_deleted_msg_body(context).await;
|
msg.text = stock_str::self_deleted_msg_body(context).await;
|
||||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||||
}
|
}
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3103,7 +3117,9 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
|
|||||||
.await?;
|
.await?;
|
||||||
for chat_id_in_archive in chat_ids_in_archive {
|
for chat_id_in_archive in chat_ids_in_archive {
|
||||||
context.emit_event(EventType::MsgsNoticed(chat_id_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 {
|
} else {
|
||||||
let exists = context
|
let exists = context
|
||||||
.sql
|
.sql
|
||||||
@@ -3130,6 +3146,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.emit_event(EventType::MsgsNoticed(chat_id));
|
context.emit_event(EventType::MsgsNoticed(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3197,6 +3214,7 @@ pub(crate) async fn mark_old_messages_as_noticed(
|
|||||||
|
|
||||||
for c in changed_chats {
|
for c in changed_chats {
|
||||||
context.emit_event(EventType::MsgsNoticed(c));
|
context.emit_event(EventType::MsgsNoticed(c));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -3359,6 +3377,8 @@ pub async fn create_group_chat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.emit_msgs_changed_without_ids();
|
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 {
|
if protect == ProtectionStatus::Protected {
|
||||||
chat_id
|
chat_id
|
||||||
@@ -3446,11 +3466,14 @@ pub(crate) async fn create_broadcast_list_ex(
|
|||||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
||||||
|
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
let id = SyncId::Grpid(grpid);
|
let id = SyncId::Grpid(grpid);
|
||||||
let action = SyncAction::CreateBroadcast(chat_name);
|
let action = SyncAction::CreateBroadcast(chat_name);
|
||||||
self::sync(context, id, action).await.log_err(context).ok();
|
self::sync(context, id, action).await.log_err(context).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(chat_id)
|
Ok(chat_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3721,6 +3744,7 @@ pub(crate) async fn set_muted_ex(
|
|||||||
.await
|
.await
|
||||||
.context(format!("Failed to set mute duration for {chat_id}"))?;
|
.context(format!("Failed to set mute duration for {chat_id}"))?;
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||||
chat.sync(context, SyncAction::SetMuted(duration))
|
chat.sync(context, SyncAction::SetMuted(duration))
|
||||||
@@ -3881,6 +3905,7 @@ async fn rename_ex(
|
|||||||
sync = Nosync;
|
sync = Nosync;
|
||||||
}
|
}
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3941,6 +3966,7 @@ pub async fn set_chat_profile_image(
|
|||||||
context.emit_msgs_changed(chat_id, msg.id);
|
context.emit_msgs_changed(chat_id, msg.id);
|
||||||
}
|
}
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4087,6 +4113,8 @@ pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
|||||||
msg_id: msg.id,
|
msg_id: msg.id,
|
||||||
});
|
});
|
||||||
msg.timestamp_sort = create_smeared_timestamp(context);
|
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() {
|
if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
|
||||||
context.scheduler.interrupt_smtp().await;
|
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,
|
duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time,
|
||||||
EmailAddress, SystemTime,
|
EmailAddress, SystemTime,
|
||||||
};
|
};
|
||||||
use crate::{chat, stock_str};
|
use crate::{chat, chatlist_events, stock_str};
|
||||||
|
|
||||||
/// Time during which a contact is considered as seen recently.
|
/// Time during which a contact is considered as seen recently.
|
||||||
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
||||||
@@ -760,6 +760,7 @@ impl Contact {
|
|||||||
if count > 0 {
|
if count > 0 {
|
||||||
// Chat name updated
|
// Chat name updated
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
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)
|
Ok(row_id)
|
||||||
}).await?;
|
}).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.
|
/// Add a number of contacts.
|
||||||
@@ -1524,6 +1527,7 @@ WHERE type=? AND id IN (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1574,6 +1578,7 @@ pub(crate) async fn set_profile_image(
|
|||||||
if changed {
|
if changed {
|
||||||
contact.update_param(context).await?;
|
contact.update_param(context).await?;
|
||||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||||
|
chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1786,6 +1791,11 @@ impl RecentlySeenLoop {
|
|||||||
// Timeout, notify about contact.
|
// Timeout, notify about contact.
|
||||||
if let Some(contact_id) = contact_id {
|
if let Some(contact_id) = contact_id {
|
||||||
context.emit_event(EventType::ContactsChanged(Some(*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();
|
unseen_queue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1818,6 +1828,11 @@ impl RecentlySeenLoop {
|
|||||||
// Event is already in the past.
|
// Event is already in the past.
|
||||||
if let Some(contact_id) = contact_id {
|
if let Some(contact_id) = contact_id {
|
||||||
context.emit_event(EventType::ContactsChanged(Some(*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();
|
unseen_queue.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use tokio::sync::{Mutex, Notify, RwLock};
|
|||||||
|
|
||||||
use crate::aheader::EncryptPreference;
|
use crate::aheader::EncryptPreference;
|
||||||
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
|
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
|
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
|
/// Emits a MsgsChanged event with specified chat and message ids
|
||||||
pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
|
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::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
|
/// Emits an IncomingMsg event with specified chat and message ids
|
||||||
pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
|
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::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.
|
/// Returns a receiver for emitted events.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::imap::session::Session;
|
|||||||
use crate::message::{Message, MsgId, Viewtype};
|
use crate::message::{Message, MsgId, Viewtype};
|
||||||
use crate::mimeparser::{MimeMessage, Part};
|
use crate::mimeparser::{MimeMessage, Part};
|
||||||
use crate::tools::time;
|
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`.
|
/// Download limits should not be used below `MIN_DOWNLOAD_LIMIT`.
|
||||||
///
|
///
|
||||||
@@ -115,6 +115,7 @@ impl MsgId {
|
|||||||
chat_id: msg.chat_id,
|
chat_id: msg.chat_id,
|
||||||
msg_id: self,
|
msg_id: self,
|
||||||
});
|
});
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
use async_channel::{self as channel, Receiver, Sender, TrySendError};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
|
pub(crate) mod chatlist_events;
|
||||||
mod payload;
|
mod payload;
|
||||||
|
|
||||||
pub use self::payload::EventType;
|
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
|
/// This event is only emitted by the account manager
|
||||||
AccountsBackgroundFetchDone,
|
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 tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::chat::{self, ChatId, ChatIdBlocked};
|
use crate::chat::{self, ChatId, ChatIdBlocked};
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{self, Blocked, Chattype, ShowEmails};
|
use crate::constants::{self, Blocked, Chattype, ShowEmails};
|
||||||
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
|
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}"))?;
|
.with_context(|| format!("failed to set MODSEQ for folder {folder}"))?;
|
||||||
for updated_chat_id in updated_chat_ids {
|
for updated_chat_id in updated_chat_ids {
|
||||||
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
|
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use crate::context::Context;
|
|||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::message::{Message, MsgId, Viewtype};
|
use crate::message::{Message, MsgId, Viewtype};
|
||||||
use crate::mimeparser::SystemMessage;
|
use crate::mimeparser::SystemMessage;
|
||||||
use crate::stock_str;
|
|
||||||
use crate::tools::{duration_to_str, time};
|
use crate::tools::{duration_to_str, time};
|
||||||
|
use crate::{chatlist_events, stock_str};
|
||||||
|
|
||||||
/// Location record.
|
/// Location record.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[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?;
|
chat::add_info_msg(context, chat_id, &stock_str, now).await?;
|
||||||
}
|
}
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
if 0 != seconds {
|
if 0 != seconds {
|
||||||
context.scheduler.interrupt_location().await;
|
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;
|
let stock_str = stock_str::msg_location_disabled(context).await;
|
||||||
chat::add_info_msg(context, chat_id, &stock_str, now).await?;
|
chat::add_info_msg(context, chat_id, &stock_str, now).await?;
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
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::blob::BlobObject;
|
||||||
use crate::chat::{Chat, ChatId};
|
use crate::chat::{Chat, ChatId};
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_DESIRED_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL,
|
||||||
@@ -138,6 +139,7 @@ WHERE id=?;
|
|||||||
chat_id,
|
chat_id,
|
||||||
msg_id: self,
|
msg_id: self,
|
||||||
});
|
});
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1527,9 +1529,12 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
|||||||
|
|
||||||
for modified_chat_id in modified_chat_ids {
|
for modified_chat_id in modified_chat_ids {
|
||||||
context.emit_msgs_changed(modified_chat_id, MsgId::new(0));
|
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() {
|
if !msg_ids.is_empty() {
|
||||||
|
context.emit_msgs_changed_without_ids();
|
||||||
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
// Run housekeeping to delete unused blobs.
|
// Run housekeeping to delete unused blobs.
|
||||||
context
|
context
|
||||||
.set_config_internal(Config::LastHousekeeping, None)
|
.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 {
|
for updated_chat_id in updated_chat_ids {
|
||||||
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
|
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1724,6 +1730,7 @@ pub(crate) async fn set_msg_failed(
|
|||||||
chat_id: msg.chat_id,
|
chat_id: msg.chat_id,
|
||||||
msg_id: msg.id,
|
msg_id: msg.id,
|
||||||
});
|
});
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,12 @@ use crate::message::{
|
|||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::peerstate::Peerstate;
|
use crate::peerstate::Peerstate;
|
||||||
use crate::simplify::{simplify, SimplifiedText};
|
use crate::simplify::{simplify, SimplifiedText};
|
||||||
use crate::stock_str;
|
|
||||||
use crate::sync::SyncItems;
|
use crate::sync::SyncItems;
|
||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
|
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
|
||||||
strip_rtlo_characters, truncate_by_lines,
|
strip_rtlo_characters, truncate_by_lines,
|
||||||
};
|
};
|
||||||
use crate::{location, tools};
|
use crate::{chatlist_events, location, stock_str, tools};
|
||||||
|
|
||||||
/// A parsed MIME message.
|
/// A parsed MIME message.
|
||||||
///
|
///
|
||||||
@@ -2153,6 +2152,8 @@ async fn handle_mdn(
|
|||||||
{
|
{
|
||||||
update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await?;
|
update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await?;
|
||||||
context.emit_event(EventType::MsgRead { chat_id, msg_id });
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
|||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
use crate::mimeparser::SystemMessage;
|
use crate::mimeparser::SystemMessage;
|
||||||
use crate::sql::Sql;
|
use crate::sql::Sql;
|
||||||
use crate::stock_str;
|
use crate::{chatlist_events, stock_str};
|
||||||
|
|
||||||
/// Type of the public key stored inside the peerstate.
|
/// Type of the public key stored inside the peerstate.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -722,6 +722,9 @@ impl Peerstate {
|
|||||||
.await?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use std::fmt;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::chat::{send_msg, Chat, ChatId};
|
use crate::chat::{send_msg, Chat, ChatId};
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::contact::ContactId;
|
use crate::contact::ContactId;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
@@ -214,6 +215,7 @@ async fn set_msg_id_reaction(
|
|||||||
msg_id,
|
msg_id,
|
||||||
contact_id,
|
contact_id,
|
||||||
});
|
});
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
|
|||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||||
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
|
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
|
||||||
use crate::location;
|
|
||||||
use crate::log::LogExt;
|
use crate::log::LogExt;
|
||||||
use crate::message::{
|
use crate::message::{
|
||||||
self, rfc724_mid_exists, rfc724_mid_exists_and, Message, MessageState, MessengerMessage, MsgId,
|
self, rfc724_mid_exists, rfc724_mid_exists_and, Message, MessageState, MessengerMessage, MsgId,
|
||||||
@@ -40,6 +39,7 @@ use crate::sync::Sync::*;
|
|||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters, validate_id,
|
self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters, validate_id,
|
||||||
};
|
};
|
||||||
|
use crate::{chatlist_events, location};
|
||||||
use crate::{contact, imap};
|
use crate::{contact, imap};
|
||||||
|
|
||||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
/// 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?;
|
chat::add_to_chat_contacts_table(context, new_chat_id, &members).await?;
|
||||||
|
|
||||||
context.emit_event(EventType::ChatModified(new_chat_id));
|
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 {
|
if let Some(chat_id) = chat_id {
|
||||||
@@ -2216,6 +2218,7 @@ async fn apply_group_changes(
|
|||||||
|
|
||||||
if send_event_chat_modified {
|
if send_event_chat_modified {
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
}
|
}
|
||||||
Ok((group_changes_msgs, better_msg))
|
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?;
|
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::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))
|
Ok(Some(new_chat_id))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
|||||||
|
|
||||||
use crate::aheader::EncryptPreference;
|
use crate::aheader::EncryptPreference;
|
||||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
|
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
|
||||||
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::Blocked;
|
use crate::constants::Blocked;
|
||||||
use crate::contact::{Contact, ContactId, Origin};
|
use crate::contact::{Contact, ContactId, Origin};
|
||||||
@@ -680,6 +681,7 @@ async fn secure_connection_established(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
context.emit_event(EventType::ChatModified(chat_id));
|
context.emit_event(EventType::ChatModified(chat_id));
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1022,6 +1022,11 @@ impl EventTracker {
|
|||||||
self.get_matching(|evt| matches!(evt, EventType::IncomingMsg { .. }))
|
self.get_matching(|evt| matches!(evt, EventType::IncomingMsg { .. }))
|
||||||
.await;
|
.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.
|
/// Gets a specific message from a chat and asserts that the chat has a specific length.
|
||||||
|
|||||||
Reference in New Issue
Block a user