feat: get contact-id for info messages (#6714)

instead of showing addresses in info message, provide an API to get the
contact-id.

UI can then make the info message tappable and open the contact profile
in scope

the corresponding iOS PR - incl. **screencast** - is at
https://github.com/deltachat/deltachat-ios/pull/2652 ; jsonrpc can come
in a subsequent PR when things are settled on android/ios

the number of parameters in `add_info_msg_with_cmd` gets bigger and
bigger, however, i did not want to refactor this in this PR. it is also
not really adding complexity



closes #6702

---------

Co-authored-by: link2xt <link2xt@testrun.org>
Co-authored-by: Hocuri <hocuri@gmx.de>
This commit is contained in:
bjoern
2025-03-31 18:56:57 +02:00
committed by GitHub
parent e2f9c80cd5
commit 97b0d09ed2
18 changed files with 311 additions and 62 deletions

View File

@@ -1,6 +1,7 @@
use super::*;
use crate::chatlist::get_archived_cnt;
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
use crate::ephemeral::Timer;
use crate::headerdef::HeaderDef;
use crate::imex::{has_backup, imex, ImexMode};
use crate::message::{delete_msgs, MessengerMessage};
@@ -331,11 +332,12 @@ async fn test_member_add_remove() -> Result<()> {
// Alice adds Bob to the chat.
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
let sent = alice.pop_sent_msg().await;
// Locally set name "robert" should not leak.
assert!(!sent.payload.contains("robert"));
assert_eq!(
sent.load_from_db().await.get_text(),
"You added member robert (bob@example.net)."
"You added member robert."
);
// Alice removes Bob from the chat.
@@ -344,7 +346,7 @@ async fn test_member_add_remove() -> Result<()> {
assert!(!sent.payload.contains("robert"));
assert_eq!(
sent.load_from_db().await.get_text(),
"You removed member robert (bob@example.net)."
"You removed member robert."
);
// Alice leaves the chat.
@@ -412,7 +414,7 @@ async fn test_parallel_member_remove() -> Result<()> {
// Test that remove message is rewritten.
assert_eq!(
bob_received_remove_msg.get_text(),
"Member Me (bob@example.net) removed by alice@example.org."
"Member Me removed by alice@example.org."
);
Ok(())
@@ -521,6 +523,14 @@ async fn test_modify_chat_multi_device() -> Result<()> {
assert!(a2_msg.is_system_message());
assert_eq!(a1_msg.get_info_type(), SystemMessage::GroupNameChanged);
assert_eq!(a2_msg.get_info_type(), SystemMessage::GroupNameChanged);
assert_eq!(
a1_msg.get_info_contact_id(&a1).await?,
Some(ContactId::SELF)
);
assert_eq!(
a2_msg.get_info_contact_id(&a2).await?,
Some(ContactId::SELF)
);
assert_eq!(Chat::load_from_db(&a1, a1_chat_id).await?.name, "bar");
assert_eq!(Chat::load_from_db(&a2, a2_chat_id).await?.name, "bar");
@@ -1574,6 +1584,7 @@ async fn test_add_info_msg_with_cmd() -> Result<()> {
None,
None,
None,
None,
)
.await?;
@@ -3332,6 +3343,128 @@ async fn test_do_not_overwrite_draft() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_info_contact_id() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let alice2 = &tcm.alice().await;
let bob = &tcm.bob().await;
async fn pop_recv_and_check(
alice: &TestContext,
alice2: &TestContext,
bob: &TestContext,
expected_type: SystemMessage,
expected_alice_id: ContactId,
expected_bob_id: ContactId,
) -> Result<()> {
let sent_msg = alice.pop_sent_msg().await;
let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?;
assert_eq!(msg.get_info_type(), expected_type);
assert_eq!(
msg.get_info_contact_id(alice).await?,
Some(expected_alice_id)
);
let msg = alice2.recv_msg(&sent_msg).await;
assert_eq!(msg.get_info_type(), expected_type);
assert_eq!(
msg.get_info_contact_id(alice2).await?,
Some(expected_alice_id)
);
let msg = bob.recv_msg(&sent_msg).await;
assert_eq!(msg.get_info_type(), expected_type);
assert_eq!(msg.get_info_contact_id(bob).await?, Some(expected_bob_id));
Ok(())
}
// Alice creates group, Bob receives group
let alice_chat_id = alice
.create_group_with_members(ProtectionStatus::Unprotected, "play", &[bob])
.await;
let sent_msg1 = alice.send_text(alice_chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg1).await;
let bob_alice_id = msg.from_id;
assert!(!bob_alice_id.is_special());
// Alice does group changes, Bob receives them
set_chat_name(alice, alice_chat_id, "games").await?;
pop_recv_and_check(
alice,
alice2,
bob,
SystemMessage::GroupNameChanged,
ContactId::SELF,
bob_alice_id,
)
.await?;
let file = alice.get_blobdir().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
tokio::fs::write(&file, bytes).await?;
set_chat_profile_image(alice, alice_chat_id, file.to_str().unwrap()).await?;
pop_recv_and_check(
alice,
alice2,
bob,
SystemMessage::GroupImageChanged,
ContactId::SELF,
bob_alice_id,
)
.await?;
alice_chat_id
.set_ephemeral_timer(alice, Timer::Enabled { duration: 60 })
.await?;
pop_recv_and_check(
alice,
alice2,
bob,
SystemMessage::EphemeralTimerChanged,
ContactId::SELF,
bob_alice_id,
)
.await?;
let fiona_id = alice.add_or_lookup_contact_id(&tcm.fiona().await).await; // contexts are in sync, fiona_id is same everywhere
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
pop_recv_and_check(
alice,
alice2,
bob,
SystemMessage::MemberAddedToGroup,
fiona_id,
fiona_id,
)
.await?;
remove_contact_from_chat(alice, alice_chat_id, fiona_id).await?;
pop_recv_and_check(
alice,
alice2,
bob,
SystemMessage::MemberRemovedFromGroup,
fiona_id,
fiona_id,
)
.await?;
// When fiona_id is deleted, get_info_contact_id() returns None.
// We raw delete in db as Contact::delete() leaves a tombstone (which is great as the tap works longer then)
alice
.sql
.execute("DELETE FROM contacts WHERE id=?", (fiona_id,))
.await?;
let msg = alice.get_last_msg().await;
assert_eq!(msg.get_info_type(), SystemMessage::MemberRemovedFromGroup);
assert!(msg.get_info_contact_id(alice).await?.is_none());
Ok(())
}
/// Test group consistency.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_add_member_bug() -> Result<()> {