Update API scheme on layer 148.

Extract message history corner buttons code.
This commit is contained in:
John Preston 2022-10-08 15:14:38 +04:00
parent 2c0b5b3210
commit 6a7f030ee7
24 changed files with 751 additions and 444 deletions

View File

@ -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

View File

@ -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<string> = 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<ForumTopic> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> 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<string> = Vector<EmojiLanguage>;
messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL;
messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector<MessagesFilter> = Vector<messages.SearchCounter>;
messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector<MessagesFilter> = Vector<messages.SearchCounter>;
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<int> = 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;

View File

@ -41,24 +41,22 @@ constexpr auto kNextRequestLimit = 100;
UnreadThings::UnreadThings(not_null<ApiWrap*> 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);
}
}

View File

@ -23,8 +23,8 @@ public:
explicit UnreadThings(not_null<ApiWrap*> 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);

View File

@ -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;

View File

@ -239,6 +239,12 @@ void Forum::clearAllUnreadMentions() {
}
}
void Forum::clearAllUnreadReactions() {
for (const auto &[rootId, topic] : _topics) {
topic->unreadReactions().clear();
}
}
ForumTopic *Forum::topicFor(not_null<const HistoryItem*> item) {
const auto maybe = topicFor(item->replyToTop());
return maybe ? maybe : topicFor(item->topicRootId());

View File

@ -60,6 +60,7 @@ public:
void created(MsgId rootId, MsgId realId);
void clearAllUnreadMentions();
void clearAllUnreadReactions();
private:
struct TopicRequest {

View File

@ -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 {

View File

@ -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<uint64, uint64> value_ordering_helper(MessageGroupId value) {
return std::make_pair(value.value, value.peer.value);
}
friend inline constexpr auto operator<=>(
MessageGroupId,
MessageGroupId) noexcept = default;
};

View File

@ -131,6 +131,14 @@ const base::flat_set<MsgId> &Entry::unreadMentionsIds() const {
return _unreadThings->mentions.ids();
}
const base::flat_set<MsgId> &Entry::unreadReactionsIds() const {
if (!_unreadThings) {
static const auto Result = base::flat_set<MsgId>();
return Result;
}
return _unreadThings->reactions.ids();
}
bool Entry::needUpdateInChatList() const {
return inChatList() || shouldBeInChatList();
}

View File

@ -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<MsgId> &unreadMentionsIds() const;
[[nodiscard]] const base::flat_set<MsgId> &unreadReactionsIds() const;
private:
enum class Flag : uchar {

View File

@ -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;

View File

@ -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 <set>

View File

@ -700,6 +700,18 @@ not_null<HistoryItem*> 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<MsgId>();
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<HistoryItem*> History::addNewToBack(
not_null<HistoryItem*> item,
bool unread) {

View File

@ -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<Data::Draft> &&draft);

View File

@ -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"

View File

@ -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<HistoryView::CornerButtonsDelegate*>(this))
, _fieldAutocomplete(this, controller)
, _supportAutocomplete(session().supportMode()
? object_ptr<Support::Autocomplete>(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<DocumentData*> document) {
if (_peer && Data::AllowEmojiWithoutPremium(_peer)) {
@ -582,7 +560,7 @@ HistoryWidget::HistoryWidget(
) | rpl::filter([=](not_null<ChannelData*> 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<HistoryItem*> 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<MsgId> HistoryWidget::replyReturns() {
return _replyReturns;
QVector<FullMsgId> HistoryWidget::replyReturns() const {
return _cornerButtons.replyReturns();
}
void HistoryWidget::setReplyReturns(PeerId peer, const QList<MsgId> &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<FullMsgId> replyReturns) {
if (!_peer || _peer->id != peer) {
return;
}
_cornerButtons.setReplyReturns(std::move(replyReturns));
}
void HistoryWidget::fastShowAtEnd(not_null<History*> 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<const HistoryItem*> 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) {

View File

@ -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<HistoryItem*> item);
QList<MsgId> replyReturns();
void setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns);
void calcNextReplyReturn();
[[nodiscard]] QVector<FullMsgId> replyReturns() const;
void setReplyReturns(PeerId peer, QVector<FullMsgId> replyReturns);
void updatePreview();
void previewCancel();
@ -321,15 +322,15 @@ private:
};
using TextUpdateEvents = base::flags<TextUpdateEvent>;
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
struct CornerButton {
template <typename ...Args>
CornerButton(Args &&...args) : widget(std::forward<Args>(args)...) {
}
Ui::Animations::Simple animation;
bool shown = false;
object_ptr<Ui::HistoryDownButton> 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<const History*> history);
@ -410,16 +408,10 @@ private:
void animationCallback();
void updateOverStates(QPoint pos);
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos = {});
void cornerButtonsAnimationFinish();
void sendButtonClicked();
void newItemAdded(not_null<HistoryItem*> item);
void maybeMarkReactionsRead(not_null<HistoryItem*> item);
void updateCornerButtonsPositions();
void updateHistoryDownVisibility();
void updateUnreadThingsVisibility();
void updateCornerButtonVisibility(CornerButton &button, bool shown);
bool canSendFiles(not_null<const QMimeData*> data) const;
bool confirmSendingFiles(
const QStringList &files,
@ -665,9 +657,6 @@ private:
bool _replyForwardPressed = false;
HistoryItem *_replyReturn = nullptr;
QList<MsgId> _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> _fieldAutocomplete;
object_ptr<Support::Autocomplete> _supportAutocomplete;

View File

@ -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<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> 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<FullMsgId> CornerButtons::replyReturns() const {
return _replyReturns;
}
void CornerButtons::setReplyReturns(QVector<FullMsgId> 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<HistoryItem*> 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<int> 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

View File

@ -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 <typename ...Args>
CornerButton(Args &&...args) : widget(std::forward<Args>(args)...) {
}
object_ptr<Ui::HistoryDownButton> 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<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate);
void downClick();
void mentionsClick();
void reactionsClick();
void clearReplyReturns();
[[nodiscard]] QVector<FullMsgId> replyReturns() const;
void setReplyReturns(QVector<FullMsgId> replyReturns);
void pushReplyReturn(not_null<HistoryItem*> item);
void skipReplyReturn(FullMsgId id);
void calculateNextReplyReturn();
void updateVisibility(CornerButtonType type, bool shown);
void updateUnreadThingsVisibility();
void updateJumpDownVisibility(std::optional<int> 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<Ui::ScrollArea*> _scroll;
const not_null<CornerButtonsDelegate*> _delegate;
HistoryItem *_replyReturn = nullptr;
QVector<FullMsgId> _replyReturns;
};
} // namespace HistoryView

View File

@ -158,7 +158,7 @@ public:
StackItemHistory(
not_null<History*> history,
MsgId msgId,
QList<MsgId> replyReturns)
QVector<FullMsgId> replyReturns)
: StackItem(history->peer)
, history(history)
, msgId(msgId)
@ -171,7 +171,7 @@ public:
not_null<History*> history;
MsgId msgId;
QList<MsgId> replyReturns;
QVector<FullMsgId> 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<StackItemSection*>(item.get());
showNewSection(

View File

@ -152,27 +152,30 @@ void SetupMenuAndShortcuts(
void SetupReadAllMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer,
Fn<Dialogs::Entry*()> currentEntry,
const QString &text,
Fn<void(not_null<PeerData*>, Fn<void()>)> sendReadRequest) {
Fn<void(not_null<Dialogs::Entry*>, Fn<void()>)> sendReadRequest) {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
base::flat_set<not_null<PeerData*>> sentForPeers;
base::flat_set<base::weak_ptr<Dialogs::Entry>> sentForEntries;
};
const auto state = std::make_shared<State>();
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<Ui::PopupMenu>(
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<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer,
MsgId topicRootId) {
Fn<Dialogs::Entry*()> currentEntry) {
const auto text = tr::lng_context_mark_read_mentions_all(tr::now);
const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) {
const auto sendOne = [=](
base::weak_ptr<Dialogs::Entry> weakEntry,
Fn<void()> 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<Dialogs::Entry*> entry,
Fn<void()> done) {
sendOne(base::make_weak(entry.get()), std::move(done), sendOne);
};
SetupReadAllMenu(button, currentEntry, text, sendRequest);
}
void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer) {
Fn<Dialogs::Entry*()> currentEntry) {
const auto text = tr::lng_context_mark_read_reactions_all(tr::now);
const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) {
peer->session().api().request(MTPmessages_ReadReactions(
peer->input
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto sendOne = [=](
base::weak_ptr<Dialogs::Entry> weakEntry,
Fn<void()> 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<Dialogs::Entry*> entry,
Fn<void()> done) {
sendOne(base::make_weak(entry.get()), std::move(done), sendOne);
};
SetupReadAllMenu(button, currentEntry, text, sendRequest);
}
} // namespace SendMenu

View File

@ -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<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer,
MsgId topicRootId);
Fn<Dialogs::Entry*()> currentEntry);
void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer);
Fn<Dialogs::Entry*()> currentEntry);
} // namespace SendMenu

@ -1 +1 @@
Subproject commit 1b524981f0caf2dc2f21c004694f63025fe96a71
Subproject commit 44923c8b12bab26c707e8a7077ed82541a444f71