From 53b739139b160df72b0a739c152fff7dec24b153 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Apr 2025 18:18:16 +0400 Subject: [PATCH] Suggest invite to empty/discarded joining call. --- Telegram/Resources/langs/lang.strings | 4 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 22 ++-- Telegram/SourceFiles/boxes/peer_list_box.h | 6 +- Telegram/SourceFiles/calls/calls.style | 29 +++-- Telegram/SourceFiles/calls/calls_instance.cpp | 11 +- Telegram/SourceFiles/calls/calls_instance.h | 1 + .../calls/group/calls_group_common.cpp | 36 ++++--- .../calls/group/calls_group_common.h | 4 + .../group/calls_group_invite_controller.cpp | 101 +++++++++++++++++- .../group/calls_group_invite_controller.h | 11 +- .../window/window_session_controller.cpp | 32 ++++-- 11 files changed, 206 insertions(+), 51 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f676ad892b..671476eef9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4932,7 +4932,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_call" = "Create New Call"; +"lng_confcall_create_call" = "Start New Call"; "lng_confcall_create_call_description#one" = "You can add up to {count} participant to a call."; "lng_confcall_create_call_description#other" = "You can add up to {count} participants to a call."; "lng_confcall_create_title" = "New Call"; @@ -4947,6 +4947,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_link_or" = "or"; "lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; "lng_confcall_link_join_link" = "Open call {arrow}"; +"lng_confcall_inactive_title" = "Start Group Call"; +"lng_confcall_inactive_about" = "This call is no longer active.\nYou can start a new one."; "lng_confcall_invite_done_user" = "You're calling {user} to join."; "lng_confcall_invite_done_many#one" = "You're calling **{count} person** to join."; "lng_confcall_invite_done_many#other" = "You're calling **{count} people** to join."; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 7a559faf3a..49a03818e0 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -138,15 +138,16 @@ void PeerListBox::createMultiSelect() { } }); _select->resizeToWidth(_controller->contentWidth()); - _select->moveToLeft(0, 0); + _select->moveToLeft(0, topSelectSkip()); } void PeerListBox::appendQueryChangedCallback(Fn callback) { _customQueryChangedCallback = std::move(callback); } -void PeerListBox::setAddedTopScrollSkip(int skip) { +void PeerListBox::setAddedTopScrollSkip(int skip, bool aboveSearch) { _addedTopScrollSkip = skip; + _addedTopScrollAboveSearch = aboveSearch; _scrollBottomFixed = false; updateScrollSkips(); } @@ -155,7 +156,7 @@ void PeerListBox::showFinished() { _controller->showFinished(); } -int PeerListBox::getTopScrollSkip() const { +int PeerListBox::topScrollSkip() const { auto result = _addedTopScrollSkip; if (_select && !_select->isHidden()) { result += _select->height(); @@ -163,12 +164,19 @@ int PeerListBox::getTopScrollSkip() const { return result; } +int PeerListBox::topSelectSkip() const { + return _addedTopScrollAboveSearch ? _addedTopScrollSkip : 0; +} + void PeerListBox::updateScrollSkips() { // If we show / hide the search field scroll top is fixed. // If we resize search field by bubbles scroll bottom is fixed. - setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed); - if (_select && !_select->animating()) { - _scrollBottomFixed = true; + setInnerTopSkip(topScrollSkip(), _scrollBottomFixed); + if (_select) { + _select->moveToLeft(0, topSelectSkip()); + if (!_select->animating()) { + _scrollBottomFixed = true; + } } } @@ -232,8 +240,6 @@ void PeerListBox::resizeEvent(QResizeEvent *e) { if (_select) { _select->resizeToWidth(width()); - _select->moveToLeft(0, 0); - updateScrollSkips(); } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index b8dbecb8ce..068906fa26 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -1142,7 +1142,7 @@ public: void peerListScrollToTop() override; std::shared_ptr peerListUiShow() override; - void setAddedTopScrollSkip(int skip); + void setAddedTopScrollSkip(int skip, bool aboveSearch = false); void showFinished() override; @@ -1178,7 +1178,8 @@ private: PaintRoundImageCallback paintUserpic, anim::type animated); void createMultiSelect(); - int getTopScrollSkip() const; + [[nodiscard]] int topScrollSkip() const; + [[nodiscard]] int topSelectSkip() const; void updateScrollSkips(); void searchQueryChanged(const QString &query); @@ -1189,6 +1190,7 @@ private: std::unique_ptr _controller; Fn _init; bool _scrollBottomFixed = false; + bool _addedTopScrollAboveSearch = false; int _addedTopScrollSkip = 0; }; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index f7b2e78eb7..8a50d88f8b 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -635,6 +635,23 @@ callDeviceSelectionMenu: PopupMenu(groupCallPopupMenu) { } } +createCallListButton: OutlineButton(defaultPeerListButton) { + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); +} +createCallListItem: PeerListItem(defaultPeerListItem) { + button: createCallListButton; + height: 52px; + photoPosition: point(12px, 6px); + namePosition: point(63px, 7px); + statusPosition: point(63px, 26px); + photoSize: 40px; +} +createCallList: PeerList(defaultPeerList) { + item: createCallListItem; + padding: margins(0px, 10px, 0px, 10px); +} + groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px); groupCallRecordingTimerFont: font(12px); @@ -658,26 +675,18 @@ groupCallMembersListCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) { selectFg: groupCallActiveFg; check: groupCallMembersListCheck; } -groupCallMembersListItem: PeerListItem(defaultPeerListItem) { - button: OutlineButton(defaultPeerListButton) { +groupCallMembersListItem: PeerListItem(createCallListItem) { + button: OutlineButton(createCallListButton) { textBg: groupCallMembersBg; textBgOver: groupCallMembersBgOver; textFg: groupCallMemberInactiveStatus; textFgOver: groupCallMemberInactiveStatus; - font: normalFont; - padding: margins(11px, 5px, 11px, 5px); - ripple: groupCallRipple; } disabledCheckFg: groupCallMemberNotJoinedStatus; checkbox: groupCallMembersListCheckbox; - height: 52px; - photoPosition: point(12px, 6px); - namePosition: point(63px, 7px); - statusPosition: point(63px, 26px); - photoSize: 40px; nameFg: groupCallMembersFg; nameFgChecked: groupCallMembersFg; statusFg: groupCallMemberInactiveStatus; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index b3c0cf3c4b..1181c8152e 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -266,6 +266,7 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { migrationInfo); _currentGroupCall = std::move(call); _currentGroupCallChanges.fire_copy(raw); + finishConferenceInvitations(args); if (args.migrating) { destroyCurrentCall(args.call.get(), args.linkSlug); } @@ -294,12 +295,18 @@ void Instance::startedConferenceReady( const auto real = call->conferenceCall().get(); const auto link = real->conferenceInviteLink(); const auto slug = Group::ExtractConferenceSlug(link); + finishConferenceInvitations(args); + destroyCurrentCall(real, slug); +} + +void Instance::finishConferenceInvitations(const StartConferenceInfo &args) { + Expects(_currentGroupCallPanel != nullptr); + if (!args.invite.empty()) { _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); } else if (args.sharingLink) { _currentGroupCallPanel->migrationShowShareLink(); } - destroyCurrentCall(real, slug); } void Instance::confirmLeaveCurrent( @@ -402,7 +409,7 @@ void Instance::destroyCall(not_null call) { void Instance::createCall( not_null user, - Call::Type type, + CallType type, bool isVideo) { struct Performer final { explicit Performer(Fn callback) diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 90b0d2f259..1c3d8b9212 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -159,6 +159,7 @@ private: void createCall(not_null user, CallType type, bool isVideo); void destroyCall(not_null call); + void finishConferenceInvitations(const StartConferenceInfo &args); void createGroupCall( Group::JoinInfo info, diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 79c79e169a..fab26cc837 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -77,23 +77,11 @@ object_ptr ScreenSharingPrivacyRequestBox() { #endif // Q_OS_MAC } -void ConferenceCallJoinConfirm( - not_null box, - std::shared_ptr call, - UserData *maybeInviter, - Fn close)> join) { - box->setStyle(st::confcallJoinBox); - box->setWidth(st::boxWideWidth); - box->setNoContentMargin(true); - box->addTopButton(st::boxTitleClose, [=] { - box->closeBox(); - }); - +object_ptr MakeJoinCallLogo(not_null parent) { 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); + auto result = object_ptr(parent); + const auto logo = result.data(); logo->resize(logo->width(), logoOuter.height()); logo->paintRequest() | rpl::start_with_next([=] { if (logo->width() < logoOuter.width()) { @@ -108,6 +96,24 @@ void ConferenceCallJoinConfirm( p.drawEllipse(outer); st::confcallJoinLogo.paintInCenter(p, outer); }, logo->lifetime()); + return result; +} + +void ConferenceCallJoinConfirm( + not_null box, + std::shared_ptr call, + UserData *maybeInviter, + Fn close)> join) { + box->setStyle(st::confcallJoinBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + box->addRow( + MakeJoinCallLogo(box), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); box->addRow( object_ptr>( diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 0afe320964..7391cc191b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -31,6 +31,7 @@ class SessionShow; namespace Ui { class Show; +class RpWidget; class GenericBox; } // namespace Ui @@ -160,6 +161,9 @@ using StickedTooltips = base::flags; [[nodiscard]] object_ptr ScreenSharingPrivacyRequestBox(); +[[nodiscard]] object_ptr MakeJoinCallLogo( + not_null parent); + void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 14c55d2aa6..b251c953dd 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -29,12 +29,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/painter.h" +#include "ui/vertical_list.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" // membersMarginTop #include "styles/style_calls.h" #include "styles/style_dialogs.h" // searchedBarHeight +#include "styles/style_layers.h" // boxWideWidth namespace Calls::Group { namespace { @@ -134,6 +136,7 @@ protected: private: [[nodiscard]] int fullCount() const; void toggleRowSelected(not_null row, bool video); + void addShareLinkButton(); const ConfInviteStyles _st; const base::flat_set> _alreadyIn; @@ -383,6 +386,12 @@ void ConfInviteController::toggleRowSelected( } void ConfInviteController::prepareViewHook() { + if (_shareLink) { + addShareLinkButton(); + } +} + +void ConfInviteController::addShareLinkButton() { auto button = object_ptr>( nullptr, object_ptr( @@ -757,9 +766,86 @@ object_ptr PrepareInviteBox( return Box(std::move(controller), initBox); } +not_null CreateReActivateHeader(not_null parent) { + const auto result = Ui::CreateChild(parent); + result->add( + MakeJoinCallLogo(result), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); + + result->add( + object_ptr>( + result, + object_ptr( + result, + tr::lng_confcall_inactive_title(), + st::boxTitle)), + st::boxRowPadding + st::confcallLinkTitlePadding); + result->add( + object_ptr( + result, + tr::lng_confcall_inactive_about(), + st::confcallLinkCenteredText), + st::boxRowPadding + st::confcallLinkTitlePadding + )->setTryMakeSimilarLines(true); + Ui::AddDivider(result); + + return result; +} + +void InitReActivate(not_null box) { + box->setTitle(rpl::producer(nullptr)); + box->setNoContentMargin(true); + + const auto header = CreateReActivateHeader(box); + header->resizeToWidth(st::boxWideWidth); + header->heightValue() | rpl::start_with_next([=](int height) { + box->setAddedTopScrollSkip(height, true); + }, header->lifetime()); + header->moveToLeft(0, 0); +} + +object_ptr PrepareInviteToEmptyBox( + std::shared_ptr call, + MsgId inviteMsgId) { + auto controller = std::make_unique( + &call->session(), + ConfInviteDefaultStyles(), + base::flat_set>(), + nullptr); + const auto raw = controller.get(); + raw->setStyleOverrides(&st::createCallList); + const auto initBox = [=](not_null box) { + InitReActivate(box); + + const auto join = [=] { + const auto weak = Ui::MakeWeak(box); + auto selected = raw->requests(box->collectSelectedRows()); + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .joinMessageId = inviteMsgId, + .invite = std::move(selected), + }); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + box->addButton( + rpl::conditional( + raw->hasSelectedValue(), + tr::lng_group_call_confcall_add(), + tr::lng_create_group_create()), + join); + box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + }; + return Box(std::move(controller), initBox); +} + object_ptr PrepareCreateCallBox( not_null<::Window::SessionController*> window, - Fn created) { + Fn created, + MsgId discardedInviteMsgId) { struct State { bool creatingLink = false; QPointer box; @@ -791,14 +877,21 @@ object_ptr PrepareCreateCallBox( &window->session(), ConfInviteDefaultStyles(), base::flat_set>(), - shareLink); + discardedInviteMsgId ? Fn() : shareLink); const auto raw = controller.get(); + if (discardedInviteMsgId) { + raw->setStyleOverrides(&st::createCallList); + } const auto initBox = [=](not_null box) { - box->setTitle(tr::lng_confcall_create_title()); + if (discardedInviteMsgId) { + InitReActivate(box); + } else { + box->setTitle(tr::lng_confcall_create_title()); + } const auto create = [=] { auto selected = raw->requests(box->collectSelectedRows()); - if (selected.size() != 1) { + if (selected.size() != 1 || discardedInviteMsgId) { Core::App().calls().startOrJoinConferenceCall({ .show = window->uiShow(), .invite = std::move(selected), diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index 72779f6d6e..965c31682b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -16,6 +16,10 @@ class GroupCall; struct InviteRequest; } // namespace Calls +namespace Data { +class GroupCall; +} // namespace Data + namespace Calls::Group { class InviteController final : public ParticipantsBoxController { @@ -87,8 +91,13 @@ private: Fn)> inviteUsers, Fn shareLink); +[[nodiscard]] object_ptr PrepareInviteToEmptyBox( + std::shared_ptr call, + MsgId inviteMsgId); + [[nodiscard]] object_ptr PrepareCreateCallBox( not_null<::Window::SessionController*> window, - Fn created = nullptr); + Fn created = nullptr, + MsgId discardedInviteMsgId = 0); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 9ca1596b89..f5d0d8799f 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -73,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_instance.h" // Core::App().calls().inCall(). #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_invite_controller.h" #include "ui/boxes/calendar_box.h" #include "ui/boxes/collectible_info_box.h" #include "ui/boxes/confirm_box.h" @@ -899,14 +900,24 @@ void SessionNavigation::resolveConferenceCall( const auto inviter = context ? context->from()->asUser() : nullptr; - uiShow()->show(Box( - Calls::Group::ConferenceCallJoinConfirm, - call, - (inviter && !inviter->isSelf()) ? inviter : nullptr, - join)); + if (inviteMsgId && call->participants().empty()) { + uiShow()->show(Calls::Group::PrepareInviteToEmptyBox( + call, + inviteMsgId)); + } else { + uiShow()->show(Box( + Calls::Group::ConferenceCallJoinConfirm, + call, + (inviter && !inviter->isSelf()) ? inviter : nullptr, + join)); + } }, [&](const MTPDgroupCallDiscarded &data) { if (inviteMsgId) { - showToast(tr::lng_confcall_not_accessible(tr::now)); + uiShow()->show( + Calls::Group::PrepareCreateCallBox( + parentController(), + nullptr, + inviteMsgId)); } else { showToast(tr::lng_confcall_link_inactive(tr::now)); } @@ -915,8 +926,13 @@ void SessionNavigation::resolveConferenceCall( _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); _conferenceCallResolveContextId = FullMsgId(); - if (base::take(_conferenceCallResolveContextId)) { - showToast(tr::lng_confcall_not_accessible(tr::now)); + const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); + if (inviteMsgId) { + uiShow()->show( + Calls::Group::PrepareCreateCallBox( + parentController(), + nullptr, + inviteMsgId)); } else { showToast(tr::lng_confcall_link_inactive(tr::now)); }