From 9a29807276323d37b52126cbd60642905b463f3b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Jul 2023 12:29:40 +0400 Subject: [PATCH] Show stories segments in contacts list. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 34 +++- Telegram/SourceFiles/boxes/peer_list_box.h | 8 + .../boxes/peer_list_controllers.cpp | 171 ++++++++++++++---- .../SourceFiles/boxes/peer_list_controllers.h | 19 ++ 4 files changed, 197 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 95920addcf..777e952b9d 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -261,15 +261,23 @@ void PeerListBox::peerListSetRowChecked( not_null row, bool checked) { if (checked) { - addSelectItem(row, anim::type::normal); + if (_controller->trackSelectedList()) { + addSelectItem(row, anim::type::normal); + } PeerListContentDelegate::peerListSetRowChecked(row, checked); peerListUpdateRow(row); // This call deletes row from _searchRows. - _select->entity()->clearQuery(); + if (_select) { + _select->entity()->clearQuery(); + } } else { // The itemRemovedCallback will call changeCheckState() here. - _select->entity()->removeItem(row->id()); + if (_select) { + _select->entity()->removeItem(row->id()); + } else { + PeerListContentDelegate::peerListSetRowChecked(row, checked); + } peerListUpdateRow(row); } } @@ -1131,6 +1139,24 @@ PeerListRow *PeerListContent::findRow(PeerListRowId id) { return (it == _rowsById.cend()) ? nullptr : it->second.get(); } +std::optional PeerListContent::lastRowMousePosition() const { + if (!_lastMousePosition) { + return std::nullopt; + } + const auto point = mapFromGlobal(*_lastMousePosition); + auto in = parentWidget()->rect().contains( + parentWidget()->mapFromGlobal(*_lastMousePosition)); + auto rowsPointY = point.y() - rowsTop(); + const auto index = (in + && rowsPointY >= 0 + && rowsPointY < shownRowsCount() * _rowHeight) + ? (rowsPointY / _rowHeight) + : -1; + return (index >= 0 && index == _selected.index.value) + ? QPoint(point.x(), rowsPointY) + : std::optional(); +} + void PeerListContent::removeRow(not_null row) { auto index = row->absoluteIndex(); auto isSearchResult = row->isSearchResult(); @@ -1998,10 +2024,12 @@ void PeerListContent::setSearchQuery( bool PeerListContent::submitted() { if (const auto row = getRow(_selected.index)) { + _lastMousePosition = std::nullopt; _controller->rowClicked(row); return true; } else if (showingSearch()) { if (const auto row = getRow(RowIndex(0))) { + _lastMousePosition = std::nullopt; _controller->rowClicked(row); return true; } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index cfa5b66bcd..4252b5fa25 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -327,6 +327,7 @@ public: virtual void peerListScrollToTop() = 0; virtual int peerListFullRowsCount() = 0; virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; + virtual std::optional peerListLastRowMousePosition() = 0; virtual void peerListSortRows(Fn compare) = 0; virtual int peerListPartitionRows(Fn border) = 0; virtual void peerListShowBox( @@ -503,6 +504,9 @@ public: return delegate()->peerListIsRowChecked(row); } + virtual bool trackSelectedList() { + return true; + } virtual bool searchInLocal() { return true; } @@ -612,6 +616,7 @@ public: void prependRow(std::unique_ptr row); void prependRowFromSearchResult(not_null row); PeerListRow *findRow(PeerListRowId id); + std::optional lastRowMousePosition() const; void updateRow(not_null row) { updateRow(row, RowIndex()); } @@ -866,6 +871,9 @@ public: PeerListRow *peerListFindRow(PeerListRowId id) override { return _content->findRow(id); } + std::optional peerListLastRowMousePosition() override { + return _content->lastRowMousePosition(); + } void peerListUpdateRow(not_null row) override { _content->updateRow(row); } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 6c0343b807..745e5eef88 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "main/main_session.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -109,6 +110,46 @@ private: }; +[[nodiscard]] std::vector PrepareSegments( + int count, + int unread, + const QBrush &unreadBrush) { + Expects(unread <= count); + Expects(count > 0); + + auto result = std::vector(); + const auto add = [&](bool unread) { + result.push_back({ + .brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b, + .width = (unread + ? st::dialogsStoriesFull.lineTwice / 2. + : st::dialogsStoriesFull.lineReadTwice / 2.), + }); + }; + result.reserve(count); + for (auto i = 0, till = count - unread; i != till; ++i) { + add(false); + } + for (auto i = 0; i != unread; ++i) { + add(true); + } + return result; +} + +[[nodiscard]] QBrush CreateStoriesGradient() { + const auto &st = st::contactsWithStories.item; + const auto left = st.photoPosition.x(); + const auto top = st.photoPosition.y(); + auto gradient = QLinearGradient( + QPoint(left + st.photoSize, top), + QPoint(left, top + st.photoSize)); + gradient.setStops({ + { 0., st::groupCallLive1->c }, + { 1., st::groupCallMuted1->c }, + }); + return QBrush(gradient); +} + StoriesRow::StoriesRow( not_null session, const QBrush &unread, @@ -133,26 +174,8 @@ void StoriesRow::updateGradient(QBrush unread) { } void StoriesRow::refreshSegments() { - Expects(_unreadCount <= _count); - Expects(_count > 0); - - auto segments = std::vector(); - const auto add = [&](bool unread) { - segments.push_back({ - .brush = unread ? _unread : st::dialogsUnreadBgMuted->b, - .width = (unread - ? st::dialogsStoriesFull.lineTwice / 2. - : st::dialogsStoriesFull.lineReadTwice / 2.), - }); - }; - segments.reserve(_count); - for (auto i = 0, count = _count - _unreadCount; i != count; ++i) { - add(false); - } - for (auto i = 0; i != _unreadCount; ++i) { - add(true); - } - setCustomizedCheckSegments(std::move(segments)); + setCustomizedCheckSegments( + PrepareSegments(_count, _unreadCount, _unread)); } StoriesController::StoriesController( @@ -164,20 +187,10 @@ StoriesController::StoriesController( , _content(std::move(content)) , _open(std::move(open)) , _loadMore(std::move(loadMore)) { - const auto createGradient = [=] { - auto gradient = QLinearGradient( - QPoint(10, 0), - QPoint(0, 10)); - gradient.setStops({ - { 0., st::groupCallLive1->c }, - { 1., st::groupCallMuted1->c }, - }); - _unread = QBrush(gradient); - }; - createGradient(); + _unread = CreateStoriesGradient(); style::PaletteChanged( ) | rpl::start_with_next([=] { - createGradient(); + _unread = CreateStoriesGradient(); for (auto i = 0, count = int(delegate()->peerListFullRowsCount()) ; i != count ; ++i) { @@ -248,6 +261,7 @@ void StoriesController::refresh(const Content &content) { delegate()->peerListAppendRow(std::move(added)); delegate()->peerListSetRowChecked(raw, true); raw->applySegments(element); + raw->finishCheckedAnimation(); } ++position; } @@ -351,6 +365,7 @@ object_ptr PrepareContactsBox( auto controller = std::make_unique( &sessionController->session()); controller->setStyleOverrides(&st::contactsWithStories); + controller->setStoriesShown(true); const auto raw = controller.get(); auto init = [=](not_null box) { using namespace Dialogs::Stories; @@ -647,6 +662,33 @@ void ContactsBoxController::prepare() { prepareViewHook(); + if (_storiesShown) { + _storiesUnread = CreateStoriesGradient(); + style::PaletteChanged() | rpl::start_with_next([=] { + _storiesUnread = CreateStoriesGradient(); + for (auto &entry : _storiesCounts) { + entry.second.count = entry.second.unread = -1; + } + updateStories(); + }, lifetime()); + + const auto stories = &session().data().stories(); + rpl::merge( + rpl::single(rpl::empty), + stories->sourcesChanged(Data::StorySourcesList::NotHidden), + stories->sourcesChanged(Data::StorySourcesList::Hidden) + ) | rpl::start_with_next([=] { + updateStories(); + }, lifetime()); + stories->sourceChanged() | rpl::start_with_next([=](PeerId id) { + const auto source = stories->source(id); + const auto info = source + ? source->info() + : Data::StoriesSourceInfo(); + updateStoriesFor(id.value, info.count, info.unreadCount); + }, lifetime()); + } + session().data().contactsLoaded().value( ) | rpl::start_with_next([=] { rebuildRows(); @@ -692,6 +734,14 @@ std::unique_ptr ContactsBoxController::createSearchRow( void ContactsBoxController::rowClicked(not_null row) { const auto peer = row->peer(); if (const auto window = peer->session().tryResolveWindow()) { + if (_storiesShown) { + const auto point = delegate()->peerListLastRowMousePosition(); + const auto &st = st::contactsWithStories.item; + if (point && point->x() < st.photoPosition.x() + st.photoSize) { + window->openPeerStories(peer->id); + return; + } + } window->showPeerHistory(row->peer()); } } @@ -717,6 +767,55 @@ void ContactsBoxController::setSortMode(SortMode mode) { } } +void ContactsBoxController::setStoriesShown(bool shown) { + _storiesShown = shown; +} + +void ContactsBoxController::updateStories() { + const auto stories = &_session->data().stories(); + const auto &a = stories->sources(Data::StorySourcesList::NotHidden); + const auto &b = stories->sources(Data::StorySourcesList::Hidden); + auto checked = base::flat_set(); + for (const auto &info : ranges::views::concat(a, b)) { + const auto id = info.id.value; + checked.emplace(id); + updateStoriesFor(id, info.count, info.unreadCount); + } + for (auto i = begin(_storiesCounts); i != end(_storiesCounts); ++i) { + if (i->second.count && !checked.contains(i->first)) { + updateStoriesFor(i->first, 0, 0); + } + } +} + +void ContactsBoxController::updateStoriesFor( + uint64 id, + int count, + int unread) { + if (const auto row = delegate()->peerListFindRow(id)) { + applyRowStories(row, count, unread); + delegate()->peerListUpdateRow(row); + } +} + +void ContactsBoxController::applyRowStories( + not_null row, + int count, + int unread, + bool force) { + auto &counts = _storiesCounts[row->id()]; + if (!force && counts.count == count && counts.unread == unread) { + return; + } + counts.count = count; + counts.unread = unread; + delegate()->peerListSetRowChecked(row, count > 0); + if (count > 0) { + row->setCustomizedCheckSegments( + PrepareSegments(count, unread, _storiesUnread)); + } +} + void ContactsBoxController::sort() { switch (_sortMode) { case SortMode::Alphabet: sortByName(); break; @@ -762,7 +861,15 @@ bool ContactsBoxController::appendRow(not_null user) { return false; } if (auto row = createRow(user)) { + const auto raw = row.get(); delegate()->peerListAppendRow(std::move(row)); + if (_storiesShown) { + const auto stories = &session().data().stories(); + if (const auto source = stories->source(user->id)) { + const auto info = source->info(); + applyRowStories(raw, info.count, info.unreadCount, true); + } + } return true; } return false; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 00106c3929..46d1a184c0 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -128,12 +128,16 @@ public: [[nodiscard]] std::unique_ptr createSearchRow( not_null peer) override final; void rowClicked(not_null row) override; + bool trackSelectedList() override { + return !_storiesShown; + } enum class SortMode { Alphabet, Online, }; void setSortMode(SortMode mode); + void setStoriesShown(bool shown); protected: virtual std::unique_ptr createRow(not_null user); @@ -143,18 +147,33 @@ protected: } private: + struct StoriesCount { + int count = 0; + int unread = 0; + }; void sort(); void sortByName(); void sortByOnline(); void rebuildRows(); + void updateStories(); void checkForEmptyRows(); bool appendRow(not_null user); + void updateStoriesFor(uint64 id, int count, int unread); + void applyRowStories( + not_null row, + int count, + int unread, + bool force = false); const not_null _session; SortMode _sortMode = SortMode::Alphabet; base::Timer _sortByOnlineTimer; rpl::lifetime _sortByOnlineLifetime; + QBrush _storiesUnread; + base::flat_map _storiesCounts; + bool _storiesShown = false; + }; class ChooseRecipientBoxController