Render confcall messages as phone calls.

This commit is contained in:
John Preston 2025-03-31 00:06:02 +05:00
parent be611c1920
commit 346f7aadd6
13 changed files with 137 additions and 127 deletions

View file

@ -2220,10 +2220,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group.";
"lng_you_paid_stars#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars.";
"lng_action_confcall_invitation" = "Call invitation...";
"lng_action_confcall_missed" = "Missed conference call";
"lng_action_confcall_ongoing" = "Ongoing conference call";
"lng_action_confcall_finished" = "Conference call";
"lng_you_joined_group" = "You joined this group";
@ -4684,6 +4680,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_video_declined" = "Declined video call";
"lng_call_duration_info" = "{time}, {duration}";
"lng_call_type_and_duration" = "{type} ({duration})";
"lng_call_invitation" = "Call invitation";
"lng_call_ongoing" = "Ongoing call";
"lng_call_rate_label" = "Please rate the quality of your call";
"lng_call_rate_comment" = "Comment (optional)";

View file

@ -430,9 +430,9 @@ BoxController::Row::Type BoxController::Row::ComputeType(
return Type::Out;
} else if (auto media = item->media()) {
if (const auto call = media->call()) {
const auto reason = call->finishReason;
if (reason == Data::Call::FinishReason::Busy
|| reason == Data::Call::FinishReason::Missed) {
using State = Data::CallState;
const auto state = call->state;
if (state == State::Busy || state == State::Missed) {
return Type::Missed;
}
}

View file

@ -236,7 +236,6 @@ void Instance::startOrJoinConferenceCall(StartConferenceCallArgs args) {
args.migrating ? args.linkSlug : QString());
const auto session = &args.call->peer()->session();
const auto showShareLink = args.migrating && args.invite.empty();
auto call = std::make_unique<GroupCall>(
_delegate.get(),
Calls::Group::ConferenceInfo{

View file

@ -457,28 +457,42 @@ Invoice ComputeInvoiceData(
Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
auto result = Call();
result.finishReason = [&] {
result.state = [&] {
if (const auto reason = call.vreason()) {
return reason->match([](const MTPDphoneCallDiscardReasonBusy &) {
return CallFinishReason::Busy;
return CallState::Busy;
}, [](const MTPDphoneCallDiscardReasonDisconnect &) {
return CallFinishReason::Disconnected;
return CallState::Disconnected;
}, [](const MTPDphoneCallDiscardReasonHangup &) {
return CallFinishReason::Hangup;
return CallState::Hangup;
}, [](const MTPDphoneCallDiscardReasonMissed &) {
return CallFinishReason::Missed;
return CallState::Missed;
}, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) {
return CallFinishReason::MigrateConferenceCall;
return CallState::MigrateConferenceCall;
});
Unexpected("Call reason type.");
}
return CallFinishReason::Hangup;
return CallState::Hangup;
}();
result.duration = call.vduration().value_or_empty();
result.video = call.is_video();
return result;
}
Call ComputeCallData(const MTPDmessageActionConferenceCall &call) {
return {
.conferenceId = call.vcall_id().v,
.duration = call.vduration().value_or_empty(),
.state = (call.vduration().value_or_empty()
? CallState::Hangup
: call.is_missed()
? CallState::Missed
: call.is_active()
? CallState::Active
: CallState::Invitation),
};
}
GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data) {
@ -1686,7 +1700,7 @@ const Call *MediaCall::call() const {
}
TextWithEntities MediaCall::notificationText() const {
auto result = Text(parent(), _call.finishReason, _call.video);
auto result = Text(parent(), _call.state, _call.video);
if (_call.duration > 0) {
result = tr::lng_call_type_and_duration(
tr::now,
@ -1727,21 +1741,25 @@ std::unique_ptr<HistoryView::Media> MediaCall::createView(
QString MediaCall::Text(
not_null<HistoryItem*> item,
CallFinishReason reason,
CallState state,
bool video) {
if (item->out()) {
return ((reason == CallFinishReason::Missed)
if (state == CallState::Invitation) {
return tr::lng_call_invitation(tr::now);
} else if (state == CallState::Active) {
return tr::lng_call_ongoing(tr::now);
} else if (item->out()) {
return ((state == CallState::Missed)
? (video
? tr::lng_call_video_cancelled
: tr::lng_call_cancelled)
: (video
? tr::lng_call_video_outgoing
: tr::lng_call_outgoing))(tr::now);
} else if (reason == CallFinishReason::Missed) {
} else if (state == CallState::Missed) {
return (video
? tr::lng_call_video_missed
: tr::lng_call_missed)(tr::now);
} else if (reason == CallFinishReason::Busy) {
} else if (state == CallState::Busy) {
return (video
? tr::lng_call_video_declined
: tr::lng_call_declined)(tr::now);

View file

@ -41,12 +41,14 @@ class WallPaper;
class Session;
struct UniqueGift;
enum class CallFinishReason : char {
enum class CallState : char {
Missed,
Busy,
Disconnected,
Hangup,
MigrateConferenceCall,
Invitation,
Active,
};
struct SharedContact final {
@ -78,10 +80,11 @@ struct SharedContact final {
};
struct Call {
using FinishReason = CallFinishReason;
using State = CallState;
CallId conferenceId = 0;
int duration = 0;
FinishReason finishReason = FinishReason::Missed;
State state = State::Missed;
bool video = false;
};
@ -462,9 +465,9 @@ public:
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing = nullptr) override;
static QString Text(
[[nodiscard]] static QString Text(
not_null<HistoryItem*> item,
CallFinishReason reason,
CallState state,
bool video);
private:
@ -799,6 +802,8 @@ private:
const MTPDmessageMediaPaidMedia &data);
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Call ComputeCallData(
const MTPDmessageActionConferenceCall &call);
[[nodiscard]] GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item,

View file

@ -1461,18 +1461,18 @@ ServiceAction ParseServiceAction(
content.duration = duration->v;
}
if (const auto reason = data.vreason()) {
using Reason = ActionPhoneCall::DiscardReason;
content.discardReason = reason->match(
using State = ActionPhoneCall::State;
content.state = reason->match(
[](const MTPDphoneCallDiscardReasonMissed &data) {
return Reason::Missed;
return State::Missed;
}, [](const MTPDphoneCallDiscardReasonDisconnect &data) {
return Reason::Disconnect;
return State::Disconnect;
}, [](const MTPDphoneCallDiscardReasonHangup &data) {
return Reason::Hangup;
return State::Hangup;
}, [](const MTPDphoneCallDiscardReasonBusy &data) {
return Reason::Busy;
return State::Busy;
}, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) {
return Reason::MigrateConferenceCall;
return State::MigrateConferenceCall;
});
}
result.content = content;
@ -1714,11 +1714,20 @@ ServiceAction ParseServiceAction(
.stars = int(data.vstars().v),
};
}, [&](const MTPDmessageActionConferenceCall &data) {
result.content = ActionConferenceCall{
.duration = data.vduration().value_or_empty(),
.active = data.is_active(),
.missed = data.is_missed(),
};
auto content = ActionPhoneCall();
using State = ActionPhoneCall::State;
content.conferenceId = data.vcall_id().v;
if (const auto duration = data.vduration()) {
content.duration = duration->v;
}
content.state = data.is_missed()
? State::Missed
: data.is_active()
? State::Active
: data.vduration().value_or_empty()
? State::Hangup
: State::Invitation;
result.content = content;
}, [](const MTPDmessageActionEmpty &data) {});
return result;
}

View file

@ -497,15 +497,19 @@ struct ActionPaymentSent {
};
struct ActionPhoneCall {
enum class DiscardReason {
enum class State {
Unknown,
Missed,
Disconnect,
Hangup,
Busy,
MigrateConferenceCall,
Invitation,
Active,
};
DiscardReason discardReason = DiscardReason::Unknown;
uint64 conferenceId = 0;
State state = State::Unknown;
int duration = 0;
};
@ -671,12 +675,6 @@ struct ActionPaidMessagesPrice {
int stars = 0;
};
struct ActionConferenceCall {
int duration = 0;
bool active = false;
bool missed = false;
};
struct ServiceAction {
std::variant<
v::null_t,
@ -724,8 +722,7 @@ struct ServiceAction {
ActionPrizeStars,
ActionStarGift,
ActionPaidMessagesRefunded,
ActionPaidMessagesPrice,
ActionConferenceCall> content;
ActionPaidMessagesPrice> content;
};
ServiceAction ParseServiceAction(

View file

@ -408,7 +408,7 @@ Stats AbstractWriter::produceTestExample(
auto message = serviceMessage();
auto action = Data::ActionPhoneCall();
action.duration = counter();
action.discardReason = Data::ActionPhoneCall::DiscardReason::Busy;
action.state = Data::ActionPhoneCall::State::Busy;
message.action.content = action;
return message;
}());

View file

@ -1387,16 +1387,6 @@ auto HtmlWriter::Wrap::pushMessage(
+ QString::number(data.stars).toUtf8()
+ " Telegram Stars.";
return result;
}, [&](const ActionConferenceCall &data) {
return data.missed
? "Missed conference call"
: data.active
? "Ongoing conference call"
: data.duration
? "Conference call ("
+ NumberToString(data.duration)
+ " seconds)"
: "Declined conference call";
}, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) {
@ -2286,16 +2276,20 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
if (const auto call = std::get_if<ActionPhoneCall>(&action.content)) {
result.classes = "media_call";
result.title = peers.peer(message.out
? message.peerId
: message.selfId).name();
? message.peerId
: message.selfId).name();
result.status = [&] {
using Reason = ActionPhoneCall::DiscardReason;
const auto reason = call->discardReason;
if (message.out) {
return reason == Reason::Missed ? "Cancelled" : "Outgoing";
} else if (reason == Reason::Missed) {
using State = ActionPhoneCall::State;
const auto state = call->state;
if (state == State::Invitation) {
return "Invitation";
} else if (state == State::Active) {
return "Ongoing";
} else if (message.out) {
return (state == State::Missed) ? "Cancelled" : "Outgoing";
} else if (state == State::Missed) {
return "Missed";
} else if (reason == Reason::Busy) {
} else if (state == State::Busy) {
return "Declined";
}
return "Incoming";

View file

@ -461,22 +461,27 @@ QByteArray SerializeMessage(
}
}, [&](const ActionPhoneCall &data) {
pushActor();
pushAction("phone_call");
pushAction(data.conferenceId ? "conference_call" : "phone_call");
if (data.duration) {
push("duration_seconds", data.duration);
}
using Reason = ActionPhoneCall::DiscardReason;
push("discard_reason", [&] {
switch (data.discardReason) {
case Reason::Busy: return "busy";
case Reason::Disconnect: return "disconnect";
case Reason::Hangup: return "hangup";
case Reason::Missed: return "missed";
case Reason::MigrateConferenceCall:
return "migrate_conference_all";
}
return "";
}());
using State = ActionPhoneCall::State;
if (data.conferenceId) {
push("is_active", data.state == State::Active);
push("is_missed", data.state == State::Missed);
} else {
push("discard_reason", [&] {
switch (data.state) {
case State::Busy: return "busy";
case State::Disconnect: return "disconnect";
case State::Hangup: return "hangup";
case State::Missed: return "missed";
case State::MigrateConferenceCall:
return "migrate_conference_all";
}
return "";
}());
}
}, [&](const ActionScreenshotTaken &data) {
pushActor();
pushAction("take_screenshot");
@ -674,12 +679,6 @@ QByteArray SerializeMessage(
pushActor();
pushAction("paid_messages_price_change");
push("price_stars", data.stars);
}, [&](const ActionConferenceCall &data) {
pushActor();
pushAction("conference_call");
push("duration_seconds", data.duration);
push("is_missed", data.missed);
push("is_active", data.active);
}, [](v::null_t) {});
if (v::is_null(message.action.content)) {

View file

@ -483,6 +483,12 @@ HistoryItem::HistoryItem(
this,
Data::ComputeCallData(data));
setTextValue({});
}, [&](const MTPDmessageActionConferenceCall &data) {
createComponents(CreateConfig());
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
setTextValue({});
}, [&](const auto &) {
createServiceFromMtp(data);
});
@ -5624,32 +5630,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
return result;
};
auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &action) {
auto result = PreparedServiceText();
const auto duration = action.vduration().value_or_empty();
result.text.text = action.is_missed()
? tr::lng_action_confcall_missed(tr::now)
: action.is_active()
? tr::lng_action_confcall_ongoing(tr::now)
: duration
? tr::lng_action_confcall_finished(tr::now)
: tr::lng_action_confcall_invitation(tr::now);
if (duration) {
result.text.text += " (" + QString::number(duration) + " seconds)";
}
const auto id = this->id;
setCustomServiceLink(std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto weak = my.sessionWindow;
if (const auto strong = weak.get()) {
strong->resolveConferenceCall(id);
}
}));
return result;
auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText {
Unexpected("PhoneCall type in setServiceMessageFromMtp.");
};
setServiceText(action.match(

View file

@ -17,20 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "calls/calls_instance.h"
#include "data/data_media_types.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
using FinishReason = Data::CallFinishReason;
using State = Data::CallState;
[[nodiscard]] int ComputeDuration(FinishReason reason, int duration) {
return (reason != FinishReason::Missed
&& reason != FinishReason::Busy)
[[nodiscard]] int ComputeDuration(State state, int duration) {
return (state != State::Missed && state != State::Busy)
? duration
: 0;
}
@ -41,11 +42,12 @@ Call::Call(
not_null<Element*> parent,
not_null<Data::Call*> call)
: Media(parent)
, _duration(ComputeDuration(call->finishReason, call->duration))
, _reason(call->finishReason)
, _duration(ComputeDuration(call->state, call->duration))
, _state(call->state)
, _conference(call->conferenceId != 0)
, _video(call->video) {
const auto item = parent->data();
_text = Data::MediaCall::Text(item, _reason, _video);
_text = Data::MediaCall::Text(item, _state, _video);
_status = QLocale().toString(
parent->dateTime().time(),
QLocale::ShortFormat);
@ -61,13 +63,20 @@ Call::Call(
QSize Call::countOptimalSize() {
const auto user = _parent->history()->peer->asUser();
const auto conference = _conference;
const auto video = _video;
_link = std::make_shared<LambdaClickHandler>([=] {
if (user) {
const auto id = _parent->data()->id;
_link = std::make_shared<LambdaClickHandler>([=](ClickContext context) {
if (conference) {
const auto my = context.other.value<ClickHandlerContext>();
const auto weak = my.sessionWindow;
if (const auto strong = weak.get()) {
strong->resolveConferenceCall(id);
}
} else if (user) {
Core::App().calls().startOutgoingCall(user, video);
}
});
auto maxWidth = st::historyCallWidth;
auto minHeight = st::historyCallHeight;
if (!isBubbleTop()) {
@ -96,8 +105,7 @@ void Call::draw(Painter &p, const PaintContext &context) const {
p.drawTextLeft(nameleft, nametop, paintw, _text);
auto statusleft = nameleft;
auto missed = (_reason == FinishReason::Missed)
|| (_reason == FinishReason::Busy);
auto missed = (_state == State::Missed) || (_state == State::Busy);
const auto &arrow = missed
? stm->historyCallArrowMissed
: stm->historyCallArrow;

View file

@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media.h"
namespace Data {
enum class CallFinishReason : char;
enum class CallState : char;
struct Call;
} // namespace Data
@ -40,12 +40,13 @@ public:
}
private:
using FinishReason = Data::CallFinishReason;
using State = Data::CallState;
QSize countOptimalSize() override;
const int _duration = 0;
const FinishReason _reason;
const State _state = {};
const bool _conference = false;
const bool _video = false;
QString _text;