feat: Add EventType::CallMissed and emit it for missed calls (#7840)

Before, only `CallEnded` was emitted for missed calls, or, if a call arrives already being stale,
`IncomingMsg`. Now:
- `CallMissed` is emitted in addition to `CallEnded`.
- `IncomingMsg` is replaced with `CallMissed` for stale calls.
Having only one event type for missed calls should simplify handling them in the apps.

This doesn't emit `CallMissed` for those who aren't allowed to call us. Also, don't emit `CallEnded`
if the caller isn't allowed to call us and the call wasn't accepted, as there's no previous
`IncomingCall` event in this case.
This commit is contained in:
iequidoo
2026-03-04 20:12:47 -03:00
parent a2bb8962cb
commit ee0a0a6c3d
6 changed files with 198 additions and 51 deletions

View File

@@ -218,10 +218,11 @@ impl Context {
let wait = RINGING_SECONDS;
let context = self.get_weak_context();
task::spawn(Context::emit_end_call_if_unaccepted(
task::spawn(Context::finalize_call_if_unaccepted(
context,
wait.try_into()?,
call.id,
true, // Doesn't matter for outgoing calls
));
Ok(call.id)
@@ -314,39 +315,67 @@ impl Context {
Ok(())
}
async fn emit_end_call_if_unaccepted(
async fn finalize_call_if_unaccepted(
context: WeakContext,
wait: u64,
call_id: MsgId,
can_call_me: bool,
) -> Result<()> {
sleep(Duration::from_secs(wait)).await;
let context = context.upgrade()?;
let Some(mut call) = context.load_call_by_id(call_id).await? else {
warn!(
context,
"emit_end_call_if_unaccepted is called with {call_id} which does not refer to a call."
"finalize_call_if_unaccepted is called with {call_id} which does not refer to a call."
);
return Ok(());
};
if !call.is_accepted() && !call.is_ended() {
let (msg_id, chat_id) = (call_id, call.msg.chat_id);
if call.is_incoming() {
call.mark_as_canceled(&context).await?;
let missed_call_str = stock_str::missed_call(&context);
call.update_text(&context, &missed_call_str).await?;
if can_call_me {
context.emit_event(EventType::CallMissed { msg_id, chat_id });
}
} else {
call.mark_as_ended(&context).await?;
let canceled_call_str = stock_str::canceled_call(&context);
call.update_text(&context, &canceled_call_str).await?;
}
if can_call_me {
context.emit_event(EventType::CallEnded { msg_id, chat_id });
}
context.emit_msgs_changed(call.msg.chat_id, call_id);
context.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
});
}
Ok(())
}
async fn can_call_me(&self, from_id: ContactId) -> Result<bool> {
Ok(match who_can_call_me(self).await? {
WhoCanCallMe::Contacts => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_some_and(|chat_id_blocked| {
match chat_id_blocked.blocked {
Blocked::Not => true,
Blocked::Yes | Blocked::Request => {
// Do not notify about incoming calls
// from contact requests and blocked contacts.
//
// User can still access the call and accept it
// via the chat in case of contact requests.
false
}
}
}),
WhoCanCallMe::Everybody => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_none_or(|chat_id_blocked| chat_id_blocked.blocked != Blocked::Yes),
WhoCanCallMe::Nobody => false,
})
}
pub(crate) async fn handle_call_msg(
&self,
call_id: MsgId,
@@ -360,50 +389,33 @@ impl Context {
};
if call.is_incoming() {
if call.is_stale() {
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
let call_str = match call.is_stale() {
true => stock_str::missed_call(self),
false => stock_str::incoming_call(self, call.has_video_initially()),
};
call.update_text(self, &call_str).await?;
let (msg_id, chat_id) = (call_id, call.msg.chat_id);
let can_call_me = self.can_call_me(from_id).await?;
if !can_call_me {
} else if call.is_stale() {
self.emit_event(EventType::CallMissed { msg_id, chat_id });
} else {
let incoming_call_str =
stock_str::incoming_call(self, call.has_video_initially());
call.update_text(self, &incoming_call_str).await?;
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
let can_call_me = match who_can_call_me(self).await? {
WhoCanCallMe::Contacts => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_some_and(|chat_id_blocked| {
match chat_id_blocked.blocked {
Blocked::Not => true,
Blocked::Yes | Blocked::Request => {
// Do not notify about incoming calls
// from contact requests and blocked contacts.
//
// User can still access the call and accept it
// via the chat in case of contact requests.
false
}
}
}),
WhoCanCallMe::Everybody => ChatIdBlocked::lookup_by_contact(self, from_id)
.await?
.is_none_or(|chat_id_blocked| chat_id_blocked.blocked != Blocked::Yes),
WhoCanCallMe::Nobody => false,
};
if can_call_me {
self.emit_event(EventType::IncomingCall {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
place_call_info: call.place_call_info.to_string(),
has_video: call.has_video_initially(),
});
}
self.emit_event(EventType::IncomingCall {
msg_id,
chat_id,
place_call_info: call.place_call_info.to_string(),
has_video: call.has_video_initially(),
});
}
self.emit_msgs_changed(chat_id, msg_id);
if !call.is_stale() {
let wait = call.remaining_ring_seconds();
let context = self.get_weak_context();
task::spawn(Context::emit_end_call_if_unaccepted(
task::spawn(Context::finalize_call_if_unaccepted(
context,
wait.try_into()?,
call.msg.id,
can_call_me,
));
}
} else {
@@ -455,6 +467,7 @@ impl Context {
return Ok(());
}
let (msg_id, chat_id) = (call_id, call.msg.chat_id);
if !call.is_accepted() {
if call.is_incoming() {
if from_id == ContactId::SELF {
@@ -465,6 +478,9 @@ impl Context {
call.mark_as_canceled(self).await?;
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
if self.can_call_me(from_id).await? {
self.emit_event(EventType::CallMissed { msg_id, chat_id });
}
}
} else {
// outgoing
@@ -482,12 +498,8 @@ impl Context {
call.mark_as_ended(self).await?;
call.update_text_duration(self).await?;
}
self.emit_event(EventType::CallEnded { msg_id, chat_id });
self.emit_msgs_changed(call.msg.chat_id, call_id);
self.emit_event(EventType::CallEnded {
msg_id: call.msg.id,
chat_id: call.msg.chat_id,
});
}
_ => {}
}