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;