From b01e0b77fe31bb71f22c262fe70a390b4f34e276 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Mar 2026 12:37:53 +0300 Subject: [PATCH] Added support of unread poll votes to corner button from history widget. --- .../icons/history_unread_poll_vote.svg | 9 ++ Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/api/api_unread_things.cpp | 56 +++++++++++++ Telegram/SourceFiles/api/api_unread_things.h | 4 + Telegram/SourceFiles/api/api_updates.cpp | 24 +++++- .../chat_helpers/chat_helpers.style | 4 + Telegram/SourceFiles/data/data_changes.h | 9 +- Telegram/SourceFiles/data/data_folder.cpp | 6 +- Telegram/SourceFiles/data/data_forum.cpp | 6 ++ Telegram/SourceFiles/data/data_forum.h | 1 + .../SourceFiles/data/data_forum_topic.cpp | 3 + Telegram/SourceFiles/data/data_forum_topic.h | 1 + .../SourceFiles/data/data_saved_sublist.cpp | 4 + .../SourceFiles/data/data_saved_sublist.h | 1 + Telegram/SourceFiles/data/data_session.cpp | 18 ++++ Telegram/SourceFiles/data/data_thread.cpp | 24 ++++++ Telegram/SourceFiles/data/data_thread.h | 4 + Telegram/SourceFiles/data/data_types.h | 1 + Telegram/SourceFiles/history/history.cpp | 82 ++++++++++++++++++- Telegram/SourceFiles/history/history.h | 8 ++ Telegram/SourceFiles/history/history_item.cpp | 44 +++++++++- Telegram/SourceFiles/history/history_item.h | 3 + .../history/history_unread_things.cpp | 52 ++++++++++-- .../history/history_unread_things.h | 6 +- .../SourceFiles/history/history_widget.cpp | 4 +- .../view/history_view_chat_section.cpp | 14 +++- .../view/history_view_corner_buttons.cpp | 62 +++++++++++++- .../view/history_view_corner_buttons.h | 3 + .../view/history_view_pinned_section.cpp | 3 +- .../info/polls/info_polls_list_widget.cpp | 3 +- Telegram/SourceFiles/menu/menu_send.cpp | 42 ++++++++++ Telegram/SourceFiles/menu/menu_send.h | 4 + 32 files changed, 481 insertions(+), 25 deletions(-) create mode 100644 Telegram/Resources/icons/history_unread_poll_vote.svg diff --git a/Telegram/Resources/icons/history_unread_poll_vote.svg b/Telegram/Resources/icons/history_unread_poll_vote.svg new file mode 100644 index 0000000000..ccc1f67190 --- /dev/null +++ b/Telegram/Resources/icons/history_unread_poll_vote.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 40f02919c4..533fa110e0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4920,6 +4920,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**"; "lng_context_mark_read_mentions_all" = "Mark all mentions as read"; "lng_context_mark_read_reactions_all" = "Read all reactions"; +"lng_context_mark_read_poll_votes_all" = "Read all poll votes"; "lng_context_archive_expand" = "Expand"; "lng_context_archive_collapse" = "Collapse"; "lng_context_archive_to_menu" = "Move to main menu"; diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index f7a43d6414..e871b71faa 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -42,6 +42,13 @@ bool UnreadThings::trackReactions(Data::Thread *thread) const { return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup()); } +bool UnreadThings::trackPollVotes(Data::Thread *thread) const { + const auto peer = thread ? thread->peer().get() : nullptr; + return peer + && (peer->isChat() || peer->isMegagroup()) + && !peer->isMonoforum(); +} + void UnreadThings::preloadEnough(Data::Thread *thread) { if (trackMentions(thread)) { preloadEnoughMentions(thread); @@ -49,6 +56,9 @@ void UnreadThings::preloadEnough(Data::Thread *thread) { if (trackReactions(thread)) { preloadEnoughReactions(thread); } + if (trackPollVotes(thread)) { + preloadEnoughPollVotes(thread); + } } void UnreadThings::mediaAndMentionsRead( @@ -84,6 +94,15 @@ void UnreadThings::preloadEnoughReactions(not_null thread) { } } +void UnreadThings::preloadEnoughPollVotes(not_null thread) { + const auto fullCount = thread->unreadPollVotes().count(); + const auto loadedCount = thread->unreadPollVotes().loadedCount(); + const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); + if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { + requestPollVotes(thread, loadedCount); + } +} + void UnreadThings::cancelRequests(not_null thread) { if (const auto requestId = _mentionsRequests.take(thread)) { _api->request(*requestId).cancel(); @@ -91,6 +110,9 @@ void UnreadThings::cancelRequests(not_null thread) { if (const auto requestId = _reactionsRequests.take(thread)) { _api->request(*requestId).cancel(); } + if (const auto requestId = _pollVotesRequests.take(thread)) { + _api->request(*requestId).cancel(); + } } void UnreadThings::requestMentions( @@ -164,4 +186,38 @@ void UnreadThings::requestReactions( _reactionsRequests.emplace(thread, requestId); } +void UnreadThings::requestPollVotes( + not_null thread, + int loaded) { + if (_pollVotesRequests.contains(thread) || thread->asSublist()) { + return; + } + const auto offsetId = loaded + ? std::max(thread->unreadPollVotes().maxLoaded(), MsgId(1)) + : MsgId(1); + const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; + const auto addOffset = loaded ? -(limit + 1) : -limit; + const auto maxId = 0; + const auto minId = 0; + const auto history = thread->owningHistory(); + const auto topic = thread->asTopic(); + using Flag = MTPmessages_GetUnreadPollVotes::Flag; + const auto requestId = _api->request(MTPmessages_GetUnreadPollVotes( + MTP_flags(topic ? Flag::f_top_msg_id : Flag()), + history->peer->input(), + MTP_int(topic ? topic->rootId() : 0), + MTP_int(offsetId), + MTP_int(addOffset), + MTP_int(limit), + MTP_int(maxId), + MTP_int(minId) + )).done([=](const MTPmessages_Messages &result) { + _pollVotesRequests.remove(thread); + thread->unreadPollVotes().addSlice(result, loaded); + }).fail([=] { + _pollVotesRequests.remove(thread); + }).send(); + _pollVotesRequests.emplace(thread, requestId); +} + } // namespace UnreadThings diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h index f3c7e1711e..dfc07459a5 100644 --- a/Telegram/SourceFiles/api/api_unread_things.h +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -23,6 +23,7 @@ public: [[nodiscard]] bool trackMentions(Data::Thread *thread) const; [[nodiscard]] bool trackReactions(Data::Thread *thread) const; + [[nodiscard]] bool trackPollVotes(Data::Thread *thread) const; void preloadEnough(Data::Thread *thread); @@ -35,14 +36,17 @@ public: private: void preloadEnoughMentions(not_null thread); void preloadEnoughReactions(not_null thread); + void preloadEnoughPollVotes(not_null thread); void requestMentions(not_null thread, int loaded); void requestReactions(not_null thread, int loaded); + void requestPollVotes(not_null thread, int loaded); const not_null _api; base::flat_map, mtpRequestId> _mentionsRequests; base::flat_map, mtpRequestId> _reactionsRequests; + base::flat_map, mtpRequestId> _pollVotesRequests; }; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index bbb0ab721a..860940f3bd 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1848,7 +1848,29 @@ void Updates::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateMessagePoll: { - session().data().applyUpdate(update.c_updateMessagePoll()); + const auto &d = update.c_updateMessagePoll(); + session().data().applyUpdate(d); + if (const auto tlPeer = d.vpeer()) { + const auto peer = peerFromMTP(*tlPeer); + const auto msgId = d.vmsg_id()->v; + if (const auto history = session().data().historyLoaded(peer)) { + if (const auto item = session().data().message( + peer, + msgId)) { + if (!item->hasUnreadPollVote()) { + item->setHasUnreadPollVote(); + item->addToUnreadThings( + HistoryUnreadThings::AddType::New); + } + } else { + if (history->unreadPollVotes().has()) { + history->unreadPollVotes().checkAdd(msgId); + } + history->owner().histories().requestDialogEntry( + history); + } + } + } } break; case mtpc_updateUserTyping: { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 7dc2408297..64f6597098 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1088,6 +1088,10 @@ historyUnreadReactions: TwoIconButton(historyToDown) { iconAbove: icon {{ "history_unread_reaction", historyToDownFg }}; iconAboveOver: icon {{ "history_unread_reaction", historyToDownFgOver }}; } +historyUnreadPollVotes: TwoIconButton(historyToDown) { + iconAbove: icon {{ "history_unread_poll_vote-40x40", historyToDownFg, point(6px, 6px) }}; + iconAboveOver: icon {{ "history_unread_poll_vote-40x40", historyToDownFgOver, point(6px, 6px) }}; +} historyUnreadThingsSkip: 4px; historyQuoteStyle: QuoteStyle(defaultQuoteStyle) { diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index f5c66416af..84faa0757e 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -152,8 +152,9 @@ struct HistoryUpdate { CloudDraft = (1U << 12), TranslateFrom = (1U << 13), TranslatedTo = (1U << 14), + UnreadPollVotes = (1U << 15), - LastUsedBit = (1U << 14), + LastUsedBit = (1U << 15), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -178,8 +179,9 @@ struct TopicUpdate { Closed = (1U << 9), Creator = (1U << 10), Destroyed = (1U << 11), + UnreadPollVotes = (1U << 12), - LastUsedBit = (1U << 11), + LastUsedBit = (1U << 12), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -197,8 +199,9 @@ struct SublistUpdate { UnreadReactions = (1U << 2), CloudDraft = (1U << 3), Destroyed = (1U << 4), + UnreadPollVotes = (1U << 5), - LastUsedBit = (1U << 4), + LastUsedBit = (1U << 5), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index f032785f3f..46897b6908 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -387,7 +387,11 @@ Dialogs::BadgesState Folder::chatListBadgesState() const { chatListUnreadState(), Dialogs::CountInBadge::Chats, Dialogs::IncludeInBadge::All); - result.unreadMuted = result.mentionMuted = result.reactionMuted = true; + result.unreadMuted + = result.mentionMuted + = result.reactionMuted + = result.pollMuted + = true; if (result.unread && !result.unreadCounter) { result.unreadCounter = 1; } diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 0c059f9490..ec8bfb59a3 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -584,6 +584,12 @@ void Forum::clearAllUnreadReactions() { } } +void Forum::clearAllUnreadPollVotes() { + for (const auto &[rootId, topic] : _topics) { + topic->unreadPollVotes().clear(); + } +} + void Forum::enumerateTopics(Fn)> action) const { for (const auto &[rootId, topic] : _topics) { action(topic.get()); diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index 96f4fcb49f..8a5fb37cf2 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -91,6 +91,7 @@ public: void clearAllUnreadMentions(); void clearAllUnreadReactions(); + void clearAllUnreadPollVotes(); void enumerateTopics(Fn)> action) const; void listMessageChanged(HistoryItem *from, HistoryItem *to); diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 4f096f8be3..a712c83ff3 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -989,6 +989,9 @@ void ForumTopic::hasUnreadReactionChanged(bool has) { notifyUnreadStateChange(was); } +void ForumTopic::hasUnreadPollVoteChanged(bool has) { +} + const QString &ForumTopic::chatListNameSortKey() const { static const auto empty = QString(); return empty; diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 068fb343ab..383fa26754 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -141,6 +141,7 @@ public: void hasUnreadMentionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override; + void hasUnreadPollVoteChanged(bool has) override; [[nodiscard]] HistoryItem *lastMessage() const; [[nodiscard]] HistoryItem *lastServerMessage() const; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 21209db5f1..1542c484a0 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -963,6 +963,7 @@ Dialogs::BadgesState SavedSublist::chatListBadgesState() const { result.unreadMuted = result.mentionMuted = result.reactionMuted + = result.pollMuted = true; } return result; @@ -1028,6 +1029,9 @@ void SavedSublist::hasUnreadReactionChanged(bool has) { notifyUnreadStateChange(was); } +void SavedSublist::hasUnreadPollVoteChanged(bool has) { +} + void SavedSublist::allowChatListMessageResolve() { if (_flags & Flag::ResolveChatListMessage) { return; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 38a06f83e3..b1d2422587 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -90,6 +90,7 @@ public: void hasUnreadMentionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override; + void hasUnreadPollVoteChanged(bool has) override; [[nodiscard]] HistoryItem *lastMessage() const; [[nodiscard]] HistoryItem *lastServerMessage() const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index ac2d58ce66..b459dedfe9 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "window/notifications_manager.h" #include "history/history.h" +#include "history/history_item.h" #include "history/history_item_components.h" #include "history/view/media/history_view_media.h" #include "history/view/history_view_element.h" @@ -4464,6 +4465,23 @@ void Session::applyUpdate(const MTPDupdateMessagePoll &update) { if (updated && updated->applyResults(update.vresults())) { notifyPollUpdateDelayed(updated); } + if (const auto tlPeer = update.vpeer()) { + const auto peer = peerFromMTP(*tlPeer); + if (const auto history = historyLoaded(peer)) { + histories().requestDialogEntry(history); + } + } else if (updated) { + if (const auto i = _pollViews.find(updated) + ; i != _pollViews.end()) { + for (const auto &view : i->second) { + if (view->data()->hasUnreadPollVote()) { + histories().requestDialogEntry( + view->data()->history()); + break; + } + } + } + } } void Session::applyUpdate(const MTPDupdateChatParticipants &update) { diff --git a/Telegram/SourceFiles/data/data_thread.cpp b/Telegram/SourceFiles/data/data_thread.cpp index cf35b979e4..5fd76d0162 100644 --- a/Telegram/SourceFiles/data/data_thread.cpp +++ b/Telegram/SourceFiles/data/data_thread.cpp @@ -98,6 +98,22 @@ HistoryUnreadThings::ConstProxy Thread::unreadReactions() const { }; } +HistoryUnreadThings::Proxy Thread::unreadPollVotes() { + return { + this, + _unreadThings, + HistoryUnreadThings::Type::PollVotes, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + +HistoryUnreadThings::ConstProxy Thread::unreadPollVotes() const { + return { + _unreadThings ? &_unreadThings->pollVotes : nullptr, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + bool Thread::canToggleUnread(bool nowUnread) const { if ((asTopic() || asForum()) && !nowUnread) { return false; @@ -125,6 +141,14 @@ const base::flat_set &Thread::unreadReactionsIds() const { return _unreadThings->reactions.ids(); } +const base::flat_set &Thread::unreadPollVotesIds() const { + if (!_unreadThings) { + static const auto Result = base::flat_set(); + return Result; + } + return _unreadThings->pollVotes.ids(); +} + void Thread::clearNotifications() { _notifications.clear(); } diff --git a/Telegram/SourceFiles/data/data_thread.h b/Telegram/SourceFiles/data/data_thread.h index 42fd2dca06..c57943baa2 100644 --- a/Telegram/SourceFiles/data/data_thread.h +++ b/Telegram/SourceFiles/data/data_thread.h @@ -78,8 +78,11 @@ public: [[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const; [[nodiscard]] HistoryUnreadThings::Proxy unreadReactions(); [[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const; + [[nodiscard]] HistoryUnreadThings::Proxy unreadPollVotes(); + [[nodiscard]] HistoryUnreadThings::ConstProxy unreadPollVotes() const; virtual void hasUnreadMentionChanged(bool has) = 0; virtual void hasUnreadReactionChanged(bool has) = 0; + virtual void hasUnreadPollVoteChanged(bool has) = 0; bool canToggleUnread(bool nowUnread) const; void removeNotification(not_null item); @@ -106,6 +109,7 @@ public: [[nodiscard]] const base::flat_set &unreadMentionsIds() const; [[nodiscard]] const base::flat_set &unreadReactionsIds() const; + [[nodiscard]] const base::flat_set &unreadPollVotesIds() const; [[nodiscard]] Ui::Text::String &cloudDraftTextCache() { return _cloudDraftTextCache; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 92c7958f4d..7017b37cd6 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -363,6 +363,7 @@ enum class MessageFlag : uint64 { HasHiddenLinks = (1ULL << 56), HasSummaryEntry = (1ULL << 57), CanBeSummarized = (1ULL << 58), + HasUnreadPollVote = (1ULL << 59), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index b7dc560cbe..9cd279ee15 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -918,6 +918,57 @@ void History::clearUnreadReactionsFor( } } +int History::unreadPollVotesCount() const { + return _unreadPollVotesCount; +} + +void History::setUnreadPollVotesCount(int count) { + if (_unreadPollVotesCount == count) { + return; + } + const auto notifier = unreadStateChangeNotifier( + useMyUnreadInParent()); + _unreadPollVotesCount = count; + _unreadPollVotesCountChanges.fire_copy(count); +} + +rpl::producer History::unreadPollVotesCountChanges() const { + return _unreadPollVotesCountChanges.events(); +} + +void History::clearUnreadPollVotesFor(MsgId topicRootId) { + const auto forum = peer->forum(); + if (!topicRootId) { + if (forum) { + forum->clearAllUnreadPollVotes(); + } + unreadPollVotes().clear(); + return; + } else if (forum) { + if (const auto topic = forum->topicFor(topicRootId)) { + topic->unreadPollVotes().clear(); + } + } + const auto &ids = unreadPollVotesIds(); + if (ids.empty()) { + return; + } + const auto owner = &this->owner(); + const auto peerId = peer->id; + auto items = base::flat_set(); + items.reserve(ids.size()); + for (const auto &id : ids) { + if (const auto item = owner->message(peerId, id)) { + if (item->topicRootId() == topicRootId) { + items.emplace(id); + } + } + } + for (const auto &id : items) { + unreadPollVotes().erase(id); + } +} + not_null History::addNewToBack( not_null item, bool unread) { @@ -2111,7 +2162,7 @@ void History::setMuted(bool muted) { const auto state = useMyUnreadInParent() ? computeBadgesState() : Dialogs::BadgesState(); - const auto notify = (state.unread || state.reaction); + const auto notify = (state.unread || state.reaction || state.poll); const auto notifier = unreadStateChangeNotifier(notify); Thread::setMuted(muted); } @@ -2247,6 +2298,20 @@ void History::hasUnreadReactionChanged(bool has) { notifyUnreadStateChange(was); } +void History::hasUnreadPollVoteChanged(bool has) { + if (isForum()) { + return; + } + auto was = chatListUnreadState(); + if (has) { + was.polls = was.pollsMuted = 0; + } else { + was.polls = 1; + was.pollsMuted = muted() ? was.polls : 0; + } + notifyUnreadStateChange(was); +} + void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (!folderKnown()) { @@ -2451,7 +2516,11 @@ Dialogs::BadgesState History::computeBadgesState() const { Dialogs::BadgesState History::adjustBadgesStateByFolder( Dialogs::BadgesState state) const { if (folder()) { - state.mentionMuted = state.reactionMuted = state.unreadMuted = true; + state.mentionMuted + = state.reactionMuted + = state.pollMuted + = state.unreadMuted + = true; } return state; } @@ -2466,10 +2535,12 @@ Dialogs::UnreadState History::computeUnreadState() const { result.marks = mark ? 1 : 0; result.mentions = unreadMentions().has() ? 1 : 0; result.reactions = unreadReactions().has() ? 1 : 0; + result.polls = (_unreadPollVotesCount > 0) ? 1 : 0; result.messagesMuted = muted ? result.messages : 0; result.chatsMuted = muted ? result.chats : 0; result.marksMuted = muted ? result.marks : 0; result.reactionsMuted = muted ? result.reactions : 0; + result.pollsMuted = muted ? result.polls : 0; result.known = _unreadCount.has_value(); return result; } @@ -2480,6 +2551,7 @@ Dialogs::UnreadState History::withMyMuted(Dialogs::UnreadState state) const { state.marksMuted = state.marks; state.messagesMuted = state.messages; state.reactionsMuted = state.reactions; + state.pollsMuted = state.polls; } return state; } @@ -3059,6 +3131,12 @@ void History::applyDialog( setUnreadMark(data.is_unread_mark()); unreadMentions().setCount(data.vunread_mentions_count().v); unreadReactions().setCount(data.vunread_reactions_count().v); + const auto pollVotesCount = data.vunread_poll_votes_count().v; + setUnreadPollVotesCount(pollVotesCount); + unreadPollVotes().setCount(pollVotesCount); + session().changes().historyUpdated( + this, + Data::HistoryUpdate::Flag::UnreadPollVotes); if (const auto channel = peer->asChannel()) { if (const auto pts = data.vpts()) { channel->ptsReceived(pts->v); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 63dac2ed2a..aec389e3f8 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -294,6 +294,11 @@ public: void clearUnreadReactionsFor( MsgId topicRootId, Data::SavedSublist *sublist); + void clearUnreadPollVotesFor(MsgId topicRootId); + + [[nodiscard]] int unreadPollVotesCount() const; + void setUnreadPollVotesCount(int count); + [[nodiscard]] rpl::producer unreadPollVotesCountChanges() const; Data::Draft *draft(Data::DraftKey key) const; void setDraft(Data::DraftKey key, std::unique_ptr &&draft); @@ -619,6 +624,7 @@ private: void hasUnreadMentionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override; + void hasUnreadPollVoteChanged(bool has) override; [[nodiscard]] bool useMyUnreadInParent() const; const std::unique_ptr _delegateMixin; @@ -639,6 +645,8 @@ private: std::optional _inboxReadBefore; std::optional _outboxReadBefore; std::optional _unreadCount; + int _unreadPollVotesCount = 0; + rpl::event_stream _unreadPollVotesCountChanges; std::optional _lastMessage; std::optional _lastServerMessage; base::flat_set> _clientSideMessages; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 75a5ccabfb..b1a65787b2 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1563,6 +1563,14 @@ bool HistoryItem::hasUnreadReaction() const { return (_flags & MessageFlag::HasUnreadReaction); } +bool HistoryItem::hasUnreadPollVote() const { + return (_flags & MessageFlag::HasUnreadPollVote); +} + +void HistoryItem::setHasUnreadPollVote() { + _flags |= MessageFlag::HasUnreadPollVote; +} + bool HistoryItem::hasUnwatchedEffect() const { return effectId() && !(_flags & MessageFlag::EffectWatched); } @@ -1642,6 +1650,16 @@ void HistoryItem::markReactionsRead() { } } +void HistoryItem::markPollVotesRead() { + _flags &= ~MessageFlag::HasUnreadPollVote; + _history->updateChatListEntry(); + _history->unreadPollVotes().erase(id); + if (const auto topic = this->topic()) { + topic->updateChatListEntry(); + topic->unreadPollVotes().erase(id); + } +} + bool HistoryItem::markContentsRead(bool fromThisClient) { if (hasUnreadReaction()) { if (fromThisClient) { @@ -2429,7 +2447,8 @@ void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) { } const auto mention = isUnreadMention(); const auto reaction = hasUnreadReaction(); - if (!mention && !reaction) { + const auto pollVote = hasUnreadPollVote(); + if (!mention && !reaction && !pollVote) { return; } const auto topic = this->topic(); @@ -2479,6 +2498,23 @@ void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) { } } } + if (pollVote) { + const auto toHistory = history->unreadPollVotes().add(id, type); + const auto toTopic = topic + && topic->unreadPollVotes().add(id, type); + if (toHistory || toTopic) { + if (toHistory) { + changes->historyUpdated( + history, + Data::HistoryUpdate::Flag::UnreadPollVotes); + } + if (toTopic) { + changes->topicUpdated( + topic, + Data::TopicUpdate::Flag::UnreadPollVotes); + } + } + } } void HistoryItem::destroyHistoryEntry() { @@ -2496,6 +2532,12 @@ void HistoryItem::destroyHistoryEntry() { sublist->unreadReactions().erase(id); } } + if (hasUnreadPollVote()) { + history()->unreadPollVotes().erase(id); + if (const auto topic = this->topic()) { + topic->unreadPollVotes().erase(id); + } + } if (isRegular() && _history->peer->isMegagroup()) { if (const auto reply = Get()) { changeReplyToTopCounter(reply, -1); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 8705b162c1..7f396c6336 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -260,12 +260,15 @@ public: [[nodiscard]] bool mentionsMe() const; [[nodiscard]] bool isUnreadMention() const; [[nodiscard]] bool hasUnreadReaction() const; + [[nodiscard]] bool hasUnreadPollVote() const; + void setHasUnreadPollVote(); [[nodiscard]] bool hasUnwatchedEffect() const; bool markEffectWatched(); [[nodiscard]] bool isUnreadMedia() const; [[nodiscard]] bool isIncomingUnreadMedia() const; [[nodiscard]] bool hasUnreadMediaFlag() const; void markReactionsRead(); + void markPollVotesRead(); void markMediaAndMentionRead(); bool markContentsRead(bool fromThisClient = false); void setIsPinned(bool isPinned); diff --git a/Telegram/SourceFiles/history/history_unread_things.cpp b/Telegram/SourceFiles/history/history_unread_things.cpp index eeed5bddea..dc55d65697 100644 --- a/Telegram/SourceFiles/history/history_unread_things.cpp +++ b/Telegram/SourceFiles/history/history_unread_things.cpp @@ -27,6 +27,7 @@ template switch (type) { case Type::Mentions: return Flag::UnreadMentions; case Type::Reactions: return Flag::UnreadReactions; + case Type::PollVotes: return Flag::UnreadPollVotes; } Unexpected("Type in HistoryUnreadThings::UpdateFlag."); } @@ -40,8 +41,11 @@ template } [[nodiscard]] Data::SublistUpdate::Flag SublistUpdateFlag(Type type) { - Expects(type == Type::Reactions); + Expects(type == Type::Reactions || type == Type::PollVotes); + if (type == Type::PollVotes) { + return Data::SublistUpdate::Flag::UnreadPollVotes; + } return Data::SublistUpdate::Flag::UnreadReactions; } @@ -58,6 +62,9 @@ void Proxy::setCount(int count) { createData(); } auto &list = resolveList(); + if (!count) { + list.clear(); + } const auto loaded = list.loadedCount(); if (loaded > count) { LOG(("API Warning: " @@ -65,10 +72,23 @@ void Proxy::setCount(int count) { count = loaded; } const auto had = (list.count() > 0); - const auto &other = (_type == Type::Mentions) - ? _data->reactions - : _data->mentions; - if (!count && other.count(-1) == 0) { + const auto othersEmpty = [&] { + for (auto t : { Type::Mentions, Type::Reactions, Type::PollVotes }) { + if (t == _type) { + continue; + } + const auto &other = (t == Type::Mentions) + ? _data->mentions + : (t == Type::Reactions) + ? _data->reactions + : _data->pollVotes; + if (other.count(-1) != 0) { + return false; + } + } + return true; + }(); + if (!count && othersEmpty) { _data = nullptr; } else { list.setCount(count); @@ -79,6 +99,8 @@ void Proxy::setCount(int count) { _thread->hasUnreadMentionChanged(has); } else if (_type == Type::Reactions) { _thread->hasUnreadReactionChanged(has); + } else if (_type == Type::PollVotes) { + _thread->hasUnreadPollVoteChanged(has); } } } @@ -183,10 +205,14 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { message, localFlags, type); + if (_type == Type::PollVotes) { + item->setHasUnreadPollVote(); + } const auto is = [&] { switch (_type) { case Type::Mentions: return item->isUnreadMention(); case Type::Reactions: return item->hasUnreadReaction(); + case Type::PollVotes: return item->hasUnreadPollVote(); } Unexpected("Type in Proxy::addSlice."); }(); @@ -203,7 +229,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { } void Proxy::checkAdd(MsgId msgId, bool resolved) { - Expects(_type == Type::Reactions); + Expects(_type == Type::Reactions || _type == Type::PollVotes); if (!_data) { return; @@ -215,11 +241,19 @@ void Proxy::checkAdd(MsgId msgId, bool resolved) { const auto history = _thread->owningHistory(); const auto peer = history->peer; const auto item = peer->owner().message(peer, msgId); - if (item && item->hasUnreadReaction()) { + const auto matches = item + && ((_type == Type::Reactions) ? item->hasUnreadReaction() + : item->hasUnreadPollVote()); + if (matches) { item->addToUnreadThings(AddType::Existing); } else if (!item && !resolved) { + const auto type = _type; peer->session().api().requestMessageData(peer, msgId, [=] { - history->unreadReactions().checkAdd(msgId, true); + if (type == Type::Reactions) { + history->unreadReactions().checkAdd(msgId, true); + } else { + history->unreadPollVotes().checkAdd(msgId, true); + } }); } } @@ -245,6 +279,7 @@ void Proxy::createData() { if (_known) { _data->mentions.setCount(0); _data->reactions.setCount(0); + _data->pollVotes.setCount(0); } } @@ -254,6 +289,7 @@ List &Proxy::resolveList() { switch (_type) { case Type::Mentions: return _data->mentions; case Type::Reactions: return _data->reactions; + case Type::PollVotes: return _data->pollVotes; } Unexpected("Unread things type in Proxy::resolveList."); } diff --git a/Telegram/SourceFiles/history/history_unread_things.h b/Telegram/SourceFiles/history/history_unread_things.h index a8b874d11d..493b46c513 100644 --- a/Telegram/SourceFiles/history/history_unread_things.h +++ b/Telegram/SourceFiles/history/history_unread_things.h @@ -23,6 +23,7 @@ enum class AddType { enum class Type { Mentions, Reactions, + PollVotes, }; class List final { @@ -70,6 +71,7 @@ private: struct All { List mentions; List reactions; + List pollVotes; }; class ConstProxy { @@ -117,7 +119,9 @@ public: ? nullptr : (type == Type::Mentions) ? &data->mentions - : &data->reactions), + : (type == Type::Reactions) + ? &data->reactions + : &data->pollVotes), known) , _thread(thread) , _data(data) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 96c929ac0b..da0f63cfba 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -715,6 +715,7 @@ HistoryWidget::HistoryWidget( | HistoryUpdateFlag::CloudDraft | HistoryUpdateFlag::UnreadMentions | HistoryUpdateFlag::UnreadReactions + | HistoryUpdateFlag::UnreadPollVotes | HistoryUpdateFlag::UnreadView | HistoryUpdateFlag::TopPromoted | HistoryUpdateFlag::ClientSideMessages @@ -735,7 +736,8 @@ HistoryWidget::HistoryWidget( updateSendButtonType(); } if ((flags & HistoryUpdateFlag::UnreadMentions) - || (flags & HistoryUpdateFlag::UnreadReactions)) { + || (flags & HistoryUpdateFlag::UnreadReactions) + || (flags & HistoryUpdateFlag::UnreadPollVotes)) { _cornerButtons.updateUnreadThingsVisibility(); } if (flags & HistoryUpdateFlag::UnreadView) { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 7b36e69aa7..65f8778f9b 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -635,9 +635,13 @@ void ChatWidget::subscribeToTopic() { _topic, (Flag::UnreadMentions | Flag::UnreadReactions + | Flag::UnreadPollVotes | Flag::CloudDraft) ) | rpl::on_next([=](const Data::TopicUpdate &update) { - if (update.flags & (Flag::UnreadMentions | Flag::UnreadReactions)) { + if (update.flags + & (Flag::UnreadMentions + | Flag::UnreadReactions + | Flag::UnreadPollVotes)) { _cornerButtons.updateUnreadThingsVisibility(); } if (update.flags & Flag::CloudDraft) { @@ -2589,12 +2593,16 @@ void ChatWidget::subscribeToSublist() { using Flag = Data::SublistUpdate::Flag; session().changes().sublistUpdates( _sublist, - Flag::UnreadView | Flag::UnreadReactions | Flag::CloudDraft + (Flag::UnreadView + | Flag::UnreadReactions + | Flag::UnreadPollVotes + | Flag::CloudDraft) ) | rpl::on_next([=](const Data::SublistUpdate &update) { if (update.flags & Flag::UnreadView) { unreadCountUpdated(); } - if (update.flags & Flag::UnreadReactions) { + if (update.flags + & (Flag::UnreadReactions | Flag::UnreadPollVotes)) { _cornerButtons.updateUnreadThingsVisibility(); } if (update.flags & Flag::CloudDraft) { diff --git a/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp index 8d9d7fed2d..270979e778 100644 --- a/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp +++ b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp @@ -68,10 +68,14 @@ CornerButtons::CornerButtons( st->value(_stLifetime, st::historyUnreadMentions)) , _reactions( parent, - st->value(_stLifetime, st::historyUnreadReactions)) { + st->value(_stLifetime, st::historyUnreadReactions)) +, _pollVotes( + parent, + st->value(_stLifetime, st::historyUnreadPollVotes)) { _down.widget->addClickHandler([=] { downClick(); }); _mentions.widget->addClickHandler([=] { mentionsClick(); }); _reactions.widget->addClickHandler([=] { reactionsClick(); }); + _pollVotes.widget->addClickHandler([=] { pollVotesClick(); }); const auto filterScroll = [&](CornerButton &button) { button.widget->installEventFilter(this); @@ -79,6 +83,7 @@ CornerButtons::CornerButtons( filterScroll(_down); filterScroll(_mentions); filterScroll(_reactions); + filterScroll(_pollVotes); SendMenu::SetupUnreadMentionsMenu(_mentions.widget.data(), [=] { return _delegate->cornerButtonsThread(); @@ -86,13 +91,17 @@ CornerButtons::CornerButtons( SendMenu::SetupUnreadReactionsMenu(_reactions.widget.data(), [=] { return _delegate->cornerButtonsThread(); }); + SendMenu::SetupUnreadPollVotesMenu(_pollVotes.widget.data(), [=] { + return _delegate->cornerButtonsThread(); + }); } bool CornerButtons::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Wheel && (o == _down.widget || o == _mentions.widget - || o == _reactions.widget)) { + || o == _reactions.widget + || o == _pollVotes.widget)) { return _scrollViewportEvent(e); } return QObject::eventFilter(o, e); @@ -142,6 +151,15 @@ void CornerButtons::reactionsClick() { showAt(thread->unreadReactions().minLoaded()); } +void CornerButtons::pollVotesClick() { + const auto history = lookupHistory(); + if (!history) { + return; + } + const auto thread = _delegate->cornerButtonsThread(); + showAt(thread->unreadPollVotes().minLoaded()); +} + void CornerButtons::clearReplyReturns() { _replyReturns.clear(); _replyReturn = nullptr; @@ -208,6 +226,7 @@ CornerButton &CornerButtons::buttonByType(Type type) { case Type::Down: return _down; case Type::Mentions: return _mentions; case Type::Reactions: return _reactions; + case Type::PollVotes: return _pollVotes; } Unexpected("Type in CornerButtons::buttonByType."); } @@ -245,6 +264,7 @@ void CornerButtons::updateUnreadThingsVisibility() { if (!thread) { updateVisibility(Type::Mentions, false); updateVisibility(Type::Reactions, false); + updateVisibility(Type::PollVotes, false); return; } auto &unreadThings = thread->session().api().unreadThings(); @@ -278,6 +298,18 @@ void CornerButtons::updateUnreadThingsVisibility() { } else { updateVisibility(Type::Reactions, false); } + + if (_delegate->cornerButtonsHas(Type::PollVotes) + && unreadThings.trackPollVotes(thread)) { + if (const auto count = thread->unreadPollVotes().count(0)) { + _pollVotes.widget->setUnreadCount(count); + } + updateWithCount( + Type::PollVotes, + thread->unreadPollVotes().loadedCount()); + } else { + updateVisibility(Type::PollVotes, false); + } } void CornerButtons::updateJumpDownVisibility(std::optional counter) { @@ -306,6 +338,7 @@ void CornerButtons::updatePositions() { const auto historyDownShown = shown(_down); const auto unreadMentionsShown = shown(_mentions); const auto unreadReactionsShown = shown(_reactions); + const auto unreadPollVotesShown = shown(_pollVotes); const auto skip = st::historyUnreadThingsSkip; { const auto top = anim::interpolate( @@ -350,16 +383,41 @@ void CornerButtons::updatePositions() { - shift; _reactions.widget->moveToRight(right, top); } + { + const auto right = anim::interpolate( + -_pollVotes.widget->width(), + st::historyToDownPosition.x(), + unreadPollVotesShown); + const auto shift = anim::interpolate( + 0, + _down.widget->height() + skip, + historyDownShown + ) + anim::interpolate( + 0, + _mentions.widget->height() + skip, + unreadMentionsShown + ) + anim::interpolate( + 0, + _reactions.widget->height() + skip, + unreadReactionsShown); + const auto top = _parent->height() + - _pollVotes.widget->height() + - st::historyToDownPosition.y() + - shift; + _pollVotes.widget->moveToRight(right, top); + } checkVisibility(_down); checkVisibility(_mentions); checkVisibility(_reactions); + checkVisibility(_pollVotes); } void CornerButtons::finishAnimations() { _down.animation.stop(); _mentions.animation.stop(); _reactions.animation.stop(); + _pollVotes.animation.stop(); updatePositions(); } diff --git a/Telegram/SourceFiles/history/view/history_view_corner_buttons.h b/Telegram/SourceFiles/history/view/history_view_corner_buttons.h index ae7b5b110c..7d3d5aae2b 100644 --- a/Telegram/SourceFiles/history/view/history_view_corner_buttons.h +++ b/Telegram/SourceFiles/history/view/history_view_corner_buttons.h @@ -42,6 +42,7 @@ enum class CornerButtonType { Down, Mentions, Reactions, + PollVotes, }; class CornerButtonsDelegate { @@ -72,6 +73,7 @@ public: void downClick(); void mentionsClick(); void reactionsClick(); + void pollVotesClick(); void clearReplyReturns(); [[nodiscard]] QVector replyReturns() const; @@ -119,6 +121,7 @@ private: CornerButton _down; CornerButton _mentions; CornerButton _reactions; + CornerButton _pollVotes; HistoryItem *_replyReturn = nullptr; QVector _replyReturns; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 47e5ba184b..29262304b1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -268,7 +268,8 @@ bool PinnedWidget::cornerButtonsUnreadMayBeShown() { } bool PinnedWidget::cornerButtonsHas(CornerButtonType type) { - return (type == CornerButtonType::Down); + return (type == CornerButtonType::Down) + || (type == CornerButtonType::PollVotes); } void PinnedWidget::showAtPosition( diff --git a/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp index f9cd50bd62..b41cc76d2f 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp @@ -722,7 +722,8 @@ bool ListWidget::Inner::cornerButtonsUnreadMayBeShown() { bool ListWidget::Inner::cornerButtonsHas( HistoryView::CornerButtonType type) { - return (type == HistoryView::CornerButtonType::Down); + return (type == HistoryView::CornerButtonType::Down) + || (type == HistoryView::CornerButtonType::PollVotes); } // --- ListMemento --- diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 8f423fa3a7..6add7bccb9 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -1028,4 +1028,46 @@ void SetupUnreadReactionsMenu( SetupReadAllMenu(button, currentThread, text, sendRequest); } +void SetupUnreadPollVotesMenu( + not_null button, + Fn currentThread) { + const auto text = tr::lng_context_mark_read_poll_votes_all(tr::now); + const auto sendOne = [=]( + base::weak_ptr weakThread, + Fn done, + auto resend) -> void { + const auto thread = weakThread.get(); + if (!thread) { + done(); + return; + } + const auto topic = thread->asTopic(); + const auto peer = thread->peer(); + const auto rootId = topic ? topic->rootId() : 0; + using Flag = MTPmessages_ReadPollVotes::Flag; + peer->session().api().request(MTPmessages_ReadPollVotes( + MTP_flags(rootId ? Flag::f_top_msg_id : Flag(0)), + peer->input(), + MTP_int(rootId) + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = peer->session().api().applyAffectedHistory( + peer, + result); + if (offset > 0) { + resend(weakThread, done, resend); + } else { + done(); + peer->owner().history(peer)->clearUnreadPollVotesFor( + rootId); + } + }).fail(done).send(); + }; + const auto sendRequest = [=]( + not_null thread, + Fn done) { + sendOne(base::make_weak(thread), std::move(done), sendOne); + }; + SetupReadAllMenu(button, currentThread, text, sendRequest); +} + } // namespace SendMenu diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 993dbffd1a..d748a57523 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -127,4 +127,8 @@ void SetupUnreadReactionsMenu( not_null button, Fn currentThread); +void SetupUnreadPollVotesMenu( + not_null button, + Fn currentThread); + } // namespace SendMenu