From 4c8187f6238d0ae416c7381b596d50ad035d3464 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Nov 2022 13:24:37 +0400 Subject: [PATCH] Topics list in forum chats list entry. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/data/data_folder.cpp | 14 ++- Telegram/SourceFiles/data/data_forum.cpp | 69 +++++++++++ Telegram/SourceFiles/data/data_forum.h | 10 ++ .../SourceFiles/data/data_forum_topic.cpp | 9 +- Telegram/SourceFiles/dialogs/dialogs.style | 22 +++- .../dialogs/dialogs_inner_widget.cpp | 48 +++++--- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 2 + .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 25 ++-- .../dialogs/ui/dialogs_message_view.cpp | 87 ++++++-------- .../dialogs/ui/dialogs_message_view.h | 13 +- .../dialogs/ui/dialogs_topics_view.cpp | 112 ++++++++++++++++++ .../dialogs/ui/dialogs_topics_view.h | 67 +++++++++++ Telegram/SourceFiles/history/history.cpp | 5 + .../history/view/history_view_list_widget.cpp | 7 -- 15 files changed, 395 insertions(+), 97 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ef63827a42..d900b1f875 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -581,6 +581,8 @@ PRIVATE dialogs/ui/dialogs_layout.h dialogs/ui/dialogs_message_view.cpp dialogs/ui/dialogs_message_view.h + dialogs/ui/dialogs_topics_view.cpp + dialogs/ui/dialogs_topics_view.h dialogs/ui/dialogs_video_userpic.cpp dialogs/ui/dialogs_video_userpic.h editor/color_picker.cpp diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 99af91d014..8c3b719092 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -179,7 +179,7 @@ void Folder::reorderLastHistories() { const auto bDate = bItem ? bItem->date() : TimeId(0); return aDate > bDate; }; - _lastHistories.erase(_lastHistories.begin(), _lastHistories.end()); + _lastHistories.clear(); _lastHistories.reserve(kShowChatNamesCount + 1); auto &&histories = ranges::views::all( *_chatsList.indexed() @@ -187,11 +187,13 @@ void Folder::reorderLastHistories() { return row->history(); }) | ranges::views::filter([](History *history) { return (history != nullptr); - }) | ranges::views::transform([](History *history) { - return not_null(history); }); + auto nonPinnedChecked = 0; for (const auto history : histories) { - const auto i = ranges::upper_bound(_lastHistories, history, pred); + const auto i = ranges::upper_bound( + _lastHistories, + not_null(history), + pred); if (size(_lastHistories) < kShowChatNamesCount || i != end(_lastHistories)) { _lastHistories.insert(i, history); @@ -199,6 +201,10 @@ void Folder::reorderLastHistories() { if (size(_lastHistories) > kShowChatNamesCount) { _lastHistories.pop_back(); } + if (!history->isPinnedDialog(FilterId()) + && ++nonPinnedChecked >= kShowChatNamesCount) { + break; + } } ++_chatListViewVersion; updateChatListEntry(); diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 6c0b835e4c..93ce8ce3f5 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -37,6 +37,7 @@ constexpr auto kTopicsFirstLoad = 20; constexpr auto kLoadedTopicsMinCount = 20; constexpr auto kTopicsPerPage = 500; constexpr auto kStalePerRequest = 100; +constexpr auto kShowTopicNamesCount = 8; // constexpr auto kGeneralColorId = 0xA9A9A9; } // namespace @@ -172,6 +173,11 @@ void Forum::applyTopicDeleted(MsgId rootId) { const auto raw = i->second.get(); Core::App().notifications().clearFromTopic(raw); owner().removeChatListEntry(raw); + + if (ranges::contains(_lastTopics, not_null(raw))) { + reorderLastTopics(); + } + _topicDestroyed.fire(raw); _topics.erase(i); @@ -183,6 +189,65 @@ void Forum::applyTopicDeleted(MsgId rootId) { } } +void Forum::reorderLastTopics() { + // We want first kShowChatNamesCount histories, by last message date. + const auto pred = [](not_null a, not_null b) { + const auto aItem = a->chatListMessage(); + const auto bItem = b->chatListMessage(); + const auto aDate = aItem ? aItem->date() : TimeId(0); + const auto bDate = bItem ? bItem->date() : TimeId(0); + return aDate > bDate; + }; + _lastTopics.clear(); + _lastTopics.reserve(kShowTopicNamesCount + 1); + auto &&topics = ranges::views::all( + *_topicsList.indexed() + ) | ranges::views::transform([](not_null row) { + return row->topic(); + }); + auto nonPinnedChecked = 0; + for (const auto topic : topics) { + const auto i = ranges::upper_bound( + _lastTopics, + not_null(topic), + pred); + if (size(_lastTopics) < kShowTopicNamesCount + || i != end(_lastTopics)) { + _lastTopics.insert(i, topic); + } + if (size(_lastTopics) > kShowTopicNamesCount) { + _lastTopics.pop_back(); + } + if (!topic->isPinnedDialog(FilterId()) + && ++nonPinnedChecked >= kShowTopicNamesCount) { + break; + } + } + ++_lastTopicsVersion; + _history->updateChatListEntry(); +} + +int Forum::recentTopicsListVersion() const { + return _lastTopicsVersion; +} + +void Forum::recentTopicsInvalidate(not_null topic) { + if (ranges::contains(_lastTopics, topic)) { + ++_lastTopicsVersion; + _history->updateChatListEntry(); + } +} + +const std::vector> &Forum::recentTopics() const { + return _lastTopics; +} + +void Forum::listMessageChanged(HistoryItem *from, HistoryItem *to) { + if (from || to) { + reorderLastTopics(); + } +} + void Forum::applyReceivedTopics( const MTPmessages_ForumTopics &topics, ForumOffsets &updateOffsets) { @@ -344,6 +409,8 @@ ForumTopic *Forum::applyTopicAdded( if (!creating(rootId)) { raw->addToChatList(FilterId(), topicsList()); _chatsListChanges.fire({}); + + reorderLastTopics(); } return raw; } @@ -395,6 +462,8 @@ void Forum::created(MsgId rootId, MsgId realId) { realId, std::move(topic) ).first->second->setRealRootId(realId); + + reorderLastTopics(); } owner().notifyItemIdChange({ id, rootId }); } diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index 7bef39ae00..732ef80097 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -90,6 +90,12 @@ public: void clearAllUnreadReactions(); void enumerateTopics(Fn)> action) const; + void listMessageChanged(HistoryItem *from, HistoryItem *to); + [[nodiscard]] int recentTopicsListVersion() const; + void recentTopicsInvalidate(not_null topic); + [[nodiscard]] auto recentTopics() const + -> const std::vector> &; + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -100,6 +106,7 @@ private: std::vector> callbacks; }; + void reorderLastTopics(); void requestSomeStale(); void finishTopicRequest(MsgId rootId); @@ -119,6 +126,9 @@ private: base::flat_set _creatingRootIds; + std::vector> _lastTopics; + int _lastTopicsVersion = 0; + rpl::event_stream<> _chatsListChanges; rpl::event_stream<> _chatsListLoadedEvents; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index c97f57171a..f1ae83e4f9 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -178,6 +178,9 @@ ForumTopic::ForumTopic(not_null forum, MsgId rootId) }) | rpl::start_with_next([=]( std::optional previous, std::optional now) { + if (previous.value_or(0) != now.value_or(0)) { + _forum->recentTopicsInvalidate(this); + } notifyUnreadStateChange(unreadStateFor( previous.value_or(0), previous.has_value())); @@ -489,7 +492,9 @@ void ForumTopic::setLastMessage(HistoryItem *item) { void ForumTopic::setChatListMessage(HistoryItem *item) { if (_chatListMessage && *_chatListMessage == item) { return; - } else if (item) { + } + const auto was = _chatListMessage.value_or(nullptr); + if (item) { if (item->isSponsored()) { return; } @@ -505,6 +510,7 @@ void ForumTopic::setChatListMessage(HistoryItem *item) { _chatListMessage = nullptr; updateChatListEntry(); } + _forum->listMessageChanged(was, item); } void ForumTopic::loadUserpic() { @@ -625,6 +631,7 @@ void ForumTopic::applyTitle(const QString &title) { } _title = title; ++_titleVersion; + _forum->recentTopicsInvalidate(this); _defaultIcon = QImage(); indexTitleParts(); updateChatListEntry(); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index dcfa17e642..9994f6188a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -17,6 +17,8 @@ DialogRow { nameTop: pixels; textLeft: pixels; textTop: pixels; + topicsSkip: pixels; + topicsHeight: pixels; } ForumTopicIcon { @@ -71,8 +73,11 @@ defaultDialogRow: DialogRow { textLeft: 68px; textTop: 34px; } -forumDialogRow: DialogRow { +forumDialogRow: DialogRow(defaultDialogRow) { height: 80px; + textTop: 32px; + topicsSkip: 8px; + topicsHeight: 20px; } dialogsOnlineBadgeStroke: 2px; @@ -151,6 +156,21 @@ dialogsTextPaletteArchiveActive: TextPalette(defaultTextPalette) { monoFg: dialogsTextFgActive; spoilerFg: dialogsTextFgActive; } +dialogsTextPaletteInTopic: TextPalette(defaultTextPalette) { + linkFg: dialogsNameFg; + monoFg: dialogsTextFg; + spoilerFg: dialogsTextFg; +} +dialogsTextPaletteInTopicOver: TextPalette(defaultTextPalette) { + linkFg: dialogsNameFgOver; + monoFg: dialogsTextFgOver; + spoilerFg: dialogsTextFgOver; +} +dialogsTextPaletteInTopicActive: TextPalette(defaultTextPalette) { + linkFg: dialogsNameFgActive; + monoFg: dialogsTextFgActive; + spoilerFg: dialogsTextFgActive; +} dialogsEmptyHeight: 160px; dialogsEmptySkip: 2px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e536127cdf..c06bc9f5d0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -532,12 +532,21 @@ void InnerWidget::paintEvent(QPaintEvent *e) { : Key())); if (shownBottom) { const auto skip = dialogsOffset(); - auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.empty()); - if (reorderingPinned) { - dialogsClip = dialogsClip.marginsAdded(QMargins(0, _st->height, 0, _st->height)); - } - const auto promoted = fixedOnTopCount(); + const auto reorderingPinned = (_aboveIndex >= 0) + && !_pinnedRows.empty(); + const auto reorderingIndex = promoted + _aboveIndex; + const auto reorderingRow = (reorderingIndex < list.size()) + ? (list.cbegin() + reorderingIndex)->get() + : nullptr; + if (reorderingRow) { + dialogsClip = dialogsClip.marginsAdded({ + 0, + reorderingRow->height(), + 0, + reorderingRow->height(), + }); + } const auto skippedTop = skipTopHeight(); const auto paintDialog = [&](not_null row) { const auto pinned = row->index() - promoted; @@ -552,8 +561,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto key = row->key(); const auto isActive = (key == active); const auto isSelected = (key == selected); + const auto isForum = key.history() + && key.history()->peer->isForum(); Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), { - .st = _st, + .st = (isForum ? &st::forumDialogRow : _st.get()), .folder = _openedFolder, .forum = _openedForum, .filter = _filterId, @@ -600,14 +611,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } // Paint the dragged chat above all others. - if (_aboveIndex >= 0) { - const auto index = promoted + _aboveIndex; - if (index < list.size()) { - const auto row = *(list.cbegin() + index); - p.translate(0, row->top() - top); - paintDialog(*i); - p.translate(0, top - row->top()); - } + if (reorderingRow) { + p.translate(0, reorderingRow->top() - top); + paintDialog(reorderingRow); + p.translate(0, top - reorderingRow->top()); } } } else { @@ -678,8 +685,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { : (from == (isPressed() ? _filteredPressed : _filteredSelected)); + const auto isForum = key.history() + && key.history()->peer->isForum(); Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), { - .st = _st, + .st = (isForum ? &st::forumDialogRow : _st.get()), .folder = _openedFolder, .forum = _openedForum, .filter = _filterId, @@ -1422,25 +1431,26 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { return false; } + const auto draggingHeight = _dragging->height(); auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); auto shift = 0; auto now = crl::now(); if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) { - shift = -floorclamp(_dragStart.y() - localPosition.y() + (_st->height / 2), _st->height, 0, _draggingIndex); + shift = -floorclamp(_dragStart.y() - localPosition.y() + (draggingHeight / 2), draggingHeight, 0, _draggingIndex); for (auto from = _draggingIndex, to = _draggingIndex + shift; from > to; --from) { _shownList->movePinned(_dragging, -1); std::swap(_pinnedRows[from], _pinnedRows[from - 1]); - _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - _st->height, 0); + _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - draggingHeight, 0); _pinnedRows[from].animStartTime = now; } } else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) { - shift = floorclamp(localPosition.y() - _dragStart.y() + (_st->height / 2), _st->height, 0, pinnedCount - _draggingIndex - 1); + shift = floorclamp(localPosition.y() - _dragStart.y() + (draggingHeight / 2), draggingHeight, 0, pinnedCount - _draggingIndex - 1); for (auto from = _draggingIndex, to = _draggingIndex + shift; from < to; ++from) { _shownList->movePinned(_dragging, 1); std::swap(_pinnedRows[from], _pinnedRows[from + 1]); - _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + _st->height, 0); + _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + draggingHeight, 0); _pinnedRows[from].animStartTime = now; } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index eeae9145d1..54ee23524b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -149,6 +149,8 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) { _height = history->peer->isForum() ? st::forumDialogRow.height : st::defaultDialogRow.height; + } else if (key.folder()) { + _height = st::defaultDialogRow.height; } else { _height = st::forumTopicRow.height; } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index cdd04937a4..95ffc9a714 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -216,17 +216,11 @@ void PaintListEntryText( row->listEntryCache().draw(p, { .position = rect.topLeft(), .availableWidth = rect.width(), - .palette = &(row->folder() - ? (context.active - ? st::dialogsTextPaletteArchiveActive - : context.selected - ? st::dialogsTextPaletteArchiveOver - : st::dialogsTextPaletteArchive) - : (context.active - ? st::dialogsTextPaletteActive - : context.selected - ? st::dialogsTextPaletteOver - : st::dialogsTextPalette)), + .palette = &(context.active + ? st::dialogsTextPaletteArchiveActive + : context.selected + ? st::dialogsTextPaletteArchiveOver + : st::dialogsTextPaletteArchive), .spoiler = Text::DefaultSpoilerCache(), .now = context.now, .paused = context.paused, @@ -897,7 +891,7 @@ void RowPainter::Paint( : context.selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService; - const auto rect = QRect( + auto rect = QRect( nameleft, texttop, availableWidth, @@ -926,6 +920,13 @@ void RowPainter::Paint( [=] { entry->updateChatListEntry(); }, { .ignoreTopic = (!history || !peer->isForum()) }); } + if (const auto topics = context.st->topicsHeight) { + view->prepareTopics( + row->history()->peer->forum(), + rect, + [=] { entry->updateChatListEntry(); }); + rect.setHeight(topics + rect.height()); + } view->paint(p, rect, context); } }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index e025b94384..119811d133 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_item_preview.h" #include "main/main_session.h" #include "dialogs/ui/dialogs_layout.h" +#include "dialogs/ui/dialogs_topics_view.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/image/image.h" @@ -111,7 +112,6 @@ struct MessageView::LoadingContext { MessageView::MessageView() : _senderCache(st::dialogsTextWidthMin) -, _topicCache(st::dialogsTextWidthMin) , _textCache(st::dialogsTextWidthMin) { } @@ -135,11 +135,11 @@ void MessageView::prepare( not_null item, Fn customEmojiRepaint, ToPreviewOptions options) { + const auto validateTopics = !options.ignoreTopic; options.existing = &_imagesCache; + options.ignoreTopic = true; auto preview = item->toPreview(options); const auto hasImages = !preview.images.empty(); - const auto hasArrow = (preview.arrowInTextPosition > 0) - && (preview.imagesInTextPosition > preview.arrowInTextPosition); const auto history = item->history(); const auto context = Core::MarkedTextContext{ .session = &history->session(), @@ -149,7 +149,7 @@ void MessageView::prepare( const auto senderTill = (preview.arrowInTextPosition > 0) ? preview.arrowInTextPosition : preview.imagesInTextPosition; - if ((hasImages || hasArrow) && senderTill > 0) { + if (hasImages && senderTill > 0) { auto sender = Text::Mid(preview.text, 0, senderTill); TextUtilities::Trim(sender); _senderCache.setMarkedText( @@ -157,24 +157,8 @@ void MessageView::prepare( std::move(sender), DialogTextOptions()); const auto topicTill = preview.imagesInTextPosition; - if (hasArrow && hasImages) { - auto topic = Text::Mid( - preview.text, - senderTill, - topicTill - senderTill); - TextUtilities::Trim(topic); - _topicCache.setMarkedText( - st::dialogsTextStyle, - std::move(topic), - DialogTextOptions(), - context); - preview.text = Text::Mid(preview.text, topicTill); - } else { - preview.text = Text::Mid(preview.text, senderTill); - _topicCache = { st::dialogsTextWidthMin }; - } + preview.text = Text::Mid(preview.text, senderTill); } else { - _topicCache = { st::dialogsTextWidthMin }; _senderCache = { st::dialogsTextWidthMin }; } TextUtilities::Trim(preview.text); @@ -199,6 +183,19 @@ void MessageView::prepare( } } +void MessageView::prepareTopics( + not_null forum, + const QRect &geometry, + Fn customEmojiRepaint) { + if (!_topics || _topics->forum() != forum) { + _topics = std::make_unique(forum); + } + _topics->prepare( + geometry, + &st::forumDialogRow, + std::move(customEmojiRepaint)); +} + void MessageView::paint( Painter &p, const QRect &geometry, @@ -212,13 +209,25 @@ void MessageView::paint( : context.selected ? st::dialogsTextFgOver : st::dialogsTextFg); - const auto palette = &(context.active - ? st::dialogsTextPaletteActive - : context.selected - ? st::dialogsTextPaletteOver - : st::dialogsTextPalette); + const auto withTopic = _topics && context.st->topicsHeight; + const auto palette = &(withTopic + ? (context.active + ? st::dialogsTextPaletteInTopicActive + : context.selected + ? st::dialogsTextPaletteInTopicOver + : st::dialogsTextPaletteInTopic) + : (context.active + ? st::dialogsTextPaletteActive + : context.selected + ? st::dialogsTextPaletteOver + : st::dialogsTextPalette)); auto rect = geometry; + if (withTopic) { + _topics->paint(p, rect, context); + rect.setTop(rect.top() + context.st->topicsHeight); + } + const auto lines = rect.height() / st::dialogsTextFont->height; if (!_senderCache.isEmpty()) { _senderCache.draw(p, { @@ -228,32 +237,6 @@ void MessageView::paint( .elisionLines = lines, }); rect.setLeft(rect.x() + _senderCache.maxWidth()); - if (!_topicCache.isEmpty() || _imagesCache.empty()) { - const auto skip = st::dialogsTopicArrowSkip; - if (rect.width() >= skip) { - const auto &icon = st::dialogsTopicArrow; - icon.paint( - p, - rect.x() + (skip - icon.width()) / 2, - rect.y() + st::dialogsTopicArrowTop, - geometry.width()); - } - rect.setLeft(rect.x() + skip); - } - if (!_topicCache.isEmpty()) { - if (!rect.isEmpty()) { - _topicCache.draw(p, { - .position = rect.topLeft(), - .availableWidth = rect.width(), - .palette = palette, - .spoiler = Text::DefaultSpoilerCache(), - .now = context.now, - .paused = context.paused, - .elisionLines = lines, - }); - } - rect.setLeft(rect.x() + _topicCache.maxWidth()); - } if (!_imagesCache.empty()) { const auto skip = st::dialogsMiniPreviewSkip + st::dialogsMiniPreviewRight; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 9b3ed4758a..f25cce9c04 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -16,6 +16,10 @@ enum class ImageRoundRadius; namespace Ui { } // namespace Ui +namespace Data { +class Forum; +} // namespace Data + namespace HistoryView { struct ToPreviewOptions; struct ItemPreviewImage; @@ -27,6 +31,7 @@ namespace Dialogs::Ui { using namespace ::Ui; struct PaintContext; +class TopicsView; [[nodiscard]] TextWithEntities DialogsPreviewText(TextWithEntities text); @@ -47,6 +52,12 @@ public: not_null item, Fn customEmojiRepaint, ToPreviewOptions options); + + void prepareTopics( + not_null forum, + const QRect &geometry, + Fn customEmojiRepaint); + void paint( Painter &p, const QRect &geometry, @@ -57,7 +68,7 @@ private: mutable const HistoryItem *_textCachedFor = nullptr; mutable Text::String _senderCache; - mutable Text::String _topicCache; + mutable std::unique_ptr _topics; mutable Text::String _textCache; mutable std::vector _imagesCache; mutable std::unique_ptr _loadingContext; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp new file mode 100644 index 0000000000..843d0eea6c --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp @@ -0,0 +1,112 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/ui/dialogs_topics_view.h" + +#include "dialogs/ui/dialogs_layout.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" +#include "core/ui_integration.h" +#include "ui/painter.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "styles/style_dialogs.h" + +namespace Dialogs::Ui { +namespace { + +constexpr auto kIconLoopCount = 1; + +} // namespace + +TopicsView::TopicsView(not_null forum) +: _forum(forum) { +} + +TopicsView::~TopicsView() = default; + +void TopicsView::prepare( + const QRect &geometry, + not_null st, + Fn customEmojiRepaint) { + auto index = 0; + auto available = geometry.width(); + for (const auto &topic : _forum->recentTopics()) { + if (available <= 0) { + break; + } else if (_titles.size() == index) { + _titles.emplace_back(); + } + auto &title = _titles[index]; + const auto rootId = topic->rootId(); + const auto unread = topic->chatListBadgesState().unread; + if (title.topicRootId != rootId || title.unread != unread) { + const auto context = Core::MarkedTextContext{ + .session = &topic->session(), + .customEmojiRepaint = customEmojiRepaint, + .customEmojiLoopLimit = kIconLoopCount, + }; + auto topicTitle = topic->titleWithIcon(); + title.title.setMarkedText( + st::dialogsTextStyle, + (unread + ? Ui::Text::PlainLink( + Ui::Text::Wrapped( + std::move(topicTitle), + EntityType::Bold)) + : std::move(topicTitle)), + DialogTextOptions(), + context); + title.topicRootId = rootId; + title.unread = unread; + } + available -= title.title.maxWidth() + st->topicsSkip; + ++index; + } + while (_titles.size() > index) { + _titles.pop_back(); + } +} + +void TopicsView::paint( + Painter &p, + const QRect &geometry, + const PaintContext &context) const { + auto available = geometry.width(); + + p.setFont(st::dialogsTextFont); + p.setPen(context.active + ? st::dialogsTextFgActive + : context.selected + ? st::dialogsTextFgOver + : st::dialogsTextFg); + const auto palette = &(context.active + ? st::dialogsTextPaletteArchiveActive + : context.selected + ? st::dialogsTextPaletteArchiveOver + : st::dialogsTextPaletteArchive); + auto index = 0; + auto rect = geometry; + for (const auto &title : _titles) { + if (rect.width() <= 0) { + break; + } + title.title.draw(p, { + .position = rect.topLeft(), + .availableWidth = rect.width(), + .palette = palette, + .spoiler = Text::DefaultSpoilerCache(), + .now = context.now, + .paused = context.paused, + .elisionLines = 1, + }); + rect.setLeft( + rect.left() + title.title.maxWidth() + context.st->topicsSkip); + } +} + +} // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.h new file mode 100644 index 0000000000..19cff4d658 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.h @@ -0,0 +1,67 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class Painter; + +namespace style { +struct DialogRow; +} // namespace style + +namespace Data { +class Forum; +class ForumTopic; +} // namespace Data + +namespace Ui { +} // namespace Ui + +namespace Dialogs::Ui { + +using namespace ::Ui; + +struct PaintContext; + +class TopicsView final { +public: + explicit TopicsView(not_null forum); + ~TopicsView(); + + [[nodiscard]] not_null forum() const { + return _forum; + } + + void prepare( + const QRect &geometry, + not_null st, + Fn customEmojiRepaint); + + void paint( + Painter &p, + const QRect &geometry, + const PaintContext &context) const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + struct Title { + Text::String title; + MsgId topicRootId = 0; + bool unread = false; + }; + const not_null _forum; + + mutable std::vector _titles; + + rpl::lifetime _lifetime; + +}; + +} // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5b74e069f0..985e07e7fc 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -3011,6 +3011,11 @@ void History::forumChanged(Data::Forum *old) { }) | rpl::start_with_next([=](const Dialogs::UnreadState &old) { notifyUnreadStateChange(old); }, forum->lifetime()); + + forum->chatsListChanges( + ) | rpl::start_with_next([=] { + updateChatListEntry(); + }, forum->lifetime()); } else { _flags &= ~Flag::IsForum; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 8d3d93ec38..e5afa58908 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -3206,13 +3206,6 @@ void ListWidget::mouseActionFinish( }; auto activated = ClickHandler::unpressed(); - - if (_overElement) { - AssertIsDebug(); - setGeometryCrashAnnotations(_overElement); - Unexpected("Test"); - } - auto simpleSelectionChange = pressState.itemId && !_pressWasInactive && (button != Qt::RightButton)