diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index f60781b44b..82cec9097e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1108,7 +1108,7 @@ void Widget::updateSuggestions(anim::type animated) { } else if (suggest && !_suggestions) { _suggestions = std::make_unique( this, - rpl::single(TopPeersContent(&session()))); + TopPeersContent(&session())); _suggestions->topPeerChosen( ) | rpl::start_with_next([=](PeerId id) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 53ef01cd17..7e312c6021 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -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 Suggestions::setupDivider() { return result; } -TopPeersList TopPeersContent(not_null 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 TopPeersContent( + not_null session) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + struct Entry { + not_null history; + int index = 0; + }; + struct State { + TopPeersList data; + base::flat_map, Entry> indices; + base::has_weak_ptr guard; + bool scheduled = true; + }; + auto state = lifetime.make_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 diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 10622576a2..ada2d4be05 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -53,7 +53,7 @@ private: }; -[[nodiscard]] TopPeersList TopPeersContent( +[[nodiscard]] rpl::producer TopPeersContent( 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 12726cee67..02ace8a021 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp @@ -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; diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h index 9875fc4b20..1662aa88ba 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h @@ -22,6 +22,7 @@ struct TopPeersEntry { QString name; std::shared_ptr userpic; uint32 badge : 28 = 0; + uint32 unread : 1 = 0; uint32 muted : 1 = 0; uint32 online : 1 = 0; }; diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index 847e7d5de1..fb977e6d70 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -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 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 callback) override; + +private: + QImage _frame; + int _paletteVersion = 0; + +}; + PeerUserpic::PeerUserpic(not_null 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 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 callback) { +} + } // namespace std::shared_ptr MakeUserpicThumbnail( @@ -321,6 +366,10 @@ std::shared_ptr MakeUserpicThumbnail( return std::make_shared(peer, forceRound); } +std::shared_ptr MakeSavedMessagesThumbnail() { + return std::make_shared(); +} + std::shared_ptr MakeStoryThumbnail( not_null story) { using Result = std::shared_ptr; diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index cbf3cb2357..ad8556d5d8 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -20,6 +20,7 @@ class DynamicImage; [[nodiscard]] std::shared_ptr MakeUserpicThumbnail( not_null peer, bool forceRound = false); +[[nodiscard]] std::shared_ptr MakeSavedMessagesThumbnail(); [[nodiscard]] std::shared_ptr MakeStoryThumbnail( not_null story);