mirror of
https://github.com/chatmail/core.git
synced 2026-04-29 03:16:29 +03:00
feat: hide call status change messages (#7175)
this PR uses the initial "call messages" (that has a separate viewtype since #7174) to show all call status. this is what most other messengers are doing as well. additional "info messages" after a call are no longer needed. on the wire, as we cannot pickpack on visible info messages, we use hidden messages, similar to eg. webxdc status updates. in future PR, it is planned to allow getting call state as a json, so that UI can render nicely. it is then decided if we want to translate the strings in the core. <img width="320" alt="IMG_0150" src="https://github.com/user-attachments/assets/41ee3fa3-8be4-42c3-8dd9-d20f49881650" /> successor of https://github.com/chatmail/core/pull/6650
This commit is contained in:
16
src/calls.rs
16
src/calls.rs
@@ -114,6 +114,7 @@ impl Context {
|
||||
chat.id.accept(self).await?;
|
||||
}
|
||||
|
||||
call.update_text(self, "Call accepted").await?;
|
||||
call.msg
|
||||
.mark_call_as_accepted(self, accept_call_info.to_string())
|
||||
.await?;
|
||||
@@ -125,6 +126,7 @@ impl Context {
|
||||
..Default::default()
|
||||
};
|
||||
msg.param.set_cmd(SystemMessage::CallAccepted);
|
||||
msg.hidden = true;
|
||||
msg.param
|
||||
.set(Param::WebrtcAccepted, accept_call_info.to_string());
|
||||
msg.set_quote(self, Some(&call.msg)).await?;
|
||||
@@ -133,6 +135,7 @@ impl Context {
|
||||
msg_id: call.msg.id,
|
||||
accept_call_info,
|
||||
});
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -140,6 +143,8 @@ impl Context {
|
||||
pub async fn end_call(&self, call_id: MsgId) -> Result<()> {
|
||||
let call: CallInfo = self.load_call_by_id(call_id).await?;
|
||||
|
||||
call.update_text(self, "Call ended").await?;
|
||||
|
||||
if call.is_accepted || !call.is_incoming {
|
||||
let mut msg = Message {
|
||||
viewtype: Viewtype::Text,
|
||||
@@ -147,6 +152,7 @@ impl Context {
|
||||
..Default::default()
|
||||
};
|
||||
msg.param.set_cmd(SystemMessage::CallEnded);
|
||||
msg.hidden = true;
|
||||
msg.set_quote(self, Some(&call.msg)).await?;
|
||||
msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?;
|
||||
} else if call.is_incoming {
|
||||
@@ -161,6 +167,7 @@ impl Context {
|
||||
self.emit_event(EventType::CallEnded {
|
||||
msg_id: call.msg.id,
|
||||
});
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -189,9 +196,9 @@ impl Context {
|
||||
if call.is_incoming {
|
||||
if call.is_stale_call() {
|
||||
call.update_text(self, "Missed call").await?;
|
||||
self.emit_incoming_msg(call.msg.chat_id, call_id);
|
||||
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
|
||||
} else {
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
|
||||
self.emit_event(EventType::IncomingCall {
|
||||
msg_id: call.msg.id,
|
||||
place_call_info: call.place_call_info.to_string(),
|
||||
@@ -210,6 +217,7 @@ impl Context {
|
||||
match mime_message.is_system_message {
|
||||
SystemMessage::CallAccepted => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
call.update_text(self, "Call accepted").await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
if call.is_incoming {
|
||||
self.emit_event(EventType::IncomingCallAccepted {
|
||||
@@ -232,6 +240,7 @@ impl Context {
|
||||
}
|
||||
SystemMessage::CallEnded => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
call.update_text(self, "Call ended").await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
self.emit_event(EventType::CallEnded {
|
||||
msg_id: call.msg.id,
|
||||
@@ -245,7 +254,10 @@ impl Context {
|
||||
|
||||
pub(crate) async fn sync_call_rejection(&self, rfc724_mid: &str) -> Result<()> {
|
||||
if let Some((msg_id, _)) = rfc724_mid_exists(self, rfc724_mid).await? {
|
||||
let call = self.load_call_by_id(msg_id).await?;
|
||||
call.update_text(self, "Call ended").await?;
|
||||
self.emit_event(EventType::CallEnded { msg_id });
|
||||
self.emit_msgs_changed(call.msg.chat_id, msg_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ struct CallSetup {
|
||||
pub alice: TestContext,
|
||||
pub alice2: TestContext,
|
||||
pub alice_call: Message,
|
||||
pub alice2_call: Message,
|
||||
pub bob: TestContext,
|
||||
pub bob2: TestContext,
|
||||
pub bob_call: Message,
|
||||
@@ -61,6 +62,7 @@ async fn setup_call() -> Result<CallSetup> {
|
||||
alice,
|
||||
alice2,
|
||||
alice_call,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
@@ -73,13 +75,14 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
alice,
|
||||
alice2,
|
||||
alice_call,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
bob2_call,
|
||||
} = setup_call().await?;
|
||||
|
||||
// Bob accepts the incoming call, this does not add an additional message to the chat
|
||||
// Bob accepts the incoming call
|
||||
bob.accept_incoming_call(bob_call.id, "accepted_info".to_string())
|
||||
.await?;
|
||||
bob.evtracker
|
||||
@@ -91,9 +94,11 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
assert_eq!(info.place_call_info, "place_info");
|
||||
assert_eq!(info.accept_call_info, "accepted_info");
|
||||
|
||||
let bob_accept_msg = bob2.recv_msg(&sent2).await;
|
||||
assert!(bob_accept_msg.is_info());
|
||||
assert_eq!(bob_accept_msg.get_info_type(), SystemMessage::CallAccepted);
|
||||
bob2.recv_msg_trash(&sent2).await;
|
||||
assert_eq!(
|
||||
Message::load_from_db(&bob, bob_call.id).await?.text,
|
||||
"Call accepted"
|
||||
);
|
||||
bob2.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. }))
|
||||
.await;
|
||||
@@ -101,11 +106,10 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
assert!(!info.is_accepted); // "accepted" is only true on the device that does the call
|
||||
|
||||
// Alice receives the acceptance message
|
||||
let alice_accept_msg = alice.recv_msg(&sent2).await;
|
||||
assert!(alice_accept_msg.is_info());
|
||||
alice.recv_msg_trash(&sent2).await;
|
||||
assert_eq!(
|
||||
alice_accept_msg.get_info_type(),
|
||||
SystemMessage::CallAccepted
|
||||
Message::load_from_db(&alice, alice_call.id).await?.text,
|
||||
"Call accepted"
|
||||
);
|
||||
alice
|
||||
.evtracker
|
||||
@@ -116,11 +120,10 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
assert_eq!(info.place_call_info, "place_info");
|
||||
assert_eq!(info.accept_call_info, "accepted_info");
|
||||
|
||||
let alice2_accept_msg = alice2.recv_msg(&sent2).await;
|
||||
assert!(alice2_accept_msg.is_info());
|
||||
alice2.recv_msg_trash(&sent2).await;
|
||||
assert_eq!(
|
||||
alice2_accept_msg.get_info_type(),
|
||||
SystemMessage::CallAccepted
|
||||
Message::load_from_db(&alice2, alice2_call.id).await?.text,
|
||||
"Call accepted"
|
||||
);
|
||||
alice2
|
||||
.evtracker
|
||||
@@ -131,6 +134,7 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
alice,
|
||||
alice2,
|
||||
alice_call,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
@@ -138,9 +142,9 @@ async fn accept_call() -> Result<CallSetup> {
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_is_call_ended_info_msg(msg: Message) {
|
||||
assert!(msg.is_info());
|
||||
assert_eq!(msg.get_info_type(), SystemMessage::CallEnded);
|
||||
async fn assert_is_call_ended(t: &TestContext, call_id: MsgId) -> Result<()> {
|
||||
assert_eq!(Message::load_from_db(t, call_id).await?.text, "Call ended");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -148,36 +152,40 @@ async fn test_accept_call_callee_ends() -> Result<()> {
|
||||
// Alice calls Bob, Bob accepts
|
||||
let CallSetup {
|
||||
alice,
|
||||
alice_call,
|
||||
alice2,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
bob2_call,
|
||||
..
|
||||
} = accept_call().await?;
|
||||
|
||||
// Bob has accepted the call and also ends it
|
||||
bob.end_call(bob_call.id).await?;
|
||||
assert_is_call_ended(&bob, bob_call.id).await?;
|
||||
bob.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
let sent3 = bob.pop_sent_msg().await;
|
||||
|
||||
let bob2_end_call_msg = bob2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(bob2_end_call_msg);
|
||||
bob2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&bob2, bob2_call.id).await?;
|
||||
bob2.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
// Alice receives the ending message
|
||||
let alice_end_call_msg = alice.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(alice_end_call_msg);
|
||||
alice.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&alice, alice_call.id).await?;
|
||||
alice
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
let alice2_end_call_msg = alice2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(alice2_end_call_msg);
|
||||
alice2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&alice2, alice2_call.id).await?;
|
||||
alice2
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
@@ -192,9 +200,11 @@ async fn test_accept_call_caller_ends() -> Result<()> {
|
||||
let CallSetup {
|
||||
alice,
|
||||
alice2,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
bob2_call,
|
||||
..
|
||||
} = accept_call().await?;
|
||||
|
||||
@@ -206,22 +216,22 @@ async fn test_accept_call_caller_ends() -> Result<()> {
|
||||
.await;
|
||||
let sent3 = alice.pop_sent_msg().await;
|
||||
|
||||
let alice2_end_call_msg = alice2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(alice2_end_call_msg);
|
||||
alice2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&alice2, alice2_call.id).await?;
|
||||
alice2
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
// Bob receives the ending message
|
||||
let bob_end_call_msg = bob.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(bob_end_call_msg);
|
||||
bob.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&bob, bob_call.id).await?;
|
||||
bob.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
let bob2_end_call_msg = bob2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(bob2_end_call_msg);
|
||||
bob2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&bob2, bob2_call.id).await?;
|
||||
bob2.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
@@ -236,6 +246,7 @@ async fn test_callee_rejects_call() -> Result<()> {
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
bob2_call,
|
||||
..
|
||||
} = setup_call().await?;
|
||||
|
||||
@@ -243,11 +254,13 @@ async fn test_callee_rejects_call() -> Result<()> {
|
||||
// To protect Bob's privacy, no message is sent to Alice (who will time out).
|
||||
// To let Bob close the call window on all devices, a sync message is used instead.
|
||||
bob.end_call(bob_call.id).await?;
|
||||
assert_is_call_ended(&bob, bob_call.id).await?;
|
||||
bob.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
sync(&bob, &bob2).await;
|
||||
assert_is_call_ended(&bob2, bob2_call.id).await?;
|
||||
bob2.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
@@ -262,35 +275,39 @@ async fn test_caller_cancels_call() -> Result<()> {
|
||||
alice,
|
||||
alice2,
|
||||
alice_call,
|
||||
alice2_call,
|
||||
bob,
|
||||
bob2,
|
||||
bob_call,
|
||||
bob2_call,
|
||||
..
|
||||
} = setup_call().await?;
|
||||
|
||||
// Alice changes their mind before Bob picks up
|
||||
alice.end_call(alice_call.id).await?;
|
||||
assert_is_call_ended(&alice, alice_call.id).await?;
|
||||
alice
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
let sent3 = alice.pop_sent_msg().await;
|
||||
|
||||
let alice2_call_ended_msg = alice2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(alice2_call_ended_msg);
|
||||
alice2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&alice2, alice2_call.id).await?;
|
||||
alice2
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
// Bob receives the ending message
|
||||
let bob_call_ended_msg = bob.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(bob_call_ended_msg);
|
||||
bob.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&bob, bob_call.id).await?;
|
||||
bob.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
let bob2_call_ended_msg = bob2.recv_msg(&sent3).await;
|
||||
assert_is_call_ended_info_msg(bob2_call_ended_msg);
|
||||
bob2.recv_msg_trash(&sent3).await;
|
||||
assert_is_call_ended(&bob2, bob2_call.id).await?;
|
||||
bob2.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
|
||||
.await;
|
||||
|
||||
@@ -1153,6 +1153,11 @@ async fn decide_chat_assignment(
|
||||
{
|
||||
info!(context, "Chat edit/delete/iroh/sync message (TRASH).");
|
||||
true
|
||||
} else if mime_parser.is_system_message == SystemMessage::CallAccepted
|
||||
|| mime_parser.is_system_message == SystemMessage::CallEnded
|
||||
{
|
||||
info!(context, "Call state changed (TRASH).");
|
||||
true
|
||||
} else if mime_parser.decrypting_failed && !mime_parser.incoming {
|
||||
// Outgoing undecryptable message.
|
||||
let last_time = context
|
||||
|
||||
Reference in New Issue
Block a user