From 2a6ff9203b771301276eae52b7390e9e688b2d97 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 Apr 2024 12:25:37 +0400 Subject: [PATCH] Implement basic recent search results. --- Telegram/Resources/animations/search.tgs | Bin 0 -> 13483 bytes Telegram/Resources/langs/lang.strings | 2 + .../Resources/qrc/telegram/animations.qrc | 1 + Telegram/SourceFiles/boxes/peer_list_box.cpp | 9 +- Telegram/SourceFiles/boxes/peer_list_box.h | 2 + .../SourceFiles/data/components/top_peers.cpp | 25 +- .../SourceFiles/data/components/top_peers.h | 1 + Telegram/SourceFiles/dialogs/dialogs.style | 24 ++ .../SourceFiles/dialogs/dialogs_widget.cpp | 17 +- .../dialogs/ui/dialogs_suggestions.cpp | 377 ++++++++++++++++-- .../dialogs/ui/dialogs_suggestions.h | 29 +- .../dialogs/ui/top_peers_strip.cpp | 21 +- Telegram/SourceFiles/main/main_session.cpp | 1 + .../SourceFiles/ui/unread_badge_paint.cpp | 4 +- 14 files changed, 456 insertions(+), 57 deletions(-) create mode 100644 Telegram/Resources/animations/search.tgs diff --git a/Telegram/Resources/animations/search.tgs b/Telegram/Resources/animations/search.tgs new file mode 100644 index 0000000000000000000000000000000000000000..db635fb57e75535b8902407baef878325790f0fd GIT binary patch literal 13483 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 K8j?pqfc_WVBv<$V literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 606f8bc2a..6215ec9fe 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5094,9 +5094,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_recent_clear" = "Clear"; "lng_recent_clear_sure" = "Do you want to clear your search history?"; "lng_recent_remove" = "Remove from Recent"; +"lng_recent_clear_all" = "Clear all"; "lng_recent_hide_top" = "Remove all & Disable"; "lng_recent_hide_sure" = "Are you sure you want to clear and disable frequent contacts list?\n\nYou can always turn this feature back on in Settings > Privacy > Suggest Frequent Contacts."; "lng_recent_hide_button" = "Hide"; +"lng_recent_none" = "Recent search results\nwill appear here."; // Wnd specific diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 5b72c461f..3322b1387 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -24,5 +24,6 @@ ../../animations/chat_link.tgs ../../animations/collectible_username.tgs ../../animations/collectible_phone.tgs + ../../animations/search.tgs diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index bda06d535..777773f0b 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -734,6 +734,10 @@ auto PeerListRow::generateNameWords() const return peer()->nameWords(); } +QPoint PeerListRow::computeNamePosition( + const style::PeerListItem &st) const { + return st.namePosition; +} void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { @@ -1745,8 +1749,9 @@ crl::time PeerListContent::paintRow( ? QMargins() : row->rightActionMargins(); const auto &name = row->name(); - const auto namex = _st.item.namePosition.x(); - const auto namey = _st.item.namePosition.y(); + const auto namePosition = row->computeNamePosition(_st.item); + const auto namex = namePosition.x(); + const auto namey = namePosition.y(); auto namew = outerWidth - namex - skipRight; if (!rightActionSize.isEmpty() && (namey < rightActionMargins.top() + rightActionSize.height()) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index ea7f73b67..010c63598 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -100,6 +100,8 @@ public: -> const base::flat_set &; [[nodiscard]] virtual auto generateNameWords() const -> const base::flat_set &; + [[nodiscard]] virtual QPoint computeNamePosition( + const style::PeerListItem &st) const; virtual void preloadUserpic(); diff --git a/Telegram/SourceFiles/data/components/top_peers.cpp b/Telegram/SourceFiles/data/components/top_peers.cpp index 03f4416f8..318a739b5 100644 --- a/Telegram/SourceFiles/data/components/top_peers.cpp +++ b/Telegram/SourceFiles/data/components/top_peers.cpp @@ -80,15 +80,13 @@ void TopPeers::remove(not_null peer) { const auto i = ranges::find(_list, peer, &TopPeer::peer); if (i != end(_list)) { _list.erase(i); - _updates.fire({}); + updated(); } _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating( MTP_topPeerCategoryCorrespondents(), peer->input )).send(); - - _session->local().writeSearchSuggestionsDelayed(); } void TopPeers::increment(not_null peer, TimeId date) { @@ -117,10 +115,10 @@ void TopPeers::increment(not_null peer, TimeId date) { } } if (changed) { - _updates.fire({}); + updated(); + } else { + _session->local().writeSearchSuggestionsDelayed(); } - - _session->local().writeSearchSuggestionsDelayed(); } } @@ -140,11 +138,11 @@ void TopPeers::toggleDisabled(bool disabled) { if (!_disabled || !_list.empty()) { _disabled = true; _list.clear(); - _updates.fire({}); + updated(); } } else if (_disabled) { _disabled = false; - _updates.fire({}); + updated(); } _session->api().request(MTPcontacts_ToggleTopPeers( @@ -154,8 +152,6 @@ void TopPeers::toggleDisabled(bool disabled) { request(); } }).send(); - - _session->local().writeSearchSuggestionsDelayed(); } void TopPeers::request() { @@ -194,12 +190,12 @@ void TopPeers::request() { LOG(("API Error: Unexpected top peer category.")); }); } - _updates.fire({}); + updated(); }, [&](const MTPDcontacts_topPeersDisabled &) { if (!_disabled) { _list.clear(); _disabled = true; - _updates.fire({}); + updated(); } }, [](const MTPDcontacts_topPeersNotModified &) { }); @@ -218,6 +214,11 @@ uint64 TopPeers::countHash() const { return HashFinalize(hash); } +void TopPeers::updated() { + _updates.fire({}); + _session->local().writeSearchSuggestionsDelayed(); +} + QByteArray TopPeers::serialize() const { _session->local().readSearchSuggestions(); diff --git a/Telegram/SourceFiles/data/components/top_peers.h b/Telegram/SourceFiles/data/components/top_peers.h index 051964066..5f1250b53 100644 --- a/Telegram/SourceFiles/data/components/top_peers.h +++ b/Telegram/SourceFiles/data/components/top_peers.h @@ -38,6 +38,7 @@ private: void request(); [[nodiscard]] uint64 countHash() const; + void updated(); const not_null _session; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 3f4fffe0a..7796afb4e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -600,6 +600,30 @@ topPeers: DialogsStories(dialogsStoriesFull) { topPeersRadius: 4px; topPeersMargin: margins(3px, 3px, 3px, 4px); +recentPeersEmptySize: 100px; +recentPeersEmptyMargin: margins(10px, 10px, 10px, 10px); +recentPeersEmptySkip: 10px; +recentPeersItem: PeerListItem(defaultPeerListItem) { + height: 56px; + photoSize: 42px; + photoPosition: point(10px, 7px); + namePosition: point(64px, 9px); + statusPosition: point(64px, 30px); + button: OutlineButton(defaultPeerListButton) { + textBg: contactsBg; + textBgOver: contactsBgOver; + ripple: defaultRippleAnimation; + } + statusFg: contactsStatusFg; + statusFgOver: contactsStatusFgOver; + statusFgActive: contactsStatusFgOnline; +} +recentPeersList: PeerList(defaultPeerList) { + padding: margins(0px, 4px, 0px, 4px); + item: recentPeersItem; +} +recentPeersSpecialNamePosition: point(64px, 19px); + dialogsStoriesList: DialogsStoriesList { small: dialogsStories; full: dialogsStoriesFull; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 3e2b7a9ed..e5746bc5c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" #include "storage/storage_domain.h" +#include "data/components/recent_peers.h" #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -514,6 +515,12 @@ Widget::Widget( void Widget::chosenRow(const ChosenRow &row) { storiesToggleExplicitExpand(false); + if (!_search->getLastText().isEmpty()) { + if (const auto history = row.key.history()) { + session().recentPeers().bump(history->peer); + } + } + const auto history = row.key.history(); const auto topicJump = history ? history->peer->forumTopicFor(row.message.fullId.msg) @@ -1120,11 +1127,13 @@ void Widget::updateSuggestions(anim::type animated) { _suggestions = std::make_unique( this, controller(), - TopPeersContent(&session())); + TopPeersContent(&session()), + RecentPeersContent(&session())); - _suggestions->topPeerChosen( - ) | rpl::start_with_next([=](PeerId id) { - const auto peer = session().data().peer(id); + rpl::merge( + _suggestions->topPeerChosen(), + _suggestions->recentPeerChosen() + ) | rpl::start_with_next([=](not_null peer) { if (base::IsCtrlPressed()) { controller()->showInNewWindow(peer); } else { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 24c453ad3..6a7dde299 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_suggestions.h" #include "base/unixtime.h" +#include "boxes/peer_list_box.h" +#include "data/components/recent_peers.h" #include "data/components/top_peers.h" #include "data/data_changes.h" #include "data/data_peer_values.h" @@ -15,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "history/history.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "main/main_session.h" +#include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/elastic_scroll.h" @@ -24,16 +28,83 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/delayed_activation.h" #include "ui/dynamic_thumbnails.h" +#include "ui/painter.h" +#include "ui/unread_badge_paint.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" +#include "styles/style_window.h" namespace Dialogs { namespace { +class RecentRow final : public PeerListRow { +public: + explicit RecentRow(not_null peer); + + bool refreshBadge(); + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + bool rightActionDisabled() const override; + + QPoint computeNamePosition(const style::PeerListItem &st) const override; + +private: + const not_null _history; + QString _badgeString; + QSize _badgeSize; + uint32 _counter : 30 = 0; + uint32 _unread : 1 = 0; + uint32 _muted : 1 = 0; + +}; + +class RecentsController final : public PeerListController { +public: + RecentsController( + not_null session, + RecentPeersList list); + + [[nodiscard]] rpl::producer count() const { + return _count.value(); + } + [[nodiscard]] rpl::producer> chosen() const { + return _chosen.events(); + } + + void prepare() override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + Main::Session &session() const override; + + QString savedMessagesChatStatus() const override; + +private: + void setupDivider(); + void subscribeToEvents(); + + const not_null _session; + RecentPeersList _recent; + rpl::variable _count; + base::unique_qptr _menu; + rpl::event_stream> _chosen; + rpl::lifetime _lifetime; + +}; + void FillTopPeerMenu( not_null controller, const ShowTopPeerMenuRequest &request, @@ -92,12 +163,210 @@ void FillTopPeerMenu( }); } +RecentRow::RecentRow(not_null peer) +: PeerListRow(peer) +, _history(peer->owner().history(peer)) { + if (peer->isSelf() || peer->isRepliesChat()) { + setCustomStatus(u" "_q); + } + refreshBadge(); +} + +bool RecentRow::refreshBadge() { + if (_history->peer->isSelf()) { + return false; + } + auto result = false; + const auto muted = _history->muted() ? 1 : 0; + if (_muted != muted) { + _muted = muted; + if (_counter || _unread) { + result = true; + } + } + const auto badges = _history->chatListBadgesState(); + const auto unread = badges.unread ? 1 : 0; + if (_counter != badges.unreadCounter || _unread != unread) { + _counter = badges.unreadCounter; + _unread = unread; + result = true; + + _badgeString = !_counter + ? (_unread ? u" "_q : QString()) + : (_counter < 1000) + ? QString::number(_counter) + : (QString::number(_counter / 1000) + 'K'); + if (_badgeString.isEmpty()) { + _badgeSize = QSize(); + } else { + auto st = Ui::UnreadBadgeStyle(); + const auto unreadRectHeight = st.size; + const auto unreadWidth = st.font->width(_badgeString); + _badgeSize = QSize( + std::max(unreadWidth + 2 * st.padding, unreadRectHeight), + unreadRectHeight); + } + } + return result; +} + +QSize RecentRow::rightActionSize() const { + return _badgeSize; +} + +QMargins RecentRow::rightActionMargins() const { + if (_badgeSize.isEmpty()) { + return {}; + } + const auto x = st::recentPeersItem.photoPosition.x(); + const auto y = (st::recentPeersItem.height - _badgeSize.height()) / 2; + return QMargins(x, y, x, y); +} + +void RecentRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (!_counter && !_unread) { + return; + } else if (_badgeString.isEmpty()) { + _badgeString = !_counter + ? u" "_q + : (_counter < 1000) + ? QString::number(_counter) + : (QString::number(_counter / 1000) + 'K'); + } + auto st = Ui::UnreadBadgeStyle(); + st.selected = selected; + st.muted = _muted; + const auto &counter = _badgeString; + PaintUnreadBadge(p, counter, x + _badgeSize.width(), y, st); +} + +bool RecentRow::rightActionDisabled() const { + return true; +} + +QPoint RecentRow::computeNamePosition(const style::PeerListItem &st) const { + return (peer()->isSelf() || peer()->isRepliesChat()) + ? st::recentPeersSpecialNamePosition + : st.namePosition; +} + +RecentsController::RecentsController( + not_null session, + RecentPeersList list) +: _session(session) +, _recent(std::move(list)) { +} + +void RecentsController::prepare() { + setupDivider(); + + for (const auto &peer : _recent.list) { + delegate()->peerListAppendRow(std::make_unique(peer)); + } + delegate()->peerListRefreshRows(); + _count = _recent.list.size(); + + subscribeToEvents(); +} + +void RecentsController::rowClicked(not_null row) { + _chosen.fire(row->peer()); +} + +base::unique_qptr RecentsController::rowContextMenu( + QWidget *parent, + not_null row) { + return nullptr; +} + +Main::Session &RecentsController::session() const { + return *_session; +} + +QString RecentsController::savedMessagesChatStatus() const { + return tr::lng_saved_forward_here(tr::now); +} + +void RecentsController::setupDivider() { + auto result = object_ptr( + (QWidget*)nullptr, + st::searchedBarHeight); + const auto raw = result.data(); + const auto label = Ui::CreateChild( + raw, + tr::lng_recent_title(), + st::searchedBarLabel); + const auto clear = Ui::CreateChild( + raw, + tr::lng_recent_clear(tr::now), + st::searchedBarLink); + rpl::combine( + raw->sizeValue(), + clear->widthValue() + ) | rpl::start_with_next([=](QSize size, int width) { + const auto x = st::searchedBarPosition.x(); + const auto y = st::searchedBarPosition.y(); + clear->moveToRight(0, 0, size.width()); + label->resizeToWidth(size.width() - x - width); + label->moveToLeft(x, y, size.width()); + }, raw->lifetime()); + raw->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(raw).fillRect(clip, st::searchedBarBg); + }, raw->lifetime()); + + delegate()->peerListSetAboveWidget(std::move(result)); +} + +void RecentsController::subscribeToEvents() { + using Flag = Data::PeerUpdate::Flag; + _session->changes().peerUpdates( + Flag::Notifications + | Flag::OnlineStatus + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + const auto peer = update.peer; + if (peer->isSelf()) { + return; + } + auto refreshed = false; + const auto row = delegate()->peerListFindRow(update.peer->id.value); + if (!row) { + return; + } else if (update.flags & Flag::Notifications) { + refreshed = static_cast(row)->refreshBadge(); + } + if (!peer->isRepliesChat() && (update.flags & Flag::OnlineStatus)) { + row->clearCustomStatus(); + refreshed = true; + } + if (refreshed) { + delegate()->peerListUpdateRow(row); + } + }, _lifetime); + + _session->data().unreadBadgeChanges( + ) | rpl::start_with_next([=] { + for (auto i = 0; i != _count.current(); ++i) { + const auto row = delegate()->peerListRowAt(i); + if (static_cast(row.get())->refreshBadge()) { + delegate()->peerListUpdateRow(row); + } + } + }, _lifetime); +} + } // namespace Suggestions::Suggestions( not_null parent, not_null controller, - rpl::producer topPeers) + rpl::producer topPeers, + RecentPeersList recentPeers) : RpWidget(parent) , _scroll(std::make_unique(this)) , _content(_scroll->setOwnedWidget(object_ptr(this))) @@ -105,13 +374,22 @@ Suggestions::Suggestions( this, object_ptr(this, std::move(topPeers))))) , _topPeers(_topPeersWrap->entity()) -, _divider(_content->add(setupDivider())) { +, _recentPeers( + _content->add( + setupRecentPeers(controller, std::move(recentPeers)))) +, _emptyRecent(_content->add(setupEmptyRecent(controller))) { + _recentCount.value() | rpl::start_with_next([=](int count) { + _recentPeers->toggle(count > 0, anim::type::instant); + _emptyRecent->toggle(count == 0, anim::type::instant); + }, _recentPeers->lifetime()); + _topPeers->emptyValue() | rpl::start_with_next([=](bool empty) { _topPeersWrap->toggle(!empty, anim::type::instant); }, _topPeers->lifetime()); _topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) { - _topPeerChosen.fire(PeerId(peerIdRaw)); + const auto peerId = PeerId(peerIdRaw); + _topPeerChosen.fire(controller->session().data().peer(peerId)); }, _topPeers->lifetime()); _topPeers->showMenuRequests( @@ -178,36 +456,79 @@ void Suggestions::paintEvent(QPaintEvent *e) { } void Suggestions::resizeEvent(QResizeEvent *e) { - _scroll->setGeometry(rect()); - _content->resizeToWidth(width()); + const auto w = std::max(width(), st::columnMinimalWidthLeft); + _scroll->setGeometry(0, 0, w, height()); + _content->resizeToWidth(w); } -object_ptr Suggestions::setupDivider() { - auto result = object_ptr( - this, - st::searchedBarHeight); - const auto raw = result.data(); +object_ptr> Suggestions::setupRecentPeers( + not_null window, + RecentPeersList recentPeers) { + auto &lifetime = _content->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + const auto controller = lifetime.make_state( + &window->session(), + std::move(recentPeers)); + controller->setStyleOverrides(&st::recentPeersList); + + _recentCount = controller->count(); + + controller->chosen( + ) | rpl::start_with_next([=](not_null peer) { + window->session().recentPeers().bump(peer); + _recentPeerChosen.fire_copy(peer); + }, lifetime); + + auto content = object_ptr(_content, controller); + delegate->setContent(content); + controller->setDelegate(delegate); + + return object_ptr>(this, std::move(content)); +} + +object_ptr> Suggestions::setupEmptyRecent( + not_null window) { + auto content = object_ptr(_content); + const auto raw = content.data(); + const auto label = Ui::CreateChild( raw, - tr::lng_recent_title(), - st::searchedBarLabel); - const auto clear = Ui::CreateChild( + tr::lng_recent_none(), + st::defaultPeerListAbout); + const auto size = st::recentPeersEmptySize; + const auto [widget, animate] = Settings::CreateLottieIcon( raw, - tr::lng_recent_clear(tr::now), - st::searchedBarLink); - rpl::combine( - raw->sizeValue(), - clear->widthValue() - ) | rpl::start_with_next([=](QSize size, int width) { - const auto x = st::searchedBarPosition.x(); - const auto y = st::searchedBarPosition.y(); - clear->moveToRight(0, 0, size.width()); - label->resizeToWidth(size.width() - x - width); - label->moveToLeft(x, y, size.width()); + { + .name = u"search"_q, + .sizeOverride = { size, size }, + }, + st::recentPeersEmptyMargin); + const auto icon = widget.data(); + + _scroll->heightValue() | rpl::start_with_next([=](int height) { + raw->resize(raw->width(), height - st::topPeers.height); }, raw->lifetime()); - raw->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(raw).fillRect(clip, st::searchedBarBg); + + raw->sizeValue() | rpl::start_with_next([=](QSize size) { + const auto x = (size.width() - icon->width()) / 2; + const auto y = (size.height() - icon->height()) / 3; + icon->move(x, y); + label->move( + (size.width() - label->width()) / 2, + y + icon->height() + st::recentPeersEmptySkip); }, raw->lifetime()); + + auto result = object_ptr>(_content, std::move(content)); + result->toggle(false, anim::type::instant); + + result->toggledValue() | rpl::filter([=](bool shown) { + return shown && window->session().data().chatsListLoaded(); + }) | rpl::start_with_next([=] { + animate(anim::repeat::once); + }, raw->lifetime()); + return result; } @@ -356,4 +677,8 @@ rpl::producer TopPeersContent( }; } +RecentPeersList RecentPeersContent(not_null session) { + return RecentPeersList{ session->recentPeers().list() }; +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index dcc2a35a0..3ddd00f72 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -28,12 +28,17 @@ class SessionController; namespace Dialogs { +struct RecentPeersList { + std::vector> list; +}; + class Suggestions final : public Ui::RpWidget { public: Suggestions( not_null parent, not_null controller, - rpl::producer topPeers); + rpl::producer topPeers, + RecentPeersList recentPeers); ~Suggestions(); void selectSkip(int delta); @@ -42,27 +47,41 @@ public: void selectRight(); void chooseRow(); - [[nodiscard]] rpl::producer topPeerChosen() const { + [[nodiscard]] rpl::producer> topPeerChosen() const { return _topPeerChosen.events(); } + [[nodiscard]] rpl::producer> recentPeerChosen() const { + return _recentPeerChosen.events(); + } private: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - [[nodiscard]] object_ptr setupDivider(); + [[nodiscard]] object_ptr> setupRecentPeers( + not_null window, + RecentPeersList recentPeers); + [[nodiscard]] object_ptr> setupEmptyRecent( + not_null window); const std::unique_ptr _scroll; const not_null _content; const not_null*> _topPeersWrap; const not_null _topPeers; - const not_null _divider; - rpl::event_stream _topPeerChosen; + rpl::variable _recentCount; + const not_null*> _recentPeers; + const not_null*> _emptyRecent; + + rpl::event_stream> _topPeerChosen; + rpl::event_stream> _recentPeerChosen; }; [[nodiscard]] rpl::producer TopPeersContent( not_null session); +[[nodiscard]] RecentPeersList RecentPeersContent( + not_null session); + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp index 01771f9e0..3f5104547 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp @@ -30,9 +30,10 @@ struct TopPeersStrip::Entry { QImage userpicFrame; float64 userpicFrameOnline = 0.; QString badgeString; - uint32 badge : 28 = 0; + uint32 badge : 27 = 0; uint32 userpicFrameDirty : 1 = 0; uint32 subscribed : 1 = 0; + uint32 unread : 1 = 0; uint32 online : 1 = 0; uint32 muted : 1 = 0; }; @@ -412,9 +413,15 @@ void TopPeersStrip::apply(Entry &entry, const TopPeersEntry &data) { entry.badgeString = QString(); entry.userpicFrameDirty = 1; } + if (entry.unread != data.unread) { + entry.unread = data.unread; + if (!entry.badge) { + entry.userpicFrameDirty = 1; + } + } if (entry.muted != data.muted) { entry.muted = data.muted; - if (entry.badge) { + if (entry.badge || entry.unread) { entry.userpicFrameDirty = 1; } } @@ -500,7 +507,7 @@ void TopPeersStrip::paintUserpic( } const auto simple = entry.userpic->image(size); const auto ratio = style::DevicePixelRatio(); - const auto renderFrame = (online > 0) || entry.badge; + const auto renderFrame = (online > 0) || entry.badge || entry.unread; if (!renderFrame) { entry.userpicFrame = QImage(); p.drawImage(rect, simple); @@ -541,9 +548,11 @@ void TopPeersStrip::paintUserpic( q.setCompositionMode(QPainter::CompositionMode_SourceOver); } - if (entry.badge) { + if (entry.badge || entry.unread) { if (entry.badgeString.isEmpty()) { - entry.badgeString = (entry.badge < 1000) + entry.badgeString = !entry.badge + ? u" "_q + : (entry.badge < 1000) ? QString::number(entry.badge) : (QString::number(entry.badge / 1000) + 'K'); } @@ -551,7 +560,7 @@ void TopPeersStrip::paintUserpic( st.selected = selected; st.muted = entry.muted; const auto &counter = entry.badgeString; - const auto badge = PaintUnreadBadge(q, counter, size, 0, st); + PaintUnreadBadge(q, counter, size, 0, st); } q.end(); diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 74b983062..f6229af6b 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -100,6 +100,7 @@ Session::Session( , _giftBoxStickersPacks(std::make_unique(this)) , _sendAsPeers(std::make_unique(this)) , _attachWebView(std::make_unique(this)) +, _recentPeers(std::make_unique(this)) , _scheduledMessages(std::make_unique(this)) , _sponsoredMessages(std::make_unique(this)) , _topPeers(std::make_unique(this)) diff --git a/Telegram/SourceFiles/ui/unread_badge_paint.cpp b/Telegram/SourceFiles/ui/unread_badge_paint.cpp index 758d48dae..d6b150a08 100644 --- a/Telegram/SourceFiles/ui/unread_badge_paint.cpp +++ b/Telegram/SourceFiles/ui/unread_badge_paint.cpp @@ -79,8 +79,8 @@ void CreateCircleMask(UnreadBadgeSizeData *data, int size) { } [[nodiscard]] QString ComputeUnreadBadgeText( - const QString &unreadCount, - int allowDigits) { + const QString &unreadCount, + int allowDigits) { return (allowDigits > 0) && (unreadCount.size() > allowDigits + 1) ? u".."_q + unreadCount.mid(unreadCount.size() - allowDigits) : unreadCount;