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_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#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars."; "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"; "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_video_declined" = "Declined video call";
"lng_call_duration_info" = "{time}, {duration}"; "lng_call_duration_info" = "{time}, {duration}";
"lng_call_type_and_duration" = "{type} ({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_label" = "Please rate the quality of your call";
"lng_call_rate_comment" = "Comment (optional)"; "lng_call_rate_comment" = "Comment (optional)";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1387,16 +1387,6 @@ auto HtmlWriter::Wrap::pushMessage(
+ QString::number(data.stars).toUtf8() + QString::number(data.stars).toUtf8()
+ " Telegram Stars."; + " Telegram Stars.";
return result; 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(); }); }, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) { if (!serviceText.isEmpty()) {
@ -2289,13 +2279,17 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
? message.peerId ? message.peerId
: message.selfId).name(); : message.selfId).name();
result.status = [&] { result.status = [&] {
using Reason = ActionPhoneCall::DiscardReason; using State = ActionPhoneCall::State;
const auto reason = call->discardReason; const auto state = call->state;
if (message.out) { if (state == State::Invitation) {
return reason == Reason::Missed ? "Cancelled" : "Outgoing"; return "Invitation";
} else if (reason == Reason::Missed) { } else if (state == State::Active) {
return "Ongoing";
} else if (message.out) {
return (state == State::Missed) ? "Cancelled" : "Outgoing";
} else if (state == State::Missed) {
return "Missed"; return "Missed";
} else if (reason == Reason::Busy) { } else if (state == State::Busy) {
return "Declined"; return "Declined";
} }
return "Incoming"; return "Incoming";

View file

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

View file

@ -483,6 +483,12 @@ HistoryItem::HistoryItem(
this, this,
Data::ComputeCallData(data)); Data::ComputeCallData(data));
setTextValue({}); setTextValue({});
}, [&](const MTPDmessageActionConferenceCall &data) {
createComponents(CreateConfig());
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
setTextValue({});
}, [&](const auto &) { }, [&](const auto &) {
createServiceFromMtp(data); createServiceFromMtp(data);
}); });
@ -5624,32 +5630,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
return result; return result;
}; };
auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &action) { auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText {
auto result = PreparedServiceText(); Unexpected("PhoneCall type in setServiceMessageFromMtp.");
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;
}; };
setServiceText(action.match( 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_element.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView { namespace HistoryView {
namespace { namespace {
using FinishReason = Data::CallFinishReason; using State = Data::CallState;
[[nodiscard]] int ComputeDuration(FinishReason reason, int duration) { [[nodiscard]] int ComputeDuration(State state, int duration) {
return (reason != FinishReason::Missed return (state != State::Missed && state != State::Busy)
&& reason != FinishReason::Busy)
? duration ? duration
: 0; : 0;
} }
@ -41,11 +42,12 @@ Call::Call(
not_null<Element*> parent, not_null<Element*> parent,
not_null<Data::Call*> call) not_null<Data::Call*> call)
: Media(parent) : Media(parent)
, _duration(ComputeDuration(call->finishReason, call->duration)) , _duration(ComputeDuration(call->state, call->duration))
, _reason(call->finishReason) , _state(call->state)
, _conference(call->conferenceId != 0)
, _video(call->video) { , _video(call->video) {
const auto item = parent->data(); const auto item = parent->data();
_text = Data::MediaCall::Text(item, _reason, _video); _text = Data::MediaCall::Text(item, _state, _video);
_status = QLocale().toString( _status = QLocale().toString(
parent->dateTime().time(), parent->dateTime().time(),
QLocale::ShortFormat); QLocale::ShortFormat);
@ -61,13 +63,20 @@ Call::Call(
QSize Call::countOptimalSize() { QSize Call::countOptimalSize() {
const auto user = _parent->history()->peer->asUser(); const auto user = _parent->history()->peer->asUser();
const auto conference = _conference;
const auto video = _video; const auto video = _video;
_link = std::make_shared<LambdaClickHandler>([=] { const auto id = _parent->data()->id;
if (user) { _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); Core::App().calls().startOutgoingCall(user, video);
} }
}); });
auto maxWidth = st::historyCallWidth; auto maxWidth = st::historyCallWidth;
auto minHeight = st::historyCallHeight; auto minHeight = st::historyCallHeight;
if (!isBubbleTop()) { if (!isBubbleTop()) {
@ -96,8 +105,7 @@ void Call::draw(Painter &p, const PaintContext &context) const {
p.drawTextLeft(nameleft, nametop, paintw, _text); p.drawTextLeft(nameleft, nametop, paintw, _text);
auto statusleft = nameleft; auto statusleft = nameleft;
auto missed = (_reason == FinishReason::Missed) auto missed = (_state == State::Missed) || (_state == State::Busy);
|| (_reason == FinishReason::Busy);
const auto &arrow = missed const auto &arrow = missed
? stm->historyCallArrowMissed ? stm->historyCallArrowMissed
: stm->historyCallArrow; : stm->historyCallArrow;

View file

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