diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e5b20fb088..bfaa87a884 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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)"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 1e3b7f740d..33184a2fe9 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -26,8 +26,10 @@ UserpicButton { } UserpicsRow { button: UserpicButton; + bg: color; shift: pixels; stroke: pixels; + complex: bool; invert: bool; } ShortInfoBox { diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index f132e82e94..354b1bb5a5 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -694,8 +694,7 @@ object_ptr 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(parent, full); const auto raw = result.data(); const auto overlay = CreateChild(raw); @@ -731,6 +730,12 @@ object_ptr 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 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 button) { @@ -788,18 +793,18 @@ object_ptr 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); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 1119b6d69e..18052cd98e 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -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; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 32beb069bd..ec27eb5f24 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -253,6 +253,7 @@ Call::Call( not_null user, CallId conferenceId, MsgId conferenceInviteMsgId, + std::vector> conferenceParticipants, bool video) : _delegate(delegate) , _user(user) @@ -279,6 +280,7 @@ Call::Call( , _id(base::RandomValue()) , _conferenceId(conferenceId) , _conferenceInviteMsgId(conferenceInviteMsgId) +, _conferenceParticipants(std::move(conferenceParticipants)) , _videoIncoming( std::make_unique( StartVideoState(video))) diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 6483db2fe4..90a8eed489 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -107,6 +107,7 @@ public: not_null user, CallId conferenceId, MsgId conferenceInviteMsgId, + std::vector> 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> & { + 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> _conferenceParticipants; std::unique_ptr _instance; std::shared_ptr _videoCapture; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index c5f8a8c3a9..d3eb954eb0 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -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(); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 23fe1975e9..0d0628f371 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -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) 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>(); + 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( + 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, diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index fdb61333ab..539342d2bc 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -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> _addPeople; object_ptr _name; object_ptr _status; + object_ptr _conferenceParticipants = { nullptr }; object_ptr _fingerprint = { nullptr }; object_ptr> _remoteAudioMute = { nullptr }; object_ptr> _remoteLowBattery diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 969cbd2340..f715cc0bb1 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -456,7 +456,9 @@ Invoice ComputeInvoiceData( return result; } -Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { +Call ComputeCallData( + not_null 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 owner, + const MTPDmessageActionConferenceCall &call) { + auto participants = std::vector>(); + 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 MediaCall::createView( QString MediaCall::Text( not_null 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); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index f47197816e..a8a0a34ac9 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -82,6 +82,7 @@ struct SharedContact final { struct Call { using State = CallState; + std::vector> otherParticipants; CallId conferenceId = 0; int duration = 0; State state = State::Missed; @@ -468,6 +469,7 @@ public: [[nodiscard]] static QString Text( not_null item, CallState state, + bool conference, bool video); private: @@ -801,8 +803,11 @@ private: not_null item, const MTPDmessageMediaPaidMedia &data); -[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); [[nodiscard]] Call ComputeCallData( + not_null owner, + const MTPDmessageActionPhoneCall &call); +[[nodiscard]] Call ComputeCallData( + not_null owner, const MTPDmessageActionConferenceCall &call); [[nodiscard]] GiveawayStart ComputeGiveawayStartData( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3330c835c6..f93592c270 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -481,13 +481,13 @@ HistoryItem::HistoryItem( createComponents(CreateConfig()); _media = std::make_unique( this, - Data::ComputeCallData(data)); + Data::ComputeCallData(&history->owner(), data)); setTextValue({}); }, [&](const MTPDmessageActionConferenceCall &data) { createComponents(CreateConfig()); _media = std::make_unique( 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( this, Data::ComputeCallData( + &history()->owner(), message.vaction().c_messageActionConferenceCall())); addToSharedMediaIndex(); finishEdition(-1); diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index cd12e9bc45..6cfd8c938b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -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); } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index daeece6ce2..a97c5b16bc 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -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 }};