From 7543351bc958a90b4808601dabd0ffa228ef6762 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 14 Oct 2021 14:17:03 +0400 Subject: [PATCH] Add pending requests bar in the chat. --- Telegram/CMakeLists.txt | 6 +- Telegram/Resources/langs/lang.strings | 4 +- .../boxes/peers/edit_peer_info_box.cpp | 12 +- .../boxes/peers/edit_peer_invite_link.cpp | 2 +- .../boxes/peers/edit_peer_requests_box.cpp | 13 ++ .../boxes/peers/edit_peer_requests_box.h | 4 + Telegram/SourceFiles/calls/calls_top_bar.cpp | 4 +- .../SourceFiles/history/history_widget.cpp | 97 +++++++- Telegram/SourceFiles/history/history_widget.h | 8 +- ...er.cpp => history_view_group_call_bar.cpp} | 29 +-- ...racker.h => history_view_group_call_bar.h} | 24 +- .../history/view/history_view_message.cpp | 2 +- .../view/history_view_requests_bar.cpp | 214 +++++++++++++++++ .../history/view/history_view_requests_bar.h | 22 ++ Telegram/SourceFiles/ui/chat/chat.style | 8 + Telegram/SourceFiles/ui/chat/requests_bar.cpp | 220 ++++++++++++++++++ Telegram/SourceFiles/ui/chat/requests_bar.h | 77 ++++++ Telegram/cmake/td_ui.cmake | 2 + 18 files changed, 682 insertions(+), 66 deletions(-) rename Telegram/SourceFiles/history/view/{history_view_group_call_tracker.cpp => history_view_group_call_bar.cpp} (94%) rename Telegram/SourceFiles/history/view/{history_view_group_call_tracker.h => history_view_group_call_bar.h} (66%) create mode 100644 Telegram/SourceFiles/history/view/history_view_requests_bar.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_requests_bar.h create mode 100644 Telegram/SourceFiles/ui/chat/requests_bar.cpp create mode 100644 Telegram/SourceFiles/ui/chat/requests_bar.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 53a8680b7..61ba859ff 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -621,8 +621,8 @@ PRIVATE history/view/history_view_emoji_interactions.h history/view/history_view_empty_list_bubble.cpp history/view/history_view_empty_list_bubble.h - history/view/history_view_group_call_tracker.cpp - history/view/history_view_group_call_tracker.h + history/view/history_view_group_call_bar.cpp + history/view/history_view_group_call_bar.h history/view/history_view_list_widget.cpp history/view/history_view_list_widget.h history/view/history_view_message.cpp @@ -636,6 +636,8 @@ PRIVATE history/view/history_view_pinned_tracker.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h + history/view/history_view_requests_bar.cpp + history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp history/view/history_view_schedule_box.h history/view/history_view_scheduled_section.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8230cbdee..a53f9a04b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1350,8 +1350,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_requests_dismiss" = "Dismiss"; "lng_group_requests_was_added" = "{user} has been added to the group."; "lng_group_requests_was_added_channel" = "{user} has been added to the channel."; -"lng_group_requests_none" = "No member requests"; -"lng_group_requests_none_channel" = "No subscriber requests"; +"lng_group_requests_none" = "You have no pending requests\nto join your group."; +"lng_group_requests_none_channel" = "You have no pending requests\nto join your channel."; "lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_context_about_private_link" = "This link will only work for members of this chat."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index b94b960f0..807b31a54 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1108,23 +1108,13 @@ void Controller::fillPendingRequestsButton() { _controls.buttonsLayout, object_ptr( _controls.buttonsLayout))); - const auto showPendingRequestsBox = [=] { - auto controller = std::make_unique( - _navigation, - _peer->migrateToOrMe()); - const auto initBox = [=](not_null box) { - box->addButton(tr::lng_close(), [=] { box->closeBox(); }); - }; - _navigation->parentController()->show( - Box(std::move(controller), initBox)); - }; AddButtonWithCount( wrap->entity(), (_isGroup ? tr::lng_manage_peer_requests() : tr::lng_manage_peer_requests_channel()), rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(), - showPendingRequestsBox, + [=] { RequestsBoxController::Start(_navigation, _peer); }, st::infoIconRequests); std::move( pendingRequestsCount diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 020593e69..e736ad4b0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/boxes/edit_invite_link.h" #include "boxes/share_box.h" -#include "history/view/history_view_group_call_tracker.h" // GenerateUs... +#include "history/view/history_view_group_call_bar.h" // GenerateUserpics... #include "history/history_message.h" // GetErrorTextForSending. #include "history/history.h" #include "ui/boxes/confirm_box.h" diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp index 2d6740075..87ede518d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp @@ -256,6 +256,19 @@ RequestsBoxController::RequestsBoxController( RequestsBoxController::~RequestsBoxController() = default; +void RequestsBoxController::Start( + not_null navigation, + not_null peer) { + auto controller = std::make_unique( + navigation, + peer->migrateToOrMe()); + const auto initBox = [=](not_null box) { + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + }; + navigation->parentController()->show( + Box(std::move(controller), initBox)); +} + Main::Session &RequestsBoxController::session() const { return _peer->session(); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h index abe857e65..cedba9e31 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h @@ -27,6 +27,10 @@ public: not_null peer); ~RequestsBoxController(); + static void Start( + not_null navigation, + not_null peer); + Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index ca5499a10..d7cd00bd5 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_signal_bars.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" // Group::LeaveBox. -#include "history/view/history_view_group_call_tracker.h" // ContentByCall. +#include "history/view/history_view_group_call_bar.h" // ContentByCall. #include "data/data_user.h" #include "data/data_group_call.h" #include "data/data_peer.h" @@ -582,7 +582,7 @@ void TopBar::subscribeToMembersChanges(not_null call) { }, lifetime()); }) | rpl::map([=](not_null real) { - return HistoryView::GroupCallTracker::ContentByCall( + return HistoryView::GroupCallBarContentByCall( real, st::groupCallTopBarUserpics.size); }) | rpl::flatten_latest( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e1d6a2a05..e5e62be90 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/share_box.h" #include "boxes/edit_caption_box.h" #include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup. +#include "boxes/peers/edit_peer_requests_box.h" #include "core/file_utilities.h" #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" @@ -81,7 +82,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_pinned_tracker.h" #include "history/view/history_view_pinned_section.h" #include "history/view/history_view_pinned_bar.h" -#include "history/view/history_view_group_call_tracker.h" +#include "history/view/history_view_group_call_bar.h" +#include "history/view/history_view_requests_bar.h" #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" @@ -109,6 +111,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/report_box.h" #include "ui/chat/pinned_bar.h" #include "ui/chat/group_call_bar.h" +#include "ui/chat/requests_bar.h" #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/chat/continuous_scroll.h" @@ -1416,6 +1419,9 @@ void HistoryWidget::orderWidgets() { if (_groupCallBar) { _groupCallBar->raise(); } + if (_requestsBar) { + _requestsBar->raise(); + } if (_chooseTheme) { _chooseTheme->raise(); } @@ -2007,7 +2013,7 @@ void HistoryWidget::showHistory( _pinnedBar = nullptr; _pinnedTracker = nullptr; _groupCallBar = nullptr; - _groupCallTracker = nullptr; + _requestsBar = nullptr; _chooseTheme = nullptr; _membersDropdown.destroy(); _scrollToAnimation.stop(); @@ -2134,7 +2140,8 @@ void HistoryWidget::showHistory( _updateHistoryItems.cancel(); setupPinnedTracker(); - setupGroupCallTracker(); + setupGroupCallBar(); + setupRequestsBar(); checkMessagesTTL(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) @@ -2404,6 +2411,9 @@ void HistoryWidget::updateControlsVisibility() { if (_groupCallBar) { _groupCallBar->show(); } + if (_requestsBar) { + _requestsBar->show(); + } if (_firstLoadRequest && !_scroll->isHidden()) { _scroll->hide(); } else if (!_firstLoadRequest && _scroll->isHidden()) { @@ -3377,6 +3387,9 @@ void HistoryWidget::hideChildWidgets() { if (_groupCallBar) { _groupCallBar->hide(); } + if (_requestsBar) { + _requestsBar->hide(); + } if (_voiceRecordBar) { _voiceRecordBar->hideFast(); } @@ -3617,6 +3630,9 @@ void HistoryWidget::showAnimated( if (_groupCallBar) { _groupCallBar->finishAnimating(); } + if (_requestsBar) { + _requestsBar->finishAnimating(); + } _topShadow->setVisible(params.withTopBarShadow ? false : true); _preserveScrollTop = false; @@ -3648,6 +3664,9 @@ void HistoryWidget::animationCallback() { if (_groupCallBar) { _groupCallBar->finishAnimating(); } + if (_requestsBar) { + _requestsBar->finishAnimating(); + } _cacheUnder = _cacheOver = QPixmap(); doneShow(); synteticScrollToY(_scroll->scrollTop()); @@ -3674,6 +3693,9 @@ void HistoryWidget::doneShow() { if (_groupCallBar) { _groupCallBar->finishAnimating(); } + if (_requestsBar) { + _requestsBar->finishAnimating(); + } checkHistoryActivation(); controller()->widget()->setInnerFocus(); _preserveScrollTop = false; @@ -4815,7 +4837,12 @@ void HistoryWidget::updateControlsGeometry() { _groupCallBar->move(0, groupCallTop); _groupCallBar->resizeToWidth(width()); } - const auto pinnedBarTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0); + const auto requestsTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0); + if (_requestsBar) { + _requestsBar->move(0, requestsTop); + _requestsBar->resizeToWidth(width()); + } + const auto pinnedBarTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0); if (_pinnedBar) { _pinnedBar->move(0, pinnedBarTop); _pinnedBar->resizeToWidth(width()); @@ -5000,6 +5027,9 @@ void HistoryWidget::updateHistoryGeometry( if (_groupCallBar) { newScrollHeight -= _groupCallBar->height(); } + if (_requestsBar) { + newScrollHeight -= _requestsBar->height(); + } if (_contactStatus) { newScrollHeight -= _contactStatus->height(); } @@ -5340,6 +5370,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - (_contactStatus ? _contactStatus->height() : 0) - (_pinnedBar ? _pinnedBar->height() : 0) - (_groupCallBar ? _groupCallBar->height() : 0) + - (_requestsBar ? _requestsBar->height() : 0) - ((_editMsgId || replyToId() || readyToForward() @@ -5611,7 +5642,8 @@ void HistoryWidget::handlePeerMigration() { _migrated = _history->migrateFrom(); _list->notifyMigrateUpdated(); setupPinnedTracker(); - setupGroupCallTracker(); + setupGroupCallBar(); + setupRequestsBar(); updateHistoryGeometry(); } const auto from = chat->owner().historyLoaded(chat); @@ -5979,20 +6011,19 @@ void HistoryWidget::refreshPinnedBarButton(bool many) { _pinnedBar->setRightButton(std::move(button)); } -void HistoryWidget::setupGroupCallTracker() { +void HistoryWidget::setupGroupCallBar() { Expects(_history != nullptr); const auto peer = _history->peer; if (!peer->isChannel() && !peer->isChat()) { - _groupCallTracker = nullptr; _groupCallBar = nullptr; return; } - _groupCallTracker = std::make_unique( - peer); _groupCallBar = std::make_unique( this, - _groupCallTracker->content(), + HistoryView::GroupCallBarContentByPeer( + peer, + st::historyGroupCallUserpics.size), Core::App().appDeactivatedValue()); controller()->adaptive().oneColumnValue( @@ -6032,6 +6063,52 @@ void HistoryWidget::setupGroupCallTracker() { } } +void HistoryWidget::setupRequestsBar() { + Expects(_history != nullptr); + + const auto peer = _history->peer; + if (!peer->isChannel() && !peer->isChat()) { + _requestsBar = nullptr; + return; + } + _requestsBar = std::make_unique( + this, + HistoryView::RequestsBarContentByPeer( + peer, + st::historyRequestsUserpics.size)); + + controller()->adaptive().oneColumnValue( + ) | rpl::start_with_next([=](bool one) { + _requestsBar->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _requestsBar->lifetime()); + + _requestsBar->barClicks( + ) | rpl::start_with_next([=] { + RequestsBoxController::Start(controller(), _peer); + }, _requestsBar->lifetime()); + + _requestsBarHeight = 0; + _requestsBar->heightValue( + ) | rpl::start_with_next([=](int height) { + _topDelta = _preserveScrollTop ? 0 : (height - _requestsBarHeight); + _requestsBarHeight = height; + updateHistoryGeometry(); + updateControlsGeometry(); + _topDelta = 0; + }, _requestsBar->lifetime()); + + orderWidgets(); + + if (_a_show.animating()) { + _requestsBar->hide(); + } +} + void HistoryWidget::requestMessageData(MsgId msgId) { const auto callback = [=](ChannelData *channel, MsgId msgId) { messageDataReceived(channel, msgId); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 94c8476ef..39d0a9217 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -73,6 +73,7 @@ class LinkButton; class RoundButton; class PinnedBar; class GroupCallBar; +class RequestsBar; struct PreparedList; class SendFilesWay; enum class ReportReason; @@ -102,7 +103,6 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; -class GroupCallTracker; namespace Controls { class RecordLock; class VoiceRecordBar; @@ -507,7 +507,8 @@ private: int nowScrollTop); void checkMessagesTTL(); - void setupGroupCallTracker(); + void setupGroupCallBar(); + void setupRequestsBar(); void sendInlineResult(InlineBots::ResultSelected result); @@ -638,9 +639,10 @@ private: FullMsgId _pinnedClickedId; std::optional _minPinnedId; - std::unique_ptr _groupCallTracker; std::unique_ptr _groupCallBar; int _groupCallBarHeight = 0; + std::unique_ptr _requestsBar; + int _requestsBarHeight = 0; bool _preserveScrollTop = false; diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp similarity index 94% rename from Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp rename to Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp index 089c279bf..04cb76a95 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/view/history_view_group_call_tracker.h" +#include "history/view/history_view_group_call_bar.h" #include "data/data_channel.h" #include "data/data_user.h" @@ -62,11 +62,7 @@ void GenerateUserpicsInRow( } } -GroupCallTracker::GroupCallTracker(not_null peer) -: _peer(peer) { -} - -rpl::producer GroupCallTracker::ContentByCall( +rpl::producer GroupCallBarContentByCall( not_null call, int userpicSize) { struct State { @@ -74,7 +70,7 @@ rpl::producer GroupCallTracker::ContentByCall( Ui::GroupCallBarContent current; base::has_weak_ptr guard; bool someUserpicsNotLoaded = false; - bool scheduled = false; + bool pushScheduled = false; }; // speaking DESC, std::max(date, lastActive) DESC @@ -251,12 +247,12 @@ rpl::producer GroupCallTracker::ContentByCall( state->current.livestream = call->peer()->isBroadcast(); const auto pushNext = [=] { - if (state->scheduled) { + if (state->pushScheduled) { return; } - state->scheduled = true; + state->pushScheduled = true; crl::on_main(&state->guard, [=] { - state->scheduled = false; + state->pushScheduled = false; consumer.put_next_copy(state->current); }); }; @@ -350,8 +346,9 @@ rpl::producer GroupCallTracker::ContentByCall( }; } -rpl::producer GroupCallTracker::content() const { - const auto peer = _peer; +rpl::producer GroupCallBarContentByPeer( + not_null peer, + int userpicSize) { return rpl::combine( peer->session().changes().peerFlagsValue( peer, @@ -363,19 +360,15 @@ rpl::producer GroupCallTracker::content() const { ? call : nullptr; }) | rpl::distinct_until_changed( - ) | rpl::map([](Data::GroupCall *call) + ) | rpl::map([=](Data::GroupCall *call) -> rpl::producer { if (!call) { return rpl::single(Ui::GroupCallBarContent{ .shown = false }); } else if (!call->fullCount() && !call->participantsLoaded()) { call->reload(); } - return ContentByCall(call, st::historyGroupCallUserpics.size); + return GroupCallBarContentByCall(call, userpicSize); }) | rpl::flatten_latest(); } -rpl::producer<> GroupCallTracker::joinClicks() const { - return _joinClicks.events(); -} - } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.h b/Telegram/SourceFiles/history/view/history_view_group_call_bar.h similarity index 66% rename from Telegram/SourceFiles/history/view/history_view_group_call_tracker.h rename to Telegram/SourceFiles/history/view/history_view_group_call_bar.h index c7b2ad1f0..7cdfdd55c 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_group_call_bar.h @@ -37,22 +37,14 @@ void GenerateUserpicsInRow( const style::GroupCallUserpics &st, int maxElements = 0); -class GroupCallTracker final { -public: - explicit GroupCallTracker(not_null peer); +[[nodiscard]] auto GroupCallBarContentByCall( + not_null call, + int userpicSize) +-> rpl::producer; - [[nodiscard]] rpl::producer content() const; - [[nodiscard]] rpl::producer<> joinClicks() const; - - [[nodiscard]] static rpl::producer ContentByCall( - not_null call, - int userpicSize); - -private: - const not_null _peer; - - rpl::event_stream<> _joinClicks; - -}; +[[nodiscard]] auto GroupCallBarContentByPeer( + not_null peer, + int userpicSize) +-> rpl::producer; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 26dca4be7..f6ba098c6 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_message.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" -#include "history/view/history_view_group_call_tracker.h" // UserpicInRow. +#include "history/view/history_view_group_call_bar.h" // UserpicInRow. #include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "ui/effects/ripple_animation.h" diff --git a/Telegram/SourceFiles/history/view/history_view_requests_bar.cpp b/Telegram/SourceFiles/history/view/history_view_requests_bar.cpp new file mode 100644 index 000000000..fa4b8eaac --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_requests_bar.cpp @@ -0,0 +1,214 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_requests_bar.h" + +#include "history/view/history_view_group_call_bar.h" +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_changes.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "ui/chat/requests_bar.h" +#include "ui/chat/group_call_userpics.h" +#include "info/profile/info_profile_values.h" +#include "apiwrap.h" + +namespace HistoryView { +namespace { + +// If less than 10 requests we request userpics each time the count changes. +constexpr auto kRequestAgainThreshold = 10; + +} // namespace + +rpl::producer RequestsBarContentByPeer( + not_null peer, + int userpicSize) { + struct State { + explicit State(not_null peer) + : api(&peer->session().api()) { + current.isGroup = !peer->isBroadcast(); + } + ~State() { + if (requestId) { + api->request(requestId).cancel(); + } + } + + not_null api; + std::vector userpics; + std::vector> users; + Ui::RequestsBarContent current; + base::has_weak_ptr guard; + mtpRequestId requestId = 0; + Fn requestUsers; + bool someUserpicsNotLoaded = false; + bool pushScheduled = false; + }; + + static constexpr auto kLimit = 3; + static const auto FillUserpics = []( + not_null state) { + const auto &users = state->users; + const auto same = ranges::equal( + state->userpics, + users, + ranges::equal_to(), + &UserpicInRow::peer); + if (same) { + return false; + } + for (auto b = begin(users), e = end(users), i = b; i != e; ++i) { + const auto user = *i; + const auto j = ranges::find( + state->userpics, + user, + &UserpicInRow::peer); + const auto place = begin(state->userpics) + (i - b); + if (j == end(state->userpics)) { + state->userpics.insert( + place, + UserpicInRow{ .peer = user }); + } else if (j > place) { + ranges::rotate(place, j, j + 1); + } + } + while (state->userpics.size() > users.size()) { + state->userpics.pop_back(); + } + return true; + }; + + static const auto RegenerateUserpics = []( + not_null state, + int userpicSize, + bool force = false) { + const auto result = FillUserpics(state) || force; + if (!result) { + return false; + } + state->current.users.reserve(state->userpics.size()); + state->current.users.clear(); + state->someUserpicsNotLoaded = false; + for (auto &userpic : state->userpics) { + userpic.peer->loadUserpic(); + const auto pic = userpic.peer->genUserpic( + userpic.view, + userpicSize); + userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view); + state->current.users.push_back({ + .userpic = pic.toImage(), + .userpicKey = userpic.uniqueKey, + .id = userpic.peer->id.value, + }); + if (userpic.peer->hasUserpic() + && userpic.peer->useEmptyUserpic(userpic.view)) { + state->someUserpicsNotLoaded = true; + } + } + return true; + }; + + return [=](auto consumer) { + const auto api = &peer->session().api(); + + auto lifetime = rpl::lifetime(); + auto state = lifetime.make_state(peer); + + const auto pushNext = [=] { + if (state->pushScheduled) { + return; + } + state->pushScheduled = true; + crl::on_main(&state->guard, [=] { + state->pushScheduled = false; + consumer.put_next_copy(state->current); + }); + }; + + state->requestUsers = [=] { + if (state->requestId) { + return; + } + using Flag = MTPmessages_GetChatInviteImporters::Flag; + state->requestId = state->api->request( + MTPmessages_GetChatInviteImporters( + MTP_flags(Flag::f_requested), + peer->input, + MTPstring(), // link + MTPstring(), // q + MTP_int(0), // offset_date + MTP_inputUserEmpty(), // offset_user + MTP_int(kLimit)) + ).done([=](const MTPmessages_ChatInviteImporters &result) { + state->requestId = 0; + + result.match([&]( + const MTPDmessages_chatInviteImporters &data) { + const auto count = data.vcount().v; + const auto changed = (state->current.count != count); + const auto &importers = data.vimporters().v; + auto &owner = peer->owner(); + state->users = std::vector>(); + state->users.reserve(importers.size()); + for (const auto &importer : importers) { + importer.match([&]( + const MTPDchatInviteImporter &data) { + state->users.push_back( + owner.user(data.vuser_id())); + }); + } + if (changed) { + state->current.count = count; + } + if (RegenerateUserpics(state, userpicSize) || changed) { + pushNext(); + } + if (state->userpics.size() > state->current.count) { + state->requestUsers(); + } + }); + }).fail([=](const MTP::Error &error) { + state->requestId = 0; + }).send(); + }; + + peer->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return state->someUserpicsNotLoaded; + }) | rpl::start_with_next([=] { + for (const auto &userpic : state->userpics) { + if (userpic.peer->userpicUniqueKey(userpic.view) + != userpic.uniqueKey) { + RegenerateUserpics(state, userpicSize, true); + pushNext(); + return; + } + } + }, lifetime); + + Info::Profile::PendingRequestsCountValue( + peer + ) | rpl::filter([=](int count) { + return (state->current.count != count); + }) | rpl::start_with_next([=](int count) { + const auto was = state->current.count; + const auto requestUsersNeeded = (was < kRequestAgainThreshold) + || (count < kRequestAgainThreshold); + state->current.count = count; + if (requestUsersNeeded) { + state->requestUsers(); + } + pushNext(); + }, lifetime); + + return lifetime; + }; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_requests_bar.h b/Telegram/SourceFiles/history/view/history_view_requests_bar.h new file mode 100644 index 000000000..772214df1 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_requests_bar.h @@ -0,0 +1,22 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace Ui { +struct RequestsBarContent; +} // namespace Ui + +namespace HistoryView { + +[[nodiscard]] rpl::producer RequestsBarContentByPeer( + not_null peer, + int userpicSize); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 4c94aebcf..7141d057c 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -909,3 +909,11 @@ whoReadChecksDisabled: icon{{ "chat/seen_checks", menuFgDisabled }}; whoReadPlayed: icon{{ "chat/seen_played", windowFg }}; whoReadPlayedOver: icon{{ "chat/seen_played", windowFgOver }}; whoReadPlayedDisabled: icon {{ "chat/seen_played", menuFgDisabled }}; + +historyRequestsUserpics: GroupCallUserpics { + size: 22px; + shift: 8px; + stroke: 4px; + align: align(left); +} +historyRequestsHeight: 33px; diff --git a/Telegram/SourceFiles/ui/chat/requests_bar.cpp b/Telegram/SourceFiles/ui/chat/requests_bar.cpp new file mode 100644 index 000000000..333df2f2f --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/requests_bar.cpp @@ -0,0 +1,220 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/requests_bar.h" + +#include "ui/chat/group_call_userpics.h" +#include "ui/widgets/shadow.h" +#include "lang/lang_keys.h" +#include "styles/style_chat.h" +#include "styles/style_calls.h" +#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget. +#include "styles/palette.h" + +#include + +namespace Ui { + +RequestsBar::RequestsBar( + not_null parent, + rpl::producer content) +: _wrap(parent, object_ptr(parent)) +, _inner(_wrap.entity()) +, _shadow(std::make_unique(_wrap.parentWidget())) +, _userpics(std::make_unique( + st::historyRequestsUserpics, + rpl::single(false), + [=] { _inner->update(); })) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + auto copy = std::move( + content + ) | rpl::start_spawning(_wrap.lifetime()); + + rpl::duplicate( + copy + ) | rpl::start_with_next([=](RequestsBarContent &&content) { + _content = content; + if (_content.count > 0) { + _text = (_content.isGroup + ? tr::lng_group_requests_pending + : tr::lng_group_requests_pending_channel)( + tr::now, + lt_count_decimal, + _content.count); + } + _userpics->update(_content.users, !_wrap.isHidden()); + _inner->update(); + }, lifetime()); + + std::move( + copy + ) | rpl::map([=](const RequestsBarContent &content) { + return !content.count; + }) | rpl::start_with_next_done([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); + }, lifetime()); + + _userpics->widthValue( + ) | rpl::start_with_next([=](int width) { + _userpicsWidth = width; + }, lifetime()); + + setupInner(); +} + +RequestsBar::~RequestsBar() = default; + +void RequestsBar::setupInner() { + _inner->resize(0, st::historyRequestsHeight); + _inner->paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + auto p = Painter(_inner); + paint(p); + }, _inner->lifetime()); + + // Clicks. + _inner->setCursor(style::cur_pointer); + _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _inner->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::map([] { + return rpl::empty_value(); + }) | rpl::start_to_stream(_barClicks, _inner->lifetime()); + + _wrap.geometryValue( + ) | rpl::start_with_next([=](QRect rect) { + updateShadowGeometry(rect); + updateControlsGeometry(rect); + }, _inner->lifetime()); +} + +void RequestsBar::paint(Painter &p) { + p.fillRect(_inner->rect(), st::historyComposeAreaBg); + + const auto userpicsSize = st::historyRequestsUserpics.size; + const auto userpicsTop = st::lineWidth + (st::historyRequestsHeight + - st::lineWidth + - userpicsSize) / 2; + const auto userpicsLeft = userpicsTop * 2; + const auto textTop = st::lineWidth + (st::historyRequestsHeight + - st::lineWidth + - st::msgServiceNameFont->height) / 2; + const auto width = _inner->width(); + const auto &font = st::defaultMessageBar.title.font; + p.setPen(st::defaultMessageBar.titleFg); + p.setFont(font); + + const auto textLeft = userpicsLeft + _userpicsWidth + userpicsLeft; + const auto available = width - textLeft - userpicsLeft; + p.drawTextLeft(textLeft, textTop, width, _text); + + // Skip shadow of the bar above. + _userpics->paint(p, userpicsLeft, userpicsTop, userpicsSize); +} + +void RequestsBar::updateControlsGeometry(QRect wrapGeometry) { + const auto hidden = _wrap.isHidden() || !wrapGeometry.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } +} + +void RequestsBar::setShadowGeometryPostprocess(Fn postprocess) { + _shadowGeometryPostprocess = std::move(postprocess); + updateShadowGeometry(_wrap.geometry()); +} + +void RequestsBar::updateShadowGeometry(QRect wrapGeometry) { + const auto regular = QRect( + wrapGeometry.x(), + wrapGeometry.y() + wrapGeometry.height(), + wrapGeometry.width(), + st::lineWidth); + _shadow->setGeometry(_shadowGeometryPostprocess + ? _shadowGeometryPostprocess(regular) + : regular); +} + +void RequestsBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void RequestsBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void RequestsBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void RequestsBar::finishAnimating() { + _wrap.finishAnimating(); +} + +void RequestsBar::move(int x, int y) { + _wrap.move(x, y); +} + +void RequestsBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); + _inner->resizeToWidth(width); +} + +int RequestsBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::historyRequestsHeight + : 0; +} + +rpl::producer RequestsBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> RequestsBar::barClicks() const { + return _barClicks.events(); +} + + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/requests_bar.h b/Telegram/SourceFiles/ui/chat/requests_bar.h new file mode 100644 index 000000000..28d5df2d8 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/requests_bar.h @@ -0,0 +1,77 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/wrap/slide_wrap.h" +#include "ui/effects/animations.h" +#include "base/object_ptr.h" +#include "base/timer.h" + +class Painter; + +namespace Ui { + +class PlainShadow; +struct GroupCallUser; +class GroupCallUserpics; + +struct RequestsBarContent { + std::vector users; + int count = 0; + bool isGroup = false; +}; + +class RequestsBar final { +public: + RequestsBar( + not_null parent, + rpl::producer content); + ~RequestsBar(); + + void show(); + void hide(); + void raise(); + void finishAnimating(); + + void setShadowGeometryPostprocess(Fn postprocess); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> barClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + using User = GroupCallUser; + + void updateShadowGeometry(QRect wrapGeometry); + void updateControlsGeometry(QRect wrapGeometry); + void updateUserpics(); + void setupInner(); + void paint(Painter &p); + + SlideWrap<> _wrap; + not_null _inner; + std::unique_ptr _shadow; + rpl::event_stream<> _barClicks; + Fn _shadowGeometryPostprocess; + bool _shouldBeShown = false; + bool _forceHidden = false; + + RequestsBarContent _content; + std::unique_ptr _userpics; + int _userpicsWidth = 0; + QString _text; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 2c457a20a..b9d71f459 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -162,6 +162,8 @@ PRIVATE ui/chat/message_bubble.h ui/chat/pinned_bar.cpp ui/chat/pinned_bar.h + ui/chat/requests_bar.cpp + ui/chat/requests_bar.h ui/controls/call_mute_button.cpp ui/controls/call_mute_button.h ui/controls/delete_message_context_action.cpp