mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 15:36:30 +03:00
feat: use dedicated 'call' viewtype (#7174)
a dedicated viewtype allows the UI to show a more advanced UI, but even when using the defaults, it has the advantage that incoming/outgoing and the date are directly visible. successor of https://github.com/chatmail/core/pull/6650
This commit is contained in:
114
src/calls.rs
114
src/calls.rs
@@ -4,6 +4,7 @@
|
||||
//! This means, the "Call ID" is a "Message ID" currently - similar to webxdc.
|
||||
use crate::chat::{Chat, ChatId, send_msg};
|
||||
use crate::constants::Chattype;
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::headerdef::HeaderDef;
|
||||
@@ -82,11 +83,10 @@ impl Context {
|
||||
ensure!(chat.typ == Chattype::Single && !chat.is_self_talk());
|
||||
|
||||
let mut call = Message {
|
||||
viewtype: Viewtype::Text,
|
||||
viewtype: Viewtype::Call,
|
||||
text: "Calling...".into(),
|
||||
..Default::default()
|
||||
};
|
||||
call.param.set_cmd(SystemMessage::OutgoingCall);
|
||||
call.param.set(Param::WebrtcRoom, &place_call_info);
|
||||
call.id = send_msg(self, chat_id, &mut call).await?;
|
||||
|
||||
@@ -184,60 +184,61 @@ impl Context {
|
||||
mime_message: &MimeMessage,
|
||||
call_id: MsgId,
|
||||
) -> Result<()> {
|
||||
match mime_message.is_system_message {
|
||||
SystemMessage::IncomingCall => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
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);
|
||||
} else {
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
self.emit_event(EventType::IncomingCall {
|
||||
msg_id: call.msg.id,
|
||||
place_call_info: call.place_call_info.to_string(),
|
||||
});
|
||||
let wait = call.remaining_ring_seconds();
|
||||
task::spawn(Context::emit_end_call_if_unaccepted(
|
||||
self.clone(),
|
||||
wait.try_into()?,
|
||||
call.msg.id,
|
||||
));
|
||||
}
|
||||
if mime_message.is_call() {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
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);
|
||||
} else {
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
}
|
||||
}
|
||||
SystemMessage::CallAccepted => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
if call.is_incoming {
|
||||
self.emit_event(EventType::IncomingCallAccepted {
|
||||
self.emit_event(EventType::IncomingCall {
|
||||
msg_id: call.msg.id,
|
||||
accept_call_info: call.accept_call_info,
|
||||
place_call_info: call.place_call_info.to_string(),
|
||||
});
|
||||
} else {
|
||||
let accept_call_info = mime_message
|
||||
.get_header(HeaderDef::ChatWebrtcAccepted)
|
||||
.unwrap_or_default();
|
||||
call.msg
|
||||
.clone()
|
||||
.mark_call_as_accepted(self, accept_call_info.to_string())
|
||||
.await?;
|
||||
self.emit_event(EventType::OutgoingCallAccepted {
|
||||
let wait = call.remaining_ring_seconds();
|
||||
task::spawn(Context::emit_end_call_if_unaccepted(
|
||||
self.clone(),
|
||||
wait.try_into()?,
|
||||
call.msg.id,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
}
|
||||
} else {
|
||||
match mime_message.is_system_message {
|
||||
SystemMessage::CallAccepted => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
if call.is_incoming {
|
||||
self.emit_event(EventType::IncomingCallAccepted {
|
||||
msg_id: call.msg.id,
|
||||
accept_call_info: call.accept_call_info,
|
||||
});
|
||||
} else {
|
||||
let accept_call_info = mime_message
|
||||
.get_header(HeaderDef::ChatWebrtcAccepted)
|
||||
.unwrap_or_default();
|
||||
call.msg
|
||||
.clone()
|
||||
.mark_call_as_accepted(self, accept_call_info.to_string())
|
||||
.await?;
|
||||
self.emit_event(EventType::OutgoingCallAccepted {
|
||||
msg_id: call.msg.id,
|
||||
accept_call_info: accept_call_info.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
SystemMessage::CallEnded => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
self.emit_event(EventType::CallEnded {
|
||||
msg_id: call.msg.id,
|
||||
accept_call_info: accept_call_info.to_string(),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
SystemMessage::CallEnded => {
|
||||
let call = self.load_call_by_id(call_id).await?;
|
||||
self.emit_msgs_changed(call.msg.chat_id, call_id);
|
||||
self.emit_event(EventType::CallEnded {
|
||||
msg_id: call.msg.id,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -255,13 +256,10 @@ impl Context {
|
||||
}
|
||||
|
||||
fn load_call_by_message(&self, call: Message) -> Result<CallInfo> {
|
||||
ensure!(
|
||||
call.get_info_type() == SystemMessage::IncomingCall
|
||||
|| call.get_info_type() == SystemMessage::OutgoingCall
|
||||
);
|
||||
ensure!(call.viewtype == Viewtype::Call);
|
||||
|
||||
Ok(CallInfo {
|
||||
is_incoming: call.get_info_type() == SystemMessage::IncomingCall,
|
||||
is_incoming: call.get_from_id() != ContactId::SELF,
|
||||
is_accepted: call.is_call_accepted()?,
|
||||
place_call_info: call
|
||||
.param
|
||||
@@ -284,10 +282,7 @@ impl Message {
|
||||
context: &Context,
|
||||
accept_call_info: String,
|
||||
) -> Result<()> {
|
||||
ensure!(
|
||||
self.get_info_type() == SystemMessage::IncomingCall
|
||||
|| self.get_info_type() == SystemMessage::OutgoingCall
|
||||
);
|
||||
ensure!(self.viewtype == Viewtype::Call);
|
||||
self.param.set_int(Param::Arg, 1);
|
||||
self.param.set(Param::WebrtcAccepted, accept_call_info);
|
||||
self.update_param(context).await?;
|
||||
@@ -295,10 +290,7 @@ impl Message {
|
||||
}
|
||||
|
||||
fn is_call_accepted(&self) -> Result<bool> {
|
||||
ensure!(
|
||||
self.get_info_type() == SystemMessage::IncomingCall
|
||||
|| self.get_info_type() == SystemMessage::OutgoingCall
|
||||
);
|
||||
ensure!(self.viewtype == Viewtype::Call);
|
||||
Ok(self.param.get_int(Param::Arg) == Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ async fn setup_call() -> Result<CallSetup> {
|
||||
let alice_call = Message::load_from_db(&alice, sent1.sender_msg_id).await?;
|
||||
let alice2_call = alice2.recv_msg(&sent1).await;
|
||||
for (t, m) in [(&alice, &alice_call), (&alice2, &alice2_call)] {
|
||||
assert!(m.is_info());
|
||||
assert_eq!(m.get_info_type(), SystemMessage::OutgoingCall);
|
||||
assert!(!m.is_info());
|
||||
assert_eq!(m.viewtype, Viewtype::Call);
|
||||
let info = t.load_call_by_id(m.id).await?;
|
||||
assert!(!info.is_incoming);
|
||||
assert!(!info.is_accepted);
|
||||
assert_eq!(info.place_call_info, "place_info");
|
||||
}
|
||||
@@ -45,12 +46,13 @@ async fn setup_call() -> Result<CallSetup> {
|
||||
let bob_call = bob.recv_msg(&sent1).await;
|
||||
let bob2_call = bob2.recv_msg(&sent1).await;
|
||||
for (t, m) in [(&bob, &bob_call), (&bob2, &bob2_call)] {
|
||||
assert!(m.is_info());
|
||||
assert_eq!(m.get_info_type(), SystemMessage::IncomingCall);
|
||||
assert!(!m.is_info());
|
||||
assert_eq!(m.viewtype, Viewtype::Call);
|
||||
t.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::IncomingCall { .. }))
|
||||
.await;
|
||||
let info = t.load_call_by_id(m.id).await?;
|
||||
assert!(info.is_incoming);
|
||||
assert!(!info.is_accepted);
|
||||
assert_eq!(info.place_call_info, "place_info");
|
||||
}
|
||||
|
||||
@@ -2691,7 +2691,10 @@ impl ChatIdBlocked {
|
||||
}
|
||||
|
||||
async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
||||
if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
|
||||
if msg.viewtype == Viewtype::Text
|
||||
|| msg.viewtype == Viewtype::VideochatInvitation
|
||||
|| msg.viewtype == Viewtype::Call
|
||||
{
|
||||
// the caller should check if the message text is empty
|
||||
} else if msg.viewtype.has_file() {
|
||||
let viewtype_orig = msg.viewtype;
|
||||
@@ -3167,6 +3170,7 @@ pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: Strin
|
||||
original_msg.viewtype != Viewtype::VideochatInvitation,
|
||||
"Cannot edit videochat invitations"
|
||||
);
|
||||
ensure!(original_msg.viewtype != Viewtype::Call, "Cannot edit calls");
|
||||
ensure!(
|
||||
!original_msg.text.is_empty(), // avoid complexity in UI element changes. focus is typos and rewordings
|
||||
"Cannot add text"
|
||||
|
||||
@@ -973,8 +973,6 @@ impl Message {
|
||||
| SystemMessage::WebxdcStatusUpdate
|
||||
| SystemMessage::WebxdcInfoMessage
|
||||
| SystemMessage::IrohNodeAddr
|
||||
| SystemMessage::OutgoingCall
|
||||
| SystemMessage::IncomingCall
|
||||
| SystemMessage::CallAccepted
|
||||
| SystemMessage::CallEnded
|
||||
| SystemMessage::Unknown => Ok(None),
|
||||
@@ -2280,6 +2278,9 @@ pub enum Viewtype {
|
||||
/// Message is an invitation to a videochat.
|
||||
VideochatInvitation = 70,
|
||||
|
||||
/// Message is an incoming or outgoing call.
|
||||
Call = 71,
|
||||
|
||||
/// Message is an webxdc instance.
|
||||
Webxdc = 80,
|
||||
|
||||
@@ -2303,6 +2304,7 @@ impl Viewtype {
|
||||
Viewtype::Video => true,
|
||||
Viewtype::File => true,
|
||||
Viewtype::VideochatInvitation => false,
|
||||
Viewtype::Call => false,
|
||||
Viewtype::Webxdc => true,
|
||||
Viewtype::Vcard => true,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
use std::io::Cursor;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow, bail, ensure};
|
||||
use anyhow::{Context as _, Result, bail, ensure};
|
||||
use base64::Engine as _;
|
||||
use data_encoding::BASE32_NOPAD;
|
||||
use deltachat_contact_tools::sanitize_bidi_characters;
|
||||
@@ -1533,15 +1533,6 @@ impl MimeFactory {
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::OutgoingCall => {
|
||||
headers.push((
|
||||
"Chat-Content",
|
||||
mail_builder::headers::raw::Raw::new("call").into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::IncomingCall => {
|
||||
return Err(anyhow!("Unexpected incoming call rendering."));
|
||||
}
|
||||
SystemMessage::CallAccepted => {
|
||||
headers.push((
|
||||
"Chat-Content",
|
||||
@@ -1578,6 +1569,11 @@ impl MimeFactory {
|
||||
"Chat-Content",
|
||||
mail_builder::headers::raw::Raw::new("videochat-invitation").into(),
|
||||
));
|
||||
} else if msg.viewtype == Viewtype::Call {
|
||||
headers.push((
|
||||
"Chat-Content",
|
||||
mail_builder::headers::raw::Raw::new("call").into(),
|
||||
));
|
||||
}
|
||||
|
||||
if msg.param.exists(Param::WebrtcRoom) {
|
||||
|
||||
@@ -217,17 +217,7 @@ pub enum SystemMessage {
|
||||
/// "Messages are end-to-end encrypted."
|
||||
ChatE2ee = 50,
|
||||
|
||||
/// This system message represents an outgoing call.
|
||||
/// This message is visible to the user as an "info" message.
|
||||
OutgoingCall = 60,
|
||||
|
||||
/// This system message represents an incoming call.
|
||||
/// This message is visible to the user as an "info" message.
|
||||
IncomingCall = 65,
|
||||
|
||||
/// Message indicating that a call was accepted.
|
||||
/// While the 1:1 call may be established elsewhere,
|
||||
/// the message is still needed for a multidevice setup, so that other devices stop ringing.
|
||||
CallAccepted = 66,
|
||||
|
||||
/// Message indicating that a call was ended.
|
||||
@@ -692,12 +682,6 @@ impl MimeMessage {
|
||||
self.is_system_message = SystemMessage::ChatProtectionDisabled;
|
||||
} else if value == "group-avatar-changed" {
|
||||
self.is_system_message = SystemMessage::GroupImageChanged;
|
||||
} else if value == "call" {
|
||||
self.is_system_message = if self.incoming {
|
||||
SystemMessage::IncomingCall
|
||||
} else {
|
||||
SystemMessage::OutgoingCall
|
||||
};
|
||||
} else if value == "call-accepted" {
|
||||
self.is_system_message = SystemMessage::CallAccepted;
|
||||
} else if value == "call-ended" {
|
||||
@@ -738,6 +722,8 @@ impl MimeMessage {
|
||||
if let Some(room) = room {
|
||||
if content == "videochat-invitation" {
|
||||
part.typ = Viewtype::VideochatInvitation;
|
||||
} else if content == "call" {
|
||||
part.typ = Viewtype::Call
|
||||
}
|
||||
part.param.set(Param::WebrtcRoom, room);
|
||||
} else if let Some(accepted) = accepted {
|
||||
@@ -767,7 +753,10 @@ impl MimeMessage {
|
||||
| Viewtype::Vcard
|
||||
| Viewtype::File
|
||||
| Viewtype::Webxdc => true,
|
||||
Viewtype::Unknown | Viewtype::Text | Viewtype::VideochatInvitation => false,
|
||||
Viewtype::Unknown
|
||||
| Viewtype::Text
|
||||
| Viewtype::VideochatInvitation
|
||||
| Viewtype::Call => false,
|
||||
})
|
||||
{
|
||||
let mut parts = std::mem::take(&mut self.parts);
|
||||
@@ -1582,6 +1571,13 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a message is a call.
|
||||
pub(crate) fn is_call(&self) -> bool {
|
||||
self.parts
|
||||
.first()
|
||||
.is_some_and(|part| part.typ == Viewtype::Call)
|
||||
}
|
||||
|
||||
pub(crate) fn get_rfc724_mid(&self) -> Option<String> {
|
||||
self.get_header(HeaderDef::MessageId)
|
||||
.and_then(|msgid| parse_message_id(msgid).ok())
|
||||
|
||||
@@ -1000,7 +1000,7 @@ pub(crate) async fn receive_imf_inner(
|
||||
}
|
||||
}
|
||||
|
||||
if mime_parser.is_system_message == SystemMessage::IncomingCall {
|
||||
if mime_parser.is_call() {
|
||||
context.handle_call_msg(&mime_parser, insert_msg_id).await?;
|
||||
} else if received_msg.hidden {
|
||||
// No need to emit an event about the changed message
|
||||
|
||||
@@ -97,7 +97,7 @@ impl Summary {
|
||||
let prefix = if msg.state == MessageState::OutDraft {
|
||||
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
|
||||
} else if msg.from_id == ContactId::SELF {
|
||||
if msg.is_info() {
|
||||
if msg.is_info() || msg.viewtype == Viewtype::Call {
|
||||
None
|
||||
} else {
|
||||
Some(SummaryPrefix::Me(stock_str::self_msg(context).await))
|
||||
@@ -233,6 +233,16 @@ impl Message {
|
||||
type_file = self.param.get(Param::Summary1).map(|s| s.to_string());
|
||||
append_text = true;
|
||||
}
|
||||
Viewtype::Call => {
|
||||
emoji = Some("📞");
|
||||
type_name = Some(if self.from_id == ContactId::SELF {
|
||||
"Outgoing call".to_string()
|
||||
} else {
|
||||
"Incoming call".to_string()
|
||||
});
|
||||
type_file = None;
|
||||
append_text = false
|
||||
}
|
||||
Viewtype::Text | Viewtype::Unknown => {
|
||||
emoji = None;
|
||||
if self.param.get_cmd() == SystemMessage::LocationOnly {
|
||||
|
||||
Reference in New Issue
Block a user