From 19ae76d8de4a492a7cacb71ebca174a09006a849 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Apr 2024 13:12:57 +0400 Subject: [PATCH] Top peers context menu. --- .../SourceFiles/data/components/top_peers.cpp | 13 +++++ .../SourceFiles/data/components/top_peers.h | 1 + .../SourceFiles/dialogs/dialogs_widget.cpp | 35 +++++++----- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 +- .../dialogs/ui/dialogs_suggestions.cpp | 54 +++++++++++++++++++ .../dialogs/ui/dialogs_suggestions.h | 5 ++ .../dialogs/ui/top_peers_strip.cpp | 31 +++++++++-- .../SourceFiles/dialogs/ui/top_peers_strip.h | 3 ++ 8 files changed, 126 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/data/components/top_peers.cpp b/Telegram/SourceFiles/data/components/top_peers.cpp index 253c716f3..7d41f2399 100644 --- a/Telegram/SourceFiles/data/components/top_peers.cpp +++ b/Telegram/SourceFiles/data/components/top_peers.cpp @@ -56,6 +56,19 @@ rpl::producer<> TopPeers::updates() const { return _updates.events(); } +void TopPeers::remove(not_null peer) { + const auto i = ranges::find(_list, peer, &TopPeer::peer); + if (i != end(_list)) { + _list.erase(i); + _updates.fire({}); + } + + _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating( + MTP_topPeerCategoryCorrespondents(), + peer->input + )).send(); +} + void TopPeers::increment(not_null peer, TimeId date) { if (date <= _lastReceivedDate) { return; diff --git a/Telegram/SourceFiles/data/components/top_peers.h b/Telegram/SourceFiles/data/components/top_peers.h index b5107381c..67e69261a 100644 --- a/Telegram/SourceFiles/data/components/top_peers.h +++ b/Telegram/SourceFiles/data/components/top_peers.h @@ -22,6 +22,7 @@ public: [[nodiscard]] bool disabled() const; [[nodiscard]] rpl::producer<> updates() const; + void remove(not_null peer); void increment(not_null peer, TimeId date); void reload(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 82cec9097..231bba6b1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1027,7 +1027,7 @@ void Widget::updateControlsVisibility(bool fast) { _suggestions->show(); } updateStoriesVisibility(); - if ((_openedFolder || _openedForum) && _searchHasFocus.current()) { + if ((_openedFolder || _openedForum) && _searchHasFocus) { setInnerFocus(); } if (_updateTelegram) { @@ -1066,7 +1066,7 @@ void Widget::updateControlsVisibility(bool fast) { if (_hideChildListCanvas) { _hideChildListCanvas->show(); } - if (_childList && _searchHasFocus.current()) { + if (_childList && _searchHasFocus) { setInnerFocus(); } updateLockUnlockPosition(); @@ -1090,16 +1090,26 @@ void Widget::updateLockUnlockPosition() { void Widget::updateHasFocus(not_null focused) { const auto has = (focused == _search.data()); - if (_searchHasFocus.current() != has) { - _searchHasFocus = (focused == _search.data()); - updateStoriesVisibility(); - updateForceDisplayWide(); - updateSuggestions(anim::type::normal); + if (_searchHasFocus != has) { + _searchHasFocus = has; + const auto update = [=] { + updateStoriesVisibility(); + updateForceDisplayWide(); + updateSuggestions(anim::type::normal); + }; + if (has) { + update(); + } else { + // Search field may loose focus from the destructor of some + // widget, in that case we don't want to destroy _suggestions + // syncrhonously, because it may lead to a crash. + crl::on_main(this, update); + } } } void Widget::updateSuggestions(anim::type animated) { - const auto suggest = _searchHasFocus.current() + const auto suggest = _searchHasFocus && !_searchInChat && (_inner->state() == WidgetState::Default); if (!suggest && _suggestions) { @@ -1108,6 +1118,7 @@ void Widget::updateSuggestions(anim::type animated) { } else if (suggest && !_suggestions) { _suggestions = std::make_unique( this, + controller(), TopPeersContent(&session())); _suggestions->topPeerChosen( @@ -1563,7 +1574,7 @@ void Widget::updateStoriesVisibility() { || _openedForum || !_widthAnimationCache.isNull() || _childList - || _searchHasFocus.current() + || _searchHasFocus || !_search->getLastText().isEmpty() || _searchInChat || _stories->empty(); @@ -1681,7 +1692,7 @@ void Widget::slideFinished() { _shownProgressValue = 1.; updateControlsVisibility(true); if ((!_subsectionTopBar || !_subsectionTopBar->searchHasFocus()) - && !_searchHasFocus.current()) { + && !_searchHasFocus) { controller()->widget()->setInnerFocus(); } } @@ -2524,7 +2535,7 @@ void Widget::applySearchUpdate(bool force) { } void Widget::updateForceDisplayWide() { - controller()->setChatsForceDisplayWide(_searchHasFocus.current() + controller()->setChatsForceDisplayWide(_searchHasFocus || !_search->getLastText().isEmpty() || _searchInChat); } @@ -3258,7 +3269,7 @@ bool Widget::cancelSearch() { _inner->clearFilter(); clearSearchField(); applySearchUpdate(); - if (!_searchInChat && _searchHasFocus.current()) { + if (!_searchInChat && _searchHasFocus) { setFocus(); } return clearingQuery || clearingInChat; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index f3b54aa49..6ebd6ab50 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -300,7 +300,7 @@ private: std::vector _searchTags; rpl::lifetime _searchTagsLifetime; QString _lastSearchText; - rpl::variable _searchHasFocus = false; + bool _searchHasFocus = false; rpl::event_stream> _storiesContents; base::flat_map _storiesUserpicsViewsHidden; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 7e312c602..62e63d554 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -22,13 +22,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/dynamic_thumbnails.h" +#include "window/window_session_controller.h" #include "styles/style_dialogs.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" namespace Dialogs { +namespace { + +void FillTopPeerMenu( + not_null controller, + const ShowTopPeerMenuRequest &request, + Fn)> remove) { + const auto owner = &controller->session().data(); + const auto peer = owner->peer(PeerId(request.id)); + const auto &add = request.callback; + const auto group = peer->isMegagroup(); + const auto channel = peer->isChannel(); + + const auto showHistoryText = group + ? tr::lng_context_open_group(tr::now) + : channel + ? tr::lng_context_open_channel(tr::now) + : tr::lng_profile_send_message(tr::now); + add(showHistoryText, [=] { + controller->showPeerHistory(peer); + }, channel ? &st::menuIconChannel : &st::menuIconChatBubble); + + const auto viewProfileText = group + ? tr::lng_context_view_group(tr::now) + : channel + ? tr::lng_context_view_channel(tr::now) + : tr::lng_context_view_profile(tr::now); + add(viewProfileText, [=] { + controller->showPeerInfo(peer); + }, channel ? &st::menuIconInfo : &st::menuIconProfile); + + add({ + .text = tr::lng_recent_remove(tr::now), + .handler = [=] { remove(peer); }, + .icon = &st::menuIconDeleteAttention, + .isAttention = true, + }); +} + +} // namespace Suggestions::Suggestions( not_null parent, + not_null controller, rpl::producer topPeers) : RpWidget(parent) , _scroll(std::make_unique(this)) @@ -45,6 +87,15 @@ Suggestions::Suggestions( _topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) { _topPeerChosen.fire(PeerId(peerIdRaw)); }, _topPeers->lifetime()); + + _topPeers->showMenuRequests( + ) | rpl::start_with_next([=](const ShowTopPeerMenuRequest &request) { + const auto remove = crl::guard(this, [=](not_null peer) { + peer->session().topPeers().remove(peer); + _topPeers->removeLocally(peer->id.value); + }); + FillTopPeerMenu(controller, request, remove); + }, _topPeers->lifetime()); } Suggestions::~Suggestions() = default; @@ -111,6 +162,9 @@ rpl::producer TopPeersContent( const auto now = base::unixtime::now(); for (const auto &peer : top) { const auto user = peer->asUser(); + if (user->isInaccessible()) { + continue; + } const auto self = user && user->isSelf(); const auto history = peer->owner().history(peer); const auto badges = history->chatListBadgesState(); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index ada2d4be0..f40455f17 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -22,12 +22,17 @@ template class SlideWrap; } // namespace Ui +namespace Window { +class SessionController; +} // namespace Window + namespace Dialogs { class Suggestions final : public Ui::RpWidget { public: Suggestions( not_null parent, + not_null controller, rpl::producer topPeers); ~Suggestions(); diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp index 02ace8a02..03e7cd09e 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp @@ -212,13 +212,29 @@ auto TopPeersStrip::showMenuRequests() const return _showMenuRequests.events(); } +void TopPeersStrip::removeLocally(uint64 id) { + _removed.emplace(id); + const auto i = ranges::find(_entries, id, &Entry::id); + if (i == end(_entries)) { + return; + } else if (i->subscribed) { + i->userpic->subscribeToUpdates(nullptr); + } + _entries.erase(i); + updateScrollMax(); + if (_entries.empty()) { + _empty = true; + } + update(); +} + void TopPeersStrip::apply(const TopPeersList &list) { auto now = std::vector(); - if (list.entries.empty()) { - _empty = true; - } for (const auto &entry : list.entries) { + if (_removed.contains(entry.id)) { + continue; + } const auto i = ranges::find(_entries, entry.id, &Entry::id); if (i != end(_entries)) { now.push_back(base::take(*i)); @@ -227,6 +243,9 @@ void TopPeersStrip::apply(const TopPeersList &list) { } apply(now.back(), entry); } + if (now.empty()) { + _empty = true; + } for (auto &entry : _entries) { if (entry.subscribed) { entry.userpic->subscribeToUpdates(nullptr); @@ -234,6 +253,7 @@ void TopPeersStrip::apply(const TopPeersList &list) { } } _entries = std::move(now); + updateScrollMax(); unsubscribeUserpics(); if (!_entries.empty()) { _empty = false; @@ -289,9 +309,10 @@ void TopPeersStrip::paintEvent(QPaintEvent *e) { const auto single = st.photoLeft * 2 + st.photo; const auto from = std::min(_scrollLeft / single, int(_entries.size())); - const auto till = std::max( + const auto till = std::clamp( (_scrollLeft + width() + single - 1) / single + 1, - from); + from, + int(_entries.size())); auto x = -_scrollLeft + from * single; for (auto i = from; i != till; ++i) { diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h index 1662aa88b..5eac0ae03 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h @@ -49,6 +49,8 @@ public: [[nodiscard]] auto showMenuRequests() const -> rpl::producer; + void removeLocally(uint64 id); + private: struct Entry; @@ -73,6 +75,7 @@ private: std::vector _entries; rpl::variable _empty = true; + base::flat_set _removed; rpl::event_stream _clicks; rpl::event_stream _showMenuRequests;