Show nice confcall invites.

This commit is contained in:
John Preston 2025-04-01 21:50:39 +05:00
parent aaa37a3e0d
commit 09229812f4
14 changed files with 170 additions and 29 deletions

View file

@ -4671,18 +4671,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_outgoing" = "Outgoing call";
"lng_call_video_outgoing" = "Outgoing video call";
"lng_call_group_outgoing" = "Outgoing group call";
"lng_call_incoming" = "Incoming call";
"lng_call_video_incoming" = "Incoming video call";
"lng_call_group_incoming" = "Incoming group call";
"lng_call_missed" = "Missed call";
"lng_call_video_missed" = "Missed video call";
"lng_call_group_missed" = "Missed group call";
"lng_call_cancelled" = "Canceled call";
"lng_call_video_cancelled" = "Canceled video call";
"lng_call_declined" = "Declined call";
"lng_call_video_declined" = "Declined video call";
"lng_call_group_declined" = "Declined group 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_invitation" = "Group call invitation";
"lng_call_ongoing" = "Ongoing group call";
"lng_call_rate_label" = "Please rate the quality of your call";
"lng_call_rate_comment" = "Comment (optional)";

View file

@ -26,8 +26,10 @@ UserpicButton {
}
UserpicsRow {
button: UserpicButton;
bg: color;
shift: pixels;
stroke: pixels;
complex: bool;
invert: bool;
}
ShortInfoBox {

View file

@ -694,8 +694,7 @@ object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
bool painting = false;
};
const auto full = st.button.size.height()
+ st::boostReplaceIconAdd.y()
+ st::lineWidth;
+ (st.complex ? (st::boostReplaceIconAdd.y() + st::lineWidth) : 0);
auto result = object_ptr<Ui::FixedHeightWidget>(parent, full);
const auto raw = result.data();
const auto overlay = CreateChild<Ui::RpWidget>(raw);
@ -731,6 +730,12 @@ object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
overlay->update();
}, raw->lifetime());
if (const auto count = state->count.current()) {
const auto single = st.button.size.width();
const auto used = std::min(count, int(state->buttons.size()));
const auto shift = st.shift;
raw->resize(used ? (single + (used - 1) * shift) : 0, raw->height());
}
rpl::combine(
raw->widthValue(),
state->count.value()
@ -768,7 +773,7 @@ object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
auto hq = PainterHighQualityEnabler(q);
const auto stroke = st.stroke;
const auto half = stroke / 2.;
auto pen = st::windowBg->p;
auto pen = st.bg->p;
pen.setWidthF(stroke * 2.);
state->painting = true;
const auto paintOne = [&](not_null<Ui::UserpicButton*> button) {
@ -788,18 +793,18 @@ object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
}
}
state->painting = false;
const auto last = state->buttons.back().get();
const auto add = st::boostReplaceIconAdd;
const auto skip = st::boostReplaceIconSkip;
const auto w = st::boostReplaceIcon.width() + 2 * skip;
const auto h = st::boostReplaceIcon.height() + 2 * skip;
const auto x = last->x() + last->width() - w + add.x();
const auto y = last->y() + last->height() - h + add.y();
const auto text = (state->count.current() > limit)
? ('+' + QString::number(state->count.current() - limit))
: QString();
if (!text.isEmpty()) {
if (st.complex && !text.isEmpty()) {
const auto last = state->buttons.back().get();
const auto add = st::boostReplaceIconAdd;
const auto skip = st::boostReplaceIconSkip;
const auto w = st::boostReplaceIcon.width() + 2 * skip;
const auto h = st::boostReplaceIcon.height() + 2 * skip;
const auto x = last->x() + last->width() - w + add.x();
const auto y = last->y() + last->height() - h + add.y();
const auto &font = st::semiboldFont;
const auto width = font->width(text);
const auto padded = std::max(w, width + 2 * font->spacew);

View file

@ -37,6 +37,7 @@ CallBodyLayout {
photoSize: pixels;
nameTop: pixels;
statusTop: pixels;
participantsTop: pixels;
muteStroke: pixels;
muteSize: pixels;
mutePosition: point;
@ -48,6 +49,7 @@ callBodyLayout: CallBodyLayout {
photoSize: 160px;
nameTop: 221px;
statusTop: 254px;
participantsTop: 294px;
muteStroke: 3px;
muteSize: 36px;
mutePosition: point(142px, 135px);
@ -58,6 +60,7 @@ callBodyWithPreview: CallBodyLayout {
photoSize: 100px;
nameTop: 132px;
statusTop: 163px;
participantsTop: 193px;
muteStroke: 3px;
muteSize: 0px;
mutePosition: point(90px, 84px);
@ -1538,11 +1541,27 @@ confcallJoinUserpics: UserpicsRow {
size: size(36px, 36px);
photoSize: 36px;
}
bg: boxBg;
shift: 16px;
stroke: 2px;
invert: true;
}
confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px);
confcallInviteUserpicsBg: groupCallMembersBg;
confcallInviteUserpics: UserpicsRow {
button: UserpicButton(defaultUserpicButton) {
size: size(24px, 24px);
photoSize: 24px;
}
bg: confcallInviteUserpicsBg;
shift: 10px;
stroke: 2px;
invert: true;
}
confcallInviteParticipants: FlatLabel(defaultFlatLabel) {
textFg: callNameFg;
}
confcallInviteParticipantsPadding: margins(8px, 3px, 12px, 2px);
confcallInviteVideo: IconButton {
width: 36px;
height: 52px;

View file

@ -253,6 +253,7 @@ Call::Call(
not_null<UserData*> user,
CallId conferenceId,
MsgId conferenceInviteMsgId,
std::vector<not_null<PeerData*>> conferenceParticipants,
bool video)
: _delegate(delegate)
, _user(user)
@ -279,6 +280,7 @@ Call::Call(
, _id(base::RandomValue<CallId>())
, _conferenceId(conferenceId)
, _conferenceInviteMsgId(conferenceInviteMsgId)
, _conferenceParticipants(std::move(conferenceParticipants))
, _videoIncoming(
std::make_unique<Webrtc::VideoTrack>(
StartVideoState(video)))

View file

@ -107,6 +107,7 @@ public:
not_null<UserData*> user,
CallId conferenceId,
MsgId conferenceInviteMsgId,
std::vector<not_null<PeerData*>> conferenceParticipants,
bool video);
[[nodiscard]] Type type() const {
@ -127,6 +128,10 @@ public:
[[nodiscard]] MsgId conferenceInviteMsgId() const {
return _conferenceInviteMsgId;
}
[[nodiscard]] auto conferenceParticipants() const
-> const std::vector<not_null<PeerData*>> & {
return _conferenceParticipants;
}
[[nodiscard]] bool isIncomingWaiting() const;
void start(bytes::const_span random);
@ -343,6 +348,7 @@ private:
CallId _conferenceId = 0;
MsgId _conferenceInviteMsgId = 0;
std::vector<not_null<PeerData*>> _conferenceParticipants;
std::unique_ptr<tgcalls::Instance> _instance;
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;

View file

@ -1024,6 +1024,11 @@ void Instance::showConferenceInvite(
return;
}
auto conferenceParticipants = call->otherParticipants;
if (!ranges::contains(conferenceParticipants, user)) {
conferenceParticipants.push_back(user);
}
const auto &config = user->session().serverConfig();
if (inCall() || inGroupCall()) {
declineIncomingConferenceInvites(conferenceId);
@ -1038,6 +1043,7 @@ void Instance::showConferenceInvite(
user,
conferenceId,
conferenceInviteMsgId,
std::move(conferenceParticipants),
video);
const auto raw = call.get();

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_panel.h"
#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
@ -215,6 +216,7 @@ Panel::Panel(not_null<Call*> call)
initWindow();
initWidget();
initControls();
initConferenceInvite();
initLayout();
initMediaDeviceToggles();
showAndActivate();
@ -534,6 +536,65 @@ void Panel::initControls() {
_screencast->finishAnimating();
}
void Panel::initConferenceInvite() {
const auto &participants = _call->conferenceParticipants();
const auto count = int(participants.size());
if (count < 2) {
return;
}
_conferenceParticipants.create(widget());
_conferenceParticipants->show();
const auto raw = _conferenceParticipants.data();
auto peers = std::vector<not_null<PeerData*>>();
for (const auto &peer : participants) {
if (peer == _user && count > 3) {
continue;
}
peers.push_back(peer);
if (peers.size() == 3) {
break;
}
}
const auto userpics = CreateUserpicsWithMoreBadge(
raw,
rpl::single(peers),
st::confcallInviteUserpics,
peers.size()).release();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
tr::lng_group_call_members(tr::now, lt_count, count),
st::confcallInviteParticipants);
const auto padding = st::confcallInviteParticipantsPadding;
const auto add = padding.bottom();
const auto width = add
+ userpics->width()
+ padding.left()
+ label->width()
+ padding.right();
const auto height = add + userpics->height() + add;
_status->geometryValue() | rpl::start_with_next([=] {
const auto top = _bodyTop + _bodySt->participantsTop;
const auto left = (widget()->width() - width) / 2;
raw->setGeometry(left, top, width, height);
userpics->move(add, add);
label->move(add + userpics->width() + padding.left(), padding.top());
}, raw->lifetime());
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
const auto radius = raw->height() / 2.;
p.setPen(Qt::NoPen);
p.setBrush(st::confcallInviteUserpicsBg);
p.drawRoundedRect(raw->rect(), radius, radius);
}, raw->lifetime());
}
void Panel::setIncomingSize(QSize size) {
if (_incomingFrameSize == size) {
return;
@ -1160,7 +1221,11 @@ void Panel::updateControlsGeometry() {
std::min(
bodyPreviewSizeMax.height(),
st::callOutgoingPreviewMax.height()));
const auto contentHeight = _bodySt->height
const auto bodyContentHeight = _bodySt->height
+ (_conferenceParticipants
? (_bodySt->participantsTop - _bodySt->statusTop)
: 0);
const auto contentHeight = bodyContentHeight
+ (_outgoingPreviewInBody ? bodyPreviewSize.height() : 0);
const auto remainingHeight = available - contentHeight;
const auto skipHeight = remainingHeight
@ -1172,7 +1237,7 @@ void Panel::updateControlsGeometry() {
widget()->height(),
_buttonsTopShown,
shown);
const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
const auto previewTop = _bodyTop + bodyContentHeight + skipHeight;
_userpic->setGeometry(
(widget()->width() - _bodySt->photoSize) / 2,

View file

@ -142,6 +142,7 @@ private:
void initWindow();
void initWidget();
void initControls();
void initConferenceInvite();
void reinitWithCall(Call *call);
void initLayout();
void initMediaDeviceToggles();
@ -211,6 +212,7 @@ private:
object_ptr < Ui::FadeWrap<Ui::CallButton>> _addPeople;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::RpWidget> _conferenceParticipants = { nullptr };
object_ptr<Ui::RpWidget> _fingerprint = { nullptr };
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _remoteAudioMute = { nullptr };
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _remoteLowBattery

View file

@ -456,7 +456,9 @@ Invoice ComputeInvoiceData(
return result;
}
Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
Call ComputeCallData(
not_null<Session*> owner,
const MTPDmessageActionPhoneCall &call) {
auto result = Call();
result.state = [&] {
if (const auto reason = call.vreason()) {
@ -480,8 +482,18 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
return result;
}
Call ComputeCallData(const MTPDmessageActionConferenceCall &call) {
Call ComputeCallData(
not_null<Session*> owner,
const MTPDmessageActionConferenceCall &call) {
auto participants = std::vector<not_null<PeerData*>>();
if (const auto list = call.vother_participants()) {
participants.reserve(list->v.size());
for (const auto &participant : list->v) {
participants.push_back(owner->peer(peerFromMTP(participant)));
}
}
return {
.otherParticipants = std::move(participants),
.conferenceId = call.vcall_id().v,
.duration = call.vduration().value_or_empty(),
.state = (call.vduration().value_or_empty()
@ -1723,7 +1735,8 @@ const Call *MediaCall::call() const {
}
TextWithEntities MediaCall::notificationText() const {
auto result = Text(parent(), _call.state, _call.video);
const auto conference = (_call.conferenceId != 0);
auto result = Text(parent(), _call.state, conference, _call.video);
if (_call.duration > 0) {
result = tr::lng_call_type_and_duration(
tr::now,
@ -1765,6 +1778,7 @@ std::unique_ptr<HistoryView::Media> MediaCall::createView(
QString MediaCall::Text(
not_null<HistoryItem*> item,
CallState state,
bool conference,
bool video) {
if (state == CallState::Invitation) {
return tr::lng_call_invitation(tr::now);
@ -1772,14 +1786,20 @@ QString MediaCall::Text(
return tr::lng_call_ongoing(tr::now);
} else if (item->out()) {
return ((state == CallState::Missed)
? (video
? (conference
? tr::lng_call_group_declined
: video
? tr::lng_call_video_cancelled
: tr::lng_call_cancelled)
: (video
: (conference
? tr::lng_call_group_outgoing
: video
? tr::lng_call_video_outgoing
: tr::lng_call_outgoing))(tr::now);
} else if (state == CallState::Missed) {
return (video
return (conference
? tr::lng_call_group_missed
: video
? tr::lng_call_video_missed
: tr::lng_call_missed)(tr::now);
} else if (state == CallState::Busy) {
@ -1787,7 +1807,9 @@ QString MediaCall::Text(
? tr::lng_call_video_declined
: tr::lng_call_declined)(tr::now);
}
return (video
return (conference
? tr::lng_call_group_incoming
: video
? tr::lng_call_video_incoming
: tr::lng_call_incoming)(tr::now);
}

View file

@ -82,6 +82,7 @@ struct SharedContact final {
struct Call {
using State = CallState;
std::vector<not_null<PeerData*>> otherParticipants;
CallId conferenceId = 0;
int duration = 0;
State state = State::Missed;
@ -468,6 +469,7 @@ public:
[[nodiscard]] static QString Text(
not_null<HistoryItem*> item,
CallState state,
bool conference,
bool video);
private:
@ -801,8 +803,11 @@ private:
not_null<HistoryItem*> item,
const MTPDmessageMediaPaidMedia &data);
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Call ComputeCallData(
not_null<Session*> owner,
const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Call ComputeCallData(
not_null<Session*> owner,
const MTPDmessageActionConferenceCall &call);
[[nodiscard]] GiveawayStart ComputeGiveawayStartData(

View file

@ -481,13 +481,13 @@ HistoryItem::HistoryItem(
createComponents(CreateConfig());
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
Data::ComputeCallData(&history->owner(), data));
setTextValue({});
}, [&](const MTPDmessageActionConferenceCall &data) {
createComponents(CreateConfig());
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
Data::ComputeCallData(&history->owner(), data));
setTextValue({});
}, [&](const auto &) {
createServiceFromMtp(data);
@ -1906,6 +1906,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(
&history()->owner(),
message.vaction().c_messageActionConferenceCall()));
addToSharedMediaIndex();
finishEdition(-1);

View file

@ -47,7 +47,7 @@ Call::Call(
, _conference(call->conferenceId != 0)
, _video(call->video) {
const auto item = parent->data();
_text = Data::MediaCall::Text(item, _state, _video);
_text = Data::MediaCall::Text(item, _state, _conference, _video);
_status = QLocale().toString(
parent->dateTime().time(),
QLocale::ShortFormat);
@ -118,10 +118,10 @@ void Call::draw(Painter &p, const PaintContext &context) const {
p.setPen(stm->mediaFg);
p.drawTextLeft(statusleft, statustop, paintw, _status);
const auto &icon = _conference
? stm->historyCallGroupIcon
: _video
const auto &icon = _video
? stm->historyCallCameraIcon
: _conference
? stm->historyCallGroupIcon
: stm->historyCallIcon;
icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw);
}

View file

@ -313,8 +313,10 @@ boostReplaceIconAdd: point(4px, 2px);
boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }};
boostReplaceUserpicsRow: UserpicsRow {
button: boostReplaceUserpic;
bg: windowBg;
shift: boostReplaceUserpicsShift;
stroke: boostReplaceIconOutline;
complex: true;
}
showOrIconLastSeen: icon{{ "settings/premium/large_lastseen", windowFgActive }};