diff --git a/Telegram/Resources/icons/calls/group_call_logo.png b/Telegram/Resources/icons/calls/group_call_logo.png new file mode 100644 index 0000000000..b083149297 Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo.png differ diff --git a/Telegram/Resources/icons/calls/group_call_logo@2x.png b/Telegram/Resources/icons/calls/group_call_logo@2x.png new file mode 100644 index 0000000000..ca935f2249 Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo@2x.png differ diff --git a/Telegram/Resources/icons/calls/group_call_logo@3x.png b/Telegram/Resources/icons/calls/group_call_logo@3x.png new file mode 100644 index 0000000000..890bab3cb2 Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4d5aafe974..8b340596dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4916,6 +4916,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_title" = "Group Call"; "lng_confcall_join_text" = "You are invited to join a Telegram Call."; +"lng_confcall_join_text_inviter" = "{user} is inviting you to join a Telegram Call."; +"lng_confcall_already_joined_one" = "{user} already joined this call."; +"lng_confcall_already_joined_two" = "{user} and {other} already joined this call."; +"lng_confcall_already_joined_three" = "{user}, {other} and {third} already joined this call."; +"lng_confcall_already_joined_many#one" = "{user}, {other} and **{count}** other person already joined this call."; +"lng_confcall_already_joined_many#other" = "{user}, {other} and **{count}** other people already joined this call."; "lng_confcall_join_button" = "Join Group Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index f77b417432..1e3b7f740d 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -24,6 +24,12 @@ UserpicButton { uploadIcon: icon; uploadIconPosition: point; } +UserpicsRow { + button: UserpicButton; + shift: pixels; + stroke: pixels; + invert: bool; +} ShortInfoBox { label: FlatLabel; labeled: FlatLabel; diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index ad0bb57e77..25853e0f85 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -179,6 +179,7 @@ void FillUpgradeToPremiumCover( CreateUserpicsWithMoreBadge( container, rpl::single(std::move(userpicPeers)), + st::boostReplaceUserpicsRow, kUserpicsLimit), st::inviteForbiddenUserpicsPadding) )->entity()->setAttribute(Qt::WA_TransparentForMouseEvents); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 634e4a2480..f132e82e94 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -550,13 +550,13 @@ object_ptr CreateUserpicsTransfer( rpl::variable count = 0; bool painting = false; }; - const auto full = st::boostReplaceUserpic.size.height() + const auto st = &st::boostReplaceUserpicsRow; + const auto full = st->button.size.height() + st::boostReplaceIconAdd.y() + st::lineWidth; auto result = object_ptr(parent, full); const auto raw = result.data(); - const auto &st = st::boostReplaceUserpic; - const auto right = CreateChild(raw, to, st); + const auto right = CreateChild(raw, to, st->button); const auto overlay = CreateChild(raw); const auto state = raw->lifetime().make_state(); @@ -564,7 +564,6 @@ object_ptr CreateUserpicsTransfer( from ) | rpl::start_with_next([=]( const std::vector> &list) { - const auto &st = st::boostReplaceUserpic; auto was = base::take(state->from); auto buttons = base::take(state->buttons); state->from.reserve(list.size()); @@ -578,7 +577,7 @@ object_ptr CreateUserpicsTransfer( state->buttons.push_back(std::move(buttons[index])); } else { state->buttons.push_back( - std::make_unique(raw, peer, st)); + std::make_unique(raw, peer, st->button)); const auto raw = state->buttons.back().get(); base::install_event_filter(raw, [=](not_null e) { return (e->type() == QEvent::Paint && !state->painting) @@ -598,7 +597,7 @@ object_ptr CreateUserpicsTransfer( const auto skip = st::boostReplaceUserpicsSkip; const auto left = width - 2 * right->width() - skip; const auto shift = std::min( - st::boostReplaceUserpicsShift, + st->shift, (count > 1 ? (left / (count - 1)) : width)); const auto total = right->width() + (count ? (skip + right->width() + (count - 1) * shift) : 0); @@ -630,7 +629,7 @@ object_ptr CreateUserpicsTransfer( auto q = QPainter(&state->layer); auto hq = PainterHighQualityEnabler(q); - const auto stroke = st::boostReplaceIconOutline; + const auto stroke = st->stroke; const auto half = stroke / 2.; auto pen = st::windowBg->p; pen.setWidthF(stroke * 2.); @@ -684,6 +683,7 @@ object_ptr CreateUserpicsTransfer( object_ptr CreateUserpicsWithMoreBadge( not_null parent, rpl::producer>> peers, + const style::UserpicsRow &st, int limit) { struct State { std::vector> from; @@ -693,7 +693,7 @@ object_ptr CreateUserpicsWithMoreBadge( rpl::variable count = 0; bool painting = false; }; - const auto full = st::boostReplaceUserpic.size.height() + const auto full = st.button.size.height() + st::boostReplaceIconAdd.y() + st::lineWidth; auto result = object_ptr(parent, full); @@ -703,9 +703,8 @@ object_ptr CreateUserpicsWithMoreBadge( const auto state = raw->lifetime().make_state(); std::move( peers - ) | rpl::start_with_next([=]( + ) | rpl::start_with_next([=, &st]( const std::vector> &list) { - const auto &st = st::boostReplaceUserpic; auto was = base::take(state->from); auto buttons = base::take(state->buttons); state->from.reserve(list.size()); @@ -719,7 +718,7 @@ object_ptr CreateUserpicsWithMoreBadge( state->buttons.push_back(std::move(buttons[index])); } else { state->buttons.push_back( - std::make_unique(raw, peer, st)); + std::make_unique(raw, peer, st.button)); const auto raw = state->buttons.back().get(); base::install_event_filter(raw, [=](not_null e) { return (e->type() == QEvent::Paint && !state->painting) @@ -735,13 +734,12 @@ object_ptr CreateUserpicsWithMoreBadge( rpl::combine( raw->widthValue(), state->count.value() - ) | rpl::start_with_next([=](int width, int count) { - const auto &st = st::boostReplaceUserpic; - const auto single = st.size.width(); + ) | rpl::start_with_next([=, &st](int width, int count) { + const auto single = st.button.size.width(); const auto left = width - single; const auto used = std::min(count, int(state->buttons.size())); const auto shift = std::min( - st::boostReplaceUserpicsShift, + st.shift, (used > 1 ? (left / (used - 1)) : width)); const auto total = used ? (single + (used - 1) * shift) : 0; auto x = (width - total) / 2; @@ -755,7 +753,7 @@ object_ptr CreateUserpicsWithMoreBadge( overlay->paintRequest( ) | rpl::filter([=] { return !state->buttons.empty(); - }) | rpl::start_with_next([=] { + }) | rpl::start_with_next([=, &st] { const auto outerw = overlay->width(); const auto ratio = style::DevicePixelRatio(); if (state->layer.size() != QSize(outerw, full) * ratio) { @@ -768,17 +766,26 @@ object_ptr CreateUserpicsWithMoreBadge( auto q = QPainter(&state->layer); auto hq = PainterHighQualityEnabler(q); - const auto stroke = st::boostReplaceIconOutline; + const auto stroke = st.stroke; const auto half = stroke / 2.; auto pen = st::windowBg->p; pen.setWidthF(stroke * 2.); state->painting = true; - for (const auto &button : state->buttons) { + const auto paintOne = [&](not_null button) { q.setPen(pen); q.setBrush(Qt::NoBrush); q.drawEllipse(button->geometry()); const auto position = button->pos(); button->render(&q, position, QRegion(), QWidget::DrawChildren); + }; + if (st.invert) { + for (const auto &button : ranges::views::reverse(state->buttons)) { + paintOne(button.get()); + } + } else { + for (const auto &button : state->buttons) { + paintOne(button.get()); + } } state->painting = false; const auto last = state->buttons.back().get(); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index 6e622479dc..71f5bab64d 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" +namespace style { +struct UserpicsRow; +} // namespace style + class ChannelData; namespace Main { @@ -66,4 +70,5 @@ enum class UserpicsTransferType { [[nodiscard]] object_ptr CreateUserpicsWithMoreBadge( not_null parent, rpl::producer>> peers, + const style::UserpicsRow &st, int limit); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 89fc1a1da0..3fbdecc1cc 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1527,6 +1527,22 @@ confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { confcallLinkFooterOrTop: 12px; confcallLinkFooterOrSkip: 8px; confcallLinkFooterOrLineTop: 9px; +confcallJoinBox: Box(confcallLinkBox) { + buttonPadding: margins(24px, 24px, 24px, 24px); +} +confcallJoinLogo: icon {{ "calls/group_call_logo", windowFgActive }}; +confcallJoinLogoPadding: margins(8px, 8px, 8px, 8px); +confcallJoinSepPadding: margins(0px, 8px, 0px, 8px); +confcallJoinUserpics: UserpicsRow { + button: UserpicButton(defaultUserpicButton) { + size: size(36px, 36px); + photoSize: 36px; + } + shift: 16px; + stroke: 2px; + invert: true; +} +confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px); groupCallLinkBox: Box(confcallLinkBox) { bg: groupCallMembersBg; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index f0dbb9628f..e1ac887e00 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/platform/base_platform_info.h" #include "base/random.h" +#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge #include "boxes/share_box.h" #include "calls/calls_instance.h" #include "core/application.h" @@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/painter.h" #include "ui/vertical_list.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -90,25 +92,144 @@ object_ptr ScreenSharingPrivacyRequestBox() { void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, + UserData *maybeInviter, Fn join) { - box->setTitle(tr::lng_confcall_join_title()); + box->setStyle(st::confcallJoinBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + const auto logoSize = st::confcallJoinLogo.size(); + const auto logoOuter = logoSize.grownBy(st::confcallJoinLogoPadding); + const auto logo = box->addRow( + object_ptr(box), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); + logo->resize(logo->width(), logoOuter.height()); + logo->paintRequest() | rpl::start_with_next([=] { + if (logo->width() < logoOuter.width()) { + return; + } + auto p = QPainter(logo); + auto hq = PainterHighQualityEnabler(p); + const auto x = (logo->width() - logoOuter.width()) / 2; + const auto outer = QRect(QPoint(x, 0), logoOuter); + p.setBrush(st::windowBgActive); + p.setPen(Qt::NoPen); + p.drawEllipse(outer); + st::confcallJoinLogo.paintInCenter(p, outer); + }, logo->lifetime()); + + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_confcall_join_title(), + st::boxTitle)), + st::boxRowPadding + st::confcallLinkTitlePadding); + const auto wrapName = [&](not_null peer) { + return rpl::single(Ui::Text::Bold(peer->shortName())); + }; box->addRow( object_ptr( box, - tr::lng_confcall_join_text(), - st::boxLabel)); + (maybeInviter + ? tr::lng_confcall_join_text_inviter( + lt_user, + wrapName(maybeInviter), + Ui::Text::RichLangValue) + : tr::lng_confcall_join_text(Ui::Text::RichLangValue)), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); - box->addButton(tr::lng_confcall_join_button(), [=] { + const auto &participants = call->participants(); + const auto known = int(participants.size()); + if (known) { + const auto sep = box->addRow( + object_ptr(box), + st::boxRowPadding + st::confcallJoinSepPadding); + sep->resize(sep->width(), st::normalFont->height); + sep->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(sep); + const auto line = st::lineWidth; + const auto top = st::confcallLinkFooterOrLineTop; + const auto fg = st::windowSubTextFg->b; + p.setOpacity(0.2); + p.fillRect(0, top, sep->width(), line, fg); + }, sep->lifetime()); + + auto peers = std::vector>(); + for (const auto &participant : participants) { + peers.push_back(participant.peer); + if (peers.size() == 3) { + break; + } + } + box->addRow( + CreateUserpicsWithMoreBadge( + box, + rpl::single(peers), + st::confcallJoinUserpics, + known), + st::boxRowPadding + st::confcallJoinUserpicsPadding); + + const auto wrapByIndex = [&](int index) { + Expects(index >= 0 && index < known); + + return wrapName(participants[index].peer); + }; + auto text = (known == 1) + ? tr::lng_confcall_already_joined_one( + lt_user, + wrapByIndex(0), + Ui::Text::RichLangValue) + : (known == 2) + ? tr::lng_confcall_already_joined_two( + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + Ui::Text::RichLangValue) + : (known == 3) + ? tr::lng_confcall_already_joined_three( + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + lt_third, + wrapByIndex(2), + Ui::Text::RichLangValue) + : tr::lng_confcall_already_joined_many( + lt_count, + rpl::single(1. * (std::max(known, call->fullCount()) - 2)), + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + Ui::Text::RichLangValue); + box->addRow( + object_ptr( + box, + std::move(text), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); + } + const auto joinAndClose = [=] { const auto weak = Ui::MakeWeak(box); join(); if (const auto strong = weak.data()) { strong->closeBox(); } - }); - box->addButton(tr::lng_cancel(), [=] { - box->closeBox(); - }); + }; + Info::BotStarRef::AddFullWidthButton( + box, + tr::lng_confcall_join_button(), + joinAndClose, + &st::confcallLinkButton); } ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 03c0770d47..0d40b1f14d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -129,6 +129,7 @@ using StickedTooltips = base::flags; void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, + UserData *maybeInviter, Fn join); struct ConferenceCallLinkStyleOverrides { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index f689603ec9..9092ba251b 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1459,8 +1459,9 @@ bool ResolveConferenceCall( if (slug.isEmpty()) { return false; } + const auto myContext = context.value(); controller->window().activate(); - controller->resolveConferenceCall(match->captured(1)); + controller->resolveConferenceCall(match->captured(1), myContext.itemId); return true; } } // namespace diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index d136b733d4..cd12e9bc45 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -65,13 +65,15 @@ QSize Call::countOptimalSize() { const auto user = _parent->history()->peer->asUser(); const auto conference = _conference; const auto video = _video; + const auto contextId = _parent->data()->fullId(); const auto id = _parent->data()->id; _link = std::make_shared([=](ClickContext context) { if (conference) { const auto my = context.other.value(); const auto weak = my.sessionWindow; if (const auto strong = weak.get()) { - strong->resolveConferenceCall(id); + QSize(); + strong->resolveConferenceCall(id, contextId); } } else if (user) { Core::App().calls().startOutgoingCall(user, video); diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h index d69864bf15..a48586b1be 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h @@ -55,7 +55,7 @@ using ConnectedBots = std::vector; rpl::producer subtitle, bool newBadge = false); -[[nodiscard]] not_null AddFullWidthButton( +not_null AddFullWidthButton( not_null box, rpl::producer text, Fn callback = nullptr, diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 261beffafa..daeece6ce2 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -311,6 +311,11 @@ boostReplaceIconSkip: 3px; boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; +boostReplaceUserpicsRow: UserpicsRow { + button: boostReplaceUserpic; + shift: boostReplaceUserpicsShift; + stroke: boostReplaceIconOutline; +} showOrIconLastSeen: icon{{ "settings/premium/large_lastseen", windowFgActive }}; showOrIconReadTime: icon{{ "settings/premium/large_readtime", windowFgActive }}; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c689c9e9aa..c2e69511ae 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -842,21 +842,21 @@ void SessionNavigation::resolveCollectible( void SessionNavigation::resolveConferenceCall( QString slug, - Fn finished) { - resolveConferenceCall(std::move(slug), 0, std::move(finished)); + FullMsgId contextId) { + resolveConferenceCall(std::move(slug), 0, contextId); } void SessionNavigation::resolveConferenceCall( MsgId inviteMsgId, - Fn finished) { - resolveConferenceCall({}, inviteMsgId, std::move(finished)); + FullMsgId contextId) { + resolveConferenceCall({}, inviteMsgId, contextId); } void SessionNavigation::resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished) { - _conferenceCallResolveFinished = std::move(finished); + FullMsgId contextId) { + _conferenceCallResolveContextId = contextId; if (_conferenceCallSlug == slug && _conferenceCallInviteMsgId == inviteMsgId) { return; @@ -875,7 +875,7 @@ void SessionNavigation::resolveConferenceCall( _conferenceCallRequestId = 0; const auto slug = base::take(_conferenceCallSlug); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); - const auto finished = base::take(_conferenceCallResolveFinished); + const auto contextId = base::take(_conferenceCallResolveContextId); result.data().vcall().match([&](const auto &data) { const auto call = session().data().sharedConferenceCall( data.vid().v, @@ -888,22 +888,21 @@ void SessionNavigation::resolveConferenceCall( .joinMessageId = inviteMsgId, }); }; + const auto context = session().data().message(contextId); + const auto inviter = context + ? context->from()->asUser() + : nullptr; uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, + (inviter && !inviter->isSelf()) ? inviter : nullptr, join)); - if (finished) { - finished(true); - } }); }).fail([=] { _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); - const auto finished = base::take(_conferenceCallResolveFinished); + _conferenceCallResolveContextId = FullMsgId(); showToast(tr::lng_group_invite_bad_link(tr::now)); - if (finished) { - finished(false); - } }).send(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index ceb30f1bd3..040739589d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -266,10 +266,10 @@ public: Fn fail = nullptr); void resolveConferenceCall( QString slug, - Fn finished = nullptr); + FullMsgId contextId); void resolveConferenceCall( MsgId inviteMsgId, - Fn finished = nullptr); + FullMsgId contextId); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -299,7 +299,7 @@ private: void resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished); + FullMsgId contextId); void resolveDone( const MTPcontacts_ResolvedPeer &result, @@ -341,7 +341,7 @@ private: QString _conferenceCallSlug; MsgId _conferenceCallInviteMsgId; - Fn _conferenceCallResolveFinished; + FullMsgId _conferenceCallResolveContextId; mtpRequestId _conferenceCallRequestId = 0; };