diff --git a/Telegram/Resources/animations/search.tgs b/Telegram/Resources/animations/search.tgs new file mode 100644 index 000000000..db635fb57 Binary files /dev/null and b/Telegram/Resources/animations/search.tgs differ 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;