From 702aa944dd7d6b5c7a3d03112d9e6d9e06351f9a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 17 Nov 2024 12:22:12 +0400 Subject: [PATCH] Move read/reacted list to a layer. --- Telegram/CMakeLists.txt | 2 + .../boxes/peers/edit_peer_requests_box.h | 2 - .../view/history_view_context_menu.cpp | 20 +- .../reactions/history_view_reactions_list.cpp | 231 ++++++++---- .../reactions/history_view_reactions_list.h | 27 +- .../reactions/history_view_reactions_tabs.h | 2 +- .../SourceFiles/info/info_content_widget.cpp | 40 +- .../SourceFiles/info/info_content_widget.h | 25 +- Telegram/SourceFiles/info/info_controller.cpp | 41 ++ Telegram/SourceFiles/info/info_controller.h | 25 +- Telegram/SourceFiles/info/info_memento.cpp | 20 + Telegram/SourceFiles/info/info_memento.h | 13 + .../polls/info_polls_results_inner_widget.cpp | 7 +- .../polls/info_polls_results_inner_widget.h | 7 +- .../info/polls/info_polls_results_widget.cpp | 6 +- .../info/polls/info_polls_results_widget.h | 6 +- .../info_reactions_list_widget.cpp | 352 ++++++++++++++++++ .../info_reactions_list_widget.h | 82 ++++ .../info_requests_list_widget.cpp | 8 - .../requests_list/info_requests_list_widget.h | 1 + 20 files changed, 788 insertions(+), 129 deletions(-) create mode 100644 Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp create mode 100644 Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d5d3b2cac..8ea6aa9ae 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -986,6 +986,8 @@ PRIVATE info/profile/info_profile_values.h info/profile/info_profile_widget.cpp info/profile/info_profile_widget.h + info/reactions_list/info_reactions_list_widget.cpp + info/reactions_list/info_reactions_list_widget.h info/requests_list/info_requests_list_widget.cpp info/requests_list/info_requests_list_widget.h info/saved/info_saved_sublists_widget.cpp diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h index 834ec5431..4db714bc3 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h @@ -59,8 +59,6 @@ private: UserData *offsetUser = nullptr; bool allLoaded = false; bool wasLoading = false; - rpl::lifetime lifetime; - }; static std::unique_ptr CreateSearchController( diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index ba765b3c7..0fb338a88 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1510,11 +1510,13 @@ void AddWhoReactedAction( strong->hideMenu(); } if (const auto item = controller->session().data().message(itemId)) { - controller->window().show(Reactions::FullListBox( - controller, - item, - {}, - whoReadIds)); + controller->showSection( + std::make_shared( + whoReadIds, + itemId, + HistoryView::Reactions::DefaultSelectedTab( + item, + whoReadIds))); } }; if (!menu->empty()) { @@ -1685,10 +1687,10 @@ void ShowWhoReactedMenu( }; const auto showAllChosen = [=, itemId = item->fullId()]{ if (const auto item = controller->session().data().message(itemId)) { - controller->window().show(Reactions::FullListBox( - controller, - item, - id)); + controller->showSection(std::make_shared( + nullptr, + itemId, + HistoryView::Reactions::DefaultSelectedTab(item, id))); } }; const auto owner = &controller->session().data(); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp index f925dcb90..209e73e3c 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp @@ -62,8 +62,8 @@ private: class Controller final : public PeerListController { public: Controller( - not_null window, - not_null item, + not_null window, + FullMsgId itemId, const ReactionId &selected, rpl::producer switches, std::shared_ptr whoReadIds); @@ -73,9 +73,26 @@ public: void rowClicked(not_null row) override; void loadMoreRows() override; + std::unique_ptr createRestoredRow( + not_null peer) override; + + std::unique_ptr saveState() const override; + void restoreState(std::unique_ptr state) override; + private: using AllEntry = std::pair, Data::ReactionId>; + struct SavedState : SavedStateBase { + ReactionId shownReaction; + base::flat_map, uint64> idsMap; + uint64 idsCounter = 0; + std::vector all; + QString allOffset; + std::vector> filtered; + QString filteredOffset; + bool wasLoading = false; + }; + void fillWhoRead(); void loadMore(const ReactionId &reaction); bool appendRow(not_null peer, ReactionId reaction); @@ -88,14 +105,15 @@ private: not_null peer, const ReactionId &reaction) const; - const not_null _window; - const not_null _item; + const not_null _window; + const not_null _peer; + const FullMsgId _itemId; const Ui::Text::CustomEmojiFactory _factory; + const std::shared_ptr _whoReadIds; + const std::vector> _whoRead; MTP::Sender _api; ReactionId _shownReaction; - std::shared_ptr _whoReadIds; - std::vector> _whoRead; mutable base::flat_map, uint64> _idsMap; mutable uint64 _idsCounter = 0; @@ -110,6 +128,22 @@ private: }; +[[nodiscard]] std::vector> ResolveWhoRead( + not_null window, + const std::shared_ptr &whoReadIds) { + if (!whoReadIds || whoReadIds->list.empty()) { + return {}; + } + auto result = std::vector>(); + auto &owner = window->session().data(); + for (const auto &peerWithDate : whoReadIds->list) { + if (const auto peer = owner.peerLoaded(peerWithDate.peer)) { + result.push_back(peer); + } + } + return result; +} + Row::Row( uint64 id, not_null peer, @@ -166,17 +200,19 @@ void Row::rightActionPaint( } Controller::Controller( - not_null window, - not_null item, + not_null window, + FullMsgId itemId, const ReactionId &selected, rpl::producer switches, std::shared_ptr whoReadIds) : _window(window) -, _item(item) +, _peer(window->session().data().peer(itemId.peer)) +, _itemId(itemId) , _factory(Data::ReactedMenuFactory(&window->session())) +, _whoReadIds(whoReadIds) +, _whoRead(ResolveWhoRead(window, _whoReadIds)) , _api(&window->session().mtp()) -, _shownReaction(selected) -, _whoReadIds(whoReadIds) { +, _shownReaction(selected) { std::move( switches ) | rpl::filter([=](const ReactionId &reaction) { @@ -248,14 +284,6 @@ uint64 Controller::id( } void Controller::fillWhoRead() { - if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) { - auto &owner = _window->session().data(); - for (const auto &peerWithDate : _whoReadIds->list) { - if (const auto peer = owner.peerLoaded(peerWithDate.peer)) { - _whoRead.push_back(peer); - } - } - } for (const auto &peer : _whoRead) { appendRow(peer, ReactionId()); } @@ -271,6 +299,60 @@ void Controller::loadMoreRows() { loadMore(_shownReaction); } +std::unique_ptr Controller::createRestoredRow( + not_null peer) { + if (_shownReaction.emoji() == u"read"_q) { + return createRow(peer, Data::ReactionId()); + } else if (_shownReaction.empty()) { + const auto i = ranges::find(_all, peer, &AllEntry::first); + const auto reaction = (i != end(_all)) ? i->second : _shownReaction; + return createRow(peer, reaction); + } + return createRow(peer, _shownReaction); +} + +std::unique_ptr Controller::saveState() const { + auto result = PeerListController::saveState(); + + auto my = std::make_unique(); + my->shownReaction = _shownReaction; + my->idsMap = _idsMap; + my->idsCounter = _idsCounter; + my->all = _all; + my->allOffset = _allOffset; + my->filtered = _filtered; + my->filteredOffset = _filteredOffset; + my->wasLoading = (_loadRequestId != 0); + result->controllerState = std::move(my); + return result; +} + +void Controller::restoreState(std::unique_ptr state) { + auto typeErasedState = state + ? state->controllerState.get() + : nullptr; + if (const auto my = dynamic_cast(typeErasedState)) { + if (const auto requestId = base::take(_loadRequestId)) { + _api.request(requestId).cancel(); + } + _shownReaction = my->shownReaction; + _idsMap = std::move(my->idsMap); + _idsCounter = my->idsCounter; + _all = std::move(my->all); + _allOffset = std::move(my->allOffset); + _filtered = std::move(my->filtered); + _filteredOffset = std::move(my->filteredOffset); + if (my->wasLoading) { + loadMoreRows(); + } + PeerListController::restoreState(std::move(state)); + if (delegate()->peerListFullRowsCount()) { + setDescriptionText(QString()); + delegate()->peerListRefreshRows(); + } + } +} + void Controller::loadMore(const ReactionId &reaction) { if (reaction.emoji() == u"read"_q) { loadMore(ReactionId()); @@ -290,8 +372,8 @@ void Controller::loadMore(const ReactionId &reaction) { | (reaction.empty() ? Flag(0) : Flag::f_reaction); _loadRequestId = _api.request(MTPmessages_GetMessageReactionsList( MTP_flags(flags), - _item->history()->peer->input, - MTP_int(_item->id), + _peer->input, + MTP_int(_itemId.msg), Data::ReactionToMTP(reaction), MTP_string(offset), MTP_int(offset.isEmpty() ? kPerPageFirst : kPerPage) @@ -332,7 +414,7 @@ void Controller::rowClicked(not_null row) { const auto window = _window; const auto peer = row->peer(); crl::on_main(window, [=] { - window->show(PrepareShortInfoBox(peer, window)); + window->showPeerInfo(peer); }); } @@ -353,72 +435,75 @@ std::unique_ptr Controller::createRow( _factory, Data::ReactionEntityData(reaction), [=](Row *row) { delegate()->peerListUpdateRow(row); }, - [=] { return _window->isGifPausedAtLeastFor( + [=] { return _window->parentController()->isGifPausedAtLeastFor( Window::GifPauseReason::Layer); }); } } // namespace -object_ptr FullListBox( - not_null window, +Data::ReactionId DefaultSelectedTab( + not_null item, + std::shared_ptr whoReadIds) { + return DefaultSelectedTab(item, {}, std::move(whoReadIds)); +} + +Data::ReactionId DefaultSelectedTab( not_null item, Data::ReactionId selected, std::shared_ptr whoReadIds) { - Expects(IsServerMsgId(item->id)); - - if (!ranges::contains( - item->reactions(), - selected, - &Data::MessageReaction::id)) { + const auto proj = &Data::MessageReaction::id; + if (!ranges::contains(item->reactions(), selected, proj)) { selected = {}; } - if (selected.empty() && whoReadIds && !whoReadIds->list.empty()) { - selected = Data::ReactionId{ u"read"_q }; - } - const auto tabRequests = std::make_shared< - rpl::event_stream>(); - const auto initBox = [=](not_null box) { - box->setNoContentMargin(true); + return (selected.empty() && whoReadIds && !whoReadIds->list.empty()) + ? Data::ReactionId{ u"read"_q } + : selected; +} - auto map = item->reactions(); - if (whoReadIds && !whoReadIds->list.empty()) { - map.push_back({ - .id = Data::ReactionId{ u"read"_q }, - .count = int(whoReadIds->list.size()), - }); - } - const auto tabs = CreateTabs( - box, - Data::ReactedMenuFactory(&item->history()->session()), - [=] { return window->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); }, - map, - selected, - whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted); - tabs->changes( - ) | rpl::start_to_stream(*tabRequests, box->lifetime()); - - box->widthValue( - ) | rpl::start_with_next([=](int width) { - tabs->resizeToWidth(width); - tabs->move(0, 0); - }, box->lifetime()); - tabs->heightValue( - ) | rpl::start_with_next([=](int height) { - box->setAddedTopScrollSkip(height); - }, box->lifetime()); - box->addButton(tr::lng_close(), [=] { - box->closeBox(); +not_null CreateReactionsTabs( + not_null parent, + not_null window, + FullMsgId itemId, + Data::ReactionId selected, + std::shared_ptr whoReadIds) { + const auto item = window->session().data().message(itemId); + auto map = item + ? item->reactions() + : std::vector(); + if (whoReadIds && !whoReadIds->list.empty()) { + map.push_back({ + .id = Data::ReactionId{ u"read"_q }, + .count = int(whoReadIds->list.size()), }); - }; - return Box( - std::make_unique( + } + return CreateTabs( + parent, + Data::ReactedMenuFactory(&window->session()), + [=] { return window->parentController()->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); }, + map, + selected, + whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted); +} + +PreparedFullList FullListController( + not_null window, + FullMsgId itemId, + Data::ReactionId selected, + std::shared_ptr whoReadIds) { + Expects(IsServerMsgId(itemId.msg)); + + const auto tab = std::make_shared< + rpl::event_stream>(); + return { + .controller = std::make_unique( window, - item, + itemId, selected, - tabRequests->events(), + tab->events(), whoReadIds), - initBox); + .switchTab = [=](Data::ReactionId id) { tab->fire_copy(id); }, + }; } } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h index b65c21065..be6dc59fe 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" class HistoryItem; +class PeerListController; namespace Data { struct ReactionId; @@ -21,6 +22,7 @@ struct WhoReadList; namespace Window { class SessionController; +class SessionNavigation; } // namespace Window namespace Ui { @@ -29,10 +31,31 @@ class BoxContent; namespace HistoryView::Reactions { -object_ptr FullListBox( - not_null window, +[[nodiscard]] Data::ReactionId DefaultSelectedTab( + not_null item, + std::shared_ptr whoReadIds); + +[[nodiscard]] Data::ReactionId DefaultSelectedTab( not_null item, Data::ReactionId selected, std::shared_ptr whoReadIds = nullptr); +struct Tabs; +[[nodiscard]] not_null CreateReactionsTabs( + not_null parent, + not_null window, + FullMsgId itemId, + Data::ReactionId selected, + std::shared_ptr whoReadIds); + +struct PreparedFullList { + std::unique_ptr controller; + Fn switchTab; +}; +[[nodiscard]] PreparedFullList FullListController( + not_null window, + FullMsgId itemId, + Data::ReactionId selected, + std::shared_ptr whoReadIds = nullptr); + } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h index 51d776004..8e3566552 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h @@ -27,7 +27,7 @@ struct Tabs { Fn()> heightValue; }; -not_null CreateTabs( +[[nodiscard]] not_null CreateTabs( not_null parent, Ui::Text::CustomEmojiFactory factory, Fn paused, diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index 942864710..15583240a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -7,27 +7,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_content_widget.h" -#include "window/window_session_controller.h" -#include "ui/widgets/scroll_area.h" -#include "ui/widgets/fields/input_field.h" -#include "ui/wrap/padding_wrap.h" -#include "ui/search_field_controller.h" -#include "ui/ui_utility.h" -#include "lang/lang_keys.h" -#include "info/profile/info_profile_widget.h" -#include "info/media/info_media_widget.h" -#include "info/common_groups/info_common_groups_widget.h" -#include "info/info_layer_widget.h" -#include "info/info_section_widget.h" -#include "info/info_controller.h" +#include "api/api_who_reacted.h" #include "boxes/peer_list_box.h" #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_session.h" #include "data/data_forum_topic.h" #include "data/data_forum.h" +#include "info/profile/info_profile_widget.h" +#include "info/media/info_media_widget.h" +#include "info/common_groups/info_common_groups_widget.h" +#include "info/info_layer_widget.h" +#include "info/info_section_widget.h" +#include "info/info_controller.h" +#include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/search_field_controller.h" +#include "ui/ui_utility.h" #include "window/window_peer_menu.h" +#include "window/window_session_controller.h" #include "styles/style_info.h" #include "styles/style_profile.h" #include "styles/style_layers.h" @@ -377,6 +378,8 @@ Key ContentMemento::key() const { return Stories::Tag{ peer, storiesTab() }; } else if (const auto peer = statisticsTag().peer) { return statisticsTag(); + } else if (const auto who = reactionsWhoReadIds()) { + return Key(who, _reactionsSelected, _pollReactionsContextId); } else { return Downloads::Tag(); } @@ -417,4 +420,15 @@ ContentMemento::ContentMemento(Statistics::Tag statistics) : _statisticsTag(statistics) { } +ContentMemento::ContentMemento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) +: _reactionsWhoReadIds(whoReadIds + ? whoReadIds + : std::make_shared()) +, _reactionsSelected(selected) +, _pollReactionsContextId(contextId) { +} + } // namespace Info diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index fd39d7384..f0c46da5a 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_wrap_widget.h" #include "info/statistics/info_statistics_tag.h" +namespace Api { +struct WhoReadList; +} // namespace Api + namespace Dialogs::Stories { struct Content; } // namespace Dialogs::Stories @@ -189,8 +193,12 @@ public: explicit ContentMemento(Statistics::Tag statistics); ContentMemento(not_null poll, FullMsgId contextId) : _poll(poll) - , _pollContextId(contextId) { + , _pollReactionsContextId(contextId) { } + ContentMemento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); virtual object_ptr createWidget( QWidget *parent, @@ -222,7 +230,16 @@ public: return _poll; } FullMsgId pollContextId() const { - return _pollContextId; + return _poll ? _pollReactionsContextId : FullMsgId(); + } + std::shared_ptr reactionsWhoReadIds() const { + return _reactionsWhoReadIds; + } + Data::ReactionId reactionsSelected() const { + return _reactionsSelected; + } + FullMsgId reactionsContextId() const { + return _reactionsWhoReadIds ? _pollReactionsContextId : FullMsgId(); } Key key() const; @@ -264,7 +281,9 @@ private: Stories::Tab _storiesTab = {}; Statistics::Tag _statisticsTag; PollData * const _poll = nullptr; - const FullMsgId _pollContextId; + std::shared_ptr _reactionsWhoReadIds; + Data::ReactionId _reactionsSelected; + const FullMsgId _pollReactionsContextId; int _scrollTop = 0; QString _searchFieldQuery; diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index 25ad70b2a..388f870fe 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -50,6 +50,13 @@ Key::Key(not_null poll, FullMsgId contextId) : _value(PollKey{ poll, contextId }) { } +Key::Key( + std::shared_ptr whoReadIds, + Data::ReactionId selected, + FullMsgId contextId) +: _value(ReactionsKey{ whoReadIds, selected, contextId }) { +} + PeerData *Key::peer() const { if (const auto peer = std::get_if>(&_value)) { return *peer; @@ -113,6 +120,27 @@ FullMsgId Key::pollContextId() const { return FullMsgId(); } +std::shared_ptr Key::reactionsWhoReadIds() const { + if (const auto data = std::get_if(&_value)) { + return data->whoReadIds; + } + return nullptr; +} + +Data::ReactionId Key::reactionsSelected() const { + if (const auto data = std::get_if(&_value)) { + return data->selected; + } + return Data::ReactionId(); +} + +FullMsgId Key::reactionsContextId() const { + if (const auto data = std::get_if(&_value)) { + return data->contextId; + } + return FullMsgId(); +} + rpl::producer AbstractController::mediaSource( SparseIdsMergedSlice::UniversalMsgId aroundId, int limitBefore, @@ -183,6 +211,19 @@ PollData *AbstractController::poll() const { return nullptr; } +auto AbstractController::reactionsWhoReadIds() const +-> std::shared_ptr { + return key().reactionsWhoReadIds(); +} + +Data::ReactionId AbstractController::reactionsSelected() const { + return key().reactionsSelected(); +} + +FullMsgId AbstractController::reactionsContextId() const { + return key().reactionsContextId(); +} + void AbstractController::showSection( std::shared_ptr memento, const Window::SectionShow ¶ms) { diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 6d7d2e92b..4fad8ca05 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -7,10 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_message_reaction_id.h" #include "data/data_search_controller.h" #include "info/statistics/info_statistics_tag.h" #include "window/window_session_controller.h" +namespace Api { +struct WhoReadList; +} // namespace Api + namespace Data { class ForumTopic; } // namespace Data @@ -67,6 +72,10 @@ public: Key(Stories::Tag stories); Key(Statistics::Tag statistics); Key(not_null poll, FullMsgId contextId); + Key( + std::shared_ptr whoReadIds, + Data::ReactionId selected, + FullMsgId contextId); PeerData *peer() const; Data::ForumTopic *topic() const; @@ -77,12 +86,20 @@ public: Statistics::Tag statisticsTag() const; PollData *poll() const; FullMsgId pollContextId() const; + std::shared_ptr reactionsWhoReadIds() const; + Data::ReactionId reactionsSelected() const; + FullMsgId reactionsContextId() const; private: struct PollKey { not_null poll; FullMsgId contextId; }; + struct ReactionsKey { + std::shared_ptr whoReadIds; + Data::ReactionId selected; + FullMsgId contextId; + }; std::variant< not_null, not_null, @@ -90,7 +107,8 @@ private: Downloads::Tag, Stories::Tag, Statistics::Tag, - PollKey> _value; + PollKey, + ReactionsKey> _value; }; @@ -107,6 +125,7 @@ public: CommonGroups, SimilarChannels, RequestsList, + ReactionsList, SavedSublists, PeerGifts, Members, @@ -187,6 +206,10 @@ public: [[nodiscard]] FullMsgId pollContextId() const { return key().pollContextId(); } + [[nodiscard]] auto reactionsWhoReadIds() const + -> std::shared_ptr; + [[nodiscard]] Data::ReactionId reactionsSelected() const; + [[nodiscard]] FullMsgId reactionsContextId() const; virtual void setSearchEnabledByContent(bool enabled) { } diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index e26bab419..7e38fb5d1 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/saved/info_saved_sublists_widget.h" #include "info/settings/info_settings_widget.h" #include "info/similar_channels/info_similar_channels_widget.h" +#include "info/reactions_list/info_reactions_list_widget.h" #include "info/requests_list/info_requests_list_widget.h" #include "info/peer_gifts/info_peer_gifts_widget.h" #include "info/polls/info_polls_results_widget.h" @@ -54,6 +55,13 @@ Memento::Memento(not_null poll, FullMsgId contextId) : Memento(DefaultStack(poll, contextId)) { } +Memento::Memento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) +: Memento(DefaultStack(std::move(whoReadIds), contextId, selected)) { +} + Memento::Memento(std::vector> stack) : _stack(std::move(stack)) { auto topics = base::flat_set>(); @@ -113,6 +121,18 @@ std::vector> Memento::DefaultStack( return result; } +std::vector> Memento::DefaultStack( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) { + auto result = std::vector>(); + result.push_back(std::make_shared( + std::move(whoReadIds), + contextId, + selected)); + return result; +} + Section Memento::DefaultSection(not_null peer) { if (peer->savedSublistsInfo()) { return Section(Section::Type::SavedSublists); diff --git a/Telegram/SourceFiles/info/info_memento.h b/Telegram/SourceFiles/info/info_memento.h index 3d18296cb..dc50f2f89 100644 --- a/Telegram/SourceFiles/info/info_memento.h +++ b/Telegram/SourceFiles/info/info_memento.h @@ -13,12 +13,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "base/object_ptr.h" +namespace Api { +struct WhoReadList; +} // namespace Api + namespace Storage { enum class SharedMediaType : signed char; } // namespace Storage namespace Data { class ForumTopic; +struct ReactionId; } // namespace Data namespace Ui { @@ -46,6 +51,10 @@ public: Memento(not_null topic, Section section); Memento(Settings::Tag settings, Section section); Memento(not_null poll, FullMsgId contextId); + Memento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); explicit Memento(std::vector> stack); object_ptr createWidget( @@ -91,6 +100,10 @@ private: static std::vector> DefaultStack( not_null poll, FullMsgId contextId); + static std::vector> DefaultStack( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); static std::shared_ptr DefaultContent( not_null peer, diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp index f54e09ea5..d2b8b31b9 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp @@ -24,8 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_info.h" -namespace Info { -namespace Polls { +namespace Info::Polls { namespace { constexpr auto kFirstPage = 15; @@ -659,6 +658,4 @@ auto InnerWidget::showPeerInfoRequests() const return _showPeerInfoRequests.events(); } -} // namespace Polls -} // namespace Info - +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h index f2fbf05af..c84e10ef9 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h @@ -16,10 +16,10 @@ class VerticalLayout; } // namespace Ui namespace Info { - class Controller; +} // namespace Info -namespace Polls { +namespace Info::Polls { class Memento; class ListController; @@ -70,5 +70,4 @@ private: }; -} // namespace Polls -} // namespace Info +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp index 02d3fe0a2..056eb35cf 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp @@ -13,8 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "ui/ui_utility.h" -namespace Info { -namespace Polls { +namespace Info::Polls { Memento::Memento(not_null poll, FullMsgId contextId) : ContentMemento(poll, contextId) { @@ -113,5 +112,4 @@ void Widget::restoreState(not_null memento) { scrollTopRestore(memento->scrollTop()); } -} // namespace Polls -} // namespace Info +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_widget.h b/Telegram/SourceFiles/info/polls/info_polls_results_widget.h index 6766da4f5..a3c4ac752 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_widget.h +++ b/Telegram/SourceFiles/info/polls/info_polls_results_widget.h @@ -12,8 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL struct PeerListState; -namespace Info { -namespace Polls { +namespace Info::Polls { class InnerWidget; @@ -68,5 +67,4 @@ private: }; -} // namespace Polls -} // namespace Info +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp new file mode 100644 index 000000000..a94f2f2a7 --- /dev/null +++ b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp @@ -0,0 +1,352 @@ +/* +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 "info/reactions_list/info_reactions_list_widget.h" + +#include "api/api_who_reacted.h" +#include "boxes/peer_list_box.h" +#include "data/data_channel.h" +#include "history/view/reactions/history_view_reactions_list.h" +#include "history/view/reactions/history_view_reactions_tabs.h" +#include "info/info_controller.h" +#include "ui/controls/who_reacted_context_action.h" +#include "ui/widgets/scroll_area.h" +#include "ui/search_field_controller.h" +#include "ui/ui_utility.h" +#include "lang/lang_keys.h" +#include "styles/style_info.h" + +namespace Info::ReactionsList { +namespace { + +} // namespace + +class InnerWidget final + : public Ui::RpWidget + , private PeerListContentDelegate { +public: + InnerWidget( + QWidget *parent, + not_null controller, + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); + + [[nodiscard]] std::shared_ptr whoReadIds() const; + [[nodiscard]] FullMsgId contextId() const; + [[nodiscard]] Data::ReactionId selected() const; + + rpl::producer scrollToRequests() const; + + int desiredHeight() const; + + void saveState(not_null memento); + void restoreState(not_null memento); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + +private: + using ListWidget = PeerListContent; + + // PeerListContentDelegate interface + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowChecked(not_null row) override; + int peerListSelectedRowsCount() override; + void peerListScrollToTop() override; + void peerListAddSelectedPeerInBunch(not_null peer) override; + void peerListAddSelectedRowInBunch(not_null row) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetDescription(object_ptr description) override; + std::shared_ptr peerListUiShow() override; + + object_ptr setupList( + RpWidget *parent, + not_null controller); + + const std::shared_ptr _show; + not_null _controller; + Data::ReactionId _selected; + not_null _tabs; + rpl::variable _tabsHeight; + HistoryView::Reactions::PreparedFullList _full; + object_ptr _list; + + rpl::event_stream _scrollToRequests; +}; + +InnerWidget::InnerWidget( + QWidget *parent, + not_null controller, + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) +: RpWidget(parent) +, _show(controller->uiShow()) +, _controller(controller) +, _selected(selected) +, _tabs(HistoryView::Reactions::CreateReactionsTabs( + this, + controller, + controller->reactionsContextId(), + _selected, + controller->reactionsWhoReadIds())) +, _tabsHeight(_tabs->heightValue()) +, _full(HistoryView::Reactions::FullListController( + controller, + controller->reactionsContextId(), + _selected, + controller->reactionsWhoReadIds())) +, _list(setupList(this, _full.controller.get())) { + setContent(_list.data()); + _full.controller->setDelegate(static_cast(this)); + _tabs->changes( + ) | rpl::start_with_next([=](Data::ReactionId reaction) { + _selected = reaction; + _full.switchTab(reaction); + }, _list->lifetime()); +} + +std::shared_ptr InnerWidget::whoReadIds() const { + return _controller->reactionsWhoReadIds(); +} + +FullMsgId InnerWidget::contextId() const { + return _controller->reactionsContextId(); +} + +Data::ReactionId InnerWidget::selected() const { + return _selected; +} + +void InnerWidget::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + setChildVisibleTopBottom(_list, visibleTop, visibleBottom); +} + +void InnerWidget::saveState(not_null memento) { + memento->setListState(_full.controller->saveState()); +} + +void InnerWidget::restoreState(not_null memento) { + _full.controller->restoreState(memento->listState()); +} + +rpl::producer InnerWidget::scrollToRequests() const { + return _scrollToRequests.events(); +} + +int InnerWidget::desiredHeight() const { + auto desired = 0; + desired += _list->fullRowsCount() * st::infoMembersList.item.height; + return qMax(height(), desired); +} + +object_ptr InnerWidget::setupList( + RpWidget *parent, + not_null controller) { + auto result = object_ptr(parent, controller); + const auto raw = result.data(); + + raw->scrollToRequests( + ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { + const auto skip = _tabsHeight.current() + + st::infoCommonGroupsMargin.top(); + auto addmin = (request.ymin < 0) ? 0 : skip; + auto addmax = (request.ymax < 0) ? 0 : skip; + _scrollToRequests.fire({ + request.ymin + addmin, + request.ymax + addmax }); + }, raw->lifetime()); + + _tabs->move(0, 0); + _tabsHeight.value() | rpl::start_with_next([=](int tabs) { + raw->moveToLeft(0, tabs + st::infoCommonGroupsMargin.top()); + }, raw->lifetime()); + + parent->widthValue( + ) | rpl::start_with_next([=](int newWidth) { + _tabs->resizeToWidth(newWidth); + raw->resizeToWidth(newWidth); + }, raw->lifetime()); + + rpl::combine( + _tabsHeight.value(), + raw->heightValue() + ) | rpl::start_with_next([parent](int tabsHeight, int listHeight) { + const auto newHeight = tabsHeight + + st::infoCommonGroupsMargin.top() + + listHeight + + st::infoCommonGroupsMargin.bottom(); + parent->resize(parent->width(), newHeight); + }, result->lifetime()); + + return result; +} + +void InnerWidget::peerListSetTitle(rpl::producer title) { +} + +void InnerWidget::peerListSetAdditionalTitle(rpl::producer title) { +} + +bool InnerWidget::peerListIsRowChecked(not_null row) { + return false; +} + +int InnerWidget::peerListSelectedRowsCount() { + return 0; +} + +void InnerWidget::peerListScrollToTop() { + _scrollToRequests.fire({ -1, -1 }); +} + +void InnerWidget::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void InnerWidget::peerListAddSelectedRowInBunch(not_null row) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void InnerWidget::peerListFinishSelectedRowsBunch() { +} + +void InnerWidget::peerListSetDescription( + object_ptr description) { + description.destroy(); +} + +std::shared_ptr InnerWidget::peerListUiShow() { + return _show; +} + +Memento::Memento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) +: ContentMemento(std::move(whoReadIds), contextId, selected) { +} + +Section Memento::section() const { + return Section(Section::Type::ReactionsList); +} + +std::shared_ptr Memento::whoReadIds() const { + return reactionsWhoReadIds(); +} + +FullMsgId Memento::contextId() const { + return reactionsContextId(); +} + +Data::ReactionId Memento::selected() const { + return reactionsSelected(); +} + +object_ptr Memento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr( + parent, + controller, + whoReadIds(), + contextId(), + selected()); + result->setInternalState(geometry, this); + return result; +} + +void Memento::setListState(std::unique_ptr state) { + _listState = std::move(state); +} + +std::unique_ptr Memento::listState() { + return std::move(_listState); +} + +Memento::~Memento() = default; + +Widget::Widget( + QWidget *parent, + not_null controller, + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected) +: ContentWidget(parent, controller) { + _inner = setInnerWidget(object_ptr( + this, + controller, + std::move(whoReadIds), + contextId, + selected)); +} + +rpl::producer Widget::title() { + const auto ids = whoReadIds(); + const auto count = ids ? int(ids->list.size()) : 0; + return !count + ? tr::lng_manage_peer_reactions() + : (ids->type == Ui::WhoReadType::Seen) + ? tr::lng_context_seen_text(lt_count, rpl::single(1. * count)) + : (ids->type == Ui::WhoReadType::Listened) + ? tr::lng_context_seen_listened(lt_count, rpl::single(1. * count)) + : (ids->type == Ui::WhoReadType::Watched) + ? tr::lng_context_seen_watched(lt_count, rpl::single(1. * count)) + : tr::lng_manage_peer_reactions(); +} + +std::shared_ptr Widget::whoReadIds() const { + return _inner->whoReadIds(); +} + +FullMsgId Widget::contextId() const { + return _inner->contextId(); +} + +Data::ReactionId Widget::selected() const { + return _inner->selected(); +} + +bool Widget::showInternal(not_null memento) { + return false; +} + +void Widget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr Widget::doCreateMemento() { + auto result = std::make_shared( + whoReadIds(), + contextId(), + selected()); + saveState(result.get()); + return result; +} + +void Widget::saveState(not_null memento) { + memento->setScrollTop(scrollTopSave()); + _inner->saveState(memento); +} + +void Widget::restoreState(not_null memento) { + _inner->restoreState(memento); + scrollTopRestore(memento->scrollTop()); +} + +} // namespace Info::ReactionsList diff --git a/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h new file mode 100644 index 000000000..a713c6e2b --- /dev/null +++ b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h @@ -0,0 +1,82 @@ +/* +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 "info/info_content_widget.h" + +class ChannelData; +struct PeerListState; + +namespace Api { +struct WhoReadList; +} // namespace Api + +namespace Info::ReactionsList { + +class InnerWidget; + +class Memento final : public ContentMemento { +public: + Memento( + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + Section section() const override; + + [[nodiscard]] std::shared_ptr whoReadIds() const; + [[nodiscard]] FullMsgId contextId() const; + [[nodiscard]] Data::ReactionId selected() const; + + void setListState(std::unique_ptr state); + std::unique_ptr listState(); + + ~Memento(); + +private: + std::unique_ptr _listState; + +}; + +class Widget final : public ContentWidget { +public: + Widget( + QWidget *parent, + not_null controller, + std::shared_ptr whoReadIds, + FullMsgId contextId, + Data::ReactionId selected); + + [[nodiscard]] std::shared_ptr whoReadIds() const; + [[nodiscard]] FullMsgId contextId() const; + [[nodiscard]] Data::ReactionId selected() const; + + bool showInternal( + not_null memento) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + rpl::producer title() override; + +private: + void saveState(not_null memento); + void restoreState(not_null memento); + + std::shared_ptr doCreateMemento() override; + + InnerWidget *_inner = nullptr; +}; + +} // namespace Info::ReactionsList diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp index ba68ea345..70f638e1c 100644 --- a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp +++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp @@ -7,22 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/requests_list/info_requests_list_widget.h" -#include "api/api_chat_participants.h" -#include "apiwrap.h" #include "boxes/peers/edit_peer_requests_box.h" -#include "boxes/peer_list_box.h" #include "data/data_channel.h" -#include "data/data_peer_values.h" -#include "data/data_session.h" #include "info/info_controller.h" -#include "main/main_session.h" #include "ui/widgets/scroll_area.h" #include "ui/search_field_controller.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "styles/style_info.h" -#include "styles/style_widgets.h" -#include "boxes/peers/edit_peer_requests_box.h" namespace Info::RequestsList { namespace { diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h index d61fc938e..32e04ac5d 100644 --- a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h +++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h @@ -36,6 +36,7 @@ public: private: std::unique_ptr _listState; + }; class Widget final : public ContentWidget {