Top peer realtime badges.

This commit is contained in:
John Preston 2024-04-11 12:41:32 +04:00
parent b259c566b7
commit 56e28feb00
7 changed files with 205 additions and 20 deletions

View File

@ -1108,7 +1108,7 @@ void Widget::updateSuggestions(anim::type animated) {
} else if (suggest && !_suggestions) {
_suggestions = std::make_unique<Suggestions>(
this,
rpl::single(TopPeersContent(&session())));
TopPeersContent(&session()));
_suggestions->topPeerChosen(
) | rpl::start_with_next([=](PeerId id) {

View File

@ -7,8 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/dialogs_suggestions.h"
#include "base/unixtime.h"
#include "data/components/top_peers.h"
#include "data/data_changes.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/widgets/buttons.h"
@ -82,17 +87,146 @@ object_ptr<Ui::RpWidget> Suggestions::setupDivider() {
return result;
}
TopPeersList TopPeersContent(not_null<Main::Session*> session) {
auto base = TopPeersList();
const auto top = session->topPeers().list();
for (const auto &peer : top) {
base.entries.push_back(TopPeersEntry{
.id = peer->id.value,
.name = peer->shortName(),
.userpic = Ui::MakeUserpicThumbnail(peer),
});
}
return base;
rpl::producer<TopPeersList> TopPeersContent(
not_null<Main::Session*> session) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
struct Entry {
not_null<History*> history;
int index = 0;
};
struct State {
TopPeersList data;
base::flat_map<not_null<PeerData*>, Entry> indices;
base::has_weak_ptr guard;
bool scheduled = true;
};
auto state = lifetime.make_state<State>();
const auto top = session->topPeers().list();
auto &entries = state->data.entries;
auto &indices = state->indices;
entries.reserve(top.size());
indices.reserve(top.size());
const auto now = base::unixtime::now();
for (const auto &peer : top) {
const auto user = peer->asUser();
const auto self = user && user->isSelf();
const auto history = peer->owner().history(peer);
const auto badges = history->chatListBadgesState();
entries.push_back({
.id = peer->id.value,
.name = (self
? tr::lng_saved_messages(tr::now)
: peer->shortName()),
.userpic = (self
? Ui::MakeSavedMessagesThumbnail()
: Ui::MakeUserpicThumbnail(peer)),
.badge = uint32(badges.unreadCounter),
.unread = badges.unread,
.muted = !self && history->muted(),
.online = user && !self && Data::IsUserOnline(user, now),
});
if (entries.back().online) {
user->owner().watchForOffline(user, now);
}
indices.emplace(peer, Entry{
.history = peer->owner().history(peer),
.index = int(entries.size()) - 1,
});
}
const auto push = [=] {
if (!state->scheduled) {
return;
}
state->scheduled = false;
consumer.put_next_copy(state->data);
};
const auto schedule = [=] {
if (state->scheduled) {
return;
}
state->scheduled = true;
crl::on_main(&state->guard, push);
};
using Flag = Data::PeerUpdate::Flag;
session->changes().peerUpdates(
Flag::Name
| Flag::Photo
| Flag::Notifications
| Flag::OnlineStatus
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
const auto peer = update.peer;
if (peer->isSelf()) {
return;
}
const auto i = state->indices.find(peer);
if (i == end(state->indices)) {
return;
}
auto changed = false;
auto &entry = state->data.entries[i->second.index];
const auto flags = update.flags;
if (flags & Flag::Name) {
const auto now = peer->shortName();
if (entry.name != now) {
entry.name = now;
changed = true;
}
}
if (flags & Flag::Photo) {
entry.userpic = Ui::MakeUserpicThumbnail(peer);
changed = true;
}
if (flags & Flag::Notifications) {
const auto now = i->second.history->muted();
if (entry.muted != now) {
entry.muted = now;
changed = true;
}
}
if (flags & Flag::OnlineStatus) {
if (const auto user = peer->asUser()) {
const auto now = base::unixtime::now();
const auto value = Data::IsUserOnline(user, now);
if (entry.online != value) {
entry.online = value;
changed = true;
if (value) {
user->owner().watchForOffline(user, now);
}
}
}
}
if (changed) {
schedule();
}
}, lifetime);
session->data().unreadBadgeChanges(
) | rpl::start_with_next([=] {
auto changed = false;
auto &entries = state->data.entries;
for (const auto &[peer, data] : state->indices) {
const auto badges = data.history->chatListBadgesState();
auto &entry = entries[data.index];
if (entry.badge != badges.unreadCounter
|| entry.unread != badges.unread) {
entry.badge = badges.unreadCounter;
entry.unread = badges.unread;
changed = true;
}
}
if (changed) {
schedule();
}
}, lifetime);
push();
return lifetime;
};
}
} // namespace Dialogs

View File

@ -53,7 +53,7 @@ private:
};
[[nodiscard]] TopPeersList TopPeersContent(
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
not_null<Main::Session*> session);
} // namespace Dialogs

View File

@ -445,7 +445,7 @@ void TopPeersStrip::updateSelected() {
const auto p = mapFromGlobal(_lastMousePosition);
const auto x = p.x();
const auto single = st.photoLeft * 2 + st.photo;
const auto index = (x - _scrollLeft) / single;
const auto index = (_scrollLeft + x) / single;
const auto selected = (index < 0 || index >= _entries.size())
? -1
: index;

View File

@ -22,6 +22,7 @@ struct TopPeersEntry {
QString name;
std::shared_ptr<Ui::DynamicImage> userpic;
uint32 badge : 28 = 0;
uint32 unread : 1 = 0;
uint32 muted : 1 = 0;
uint32 online : 1 = 0;
};

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "data/data_story.h"
#include "main/main_session.h"
#include "ui/empty_userpic.h"
#include "ui/dynamic_image.h"
#include "ui/painter.h"
#include "ui/userpic_view.h"
@ -39,6 +40,7 @@ private:
Ui::PeerUserpicView view;
Fn<void()> callback;
InMemoryKey key;
int paletteVersion = 0;
rpl::lifetime photoLifetime;
rpl::lifetime downloadLifetime;
};
@ -117,6 +119,17 @@ private:
};
class SavedMessagesUserpic final : public DynamicImage {
public:
QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override;
private:
QImage _frame;
int _paletteVersion = 0;
};
PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
: _peer(peer)
, _forceRound(forceRound) {
@ -127,13 +140,21 @@ QImage PeerUserpic::image(int size) {
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
const auto key = _peer->userpicUniqueKey(_subscribed->view);
if (!good || (_subscribed->key != key && !waitingUserpicLoad())) {
const auto ratio = style::DevicePixelRatio();
const auto paletteVersion = style::PaletteVersion();
if (!good
|| (_subscribed->paletteVersion != paletteVersion
&& _peer->useEmptyUserpic(_subscribed->view))
|| (_subscribed->key != key && !waitingUserpicLoad())) {
_subscribed->key = key;
_frame = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
_subscribed->paletteVersion = paletteVersion;
const auto ratio = style::DevicePixelRatio();
if (!good) {
_frame = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
}
_frame.fill(Qt::transparent);
auto p = Painter(&_frame);
@ -313,6 +334,30 @@ QImage EmptyThumbnail::image(int size) {
void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
}
QImage SavedMessagesUserpic::image(int size) {
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
const auto paletteVersion = style::PaletteVersion();
if (!good || _paletteVersion != paletteVersion) {
_paletteVersion = paletteVersion;
const auto ratio = style::DevicePixelRatio();
if (!good) {
_frame = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
}
_frame.fill(Qt::transparent);
auto p = Painter(&_frame);
Ui::EmptyUserpic::PaintSavedMessages(p, 0, 0, size, size);
}
return _frame;
}
void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
}
} // namespace
std::shared_ptr<DynamicImage> MakeUserpicThumbnail(
@ -321,6 +366,10 @@ std::shared_ptr<DynamicImage> MakeUserpicThumbnail(
return std::make_shared<PeerUserpic>(peer, forceRound);
}
std::shared_ptr<DynamicImage> MakeSavedMessagesThumbnail() {
return std::make_shared<SavedMessagesUserpic>();
}
std::shared_ptr<DynamicImage> MakeStoryThumbnail(
not_null<Data::Story*> story) {
using Result = std::shared_ptr<DynamicImage>;

View File

@ -20,6 +20,7 @@ class DynamicImage;
[[nodiscard]] std::shared_ptr<DynamicImage> MakeUserpicThumbnail(
not_null<PeerData*> peer,
bool forceRound = false);
[[nodiscard]] std::shared_ptr<DynamicImage> MakeSavedMessagesThumbnail();
[[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail(
not_null<Data::Story*> story);