From 6a7f030ee7cc527aa25b9e9c8e8b6f94c1466c1c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 8 Oct 2022 15:14:38 +0400 Subject: [PATCH] Update API scheme on layer 148. Extract message history corner buttons code. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/tl/api.tl | 8 +- .../SourceFiles/api/api_unread_things.cpp | 20 +- Telegram/SourceFiles/api/api_unread_things.h | 4 +- Telegram/SourceFiles/data/data_folder.h | 2 +- Telegram/SourceFiles/data/data_forum.cpp | 6 + Telegram/SourceFiles/data/data_forum.h | 1 + .../SourceFiles/data/data_search_controller.h | 12 +- Telegram/SourceFiles/data/data_types.h | 7 +- .../SourceFiles/dialogs/dialogs_entry.cpp | 8 + Telegram/SourceFiles/dialogs/dialogs_entry.h | 5 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 26 +- .../SourceFiles/export/export_api_wrap.cpp | 1 - Telegram/SourceFiles/history/history.cpp | 45 ++ Telegram/SourceFiles/history/history.h | 1 + Telegram/SourceFiles/history/history_item.h | 1 - .../SourceFiles/history/history_widget.cpp | 428 +++++------------- Telegram/SourceFiles/history/history_widget.h | 43 +- .../view/history_view_corner_buttons.cpp | 335 ++++++++++++++ .../view/history_view_corner_buttons.h | 110 +++++ Telegram/SourceFiles/mainwidget.cpp | 8 +- Telegram/SourceFiles/menu/menu_send.cpp | 111 +++-- Telegram/SourceFiles/menu/menu_send.h | 9 +- Telegram/lib_base | 2 +- 24 files changed, 751 insertions(+), 444 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_corner_buttons.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a26e68bc8..f21526288 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -690,6 +690,8 @@ PRIVATE history/view/history_view_contact_status.h history/view/history_view_context_menu.cpp history/view/history_view_context_menu.h + history/view/history_view_corner_buttons.cpp + history/view/history_view_corner_buttons.h history/view/history_view_cursor_state.cpp history/view/history_view_cursor_state.h history/view/history_view_element.cpp diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 9897977ab..d2f3f423e 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -222,6 +222,7 @@ inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer; inputNotifyChats#4a95e84e = InputNotifyPeer; inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer; +inputNotifyForumTopic#5c467992 peer:InputPeer top_msg_id:int = InputNotifyPeer; inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings; @@ -1454,7 +1455,7 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username; -forumTopic#18a9a864 flags:# my:flags.1?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer = ForumTopic; +forumTopic#5920d6dc flags:# my:flags.1?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings = ForumTopic; messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector pts:int = messages.ForumTopics; @@ -1704,7 +1705,7 @@ messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference; messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference; messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector = Vector; messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; -messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector; +messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector = Vector; messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; @@ -1754,7 +1755,7 @@ messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText; messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; -messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory; +messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages; messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; @@ -1865,6 +1866,7 @@ channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string ico channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics; channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = messages.ForumTopics; channels.editForumTopic#8a7f464b flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long = Updates; +channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index 2a4ced220..9fc076973 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -41,24 +41,22 @@ constexpr auto kNextRequestLimit = 100; UnreadThings::UnreadThings(not_null api) : _api(api) { } -bool UnreadThings::trackMentions(PeerData *peer) const { +bool UnreadThings::trackMentions(DialogsEntry *entry) const { + const auto peer = entry ? ResolveHistory(entry)->peer.get() : nullptr; return peer && (peer->isChat() || peer->isMegagroup()); } -bool UnreadThings::trackReactions(PeerData *peer) const { - return trackMentions(peer) || (peer && peer->isUser()); +bool UnreadThings::trackReactions(DialogsEntry *entry) const { + const auto peer = entry ? ResolveHistory(entry)->peer.get() : nullptr; + return peer && (peer->isChat() || peer->isMegagroup()); } void UnreadThings::preloadEnough(DialogsEntry *entry) { - if (!entry) { - return; + if (trackMentions(entry)) { + preloadEnoughMentions(entry); } - const auto history = ResolveHistory(entry); - if (trackMentions(history->peer)) { - preloadEnoughMentions(history); - } - if (trackReactions(history->peer)) { - preloadEnoughReactions(history); + if (trackReactions(entry)) { + preloadEnoughReactions(entry); } } diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h index 2e8563d48..f02b86368 100644 --- a/Telegram/SourceFiles/api/api_unread_things.h +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -23,8 +23,8 @@ public: explicit UnreadThings(not_null api); - [[nodiscard]] bool trackMentions(PeerData *peer) const; - [[nodiscard]] bool trackReactions(PeerData *peer) const; + [[nodiscard]] bool trackMentions(DialogsEntry *entry) const; + [[nodiscard]] bool trackReactions(DialogsEntry *entry) const; void preloadEnough(DialogsEntry *entry); diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h index 97d46f710..dd18c6d67 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -22,7 +22,7 @@ namespace Data { class Session; -class Folder final : public Dialogs::Entry, public base::has_weak_ptr { +class Folder final : public Dialogs::Entry { public: static constexpr auto kId = 1; diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 29cd4a675..7e184988f 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -239,6 +239,12 @@ void Forum::clearAllUnreadMentions() { } } +void Forum::clearAllUnreadReactions() { + for (const auto &[rootId, topic] : _topics) { + topic->unreadReactions().clear(); + } +} + ForumTopic *Forum::topicFor(not_null item) { const auto maybe = topicFor(item->replyToTop()); return maybe ? maybe : topicFor(item->topicRootId()); diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index f7ab2c882..354f15e6b 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -60,6 +60,7 @@ public: void created(MsgId rootId, MsgId realId); void clearAllUnreadMentions(); + void clearAllUnreadReactions(); private: struct TopicRequest { diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index 27f0904d2..cd937cf31 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_sparse_ids.h" #include "storage/storage_sparse_ids_list.h" #include "storage/storage_shared_media.h" -#include "base/value_ordering.h" #include "base/timer.h" +#include "base/qt/qt_compare.h" namespace Main { class Session; @@ -58,13 +58,9 @@ public: QString query; // from_id, min_date, max_date - friend inline auto value_ordering_helper(const Query &value) { - return std::tie( - value.peerId, - value.migratedPeerId, - value.type, - value.query); - } + friend inline std::strong_ordering operator<=>( + const Query &a, + const Query &b) noexcept = default; }; struct SavedState { diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 3896ad2c6..33fdd956d 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/value_ordering.h" #include "ui/text/text.h" // For QFIXED_MAX #include "data/data_peer_id.h" #include "data/data_msg_id.h" @@ -84,9 +83,9 @@ struct MessageGroupId { return value; } - friend inline std::pair value_ordering_helper(MessageGroupId value) { - return std::make_pair(value.value, value.peer.value); - } + friend inline constexpr auto operator<=>( + MessageGroupId, + MessageGroupId) noexcept = default; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 246dfd8b5..af0320487 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -131,6 +131,14 @@ const base::flat_set &Entry::unreadMentionsIds() const { return _unreadThings->mentions.ids(); } +const base::flat_set &Entry::unreadReactionsIds() const { + if (!_unreadThings) { + static const auto Result = base::flat_set(); + return Result; + } + return _unreadThings->reactions.ids(); +} + bool Entry::needUpdateInChatList() const { return inChatList() || shouldBeInChatList(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 73ccb4b47..8cc353d05 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/flat_map.h" - +#include "base/weak_ptr.h" #include "dialogs/dialogs_key.h" #include "ui/unread_badge.h" @@ -105,7 +105,7 @@ inline UnreadState operator-(const UnreadState &a, const UnreadState &b) { return result; } -class Entry { +class Entry : public base::has_weak_ptr { public: enum class Type : uchar { History, @@ -223,6 +223,7 @@ protected: void cacheTopPromoted(bool promoted); [[nodiscard]] const base::flat_set &unreadMentionsIds() const; + [[nodiscard]] const base::flat_set &unreadReactionsIds() const; private: enum class Flag : uchar { diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index af91450f0..2bb5f2401 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -7,8 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/value_ordering.h" - class History; class PeerData; @@ -45,29 +43,7 @@ public: History *parentHistory() const; PeerData *peer() const; - inline bool operator<(const Key &other) const { - return _value < other._value; - } - inline bool operator>(const Key &other) const { - return (other < *this); - } - inline bool operator<=(const Key &other) const { - return !(other < *this); - } - inline bool operator>=(const Key &other) const { - return !(*this < other); - } - inline bool operator==(const Key &other) const { - return _value == other._value; - } - inline bool operator!=(const Key &other) const { - return !(*this == other); - } - - // Not working :( - //friend inline auto value_ordering_helper(const Key &key) { - // return key.value; - //} + friend inline constexpr auto operator<=>(Key, Key) noexcept = default; private: Entry *_value = nullptr; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 45e9e0665..6910955a0 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "export/output/export_output_result.h" #include "export/output/export_output_file.h" #include "mtproto/mtproto_response.h" -#include "base/value_ordering.h" #include "base/bytes.h" #include "base/random.h" #include diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index faa410500..bde7d36cd 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -700,6 +700,18 @@ not_null History::addNewLocalMessage( } void History::clearUnreadMentionsFor(MsgId topicRootId) { + const auto forum = peer->forum(); + if (!topicRootId) { + if (forum) { + forum->clearAllUnreadMentions(); + } + unreadMentions().clear(); + return; + } else if (forum) { + if (const auto topic = forum->topicFor(topicRootId)) { + topic->unreadMentions().clear(); + } + } const auto &ids = unreadMentionsIds(); if (ids.empty()) { return; @@ -720,6 +732,39 @@ void History::clearUnreadMentionsFor(MsgId topicRootId) { } } +void History::clearUnreadReactionsFor(MsgId topicRootId) { + const auto forum = peer->forum(); + if (!topicRootId) { + if (forum) { + forum->clearAllUnreadReactions(); + } + unreadReactions().clear(); + return; + } else if (forum) { + if (const auto topic = forum->topicFor(topicRootId)) { + topic->unreadReactions().clear(); + } + } + const auto &ids = unreadReactionsIds(); + 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) { + unreadReactions().erase(id); + } +} + not_null History::addNewToBack( not_null item, bool unread) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 15d842bdb..f164a421d 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -331,6 +331,7 @@ public: void clearLastKeyboard(); void clearUnreadMentionsFor(MsgId topicRootId); + void clearUnreadReactionsFor(MsgId topicRootId); Data::Draft *draft(Data::DraftKey key) const; void setDraft(Data::DraftKey key, std::unique_ptr &&draft); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index db91f82aa..544020879 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/runtime_composer.h" #include "base/flags.h" -#include "base/value_ordering.h" #include "data/data_media_types.h" #include "history/history_item_edition.h" #include "history/history_item_reply_markup.h" diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e6f5e7d0f..766ec38b9 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -228,15 +228,10 @@ HistoryWidget::HistoryWidget( controller->chatStyle()->value(lifetime(), st::historyScroll), false) , _updateHistoryItems([=] { updateHistoryItemsByTimer(); }) -, _historyDown( - _scroll, - controller->chatStyle()->value(lifetime(), st::historyToDown)) -, _unreadMentions( - _scroll, - controller->chatStyle()->value(lifetime(), st::historyUnreadMentions)) -, _unreadReactions( - _scroll, - controller->chatStyle()->value(lifetime(), st::historyUnreadReactions)) +, _cornerButtons( + _scroll.data(), + controller->chatStyle(), + static_cast(this)) , _fieldAutocomplete(this, controller) , _supportAutocomplete(session().supportMode() ? object_ptr(this, &session()) @@ -306,13 +301,6 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - _historyDown.widget->addClickHandler([=] { historyDownClicked(); }); - _unreadMentions.widget->addClickHandler([=] { - showNextUnreadMention(); - }); - _unreadReactions.widget->addClickHandler([=] { - showNextUnreadReaction(); - }); _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); }); _send->addClickHandler([=] { sendButtonClicked(); }); @@ -385,16 +373,6 @@ HistoryWidget::HistoryWidget( _scroll->updateBars(); }, lifetime()); - _historyDown.widget->installEventFilter(this); - _unreadMentions.widget->installEventFilter(this); - _unreadReactions.widget->installEventFilter(this); - SendMenu::SetupUnreadMentionsMenu(_unreadMentions.widget.data(), [=] { - return _history ? _history->peer.get() : nullptr; - }, MsgId(0)); - SendMenu::SetupUnreadReactionsMenu(_unreadReactions.widget.data(), [=] { - return _history ? _history->peer.get() : nullptr; - }); - InitMessageField(controller, _field, [=]( not_null document) { if (_peer && Data::AllowEmojiWithoutPremium(_peer)) { @@ -582,7 +560,7 @@ HistoryWidget::HistoryWidget( ) | rpl::filter([=](not_null channel) { return _peer == channel.get(); }) | rpl::start_with_next([=] { - updateHistoryDownVisibility(); + _cornerButtons.updateJumpDownVisibility(); preloadHistoryIfNeeded(); }, lifetime()); @@ -648,7 +626,7 @@ HistoryWidget::HistoryWidget( } if ((flags & HistoryUpdateFlag::UnreadMentions) || (flags & HistoryUpdateFlag::UnreadReactions)) { - updateUnreadThingsVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); } if (flags & HistoryUpdateFlag::UnreadView) { unreadCountUpdated(); @@ -1007,8 +985,8 @@ void HistoryWidget::initVoiceRecordBar() { _voiceRecordBar->lockShowStarts( ) | rpl::start_with_next([=] { - updateHistoryDownVisibility(); - updateUnreadThingsVisibility(); + _cornerButtons.updateJumpDownVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); }, lifetime()); _voiceRecordBar->updateSendButtonTypeRequests( @@ -1855,73 +1833,25 @@ void HistoryWidget::setupShortcuts() { }, lifetime()); } -void HistoryWidget::clearReplyReturns() { - _replyReturns.clear(); - _replyReturn = nullptr; -} - void HistoryWidget::pushReplyReturn(not_null item) { - if (item->history() == _history) { - _replyReturns.push_back(item->id); - } else if (item->history() == _migrated) { - _replyReturns.push_back(-item->id); - } else { + if (item->history() != _history && item->history() != _migrated) { return; } - _replyReturn = item; + _cornerButtons.pushReplyReturn(item); updateControlsVisibility(); } -QList HistoryWidget::replyReturns() { - return _replyReturns; +QVector HistoryWidget::replyReturns() const { + return _cornerButtons.replyReturns(); } -void HistoryWidget::setReplyReturns(PeerId peer, const QList &replyReturns) { - if (!_peer || _peer->id != peer) return; - - _replyReturns = replyReturns; - if (_replyReturns.isEmpty()) { - _replyReturn = nullptr; - } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { - _replyReturn = _migrated - ? session().data().message(_migrated->peer, -_replyReturns.back()) - : nullptr; - } else { - _replyReturn = session().data().message(peer, _replyReturns.back()); - } - while (!_replyReturns.isEmpty() && !_replyReturn) { - _replyReturns.pop_back(); - if (_replyReturns.isEmpty()) { - _replyReturn = nullptr; - } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { - _replyReturn = _migrated - ? session().data().message(_migrated->peer, -_replyReturns.back()) - : nullptr; - } else { - _replyReturn = session().data().message(peer, _replyReturns.back()); - } - } -} - -void HistoryWidget::calcNextReplyReturn() { - _replyReturn = nullptr; - while (!_replyReturns.isEmpty() && !_replyReturn) { - _replyReturns.pop_back(); - if (_replyReturns.isEmpty()) { - _replyReturn = nullptr; - } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) { - _replyReturn = _migrated - ? session().data().message(_migrated->peer, -_replyReturns.back()) - : nullptr; - } else { - _replyReturn = _peer - ? session().data().message(_peer, _replyReturns.back()) - : nullptr; - } - } - if (!_replyReturn) { - updateControlsVisibility(); +void HistoryWidget::setReplyReturns( + PeerId peer, + QVector replyReturns) { + if (!_peer || _peer->id != peer) { + return; } + _cornerButtons.setReplyReturns(std::move(replyReturns)); } void HistoryWidget::fastShowAtEnd(not_null history) { @@ -2074,14 +2004,13 @@ void HistoryWidget::showHistory( } clearDelayedShowAt(); - while (_replyReturn) { - if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) { - calcNextReplyReturn(); - } else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) { - calcNextReplyReturn(); - } else { - break; - } + const auto skipId = (_migrated && showAtMsgId < 0) + ? FullMsgId(_migrated->peer->id, -showAtMsgId) + : (showAtMsgId > 0) + ? FullMsgId(_history->peer->id, showAtMsgId) + : FullMsgId(); + if (skipId) { + _cornerButtons.skipReplyReturn(skipId); } setMsgId(showAtMsgId); @@ -2127,7 +2056,7 @@ void HistoryWidget::showHistory( session().sendProgressManager().cancelTyping(_history); } - clearReplyReturns(); + _cornerButtons.clearReplyReturns(); if (_history) { if (Ui::InFocusChain(_list)) { // Removing focus from list clears selected and updates top bar. @@ -2632,8 +2561,8 @@ void HistoryWidget::updateControlsVisibility() { _topShadow->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr); } - updateHistoryDownVisibility(); - updateUnreadThingsVisibility(); + _cornerButtons.updateJumpDownVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); if (!_history || _a_show.animating()) { hideChildWidgets(); return; @@ -2975,8 +2904,8 @@ void HistoryWidget::unreadCountUpdated() { } }); } else { - updateHistoryDownVisibility(); - _historyDown.widget->setUnreadCount(_history->chatListUnreadCount()); + _cornerButtons.updateJumpDownVisibility( + _history->chatListUnreadCount()); } } @@ -3123,16 +3052,13 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages firstLoadMessages(); return; } - while (_replyReturn) { - if (_replyReturn->history() == _history - && _replyReturn->id == _delayedShowAtMsgId) { - calcNextReplyReturn(); - } else if (_replyReturn->history() == _migrated - && -_replyReturn->id == _delayedShowAtMsgId) { - calcNextReplyReturn(); - } else { - break; - } + const auto skipId = (_migrated && _delayedShowAtMsgId < 0) + ? FullMsgId(_migrated->peer->id, -_delayedShowAtMsgId) + : (_delayedShowAtMsgId > 0) + ? FullMsgId(_history->peer->id, _delayedShowAtMsgId) + : FullMsgId(); + if (skipId) { + _cornerButtons.skipReplyReturn(skipId); } _delayedShowAtRequest = 0; @@ -3464,8 +3390,8 @@ void HistoryWidget::preloadHistoryIfNeeded() { return; } - updateHistoryDownVisibility(); - updateUnreadThingsVisibility(); + _cornerButtons.updateJumpDownVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); if (!_scrollToAnimation.animating()) { preloadHistoryByScroll(); checkReplyReturns(); @@ -3535,19 +3461,32 @@ void HistoryWidget::checkReplyReturns() { auto scrollTop = _scroll->scrollTop(); auto scrollTopMax = _scroll->scrollTopMax(); auto scrollHeight = _scroll->height(); - while (_replyReturn) { - auto below = (!_replyReturn->mainView() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->messages.back()->data()->id); + while (const auto replyReturn = _cornerButtons.replyReturn()) { + auto below = !replyReturn->mainView() + && (replyReturn->history() == _history) + && !_history->isEmpty() + && (replyReturn->id + < _history->blocks.back()->messages.back()->data()->id); if (!below) { - below = (!_replyReturn->mainView() && _replyReturn->history() == _migrated && !_history->isEmpty()); + below = !replyReturn->mainView() + && (replyReturn->history() == _migrated) + && !_history->isEmpty(); } if (!below) { - below = (!_replyReturn->mainView() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->messages.back()->data()->id); + below = !replyReturn->mainView() + && _migrated + && (replyReturn->history() == _migrated) + && !_migrated->isEmpty() + && (replyReturn->id + < _migrated->blocks.back()->messages.back()->data()->id); } - if (!below && _replyReturn->mainView()) { - below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2); + if (!below && replyReturn->mainView()) { + below = (scrollTop >= scrollTopMax) + || (_list->itemTop(replyReturn) + < scrollTop + scrollHeight / 2); } if (below) { - calcNextReplyReturn(); + _cornerButtons.calculateNextReplyReturn(); } else { break; } @@ -3574,46 +3513,6 @@ void HistoryWidget::windowIsVisibleChanged() { }); } -void HistoryWidget::historyDownClicked() { - if (base::IsCtrlPressed()) { - showHistory(_peer->id, ShowAtUnreadMsgId); - } else if (_replyReturn && _replyReturn->history() == _history) { - showHistory(_peer->id, _replyReturn->id); - } else if (_replyReturn && _replyReturn->history() == _migrated) { - showHistory(_peer->id, -_replyReturn->id); - } else if (_peer) { - showHistory(_peer->id, ShowAtUnreadMsgId); - } -} - -void HistoryWidget::showNextUnreadMention() { - const auto msgId = _history->unreadMentions().minLoaded(); - const auto already = (_showAtMsgId == msgId); - - // Mark mention voice/video message as read. - // See https://github.com/telegramdesktop/tdesktop/issues/5623 - if (msgId && already) { - const auto item = _history->owner().message( - _history->peer->id, - msgId); - if (const auto media = item ? item->media() : nullptr) { - if (const auto document = media->document()) { - if (!media->webpage() - && (document->isVoiceMessage() - || document->isVideoMessage())) { - document->owner().markMediaRead(document); - } - } - } - } - showHistory(_peer->id, msgId); -} - -void HistoryWidget::showNextUnreadReaction() { - const auto msgId = _history->unreadReactions().minLoaded(); - showHistory(_peer->id, msgId); -} - void HistoryWidget::saveEditMsg() { Expects(_history != nullptr); @@ -3771,7 +3670,7 @@ void HistoryWidget::send(Api::SendOptions options) { } if (!options.scheduled) { - clearReplyReturns(); + _cornerButtons.clearReplyReturns(); } const auto webPageId = (_previewState != Data::PreviewState::Allowed) @@ -3965,7 +3864,7 @@ void HistoryWidget::showAnimated( _preserveScrollTop = true; show(); _topBar->finishAnimating(); - cornerButtonsAnimationFinish(); + _cornerButtons.finishAnimations(); if (_pinnedBar) { _pinnedBar->finishAnimating(); } @@ -3999,7 +3898,7 @@ void HistoryWidget::showAnimated( void HistoryWidget::animationCallback() { update(); if (!_a_show.animating()) { - cornerButtonsAnimationFinish(); + _cornerButtons.finishAnimations(); if (_pinnedBar) { _pinnedBar->finishAnimating(); } @@ -4044,6 +3943,29 @@ void HistoryWidget::doneShow() { checkSuggestToGigagroup(); } +void HistoryWidget::cornerButtonsShowAtPosition( + Data::MessagePosition position) { + if (position == Data::UnreadMessagePosition) { + showHistory(_peer->id, ShowAtUnreadMsgId); + } else if (_peer && position.fullId.peer == _peer->id) { + showHistory(_peer->id, position.fullId.msg); + } else if (_migrated && position.fullId.peer == _migrated->peer->id) { + showHistory(_peer->id, -position.fullId.msg); + } +} + +Dialogs::Entry *HistoryWidget::cornerButtonsEntry() { + return _history; +} + +FullMsgId HistoryWidget::cornerButtonsCurrentId() { + return (_migrated && _showAtMsgId < 0) + ? FullMsgId(_migrated->peer->id, -_showAtMsgId) + : (_history && _showAtMsgId > 0) + ? FullMsgId(_history->peer->id, _showAtMsgId) + : FullMsgId(); +} + void HistoryWidget::checkSuggestToGigagroup() { const auto group = _peer ? _peer->asMegagroup() : nullptr; if (!group || !group->owner().suggestToGigagroup(group)) { @@ -4080,14 +4002,7 @@ void HistoryWidget::finishAnimating() { _a_show.stop(); _topShadow->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr); - cornerButtonsAnimationFinish(); -} - -void HistoryWidget::cornerButtonsAnimationFinish() { - _historyDown.animation.stop(); - _unreadMentions.animation.stop(); - _unreadReactions.animation.stop(); - updateCornerButtonsPositions(); + _cornerButtons.finishAnimations(); } void HistoryWidget::chooseAttach( @@ -4320,12 +4235,6 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) { } } } - if (e->type() == QEvent::Wheel - && (obj == _historyDown.widget - || obj == _unreadMentions.widget - || obj == _unreadReactions.widget)) { - return _scroll->viewportEvent(e); - } return TWidget::eventFilter(obj, e); } @@ -5296,7 +5205,7 @@ void HistoryWidget::updateControlsGeometry() { updateFieldSize(); - updateCornerButtonsPositions(); + _cornerButtons.updatePositions(); if (_membersDropdown) { _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); @@ -5324,8 +5233,8 @@ void HistoryWidget::itemRemoved(not_null item) { if (item == _replyEditMsg && _replyToId) { cancelReply(); } - while (item == _replyReturn) { - calcNextReplyReturn(); + while (item == _cornerButtons.replyReturn()) { + _cornerButtons.calculateNextReplyReturn(); } if (_kbReplyTo && item == _kbReplyTo) { toggleKeyboard(); @@ -5514,7 +5423,7 @@ void HistoryWidget::updateHistoryGeometry( if (_supportAutocomplete) { _supportAutocomplete->setBoundings(_scroll->geometry()); } - updateCornerButtonsPositions(); + _cornerButtons.updatePositions(); controller()->floatPlayerAreaUpdated(); } @@ -5856,75 +5765,24 @@ int HistoryWidget::computeMaxFieldHeight() const { return std::min(st::historyComposeFieldMaxHeight, available); } -void HistoryWidget::updateCornerButtonsPositions() { - const auto checkVisibility = [](CornerButton &button) { - const auto shouldBeHidden = !button.shown - && !button.animation.animating(); - if (shouldBeHidden != button.widget->isHidden()) { - button.widget->setVisible(!shouldBeHidden); - } - }; - const auto shown = [](CornerButton &button) { - return button.animation.value(button.shown ? 1. : 0.); - }; - - // All corner buttons is a child widgets of _scroll, not me. - - const auto historyDownShown = shown(_historyDown); - const auto unreadMentionsShown = shown(_unreadMentions); - const auto unreadReactionsShown = shown(_unreadReactions); - const auto skip = st::historyUnreadThingsSkip; - { - const auto top = anim::interpolate( - 0, - _historyDown.widget->height() + st::historyToDownPosition.y(), - historyDownShown); - _historyDown.widget->moveToRight( - st::historyToDownPosition.x(), - _scroll->height() - top); - } - { - const auto right = anim::interpolate( - -_unreadMentions.widget->width(), - st::historyToDownPosition.x(), - unreadMentionsShown); - const auto shift = anim::interpolate( - 0, - _historyDown.widget->height() + skip, - historyDownShown); - const auto top = _scroll->height() - - _unreadMentions.widget->height() - - st::historyToDownPosition.y() - - shift; - _unreadMentions.widget->moveToRight(right, top); - } - { - const auto right = anim::interpolate( - -_unreadReactions.widget->width(), - st::historyToDownPosition.x(), - unreadReactionsShown); - const auto shift = anim::interpolate( - 0, - _historyDown.widget->height() + skip, - historyDownShown - ) + anim::interpolate( - 0, - _unreadMentions.widget->height() + skip, - unreadMentionsShown); - const auto top = _scroll->height() - - _unreadReactions.widget->height() - - st::historyToDownPosition.y() - - shift; - _unreadReactions.widget->moveToRight(right, top); - } - - checkVisibility(_historyDown); - checkVisibility(_unreadMentions); - checkVisibility(_unreadReactions); +bool HistoryWidget::cornerButtonsIgnoreVisibility() { + return _a_show.animating(); } -void HistoryWidget::updateHistoryDownVisibility() { - if (_a_show.animating()) return; +bool HistoryWidget::cornerButtonsDownShown() { + if (!_list || _firstLoadRequest) { + return false; + } + if (_voiceRecordBar->isLockPresent()) { + return false; + } + if (!_history->loadedAtBottom() || _cornerButtons.replyReturn()) { + return true; + } + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax()) { + return true; + } const auto haveUnreadBelowBottom = [&](History *history) { if (!_list || !history || history->unreadCount() <= 0) { @@ -5937,75 +5795,15 @@ void HistoryWidget::updateHistoryDownVisibility() { const auto top = _list->itemTop(unread); return (top >= _scroll->scrollTop() + _scroll->height()); }; - updateCornerButtonVisibility(_historyDown, [&] { - if (!_list || _firstLoadRequest) { - return false; - } - if (_voiceRecordBar->isLockPresent()) { - return false; - } - if (!_history->loadedAtBottom() || _replyReturn) { - return true; - } - const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; - if (top < _scroll->scrollTopMax()) { - return true; - } - if (haveUnreadBelowBottom(_history) - || haveUnreadBelowBottom(_migrated)) { - return true; - } - return false; - }()); + if (haveUnreadBelowBottom(_history) + || haveUnreadBelowBottom(_migrated)) { + return true; + } + return false; } -void HistoryWidget::updateCornerButtonVisibility( - CornerButton &button, - bool shown) { - if (button.shown != shown) { - button.shown = shown; - button.animation.start( - [=] { updateCornerButtonsPositions(); }, - shown ? 0. : 1., - shown ? 1. : 0., - st::historyToDownDuration); - } -} - -void HistoryWidget::updateUnreadThingsVisibility() { - if (_a_show.animating()) { - return; - } - - auto &unreadThings = session().api().unreadThings(); - unreadThings.preloadEnough(_history); - - const auto updateWithLoadedCount = [&](CornerButton &button, int count) { - updateCornerButtonVisibility(button, (count > 0) - && !_firstLoadRequest - && !_voiceRecordBar->isLockPresent()); - }; - if (unreadThings.trackMentions(_peer)) { - if (const auto count = _history->unreadMentions().count(0)) { - _unreadMentions.widget->setUnreadCount(count); - } - updateWithLoadedCount( - _unreadMentions, - _history->unreadMentions().loadedCount()); - } else { - updateCornerButtonVisibility(_unreadMentions, false); - } - - if (unreadThings.trackReactions(_peer)) { - if (const auto count = _history->unreadReactions().count(0)) { - _unreadReactions.widget->setUnreadCount(count); - } - updateWithLoadedCount( - _unreadReactions, - _history->unreadReactions().loadedCount()); - } else { - updateCornerButtonVisibility(_unreadReactions, false); - } +bool HistoryWidget::cornerButtonsUnreadMayBeShown() { + return !_firstLoadRequest && !_voiceRecordBar->isLockPresent(); } void HistoryWidget::mousePressEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index f3fa55d36..e296c3eb2 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "history/view/history_view_corner_buttons.h" #include "history/history_drag_area.h" #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" @@ -107,7 +108,9 @@ class HistoryInner; extern const char kOptionAutoScrollInactiveChat[]; -class HistoryWidget final : public Window::AbstractSectionWidget { +class HistoryWidget final + : public Window::AbstractSectionWidget + , private HistoryView::CornerButtonsDelegate { public: using FieldHistoryAction = Ui::InputField::HistoryAction; using RecordLock = HistoryView::Controls::RecordLock; @@ -193,11 +196,9 @@ public: void updateForwarding(); void updateForwardingTexts(); - void clearReplyReturns(); void pushReplyReturn(not_null item); - QList replyReturns(); - void setReplyReturns(PeerId peer, const QList &replyReturns); - void calcNextReplyReturn(); + [[nodiscard]] QVector replyReturns() const; + void setReplyReturns(PeerId peer, QVector replyReturns); void updatePreview(); void previewCancel(); @@ -321,15 +322,15 @@ private: }; using TextUpdateEvents = base::flags; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; - struct CornerButton { - template - CornerButton(Args &&...args) : widget(std::forward(args)...) { - } - Ui::Animations::Simple animation; - bool shown = false; - object_ptr widget; - }; + void cornerButtonsShowAtPosition( + Data::MessagePosition position) override; + Dialogs::Entry *cornerButtonsEntry() override; + FullMsgId cornerButtonsCurrentId() override; + bool cornerButtonsIgnoreVisibility() override; + bool cornerButtonsDownShown() override; + bool cornerButtonsUnreadMayBeShown() override; + void checkSuggestToGigagroup(); void initTabbedSelector(); @@ -380,9 +381,6 @@ private: void fullInfoUpdated(); void toggleTabbedSelectorMode(); void recountChatWidth(); - void historyDownClicked(); - void showNextUnreadMention(); - void showNextUnreadReaction(); void handlePeerUpdate(); void setMembersShowAreaActive(bool active); void handleHistoryChange(not_null history); @@ -410,16 +408,10 @@ private: void animationCallback(); void updateOverStates(QPoint pos); void chooseAttach(std::optional overrideSendImagesAsPhotos = {}); - void cornerButtonsAnimationFinish(); void sendButtonClicked(); void newItemAdded(not_null item); void maybeMarkReactionsRead(not_null item); - void updateCornerButtonsPositions(); - void updateHistoryDownVisibility(); - void updateUnreadThingsVisibility(); - void updateCornerButtonVisibility(CornerButton &button, bool shown); - bool canSendFiles(not_null data) const; bool confirmSendingFiles( const QStringList &files, @@ -665,9 +657,6 @@ private: bool _replyForwardPressed = false; - HistoryItem *_replyReturn = nullptr; - QList _replyReturns; - PeerData *_peer = nullptr; bool _canSendMessages = false; @@ -701,9 +690,7 @@ private: bool _synteticScrollEvent = false; Ui::Animations::Simple _scrollToAnimation; - CornerButton _historyDown; - CornerButton _unreadMentions; - CornerButton _unreadReactions; + HistoryView::CornerButtons _cornerButtons; const object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; diff --git a/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp new file mode 100644 index 000000000..ab70d9e70 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp @@ -0,0 +1,335 @@ +/* +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 "history/view/history_view_corner_buttons.h" + +#include "ui/widgets/scroll_area.h" +#include "ui/chat/chat_style.h" +#include "ui/special_buttons.h" +#include "base/qt/qt_key_modifiers.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_unread_things.h" +#include "dialogs/dialogs_entry.h" +#include "main/main_session.h" +#include "menu/menu_send.h" +#include "apiwrap.h" +#include "api/api_unread_things.h" +#include "data/data_document.h" +#include "data/data_messages.h" +#include "data/data_session.h" +#include "data/data_forum_topic.h" +#include "styles/style_chat.h" + +namespace HistoryView { + +CornerButtons::CornerButtons( + not_null parent, + not_null st, + not_null delegate) +: down( + parent, + st->value(parent->lifetime(), st::historyToDown)) +, mentions( + parent, + st->value(parent->lifetime(), st::historyUnreadMentions)) +, reactions( + parent, + st->value(parent->lifetime(), st::historyUnreadReactions)) +, _scroll(parent) +, _delegate(delegate) { + down.widget->addClickHandler([=] { downClick(); }); + mentions.widget->addClickHandler([=] { mentionsClick(); }); + reactions.widget->addClickHandler([=] { reactionsClick(); }); + + const auto filterScroll = [&](CornerButton &button) { + button.widget->installEventFilter(this); + }; + filterScroll(down); + filterScroll(mentions); + filterScroll(reactions); + + SendMenu::SetupUnreadMentionsMenu(mentions.widget.data(), [=] { + return _delegate->cornerButtonsEntry(); + }); + SendMenu::SetupUnreadReactionsMenu(reactions.widget.data(), [=] { + return _delegate->cornerButtonsEntry(); + }); +} + +bool CornerButtons::eventFilter(QObject *o, QEvent *e) { + if (e->type() == QEvent::Wheel + && (o == down.widget + || o == mentions.widget + || o == reactions.widget)) { + return _scroll->viewportEvent(e); + } + return QObject::eventFilter(o, e); +} + +void CornerButtons::downClick() { + if (base::IsCtrlPressed() || !_replyReturn) { + _delegate->cornerButtonsShowAtPosition(Data::UnreadMessagePosition); + } else { + _delegate->cornerButtonsShowAtPosition(_replyReturn->position()); + } +} + +void CornerButtons::mentionsClick() { + const auto history = lookupHistory(); + if (!history) { + return; + } + const auto entry = _delegate->cornerButtonsEntry(); + const auto msgId = entry->unreadMentions().minLoaded(); + const auto already = (_delegate->cornerButtonsCurrentId().msg == msgId); + + // Mark mention voice/video message as read. + // See https://github.com/telegramdesktop/tdesktop/issues/5623 + if (msgId && already) { + if (const auto item = entry->owner().message(history->peer, msgId)) { + if (const auto media = item->media()) { + if (const auto document = media->document()) { + if (!media->webpage() + && (document->isVoiceMessage() + || document->isVideoMessage())) { + document->owner().markMediaRead(document); + } + } + } + } + } + showAt(msgId); +} + +void CornerButtons::reactionsClick() { + const auto history = lookupHistory(); + if (!history) { + return; + } + const auto entry = _delegate->cornerButtonsEntry(); + showAt(entry->unreadReactions().minLoaded()); +} + +void CornerButtons::clearReplyReturns() { + _replyReturns.clear(); + _replyReturn = nullptr; +} + +QVector CornerButtons::replyReturns() const { + return _replyReturns; +} + +void CornerButtons::setReplyReturns(QVector replyReturns) { + _replyReturns = std::move(replyReturns); + computeCurrentReplyReturn(); + if (!_replyReturn) { + calculateNextReplyReturn(); + } +} + +void CornerButtons::computeCurrentReplyReturn() { + const auto entry = _delegate->cornerButtonsEntry(); + _replyReturn = (!entry || _replyReturns.empty()) + ? nullptr + : entry->owner().message(_replyReturns.back()); +} + +void CornerButtons::skipReplyReturn(FullMsgId id) { + while (_replyReturn) { + if (_replyReturn->fullId() == id) { + calculateNextReplyReturn(); + } else { + break; + } + } +} + +void CornerButtons::calculateNextReplyReturn() { + _replyReturn = nullptr; + while (!_replyReturns.empty() && !_replyReturn) { + _replyReturns.pop_back(); + computeCurrentReplyReturn(); + } + if (!_replyReturn) { + updateJumpDownVisibility(); + updateUnreadThingsVisibility(); + } +} + +void CornerButtons::pushReplyReturn(not_null item) { + _replyReturns.push_back(item->fullId()); + _replyReturn = item; +} + +CornerButton &CornerButtons::buttonByType(CornerButtonType type) { + switch (type) { + case CornerButtonType::Down: return down; + case CornerButtonType::Mentions: return mentions; + case CornerButtonType::Reactions: return reactions; + } + Unexpected("Type in CornerButtons::buttonByType."); +} + +History *CornerButtons::lookupHistory() const { + const auto entry = _delegate->cornerButtonsEntry(); + if (!entry) { + return nullptr; + } else if (const auto history = entry->asHistory()) { + return history; + } else if (const auto topic = entry->asTopic()) { + return topic->history(); + } + return nullptr; +} + +void CornerButtons::showAt(MsgId id) { + if (const auto history = lookupHistory()) { + if (const auto item = history->owner().message(history->peer, id)) { + _delegate->cornerButtonsShowAtPosition(item->position()); + } + } +} + +void CornerButtons::updateVisibility( + CornerButtonType type, + bool shown) { + auto &button = buttonByType(type); + if (button.shown != shown) { + button.shown = shown; + button.animation.start( + [=] { updatePositions(); }, + shown ? 0. : 1., + shown ? 1. : 0., + st::historyToDownDuration); + } +} + +void CornerButtons::updateUnreadThingsVisibility() { + if (_delegate->cornerButtonsIgnoreVisibility()) { + return; + } + const auto entry = _delegate->cornerButtonsEntry(); + if (!entry) { + updateVisibility(CornerButtonType::Mentions, false); + updateVisibility(CornerButtonType::Reactions, false); + return; + } + auto &unreadThings = entry->session().api().unreadThings(); + unreadThings.preloadEnough(entry); + + const auto updateWithCount = [&](CornerButtonType type, int count) { + updateVisibility( + type, + (count > 0) && _delegate->cornerButtonsUnreadMayBeShown()); + }; + if (unreadThings.trackMentions(entry)) { + if (const auto count = entry->unreadMentions().count(0)) { + mentions.widget->setUnreadCount(count); + } + updateWithCount( + CornerButtonType::Mentions, + entry->unreadMentions().loadedCount()); + } else { + updateVisibility(CornerButtonType::Mentions, false); + } + + if (unreadThings.trackReactions(entry)) { + if (const auto count = entry->unreadReactions().count(0)) { + reactions.widget->setUnreadCount(count); + } + updateWithCount( + CornerButtonType::Reactions, + entry->unreadReactions().loadedCount()); + } else { + updateVisibility(CornerButtonType::Reactions, false); + } +} + +void CornerButtons::updateJumpDownVisibility(std::optional counter) { + const auto shown = _delegate->cornerButtonsDownShown(); + updateVisibility(CornerButtonType::Down, shown); + if (counter) { + down.widget->setUnreadCount(*counter); + } +} + +void CornerButtons::updatePositions() { + const auto checkVisibility = [](CornerButton &button) { + const auto shouldBeHidden = !button.shown + && !button.animation.animating(); + if (shouldBeHidden != button.widget->isHidden()) { + button.widget->setVisible(!shouldBeHidden); + } + }; + const auto shown = [](CornerButton &button) { + return button.animation.value(button.shown ? 1. : 0.); + }; + + // All corner buttons is a child widgets of _scroll, not me. + + const auto historyDownShown = shown(down); + const auto unreadMentionsShown = shown(mentions); + const auto unreadReactionsShown = shown(reactions); + const auto skip = st::historyUnreadThingsSkip; + { + const auto top = anim::interpolate( + 0, + down.widget->height() + st::historyToDownPosition.y(), + historyDownShown); + down.widget->moveToRight( + st::historyToDownPosition.x(), + _scroll->height() - top); + } + { + const auto right = anim::interpolate( + -mentions.widget->width(), + st::historyToDownPosition.x(), + unreadMentionsShown); + const auto shift = anim::interpolate( + 0, + down.widget->height() + skip, + historyDownShown); + const auto top = _scroll->height() + - mentions.widget->height() + - st::historyToDownPosition.y() + - shift; + mentions.widget->moveToRight(right, top); + } + { + const auto right = anim::interpolate( + -reactions.widget->width(), + st::historyToDownPosition.x(), + unreadReactionsShown); + const auto shift = anim::interpolate( + 0, + down.widget->height() + skip, + historyDownShown + ) + anim::interpolate( + 0, + mentions.widget->height() + skip, + unreadMentionsShown); + const auto top = _scroll->height() + - reactions.widget->height() + - st::historyToDownPosition.y() + - shift; + reactions.widget->moveToRight(right, top); + } + + checkVisibility(down); + checkVisibility(mentions); + checkVisibility(reactions); +} + +void CornerButtons::finishAnimations() { + down.animation.stop(); + mentions.animation.stop(); + reactions.animation.stop(); + updatePositions(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_corner_buttons.h b/Telegram/SourceFiles/history/view/history_view_corner_buttons.h new file mode 100644 index 000000000..4233744c3 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_corner_buttons.h @@ -0,0 +1,110 @@ +/* +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 + +#include "ui/effects/animations.h" +#include "base/object_ptr.h" + +class History; +class HistoryItem; +struct FullMsgId; + +namespace Ui { +class ChatStyle; +class ScrollArea; +class HistoryDownButton; +} // namespace Ui + +namespace Data { +struct MessagePosition; +} // namespace Data + +namespace Dialogs { +class Entry; +} // namespace Dialogs + +namespace HistoryView { + +struct CornerButton { + template + CornerButton(Args &&...args) : widget(std::forward(args)...) { + } + + object_ptr widget; + Ui::Animations::Simple animation; + bool shown = false; +}; + +enum class CornerButtonType { + Down, + Mentions, + Reactions, +}; + +class CornerButtonsDelegate { +public: + virtual void cornerButtonsShowAtPosition( + Data::MessagePosition position) = 0; + [[nodiscard]] virtual Dialogs::Entry *cornerButtonsEntry() = 0; + [[nodiscard]] virtual FullMsgId cornerButtonsCurrentId() = 0; + [[nodiscard]] virtual bool cornerButtonsIgnoreVisibility() = 0; + [[nodiscard]] virtual bool cornerButtonsDownShown() = 0; + [[nodiscard]] virtual bool cornerButtonsUnreadMayBeShown() = 0; +}; + +class CornerButtons final : private QObject { +public: + CornerButtons( + not_null parent, + not_null st, + not_null delegate); + + void downClick(); + void mentionsClick(); + void reactionsClick(); + + void clearReplyReturns(); + [[nodiscard]] QVector replyReturns() const; + void setReplyReturns(QVector replyReturns); + void pushReplyReturn(not_null item); + void skipReplyReturn(FullMsgId id); + void calculateNextReplyReturn(); + + void updateVisibility(CornerButtonType type, bool shown); + void updateUnreadThingsVisibility(); + void updateJumpDownVisibility(std::optional counter = {}); + void updatePositions(); + + void finishAnimations(); + + [[nodiscard]] HistoryItem *replyReturn() const { + return _replyReturn; + } + + CornerButton down; + CornerButton mentions; + CornerButton reactions; + +private: + bool eventFilter(QObject *o, QEvent *e) override; + + void computeCurrentReplyReturn(); + + [[nodiscard]] CornerButton &buttonByType(CornerButtonType type); + [[nodiscard]] History *lookupHistory() const; + void showAt(MsgId id); + + const not_null _scroll; + const not_null _delegate; + + HistoryItem *_replyReturn = nullptr; + QVector _replyReturns; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 6e0fa843f..227e1758d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -158,7 +158,7 @@ public: StackItemHistory( not_null history, MsgId msgId, - QList replyReturns) + QVector replyReturns) : StackItem(history->peer) , history(history) , msgId(msgId) @@ -171,7 +171,7 @@ public: not_null history; MsgId msgId; - QList replyReturns; + QVector replyReturns; }; @@ -1854,7 +1854,9 @@ void MainWidget::showBackFromStack( historyItem->peer()->id, params.withWay(SectionShow::Way::Backward), ShowAtUnreadMsgId); - _history->setReplyReturns(historyItem->peer()->id, historyItem->replyReturns); + _history->setReplyReturns( + historyItem->peer()->id, + std::move(historyItem->replyReturns)); } else if (item->type() == SectionStackItem) { auto sectionItem = static_cast(item.get()); showNewSection( diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index eddf8a84f..9262e406a 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -152,27 +152,30 @@ void SetupMenuAndShortcuts( void SetupReadAllMenu( not_null button, - Fn currentPeer, + Fn currentEntry, const QString &text, - Fn, Fn)> sendReadRequest) { + Fn, Fn)> sendReadRequest) { struct State { base::unique_qptr menu; - base::flat_set> sentForPeers; + base::flat_set> sentForEntries; }; const auto state = std::make_shared(); const auto showMenu = [=] { - const auto peer = currentPeer(); - if (!peer) { + const auto entry = base::make_weak(currentEntry()); + if (!entry) { return; } state->menu = base::make_unique_q( button, st::popupMenuWithIcons); state->menu->addAction(text, [=] { - if (!state->sentForPeers.emplace(peer).second) { + const auto strong = entry.get(); + if (!strong || !state->sentForEntries.emplace(entry).second) { return; } - sendReadRequest(peer, [=] { state->sentForPeers.remove(peer); }); + sendReadRequest(strong, [=] { + state->sentForEntries.remove(entry); + }); }, &st::menuIconMarkRead); state->menu->popup(QCursor::pos()); }; @@ -188,52 +191,88 @@ void SetupReadAllMenu( void SetupUnreadMentionsMenu( not_null button, - Fn currentPeer, - MsgId topicRootId) { + Fn currentEntry) { const auto text = tr::lng_context_mark_read_mentions_all(tr::now); - const auto sendRequest = [=](not_null peer, Fn done) { + const auto sendOne = [=]( + base::weak_ptr weakEntry, + Fn done, + auto resend) -> void { + const auto entry = weakEntry.get(); + if (!entry) { + done(); + return; + } + const auto history = entry->asHistory(); + const auto topic = entry->asTopic(); + Assert(history || topic); + const auto peer = (history ? history : topic->history().get())->peer; + const auto rootId = topic ? topic->rootId() : 0; using Flag = MTPmessages_ReadMentions::Flag; peer->session().api().request(MTPmessages_ReadMentions( - MTP_flags(topicRootId ? Flag::f_top_msg_id : Flag()), + MTP_flags(rootId ? Flag::f_top_msg_id : Flag()), peer->input, - MTP_int(topicRootId) + MTP_int(rootId) )).done([=](const MTPmessages_AffectedHistory &result) { - done(); - peer->session().api().applyAffectedHistory(peer, result); - const auto forum = peer->forum(); - const auto history = peer->owner().history(peer); - if (!topicRootId) { - history->unreadMentions().clear(); - if (forum) { - forum->clearAllUnreadMentions(); - } + const auto offset = peer->session().api().applyAffectedHistory( + peer, + result); + if (offset > 0) { + resend(weakEntry, done, resend); } else { - if (forum) { - if (const auto topic = forum->topicFor(topicRootId)) { - topic->unreadMentions().clear(); - } - } - history->clearUnreadMentionsFor(topicRootId); + done(); + peer->owner().history(peer)->clearUnreadMentionsFor(rootId); } }).fail(done).send(); }; - SetupReadAllMenu(button, currentPeer, text, sendRequest); + const auto sendRequest = [=]( + not_null entry, + Fn done) { + sendOne(base::make_weak(entry.get()), std::move(done), sendOne); + }; + SetupReadAllMenu(button, currentEntry, text, sendRequest); } void SetupUnreadReactionsMenu( not_null button, - Fn currentPeer) { + Fn currentEntry) { const auto text = tr::lng_context_mark_read_reactions_all(tr::now); - const auto sendRequest = [=](not_null peer, Fn done) { - peer->session().api().request(MTPmessages_ReadReactions( - peer->input - )).done([=](const MTPmessages_AffectedHistory &result) { + const auto sendOne = [=]( + base::weak_ptr weakEntry, + Fn done, + auto resend) -> void { + const auto entry = weakEntry.get(); + if (!entry) { done(); - peer->session().api().applyAffectedHistory(peer, result); - peer->owner().history(peer)->unreadReactions().clear(); + return; + } + const auto history = entry->asHistory(); + const auto topic = entry->asTopic(); + Assert(history || topic); + const auto peer = (history ? history : topic->history().get())->peer; + const auto rootId = topic ? topic->rootId() : 0; + using Flag = MTPmessages_ReadReactions::Flag; + peer->session().api().request(MTPmessages_ReadReactions( + 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(weakEntry, done, resend); + } else { + done(); + peer->owner().history(peer)->clearUnreadReactionsFor(rootId); + } }).fail(done).send(); }; - SetupReadAllMenu(button, currentPeer, text, sendRequest); + const auto sendRequest = [=]( + not_null entry, + Fn done) { + sendOne(base::make_weak(entry.get()), std::move(done), sendOne); + }; + SetupReadAllMenu(button, currentEntry, text, sendRequest); } } // namespace SendMenu diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index b76c090fe..8aac254d8 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -16,6 +16,10 @@ class PopupMenu; class RpWidget; } // namespace Ui +namespace Dialogs { +class Entry; +} // namespace Dialogs + namespace SendMenu { enum class Type { @@ -51,11 +55,10 @@ void SetupMenuAndShortcuts( void SetupUnreadMentionsMenu( not_null button, - Fn currentPeer, - MsgId topicRootId); + Fn currentEntry); void SetupUnreadReactionsMenu( not_null button, - Fn currentPeer); + Fn currentEntry); } // namespace SendMenu diff --git a/Telegram/lib_base b/Telegram/lib_base index 1b524981f..44923c8b1 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1b524981f0caf2dc2f21c004694f63025fe96a71 +Subproject commit 44923c8b12bab26c707e8a7077ed82541a444f71