Implement basic recent search results.

This commit is contained in:
John Preston 2024-04-12 12:25:37 +04:00
parent 2e0529bd9a
commit 2a6ff9203b
14 changed files with 456 additions and 57 deletions

Binary file not shown.

View file

@ -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

View file

@ -24,5 +24,6 @@
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>
</qresource>
</RCC>

View file

@ -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())

View file

@ -100,6 +100,8 @@ public:
-> const base::flat_set<QChar> &;
[[nodiscard]] virtual auto generateNameWords() const
-> const base::flat_set<QString> &;
[[nodiscard]] virtual QPoint computeNamePosition(
const style::PeerListItem &st) const;
virtual void preloadUserpic();

View file

@ -80,15 +80,13 @@ void TopPeers::remove(not_null<PeerData*> 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<PeerData*> peer, TimeId date) {
@ -117,10 +115,10 @@ void TopPeers::increment(not_null<PeerData*> 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();

View file

@ -38,6 +38,7 @@ private:
void request();
[[nodiscard]] uint64 countHash() const;
void updated();
const not_null<Main::Session*> _session;

View file

@ -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;

View file

@ -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<Suggestions>(
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<PeerData*> peer) {
if (base::IsCtrlPressed()) {
controller()->showInNewWindow(peer);
} else {

View file

@ -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<PeerData*> 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*> _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<Main::Session*> session,
RecentPeersList list);
[[nodiscard]] rpl::producer<int> count() const {
return _count.value();
}
[[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const {
return _chosen.events();
}
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
Main::Session &session() const override;
QString savedMessagesChatStatus() const override;
private:
void setupDivider();
void subscribeToEvents();
const not_null<Main::Session*> _session;
RecentPeersList _recent;
rpl::variable<int> _count;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<not_null<PeerData*>> _chosen;
rpl::lifetime _lifetime;
};
void FillTopPeerMenu(
not_null<Window::SessionController*> controller,
const ShowTopPeerMenuRequest &request,
@ -92,12 +163,210 @@ void FillTopPeerMenu(
});
}
RecentRow::RecentRow(not_null<PeerData*> 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<Main::Session*> session,
RecentPeersList list)
: _session(session)
, _recent(std::move(list)) {
}
void RecentsController::prepare() {
setupDivider();
for (const auto &peer : _recent.list) {
delegate()->peerListAppendRow(std::make_unique<RecentRow>(peer));
}
delegate()->peerListRefreshRows();
_count = _recent.list.size();
subscribeToEvents();
}
void RecentsController::rowClicked(not_null<PeerListRow*> row) {
_chosen.fire(row->peer());
}
base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> 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<Ui::FixedHeightWidget>(
(QWidget*)nullptr,
st::searchedBarHeight);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
tr::lng_recent_title(),
st::searchedBarLabel);
const auto clear = Ui::CreateChild<Ui::LinkButton>(
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<RecentRow*>(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<RecentRow*>(row.get())->refreshBadge()) {
delegate()->peerListUpdateRow(row);
}
}
}, _lifetime);
}
} // namespace
Suggestions::Suggestions(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
rpl::producer<TopPeersList> topPeers)
rpl::producer<TopPeersList> topPeers,
RecentPeersList recentPeers)
: RpWidget(parent)
, _scroll(std::make_unique<Ui::ElasticScroll>(this))
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
@ -105,13 +374,22 @@ Suggestions::Suggestions(
this,
object_ptr<TopPeersStrip>(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<Ui::RpWidget> Suggestions::setupDivider() {
auto result = object_ptr<Ui::FixedHeightWidget>(
this,
st::searchedBarHeight);
const auto raw = result.data();
object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
not_null<Window::SessionController*> window,
RecentPeersList recentPeers) {
auto &lifetime = _content->lifetime();
const auto delegate = lifetime.make_state<
PeerListContentDelegateSimple
>();
const auto controller = lifetime.make_state<RecentsController>(
&window->session(),
std::move(recentPeers));
controller->setStyleOverrides(&st::recentPeersList);
_recentCount = controller->count();
controller->chosen(
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
window->session().recentPeers().bump(peer);
_recentPeerChosen.fire_copy(peer);
}, lifetime);
auto content = object_ptr<PeerListContent>(_content, controller);
delegate->setContent(content);
controller->setDelegate(delegate);
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
}
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent(
not_null<Window::SessionController*> window) {
auto content = object_ptr<Ui::RpWidget>(_content);
const auto raw = content.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
tr::lng_recent_title(),
st::searchedBarLabel);
const auto clear = Ui::CreateChild<Ui::LinkButton>(
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<Ui::SlideWrap<>>(_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<TopPeersList> TopPeersContent(
};
}
RecentPeersList RecentPeersContent(not_null<Main::Session*> session) {
return RecentPeersList{ session->recentPeers().list() };
}
} // namespace Dialogs

View file

@ -28,12 +28,17 @@ class SessionController;
namespace Dialogs {
struct RecentPeersList {
std::vector<not_null<PeerData*>> list;
};
class Suggestions final : public Ui::RpWidget {
public:
Suggestions(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
rpl::producer<TopPeersList> topPeers);
rpl::producer<TopPeersList> topPeers,
RecentPeersList recentPeers);
~Suggestions();
void selectSkip(int delta);
@ -42,27 +47,41 @@ public:
void selectRight();
void chooseRow();
[[nodiscard]] rpl::producer<PeerId> topPeerChosen() const {
[[nodiscard]] rpl::producer<not_null<PeerData*>> topPeerChosen() const {
return _topPeerChosen.events();
}
[[nodiscard]] rpl::producer<not_null<PeerData*>> recentPeerChosen() const {
return _recentPeerChosen.events();
}
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
[[nodiscard]] object_ptr<Ui::RpWidget> setupDivider();
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers(
not_null<Window::SessionController*> window,
RecentPeersList recentPeers);
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent(
not_null<Window::SessionController*> window);
const std::unique_ptr<Ui::ElasticScroll> _scroll;
const not_null<Ui::VerticalLayout*> _content;
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
const not_null<TopPeersStrip*> _topPeers;
const not_null<Ui::RpWidget*> _divider;
rpl::event_stream<PeerId> _topPeerChosen;
rpl::variable<int> _recentCount;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen;
};
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
not_null<Main::Session*> session);
[[nodiscard]] RecentPeersList RecentPeersContent(
not_null<Main::Session*> session);
} // namespace Dialogs

View file

@ -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();

View file

@ -100,6 +100,7 @@ Session::Session(
, _giftBoxStickersPacks(std::make_unique<Stickers::GiftBoxPack>(this))
, _sendAsPeers(std::make_unique<SendAsPeers>(this))
, _attachWebView(std::make_unique<InlineBots::AttachWebView>(this))
, _recentPeers(std::make_unique<Data::RecentPeers>(this))
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
, _topPeers(std::make_unique<Data::TopPeers>(this))

View file

@ -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;