Track unread mentions and unread reactions the same way.

This commit is contained in:
John Preston 2022-01-26 19:01:40 +03:00
parent 6207770120
commit e9c79886d2
31 changed files with 885 additions and 347 deletions

View file

@ -147,6 +147,8 @@ PRIVATE
api/api_text_entities.h api/api_text_entities.h
api/api_toggling_media.cpp api/api_toggling_media.cpp
api/api_toggling_media.h api/api_toggling_media.h
api/api_unread_things.cpp
api/api_unread_things.h
api/api_updates.cpp api/api_updates.cpp
api/api_updates.h api/api_updates.h
api/api_user_privacy.cpp api/api_user_privacy.cpp
@ -686,6 +688,8 @@ PRIVATE
history/history_message.h history/history_message.h
history/history_service.cpp history/history_service.cpp
history/history_service.h history/history_service.h
history/history_unread_things.cpp
history/history_unread_things.h
history/history_widget.cpp history/history_widget.cpp
history/history_widget.h history/history_widget.h
info/info_content_widget.cpp info/info_content_widget.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,138 @@
/*
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 "api/api_unread_things.h"
#include "data/data_peer.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_unread_things.h"
#include "apiwrap.h"
namespace Api {
namespace {
constexpr auto kPreloadIfLess = 5;
constexpr auto kFirstRequestLimit = 10;
constexpr auto kNextRequestLimit = 100;
} // namespace
UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) {
}
bool UnreadThings::trackMentions(PeerData *peer) const {
return peer && (peer->isChat() || peer->isMegagroup());
}
bool UnreadThings::trackReactions(PeerData *peer) const {
return trackMentions(peer) || (peer && peer->isUser());
}
void UnreadThings::preloadEnough(History *history) {
if (!history) {
return;
}
if (trackMentions(history->peer)) {
preloadEnoughMentions(history);
}
if (trackReactions(history->peer)) {
preloadEnoughReactions(history);
}
}
void UnreadThings::mediaAndMentionsRead(
const base::flat_set<MsgId> &readIds,
ChannelData *channel) {
for (const auto &msgId : readIds) {
_api->requestMessageData(channel, msgId, [=] {
const auto item = channel
? _api->session().data().message(channel->id, msgId)
: _api->session().data().nonChannelMessage(msgId);
if (item && item->mentionsMe()) {
item->markMediaAndMentionRead();
}
});
}
}
void UnreadThings::preloadEnoughMentions(not_null<History*> history) {
const auto fullCount = history->unreadMentions().count();
const auto loadedCount = history->unreadMentions().loadedCount();
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
requestMentions(history, loadedCount);
}
}
void UnreadThings::preloadEnoughReactions(not_null<History*> history) {
const auto fullCount = history->unreadReactions().count();
const auto loadedCount = history->unreadReactions().loadedCount();
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
requestReactions(history, loadedCount);
}
}
void UnreadThings::requestMentions(not_null<History*> history, int loaded) {
if (_mentionsRequests.contains(history)) {
return;
}
const auto offsetId = std::max(
history->unreadMentions().maxLoaded(),
MsgId(1));
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
const auto addOffset = loaded ? -(limit + 1) : -limit;
const auto maxId = 0;
const auto minId = 0;
const auto requestId = _api->request(MTPmessages_GetUnreadMentions(
history->peer->input,
MTP_int(offsetId),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(maxId),
MTP_int(minId)
)).done([=](const MTPmessages_Messages &result) {
_mentionsRequests.remove(history);
history->unreadMentions().addSlice(result);
}).fail([=] {
_mentionsRequests.remove(history);
}).send();
_mentionsRequests.emplace(history, requestId);
}
void UnreadThings::requestReactions(not_null<History*> history, int loaded) {
if (_reactionsRequests.contains(history)) {
return;
}
const auto offsetId = std::max(
history->unreadMentions().maxLoaded(),
MsgId(1));
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
const auto addOffset = loaded ? -(limit + 1) : -limit;
const auto maxId = 0;
const auto minId = 0;
const auto requestId = _api->request(MTPmessages_GetUnreadReactions(
history->peer->input,
MTP_int(offsetId),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(maxId),
MTP_int(minId)
)).done([=](const MTPmessages_Messages &result) {
_reactionsRequests.remove(history);
history->unreadReactions().addSlice(result);
}).fail([this, history] {
_reactionsRequests.remove(history);
}).send();
_reactionsRequests.emplace(history, requestId);
}
} // namespace UnreadThings

View file

@ -0,0 +1,44 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class History;
class ApiWrap;
class PeerData;
class ChannelData;
namespace Api {
class UnreadThings final {
public:
explicit UnreadThings(not_null<ApiWrap*> api);
[[nodiscard]] bool trackMentions(PeerData *peer) const;
[[nodiscard]] bool trackReactions(PeerData *peer) const;
void preloadEnough(History *history);
void mediaAndMentionsRead(
const base::flat_set<MsgId> &readIds,
ChannelData *channel = nullptr);
private:
void preloadEnoughMentions(not_null<History*> history);
void preloadEnoughReactions(not_null<History*> history);
void requestMentions(not_null<History*> history, int loaded);
void requestReactions(not_null<History*> history, int loaded);
const not_null<ApiWrap*> _api;
base::flat_map<not_null<History*>, mtpRequestId> _mentionsRequests;
base::flat_map<not_null<History*>, mtpRequestId> _reactionsRequests;
};
} // namespace Api

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "api/api_user_privacy.h" #include "api/api_user_privacy.h"
#include "api/api_unread_things.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "mtproto/mtp_instance.h" #include "mtproto/mtp_instance.h"
@ -1178,25 +1179,29 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
case mtpc_updateReadMessagesContents: { case mtpc_updateReadMessagesContents: {
const auto &d = update.c_updateReadMessagesContents(); const auto &d = update.c_updateReadMessagesContents();
auto possiblyReadMentions = base::flat_set<MsgId>(); auto unknownReadIds = base::flat_set<MsgId>();
for (const auto &msgId : d.vmessages().v) { for (const auto &msgId : d.vmessages().v) {
if (const auto item = _session->data().nonChannelMessage(msgId.v)) { if (const auto item = _session->data().nonChannelMessage(msgId.v)) {
const auto unreadForPeer = item->isUnreadMedia()
|| item->isUnreadMention();
const auto unreadForMe = item->hasUnreadReaction();
if (item->isUnreadMedia() || item->isUnreadMention()) { if (item->isUnreadMedia() || item->isUnreadMention()) {
item->markMediaRead(); item->markMediaAndMentionRead();
_session->data().requestItemRepaint(item); _session->data().requestItemRepaint(item);
if (item->out() if (item->out()
&& item->history()->peer->isUser() && item->history()->peer->isUser()
&& !requestingDifference()) { && !requestingDifference()) {
item->history()->peer->asUser()->madeAction(base::unixtime::now()); item->history()->peer->asUser()->madeAction(
base::unixtime::now());
} }
} }
} else { } else {
// Perhaps it was an unread mention! // Perhaps it was an unread mention!
possiblyReadMentions.insert(msgId.v); unknownReadIds.insert(msgId.v);
} }
} }
session().api().checkForUnreadMentions(possiblyReadMentions); session().api().unreadThings().mediaAndMentionsRead(unknownReadIds);
} break; } break;
case mtpc_updateReadHistoryInbox: { case mtpc_updateReadHistoryInbox: {
@ -1565,19 +1570,21 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} }
return; return;
} }
auto possiblyReadMentions = base::flat_set<MsgId>(); auto unknownReadIds = base::flat_set<MsgId>();
for (const auto &msgId : d.vmessages().v) { for (const auto &msgId : d.vmessages().v) {
if (auto item = session().data().message(channel->id, msgId.v)) { if (auto item = session().data().message(channel->id, msgId.v)) {
if (item->isUnreadMedia() || item->isUnreadMention()) { if (item->isUnreadMedia() || item->isUnreadMention()) {
item->markMediaRead(); item->markMediaAndMentionRead();
session().data().requestItemRepaint(item); session().data().requestItemRepaint(item);
} }
} else { } else {
// Perhaps it was an unread mention! // Perhaps it was an unread mention!
possiblyReadMentions.insert(msgId.v); unknownReadIds.insert(msgId.v);
} }
} }
session().api().checkForUnreadMentions(possiblyReadMentions, channel); session().api().unreadThings().mediaAndMentionsRead(
unknownReadIds,
channel);
} break; } break;
// Edited messages. // Edited messages.

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_user_privacy.h" #include "api/api_user_privacy.h"
#include "api/api_views.h" #include "api/api_views.h"
#include "api/api_confirm_phone.h" #include "api/api_confirm_phone.h"
#include "api/api_unread_things.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
#include "data/data_changes.h" #include "data/data_changes.h"
@ -96,9 +97,6 @@ constexpr auto kSaveCloudDraftTimeout = 1000;
constexpr auto kTopPromotionInterval = TimeId(60 * 60); constexpr auto kTopPromotionInterval = TimeId(60 * 60);
constexpr auto kTopPromotionMinDelay = TimeId(10); constexpr auto kTopPromotionMinDelay = TimeId(10);
constexpr auto kSmallDelayMs = 5; constexpr auto kSmallDelayMs = 5;
constexpr auto kUnreadMentionsPreloadIfLess = 5;
constexpr auto kUnreadMentionsFirstRequestLimit = 10;
constexpr auto kUnreadMentionsNextRequestLimit = 100;
constexpr auto kSharedMediaLimit = 100; constexpr auto kSharedMediaLimit = 100;
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000); constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000); constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
@ -142,7 +140,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this)) , _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this)) , _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
, _polls(std::make_unique<Api::Polls>(this)) , _polls(std::make_unique<Api::Polls>(this))
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this)) { , _chatParticipants(std::make_unique<Api::ChatParticipants>(this))
, _unreadThings(std::make_unique<Api::UnreadThings>(this)) {
crl::on_main(session, [=] { crl::on_main(session, [=] {
// You can't use _session->lifetime() in the constructor, // You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet. // only queued, because it is not constructed yet.
@ -1287,7 +1286,7 @@ void ApiWrap::migrateFail(not_null<PeerData*> peer, const QString &error) {
} }
} }
void ApiWrap::markMediaRead( void ApiWrap::markContentsRead(
const base::flat_set<not_null<HistoryItem*>> &items) { const base::flat_set<not_null<HistoryItem*>> &items) {
auto markedIds = QVector<MTPint>(); auto markedIds = QVector<MTPint>();
auto channelMarkedIds = base::flat_map< auto channelMarkedIds = base::flat_map<
@ -1295,12 +1294,7 @@ void ApiWrap::markMediaRead(
QVector<MTPint>>(); QVector<MTPint>>();
markedIds.reserve(items.size()); markedIds.reserve(items.size());
for (const auto &item : items) { for (const auto &item : items) {
if ((!item->isUnreadMedia() || item->out()) if (!item->markContentsRead() || !item->isRegular()) {
&& !item->isUnreadMention()) {
continue;
}
item->markMediaRead();
if (!item->isRegular()) {
continue; continue;
} }
if (const auto channel = item->history()->peer->asChannel()) { if (const auto channel = item->history()->peer->asChannel()) {
@ -1324,13 +1318,8 @@ void ApiWrap::markMediaRead(
} }
} }
void ApiWrap::markMediaRead(not_null<HistoryItem*> item) { void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
if ((!item->isUnreadMedia() || item->out()) if (!item->markContentsRead() || !item->isRegular()) {
&& !item->isUnreadMention()) {
return;
}
item->markMediaRead();
if (!item->isRegular()) {
return; return;
} }
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id)); const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
@ -2910,45 +2899,6 @@ void ApiWrap::jumpToHistoryDate(not_null<PeerData*> peer, const QDate &date) {
} }
} }
void ApiWrap::preloadEnoughUnreadMentions(not_null<History*> history) {
auto fullCount = history->getUnreadMentionsCount();
auto loadedCount = history->getUnreadMentionsLoadedCount();
auto allLoaded = (fullCount >= 0) ? (loadedCount >= fullCount) : false;
if (fullCount < 0 || loadedCount >= kUnreadMentionsPreloadIfLess || allLoaded) {
return;
}
if (_unreadMentionsRequests.contains(history)) {
return;
}
auto offsetId = loadedCount ? history->getMaxLoadedUnreadMention() : 1;
auto limit = loadedCount ? kUnreadMentionsNextRequestLimit : kUnreadMentionsFirstRequestLimit;
auto addOffset = loadedCount ? -(limit + 1) : -limit;
auto maxId = 0;
auto minId = 0;
auto requestId = request(MTPmessages_GetUnreadMentions(history->peer->input, MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId))).done([this, history](const MTPmessages_Messages &result) {
_unreadMentionsRequests.remove(history);
history->addUnreadMentionsSlice(result);
}).fail([this, history] {
_unreadMentionsRequests.remove(history);
}).send();
_unreadMentionsRequests.emplace(history, requestId);
}
void ApiWrap::checkForUnreadMentions(
const base::flat_set<MsgId> &possiblyReadMentions,
ChannelData *channel) {
for (const auto &msgId : possiblyReadMentions) {
requestMessageData(channel, msgId, [=] {
const auto item = channel
? _session->data().message(channel->id, msgId)
: _session->data().nonChannelMessage(msgId);
if (item && item->mentionsMe()) {
item->markMediaRead();
}
});
}
}
void ApiWrap::requestSharedMediaCount( void ApiWrap::requestSharedMediaCount(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Storage::SharedMediaType type) { Storage::SharedMediaType type) {
@ -4146,3 +4096,7 @@ Api::Polls &ApiWrap::polls() {
Api::ChatParticipants &ApiWrap::chatParticipants() { Api::ChatParticipants &ApiWrap::chatParticipants() {
return *_chatParticipants; return *_chatParticipants;
} }
Api::UnreadThings &ApiWrap::unreadThings() {
return *_unreadThings;
}

View file

@ -67,6 +67,7 @@ class ConfirmPhone;
class PeerPhoto; class PeerPhoto;
class Polls; class Polls;
class ChatParticipants; class ChatParticipants;
class UnreadThings;
namespace details { namespace details {
@ -206,8 +207,9 @@ public:
FnMut<void(not_null<ChannelData*>)> done, FnMut<void(not_null<ChannelData*>)> done,
Fn<void(const QString &)> fail = nullptr); Fn<void(const QString &)> fail = nullptr);
void markMediaRead(const base::flat_set<not_null<HistoryItem*>> &items); void markContentsRead(
void markMediaRead(not_null<HistoryItem*> item); const base::flat_set<not_null<HistoryItem*>> &items);
void markContentsRead(not_null<HistoryItem*> item);
void deleteAllFromParticipant( void deleteAllFromParticipant(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
@ -250,11 +252,6 @@ public:
void jumpToDate(Dialogs::Key chat, const QDate &date); void jumpToDate(Dialogs::Key chat, const QDate &date);
void preloadEnoughUnreadMentions(not_null<History*> history);
void checkForUnreadMentions(
const base::flat_set<MsgId> &possiblyReadMentions,
ChannelData *channel = nullptr);
using SliceType = Data::LoadDirection; using SliceType = Data::LoadDirection;
void requestSharedMedia( void requestSharedMedia(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -356,6 +353,7 @@ public:
[[nodiscard]] Api::PeerPhoto &peerPhoto(); [[nodiscard]] Api::PeerPhoto &peerPhoto();
[[nodiscard]] Api::Polls &polls(); [[nodiscard]] Api::Polls &polls();
[[nodiscard]] Api::ChatParticipants &chatParticipants(); [[nodiscard]] Api::ChatParticipants &chatParticipants();
[[nodiscard]] Api::UnreadThings &unreadThings();
void updatePrivacyLastSeens(); void updatePrivacyLastSeens();
@ -562,8 +560,6 @@ private:
mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsRequestId = 0;
mtpRequestId _contactsStatusesRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0;
base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
base::flat_set<std::tuple< base::flat_set<std::tuple<
not_null<PeerData*>, not_null<PeerData*>,
SharedMediaType, SharedMediaType,
@ -636,6 +632,7 @@ private:
const std::unique_ptr<Api::PeerPhoto> _peerPhoto; const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
const std::unique_ptr<Api::Polls> _polls; const std::unique_ptr<Api::Polls> _polls;
const std::unique_ptr<Api::ChatParticipants> _chatParticipants; const std::unique_ptr<Api::ChatParticipants> _chatParticipants;
const std::unique_ptr<Api::UnreadThings> _unreadThings;
mtpRequestId _wallPaperRequestId = 0; mtpRequestId _wallPaperRequestId = 0;
QString _wallPaperSlug; QString _wallPaperSlug;

View file

@ -183,7 +183,11 @@ void SetupUnreadMentionsMenu(
} }
return base::EventFilterResult::Continue; return base::EventFilterResult::Continue;
}); });
}
void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer) {
} }
} // namespace SendMenu } // namespace SendMenu

View file

@ -54,4 +54,8 @@ void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button, not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer); Fn<PeerData*()> currentPeer);
void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer);
} // namespace SendMenu } // namespace SendMenu

View file

@ -116,18 +116,19 @@ struct HistoryUpdate {
TopPromoted = (1U << 2), TopPromoted = (1U << 2),
Folder = (1U << 3), Folder = (1U << 3),
UnreadMentions = (1U << 4), UnreadMentions = (1U << 4),
ClientSideMessages = (1U << 5), UnreadReactions = (1U << 5),
ChatOccupied = (1U << 6), ClientSideMessages = (1U << 6),
MessageSent = (1U << 7), ChatOccupied = (1U << 7),
ScheduledSent = (1U << 8), MessageSent = (1U << 8),
ForwardDraft = (1U << 9), ScheduledSent = (1U << 9),
OutboxRead = (1U << 10), ForwardDraft = (1U << 10),
BotKeyboard = (1U << 11), OutboxRead = (1U << 11),
CloudDraft = (1U << 12), BotKeyboard = (1U << 12),
LocalDraftSet = (1U << 13), CloudDraft = (1U << 13),
PinnedMessages = (1U << 14), LocalDraftSet = (1U << 14),
PinnedMessages = (1U << 15),
LastUsedBit = (1U << 14), LastUsedBit = (1U << 15),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "dialogs/dialogs_main_list.h" #include "dialogs/dialogs_main_list.h"
#include "history/history_unread_things.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -202,13 +203,13 @@ bool ChatFilter::contains(not_null<History*> history) const {
|| ((_flags & flag) || ((_flags & flag)
&& (!(_flags & Flag::NoMuted) && (!(_flags & Flag::NoMuted)
|| !history->mute() || !history->mute()
|| (history->hasUnreadMentions() || (history->unreadMentions().has()
&& history->folderKnown() && history->folderKnown()
&& !history->folder())) && !history->folder()))
&& (!(_flags & Flag::NoRead) && (!(_flags & Flag::NoRead)
|| history->unreadCount() || history->unreadCount()
|| history->unreadMark() || history->unreadMark()
|| history->hasUnreadMentions() || history->unreadMentions().has()
|| history->fakeUnreadWhileOpened()) || history->fakeUnreadWhileOpened())
&& (!(_flags & Flag::NoArchived) && (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder()))) || (history->folderKnown() && !history->folder())))

View file

@ -1303,7 +1303,14 @@ void Session::photoLoadFail(
void Session::markMediaRead(not_null<const DocumentData*> document) { void Session::markMediaRead(not_null<const DocumentData*> document) {
const auto i = _documentItems.find(document); const auto i = _documentItems.find(document);
if (i != end(_documentItems)) { if (i != end(_documentItems)) {
_session->api().markMediaRead({ begin(i->second), end(i->second) }); auto items = base::flat_set<not_null<HistoryItem*>>();
items.reserve(i->second.size());
for (const auto &item : i->second) {
if (item->isUnreadMention() || item->isIncomingUnreadMedia()) {
items.emplace(item);
}
}
_session->api().markContentsRead(items);
} }
} }

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "history/view/history_view_send_action.h" #include "history/view/history_view_send_action.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "history/history_unread_things.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
@ -778,7 +779,7 @@ void RowPainter::paint(
: QDateTime(); : QDateTime();
}(); }();
const auto displayMentionBadge = history const auto displayMentionBadge = history
? history->hasUnreadMentions() ? history->unreadMentions().has()
: false; : false;
const auto displayUnreadCounter = [&] { const auto displayUnreadCounter = [&] {
if (displayMentionBadge if (displayMentionBadge
@ -941,7 +942,7 @@ void RowPainter::paint(
const auto unreadMuted = history->chatListMutedBadge(); const auto unreadMuted = history->chatListMutedBadge();
const auto mentionMuted = (history->folder() != nullptr); const auto mentionMuted = (history->folder() != nullptr);
const auto displayMentionBadge = displayUnreadInfo const auto displayMentionBadge = displayUnreadInfo
&& history->hasUnreadMentions(); && history->unreadMentions().has();
const auto displayUnreadCounter = (unreadCount > 0); const auto displayUnreadCounter = (unreadCount > 0);
const auto displayUnreadMark = !displayUnreadCounter const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge && !displayMentionBadge

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_service.h" #include "history/history_service.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_inner_widget.h" #include "history/history_inner_widget.h"
#include "history/history_unread_things.h"
#include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_indexed_list.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
@ -128,11 +129,11 @@ void History::popNotification(ItemNotification notification) {
} }
bool History::hasPendingResizedItems() const { bool History::hasPendingResizedItems() const {
return _flags & Flag::f_has_pending_resized_items; return _flags & Flag::HasPendingResizedItems;
} }
void History::setHasPendingResizedItems() { void History::setHasPendingResizedItems() {
_flags |= Flag::f_has_pending_resized_items; _flags |= Flag::HasPendingResizedItems;
} }
void History::itemRemoved(not_null<HistoryItem*> item) { void History::itemRemoved(not_null<HistoryItem*> item) {
@ -691,106 +692,40 @@ not_null<HistoryItem*> History::addNewLocalMessage(
true); true);
} }
void History::setUnreadMentionsCount(int count) { void History::setUnreadThingsKnown() {
const auto had = _unreadMentionsCount && (*_unreadMentionsCount > 0); _flags &= ~Flag::UnreadThingsKnown;
if (_unreadMentions.size() > count) {
LOG(("API Warning: real mentions count is greater than received mentions count"));
count = _unreadMentions.size();
}
_unreadMentionsCount = count;
const auto has = (count > 0);
if (has != had) {
owner().chatsFilters().refreshHistory(this);
updateChatListEntry();
}
} }
bool History::addToUnreadMentions( HistoryUnreadThings::Proxy History::unreadMentions() {
MsgId msgId, return {
UnreadMentionType type) { this,
if (peer->isChannel() && !peer->isMegagroup()) { _unreadThings,
return false; HistoryUnreadThings::Type::Mentions,
} !!(_flags & Flag::UnreadThingsKnown),
auto allLoaded = _unreadMentionsCount
? (_unreadMentions.size() >= *_unreadMentionsCount)
: false;
if (allLoaded) {
if (type == UnreadMentionType::New) {
_unreadMentions.insert(msgId);
setUnreadMentionsCount(*_unreadMentionsCount + 1);
return true;
}
} else if (!_unreadMentions.empty() && type != UnreadMentionType::New) {
_unreadMentions.insert(msgId);
return true;
}
return false;
}
void History::eraseFromUnreadMentions(MsgId msgId) {
_unreadMentions.remove(msgId);
if (_unreadMentionsCount && *_unreadMentionsCount > 0) {
setUnreadMentionsCount(*_unreadMentionsCount - 1);
}
session().changes().historyUpdated(this, UpdateFlag::UnreadMentions);
}
void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
auto count = 0;
auto messages = (const QVector<MTPMessage>*)nullptr;
auto getMessages = [&](auto &list) {
owner().processUsers(list.vusers());
owner().processChats(list.vchats());
return &list.vmessages().v;
}; };
switch (result.type()) { }
case mtpc_messages_messages: {
auto &d = result.c_messages_messages();
messages = getMessages(d);
count = messages->size();
} break;
case mtpc_messages_messagesSlice: { HistoryUnreadThings::ConstProxy History::unreadMentions() const {
auto &d = result.c_messages_messagesSlice(); return {
messages = getMessages(d); _unreadThings ? &_unreadThings->mentions : nullptr,
count = d.vcount().v; !!(_flags & Flag::UnreadThingsKnown),
} break; };
}
case mtpc_messages_channelMessages: { HistoryUnreadThings::Proxy History::unreadReactions() {
LOG(("API Error: unexpected messages.channelMessages! (History::addUnreadMentionsSlice)")); return {
auto &d = result.c_messages_channelMessages(); this,
messages = getMessages(d); _unreadThings,
count = d.vcount().v; HistoryUnreadThings::Type::Reactions,
} break; !!(_flags & Flag::UnreadThingsKnown),
};
}
case mtpc_messages_messagesNotModified: { HistoryUnreadThings::ConstProxy History::unreadReactions() const {
LOG(("API Error: received messages.messagesNotModified! (History::addUnreadMentionsSlice)")); return {
} break; _unreadThings ? &_unreadThings->reactions : nullptr,
!!(_flags & Flag::UnreadThingsKnown),
default: Unexpected("type in History::addUnreadMentionsSlice"); };
}
auto added = false;
if (messages) {
const auto localFlags = MessageFlags();
const auto type = NewMessageType::Existing;
for (const auto &message : *messages) {
const auto item = addNewMessage(
IdFromMessage(message),
message,
localFlags,
type);
if (item && item->isUnreadMention()) {
_unreadMentions.insert(item->id);
added = true;
}
}
}
if (!added) {
count = _unreadMentions.size();
}
setUnreadMentionsCount(count);
session().changes().historyUpdated(this, UpdateFlag::UnreadMentions);
} }
not_null<HistoryItem*> History::addNewToBack( not_null<HistoryItem*> History::addNewToBack(
@ -1368,7 +1303,7 @@ void History::addItemsToLists(
markupSenders = &peer->asChannel()->mgInfo->markupSenders; markupSenders = &peer->asChannel()->mgInfo->markupSenders;
} }
for (const auto &item : ranges::views::reverse(items)) { for (const auto &item : ranges::views::reverse(items)) {
item->addToUnreadMentions(UnreadMentionType::Existing); item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
if (item->from()->id) { if (item->from()->id) {
if (lastAuthors) { // chats if (lastAuthors) { // chats
if (auto user = item->from()->asUser()) { if (auto user = item->from()->asUser()) {
@ -1433,7 +1368,7 @@ void History::checkAddAllToUnreadMentions() {
for (const auto &block : blocks) { for (const auto &block : blocks) {
for (const auto &message : block->messages) { for (const auto &message : block->messages) {
const auto item = message->data(); const auto item = message->data();
item->addToUnreadMentions(UnreadMentionType::Existing); item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
} }
} }
} }
@ -1754,7 +1689,7 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
&& (!inChatList() && (!inChatList()
|| (!unreadCount() || (!unreadCount()
&& !unreadMark() && !unreadMark()
&& !hasUnreadMentions())))) { && !unreadMentions().has())))) {
return; return;
} }
_fakeUnreadWhileOpened = enabled; _fakeUnreadWhileOpened = enabled;
@ -2601,7 +2536,8 @@ void History::applyDialog(
data.vread_outbox_max_id().v); data.vread_outbox_max_id().v);
applyDialogTopMessage(data.vtop_message().v); applyDialogTopMessage(data.vtop_message().v);
setUnreadMark(data.is_unread_mark()); setUnreadMark(data.is_unread_mark());
setUnreadMentionsCount(data.vunread_mentions_count().v); unreadMentions().setCount(data.vunread_mentions_count().v);
unreadReactions().setCount(data.vunread_reactions_count().v);
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
if (const auto pts = data.vpts()) { if (const auto pts = data.vpts()) {
channel->ptsReceived(pts->v); channel->ptsReceived(pts->v);
@ -2832,7 +2768,7 @@ void History::resizeToWidth(int newWidth) {
if (!resizeAllItems && !hasPendingResizedItems()) { if (!resizeAllItems && !hasPendingResizedItems()) {
return; return;
} }
_flags &= ~(Flag::f_has_pending_resized_items); _flags &= ~(Flag::HasPendingResizedItems);
_width = newWidth; _width = newWidth;
int y = 0; int y = 0;
@ -2845,7 +2781,7 @@ void History::resizeToWidth(int newWidth) {
void History::forceFullResize() { void History::forceFullResize() {
_width = 0; _width = 0;
_flags |= Flag::f_has_pending_resized_items; _flags |= Flag::HasPendingResizedItems;
} }
not_null<History*> History::migrateToOrMe() const { not_null<History*> History::migrateToOrMe() const {

View file

@ -27,6 +27,13 @@ class HistoryService;
struct HistoryMessageMarkupData; struct HistoryMessageMarkupData;
class HistoryMainElementDelegateMixin; class HistoryMainElementDelegateMixin;
namespace HistoryUnreadThings {
enum class AddType;
struct All;
class Proxy;
class ConstProxy;
} // namespace HistoryUnreadThings
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -71,11 +78,6 @@ enum class NewMessageType {
Existing, Existing,
}; };
enum class UnreadMentionType {
New, // when new message is added to history
Existing, // when some messages slice was received
};
enum class ItemNotificationType { enum class ItemNotificationType {
Message, Message,
Reaction, Reaction,
@ -333,25 +335,11 @@ public:
void clearLastKeyboard(); void clearLastKeyboard();
int getUnreadMentionsLoadedCount() const { void setUnreadThingsKnown();
return _unreadMentions.size(); [[nodiscard]] HistoryUnreadThings::Proxy unreadMentions();
} [[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const;
MsgId getMinLoadedUnreadMention() const { [[nodiscard]] HistoryUnreadThings::Proxy unreadReactions();
return _unreadMentions.empty() ? 0 : _unreadMentions.front(); [[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
}
MsgId getMaxLoadedUnreadMention() const {
return _unreadMentions.empty() ? 0 : _unreadMentions.back();
}
int getUnreadMentionsCount(int notLoadedValue = -1) const {
return _unreadMentionsCount ? *_unreadMentionsCount : notLoadedValue;
}
bool hasUnreadMentions() const {
return (getUnreadMentionsCount() > 0);
}
void setUnreadMentionsCount(int count);
bool addToUnreadMentions(MsgId msgId, UnreadMentionType type);
void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
Data::Draft *draft(Data::DraftKey key) const; Data::Draft *draft(Data::DraftKey key) const;
void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft); void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft);
@ -493,7 +481,8 @@ private:
friend class HistoryBlock; friend class HistoryBlock;
enum class Flag { enum class Flag {
f_has_pending_resized_items = (1 << 0), HasPendingResizedItems = (1 << 0),
UnreadThingsKnown = (1 << 1),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { friend inline constexpr auto is_flag_type(Flag) {
@ -622,12 +611,11 @@ private:
std::optional<MsgId> _inboxReadBefore; std::optional<MsgId> _inboxReadBefore;
std::optional<MsgId> _outboxReadBefore; std::optional<MsgId> _outboxReadBefore;
std::optional<int> _unreadCount; std::optional<int> _unreadCount;
std::optional<int> _unreadMentionsCount;
base::flat_set<MsgId> _unreadMentions;
std::optional<HistoryItem*> _lastMessage; std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage; std::optional<HistoryItem*> _lastServerMessage;
base::flat_set<not_null<HistoryItem*>> _clientSideMessages; base::flat_set<not_null<HistoryItem*>> _clientSideMessages;
std::unordered_set<std::unique_ptr<HistoryItem>> _messages; std::unordered_set<std::unique_ptr<HistoryItem>> _messages;
std::unique_ptr<HistoryUnreadThings::All> _unreadThings;
// This almost always is equal to _lastMessage. The only difference is // This almost always is equal to _lastMessage. The only difference is
// for a group that migrated to a supergroup. Then _lastMessage can // for a group that migrated to a supergroup. Then _lastMessage can

View file

@ -1019,7 +1019,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
if (item->hasViews()) { if (item->hasViews()) {
session().api().views().scheduleIncrement(item); session().api().views().scheduleIncrement(item);
} }
if (item->isUnreadMention() && !item->isUnreadMedia()) { if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readMentions.insert(item); readMentions.insert(item);
_widget->enqueueMessageHighlight(view); _widget->enqueueMessageHighlight(view);
} }
@ -1051,7 +1052,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} }
if (!readMentions.empty() && _widget->doWeReadMentions()) { if (!readMentions.empty() && _widget->doWeReadMentions()) {
session().api().markMediaRead(readMentions); session().api().markContentsRead(readMentions);
} }
if (mtop >= 0 || htop >= 0) { if (mtop >= 0 || htop >= 0) {

View file

@ -12,10 +12,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/history_item_components.h"
#include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_media_grouped.h"
#include "history/history_item_components.h"
#include "history/history_service.h" #include "history/history_service.h"
#include "history/history_message.h" #include "history/history_message.h"
#include "history/history_unread_things.h"
#include "history/history.h" #include "history/history.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
@ -332,7 +333,11 @@ bool HistoryItem::hasUnreadMediaFlag() const {
} }
bool HistoryItem::isUnreadMention() const { bool HistoryItem::isUnreadMention() const {
return mentionsMe() && (_flags & MessageFlag::MediaIsUnread); return !out() && mentionsMe() && (_flags & MessageFlag::MediaIsUnread);
}
bool HistoryItem::hasUnreadReaction() const {
return false;
} }
bool HistoryItem::mentionsMe() const { bool HistoryItem::mentionsMe() const {
@ -356,15 +361,34 @@ bool HistoryItem::isUnreadMedia() const {
return false; return false;
} }
void HistoryItem::markMediaRead() { bool HistoryItem::isIncomingUnreadMedia() const {
return !out() && isUnreadMedia();
}
void HistoryItem::markMediaAndMentionRead() {
_flags &= ~MessageFlag::MediaIsUnread; _flags &= ~MessageFlag::MediaIsUnread;
if (mentionsMe()) { if (mentionsMe()) {
history()->updateChatListEntry(); history()->updateChatListEntry();
history()->eraseFromUnreadMentions(id); history()->unreadMentions().erase(id);
} }
} }
void HistoryItem::markReactionsRead() {
}
bool HistoryItem::markContentsRead() {
if (hasUnreadReaction()) {
markReactionsRead();
return true;
} else if (isUnreadMention() || isIncomingUnreadMedia()) {
markMediaAndMentionRead();
return true;
}
return false;
}
void HistoryItem::setIsPinned(bool pinned) { void HistoryItem::setIsPinned(bool pinned) {
const auto changed = (isPinned() != pinned); const auto changed = (isPinned() != pinned);
if (pinned) { if (pinned) {
@ -526,7 +550,7 @@ void HistoryItem::clearMainView() {
_mainView = nullptr; _mainView = nullptr;
} }
void HistoryItem::addToUnreadMentions(UnreadMentionType type) { void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
} }
void HistoryItem::applyEditionToHistoryCleared() { void HistoryItem::applyEditionToHistoryCleared() {
@ -592,7 +616,7 @@ void HistoryItem::applySentMessage(
void HistoryItem::indexAsNewItem() { void HistoryItem::indexAsNewItem() {
if (isRegular()) { if (isRegular()) {
addToUnreadMentions(UnreadMentionType::New); addToUnreadThings(HistoryUnreadThings::AddType::New);
if (const auto types = sharedMediaTypes()) { if (const auto types = sharedMediaTypes()) {
_history->session().storage().add(Storage::SharedMediaAddNew( _history->session().storage().add(Storage::SharedMediaAddNew(
_history->peer->id, _history->peer->id,

View file

@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <any> #include <any>
enum class UnreadMentionType;
struct HistoryMessageReplyMarkup; struct HistoryMessageReplyMarkup;
class ReplyKeyboard; class ReplyKeyboard;
class HistoryMessage; class HistoryMessage;
@ -50,6 +49,10 @@ namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
namespace HistoryUnreadThings {
enum class AddType;
} // namespace HistoryUnreadThings
namespace HistoryView { namespace HistoryView {
struct TextState; struct TextState;
struct StateRequest; struct StateRequest;
@ -140,9 +143,13 @@ public:
void markClientSideAsRead(); void markClientSideAsRead();
[[nodiscard]] bool mentionsMe() const; [[nodiscard]] bool mentionsMe() const;
[[nodiscard]] bool isUnreadMention() const; [[nodiscard]] bool isUnreadMention() const;
[[nodiscard]] bool hasUnreadReaction() const;
[[nodiscard]] bool isUnreadMedia() const; [[nodiscard]] bool isUnreadMedia() const;
[[nodiscard]] bool isIncomingUnreadMedia() const;
[[nodiscard]] bool hasUnreadMediaFlag() const; [[nodiscard]] bool hasUnreadMediaFlag() const;
void markMediaRead(); void markReactionsRead();
void markMediaAndMentionRead();
bool markContentsRead();
void setIsPinned(bool isPinned); void setIsPinned(bool isPinned);
// For edit media in history_message. // For edit media in history_message.
@ -274,7 +281,7 @@ public:
virtual void contributeToSlowmode(TimeId realDate = 0) { virtual void contributeToSlowmode(TimeId realDate = 0) {
} }
virtual void addToUnreadMentions(UnreadMentionType type); virtual void addToUnreadThings(HistoryUnreadThings::AddType type);
virtual void destroyHistoryEntry() { virtual void destroyHistoryEntry() {
} }
[[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0; [[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0;

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_location_manager.h" #include "history/history_location_manager.h"
#include "history/history_service.h" #include "history/history_service.h"
#include "history/history_unread_things.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_context_menu.h" // CopyPostLink. #include "history/view/history_view_context_menu.h" // CopyPostLink.
#include "history/view/history_view_spoiler_click_handler.h" #include "history/view/history_view_spoiler_click_handler.h"
@ -1529,19 +1530,32 @@ void HistoryMessage::contributeToSlowmode(TimeId realDate) {
} }
} }
void HistoryMessage::addToUnreadMentions(UnreadMentionType type) { void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) {
if (isRegular() && isUnreadMention()) { if (!isRegular()) {
if (history()->addToUnreadMentions(id, type)) { return;
}
if (isUnreadMention()) {
if (history()->unreadMentions().add(id, type)) {
history()->session().changes().historyUpdated( history()->session().changes().historyUpdated(
history(), history(),
Data::HistoryUpdate::Flag::UnreadMentions); Data::HistoryUpdate::Flag::UnreadMentions);
} }
} }
if (hasUnreadReaction()) {
if (history()->unreadReactions().add(id, type)) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::UnreadReactions);
}
}
} }
void HistoryMessage::destroyHistoryEntry() { void HistoryMessage::destroyHistoryEntry() {
if (isUnreadMention()) { if (isUnreadMention()) {
history()->eraseFromUnreadMentions(id); history()->unreadMentions().erase(id);
}
if (hasUnreadReaction()) {
history()->unreadReactions().erase(id);
} }
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
changeReplyToTopCounter(reply, -1); changeReplyToTopCounter(reply, -1);

View file

@ -175,7 +175,7 @@ public:
void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override; void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override;
void contributeToSlowmode(TimeId realDate = 0) override; void contributeToSlowmode(TimeId realDate = 0) override;
void addToUnreadMentions(UnreadMentionType type) override; void addToUnreadThings(HistoryUnreadThings::AddType type) override;
void destroyHistoryEntry() override; void destroyHistoryEntry() override;
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override; [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;

View file

@ -0,0 +1,190 @@
/*
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/history_unread_things.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat_filters.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
namespace HistoryUnreadThings {
void Proxy::setCount(int count) {
if (!_known) {
_history->setUnreadThingsKnown();
}
if (!_data) {
if (!count) {
return;
}
createData();
}
auto &list = resolveList();
const auto loaded = list.loadedCount();
if (loaded > count) {
LOG(("API Warning: "
"real count is greater than received unread count"));
count = loaded;
}
if (!count) {
const auto &other = (_type == Type::Mentions)
? _data->reactions
: _data->mentions;
if (other.count(-1) == 0) {
_data = nullptr;
return;
}
}
const auto had = (list.count() > 0);
list.setCount(count);
const auto has = (count > 0);
if (has != had) {
_history->owner().chatsFilters().refreshHistory(_history);
_history->updateChatListEntry();
}
}
bool Proxy::add(MsgId msgId, AddType type) {
const auto peer = _history->peer;
if (peer->isChannel() && !peer->isMegagroup()) {
return false;
}
if (!_data) {
createData();
}
auto &list = resolveList();
const auto count = list.count();
const auto loaded = list.loadedCount();
const auto allLoaded = (count >= 0) && (loaded >= count);
if (allLoaded) {
if (type == AddType::New) {
list.insert(msgId);
setCount(count + 1);
return true;
}
} else if (loaded > 0 && type != AddType::New) {
list.insert(msgId);
return true;
}
return false;
}
void Proxy::erase(MsgId msgId) {
if (!_data) {
return;
}
auto &list = resolveList();
list.erase(msgId);
if (const auto count = list.count(); count > 0) {
setCount(count - 1);
}
_history->session().changes().historyUpdated(
_history,
Data::HistoryUpdate::Flag::UnreadMentions);
}
void Proxy::addSlice(const MTPmessages_Messages &slice) {
auto fullCount = slice.match([&](
const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! "
"(Proxy::addSlice)"));
return 0;
}, [&](const MTPDmessages_messages &data) {
return int(data.vmessages().v.size());
}, [&](const MTPDmessages_messagesSlice &data) {
return data.vcount().v;
}, [&](const MTPDmessages_channelMessages &data) {
if (_history->peer->isChannel()) {
_history->peer->asChannel()->ptsReceived(data.vpts().v);
} else {
LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (Proxy::addSlice)"));
}
return data.vcount().v;
});
auto &owner = _history->owner();
const auto messages = slice.match([&](
const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! "
"(Proxy::addSlice)"));
return QVector<MTPMessage>();
}, [&](const auto &data) {
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
return data.vmessages().v;
});
if (messages.isEmpty()) {
return;
}
if (!_data) {
createData();
}
auto added = false;
auto &list = resolveList();
const auto localFlags = MessageFlags();
const auto type = NewMessageType::Existing;
for (const auto &message : messages) {
const auto item = _history->addNewMessage(
IdFromMessage(message),
message,
localFlags,
type);
const auto is = [&] {
switch (_type) {
case Type::Mentions: return item->isUnreadMention();
case Type::Reactions: return item->hasUnreadReaction();
}
Unexpected("Type in Proxy::addSlice.");
}();
if (is) {
list.insert(item->id);
added = true;
}
}
if (!added) {
fullCount = list.loadedCount();
}
setCount(fullCount);
const auto flag = [&] {
using Flag = Data::HistoryUpdate::Flag;
switch (_type) {
case Type::Mentions: return Flag::UnreadMentions;
case Type::Reactions: return Flag::UnreadReactions;
}
Unexpected("Type in Proxy::addSlice.");
}();
_history->session().changes().historyUpdated(_history, flag);
}
void Proxy::createData() {
_data = std::make_unique<All>();
if (_known) {
_data->mentions.setCount(0);
_data->reactions.setCount(0);
}
}
[[nodiscard]] List &Proxy::resolveList() {
Expects(_data != nullptr);
switch (_type) {
case Type::Mentions: return _data->mentions;
case Type::Reactions: return _data->reactions;
}
Unexpected("Unread things type in Proxy::resolveList.");
}
} // namespace HistoryUnreadThings

View file

@ -0,0 +1,131 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class History;
namespace HistoryUnreadThings {
enum class AddType {
New,
Existing,
};
enum class Type {
Mentions,
Reactions,
};
class List final {
public:
[[nodiscard]] int loadedCount() const {
return _messages.size();
}
[[nodiscard]] MsgId minLoaded() const {
return _messages.empty() ? 0 : _messages.front();
}
[[nodiscard]] MsgId maxLoaded() const {
return _messages.empty() ? 0 : _messages.back();
}
[[nodiscard]] int count(int notKnownValue = -1) const {
return _count.value_or(notKnownValue);
}
[[nodiscard]] bool has() const {
return (count() > 0);
}
void setCount(int count) {
_count = count;
}
void insert(MsgId msgId) {
_messages.insert(msgId);
}
void erase(MsgId msgId) {
_messages.remove(msgId);
}
private:
std::optional<int> _count;
base::flat_set<MsgId> _messages;
};
struct All {
List mentions;
List reactions;
};
class ConstProxy {
public:
ConstProxy(const List *list, bool known) : _list(list), _known(known) {
}
ConstProxy(const ConstProxy &) = delete;
ConstProxy &operator=(const ConstProxy &) = delete;
[[nodiscard]] int loadedCount() const {
return _list ? _list->loadedCount() : 0;
}
[[nodiscard]] MsgId minLoaded() const {
return _list ? _list->minLoaded() : 0;
}
[[nodiscard]] MsgId maxLoaded() const {
return _list ? _list->maxLoaded() : 0;
}
[[nodiscard]] int count(int notKnownValue = -1) const {
return _list
? _list->count(notKnownValue)
: _known
? 0
: notKnownValue;
}
[[nodiscard]] bool has() const {
return _list && _list->has();
}
private:
const List *_list = nullptr;
const bool _known = false;
};
class Proxy final : public ConstProxy {
public:
Proxy(
not_null<History*> history,
std::unique_ptr<All> &data,
Type type,
bool known)
: ConstProxy(
(!data
? nullptr
: (type == Type::Mentions)
? &data->mentions
: &data->reactions),
known)
, _history(history)
, _data(data)
, _type(type) {
}
void setCount(int count);
bool add(MsgId msgId, AddType type);
void erase(MsgId msgId);
void addSlice(const MTPmessages_Messages &slice);
private:
void createData();
[[nodiscard]] List &resolveList();
const not_null<History*> _history;
std::unique_ptr<All> &_data;
Type _type = Type::Mentions;
bool _known = false;
};
} // namespace HistoryUnreadThings

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_sending.h" #include "api/api_sending.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "api/api_send_progress.h" #include "api/api_send_progress.h"
#include "api/api_unread_things.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/delete_messages_box.h" #include "boxes/delete_messages_box.h"
#include "boxes/send_files_box.h" #include "boxes/send_files_box.h"
@ -73,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_inner_widget.h" #include "history/history_inner_widget.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_unread_things.h"
#include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_voice_record_bar.h"
#include "history/view/controls/history_view_ttl_button.h" #include "history/view/controls/history_view_ttl_button.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
@ -215,6 +217,9 @@ HistoryWidget::HistoryWidget(
, _unreadMentions( , _unreadMentions(
_scroll, _scroll,
controller->chatStyle()->value(lifetime(), st::historyUnreadMentions)) controller->chatStyle()->value(lifetime(), st::historyUnreadMentions))
, _unreadReactions(
_scroll,
controller->chatStyle()->value(lifetime(), st::historyUnreadReactions))
, _fieldAutocomplete(this, controller) , _fieldAutocomplete(this, controller)
, _supportAutocomplete(session().supportMode() , _supportAutocomplete(session().supportMode()
? object_ptr<Support::Autocomplete>(this, &session()) ? object_ptr<Support::Autocomplete>(this, &session())
@ -278,8 +283,13 @@ HistoryWidget::HistoryWidget(
} }
}, lifetime()); }, lifetime());
_historyDown->addClickHandler([=] { historyDownClicked(); }); _historyDown.widget->addClickHandler([=] { historyDownClicked(); });
_unreadMentions->addClickHandler([=] { showNextUnreadMention(); }); _unreadMentions.widget->addClickHandler([=] {
showNextUnreadMention();
});
_unreadReactions.widget->addClickHandler([=] {
showNextUnreadReaction();
});
_fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); }); _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
_send->addClickHandler([=] { sendButtonClicked(); }); _send->addClickHandler([=] { sendButtonClicked(); });
@ -353,9 +363,13 @@ HistoryWidget::HistoryWidget(
_scroll->updateBars(); _scroll->updateBars();
}, lifetime()); }, lifetime());
_historyDown->installEventFilter(this); _historyDown.widget->installEventFilter(this);
_unreadMentions->installEventFilter(this); _unreadMentions.widget->installEventFilter(this);
SendMenu::SetupUnreadMentionsMenu(_unreadMentions.data(), [=] { _unreadReactions.widget->installEventFilter(this);
SendMenu::SetupUnreadMentionsMenu(_unreadMentions.widget.data(), [=] {
return _history ? _history->peer.get() : nullptr;
});
SendMenu::SetupUnreadReactionsMenu(_unreadReactions.widget.data(), [=] {
return _history ? _history->peer.get() : nullptr; return _history ? _history->peer.get() : nullptr;
}); });
@ -562,6 +576,7 @@ HistoryWidget::HistoryWidget(
| HistoryUpdateFlag::BotKeyboard | HistoryUpdateFlag::BotKeyboard
| HistoryUpdateFlag::CloudDraft | HistoryUpdateFlag::CloudDraft
| HistoryUpdateFlag::UnreadMentions | HistoryUpdateFlag::UnreadMentions
| HistoryUpdateFlag::UnreadReactions
| HistoryUpdateFlag::UnreadView | HistoryUpdateFlag::UnreadView
| HistoryUpdateFlag::TopPromoted | HistoryUpdateFlag::TopPromoted
| HistoryUpdateFlag::ClientSideMessages | HistoryUpdateFlag::ClientSideMessages
@ -591,8 +606,9 @@ HistoryWidget::HistoryWidget(
if (flags & HistoryUpdateFlag::ClientSideMessages) { if (flags & HistoryUpdateFlag::ClientSideMessages) {
updateSendButtonType(); updateSendButtonType();
} }
if (flags & HistoryUpdateFlag::UnreadMentions) { if ((flags & HistoryUpdateFlag::UnreadMentions)
updateUnreadMentionsVisibility(); || (flags & HistoryUpdateFlag::UnreadReactions)) {
updateUnreadThingsVisibility();
} }
if (flags & HistoryUpdateFlag::UnreadView) { if (flags & HistoryUpdateFlag::UnreadView) {
unreadCountUpdated(); unreadCountUpdated();
@ -907,7 +923,7 @@ void HistoryWidget::initVoiceRecordBar() {
_voiceRecordBar->lockShowStarts( _voiceRecordBar->lockShowStarts(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updateHistoryDownVisibility(); updateHistoryDownVisibility();
updateUnreadMentionsVisibility(); updateUnreadThingsVisibility();
}, lifetime()); }, lifetime());
_voiceRecordBar->updateSendButtonTypeRequests( _voiceRecordBar->updateSendButtonTypeRequests(
@ -2459,7 +2475,7 @@ void HistoryWidget::updateControlsVisibility() {
_topBar->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr);
} }
updateHistoryDownVisibility(); updateHistoryDownVisibility();
updateUnreadMentionsVisibility(); updateUnreadThingsVisibility();
if (!_history || _a_show.animating()) { if (!_history || _a_show.animating()) {
hideChildWidgets(); hideChildWidgets();
return; return;
@ -2744,7 +2760,7 @@ void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
destroyUnreadBar(); destroyUnreadBar();
if (doWeReadServerHistory()) { if (doWeReadServerHistory()) {
if (item->isUnreadMention() && !item->isUnreadMedia()) { if (item->isUnreadMention() && !item->isUnreadMedia()) {
session().api().markMediaRead(item); session().api().markContentsRead(item);
} }
session().data().histories().readInboxOnNewMessage(item); session().data().histories().readInboxOnNewMessage(item);
@ -2769,7 +2785,7 @@ void HistoryWidget::unreadCountUpdated() {
}); });
} else { } else {
updateHistoryDownVisibility(); updateHistoryDownVisibility();
_historyDown->setUnreadCount(_history->chatListUnreadCount()); _historyDown.widget->setUnreadCount(_history->chatListUnreadCount());
} }
} }
@ -3243,7 +3259,7 @@ void HistoryWidget::preloadHistoryIfNeeded() {
} }
updateHistoryDownVisibility(); updateHistoryDownVisibility();
updateUnreadMentionsVisibility(); updateUnreadThingsVisibility();
if (!_scrollToAnimation.animating()) { if (!_scrollToAnimation.animating()) {
preloadHistoryByScroll(); preloadHistoryByScroll();
checkReplyReturns(); checkReplyReturns();
@ -3332,7 +3348,7 @@ void HistoryWidget::historyDownClicked() {
} }
void HistoryWidget::showNextUnreadMention() { void HistoryWidget::showNextUnreadMention() {
const auto msgId = _history->getMinLoadedUnreadMention(); const auto msgId = _history->unreadMentions().minLoaded();
const auto already = (_showAtMsgId == msgId); const auto already = (_showAtMsgId == msgId);
// Mark mention voice/video message as read. // Mark mention voice/video message as read.
@ -3354,6 +3370,12 @@ void HistoryWidget::showNextUnreadMention() {
showHistory(_peer->id, msgId); showHistory(_peer->id, msgId);
} }
void HistoryWidget::showNextUnreadReaction() {
const auto msgId = _history->unreadReactions().minLoaded();
const auto already = (_showAtMsgId == msgId);
showHistory(_peer->id, msgId);
}
void HistoryWidget::saveEditMsg() { void HistoryWidget::saveEditMsg() {
Expects(_history != nullptr); Expects(_history != nullptr);
@ -3698,8 +3720,7 @@ void HistoryWidget::showAnimated(
_preserveScrollTop = true; _preserveScrollTop = true;
show(); show();
_topBar->finishAnimating(); _topBar->finishAnimating();
historyDownAnimationFinish(); cornerButtonsAnimationFinish();
unreadMentionsAnimationFinish();
if (_pinnedBar) { if (_pinnedBar) {
_pinnedBar->finishAnimating(); _pinnedBar->finishAnimating();
} }
@ -3732,8 +3753,7 @@ void HistoryWidget::showAnimated(
void HistoryWidget::animationCallback() { void HistoryWidget::animationCallback() {
update(); update();
if (!_a_show.animating()) { if (!_a_show.animating()) {
historyDownAnimationFinish(); cornerButtonsAnimationFinish();
unreadMentionsAnimationFinish();
if (_pinnedBar) { if (_pinnedBar) {
_pinnedBar->finishAnimating(); _pinnedBar->finishAnimating();
} }
@ -3808,22 +3828,20 @@ void HistoryWidget::checkSuggestToGigagroup() {
} }
void HistoryWidget::finishAnimating() { void HistoryWidget::finishAnimating() {
if (!_a_show.animating()) return; if (!_a_show.animating()) {
return;
}
_a_show.stop(); _a_show.stop();
_topShadow->setVisible(_peer != nullptr); _topShadow->setVisible(_peer != nullptr);
_topBar->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr);
historyDownAnimationFinish(); cornerButtonsAnimationFinish();
unreadMentionsAnimationFinish();
} }
void HistoryWidget::historyDownAnimationFinish() { void HistoryWidget::cornerButtonsAnimationFinish() {
_historyDownShown.stop(); _historyDown.animation.stop();
updateHistoryDownPosition(); _unreadMentions.animation.stop();
} _unreadReactions.animation.stop();
updateCornerButtonsPositions();
void HistoryWidget::unreadMentionsAnimationFinish() {
_unreadMentionsShown.stop();
updateUnreadMentionsPosition();
} }
void HistoryWidget::chooseAttach() { void HistoryWidget::chooseAttach() {
@ -4047,7 +4065,10 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
} }
} }
} }
if ((obj == _historyDown || obj == _unreadMentions) && e->type() == QEvent::Wheel) { if (e->type() == QEvent::Wheel
&& (obj == _historyDown.widget
|| obj == _unreadMentions.widget
|| obj == _unreadReactions.widget)) {
return _scroll->viewportEvent(e); return _scroll->viewportEvent(e);
} }
return TWidget::eventFilter(obj, e); return TWidget::eventFilter(obj, e);
@ -4946,7 +4967,7 @@ void HistoryWidget::updateControlsGeometry() {
updateFieldSize(); updateFieldSize();
updateHistoryDownPosition(); updateCornerButtonsPositions();
if (_membersDropdown) { if (_membersDropdown) {
_membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); _membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
@ -5156,16 +5177,7 @@ void HistoryWidget::updateHistoryGeometry(
if (_supportAutocomplete) { if (_supportAutocomplete) {
_supportAutocomplete->setBoundings(_scroll->geometry()); _supportAutocomplete->setBoundings(_scroll->geometry());
} }
if (!_historyDownShown.animating()) { updateCornerButtonsPositions();
// _historyDown is a child widget of _scroll, not me.
_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _historyDown->height() - st::historyToDownPosition.y());
if (!_unreadMentionsShown.animating()) {
// _unreadMentions is a child widget of _scroll, not me.
auto additionalSkip = _historyDownIsShown ? (_historyDown->height() + st::historyUnreadMentionsSkip) : 0;
_unreadMentions->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _unreadMentions->height() - additionalSkip - st::historyToDownPosition.y());
}
}
controller()->floatPlayerAreaUpdated(); controller()->floatPlayerAreaUpdated();
} }
@ -5470,15 +5482,71 @@ int HistoryWidget::computeMaxFieldHeight() const {
return std::min(st::historyComposeFieldMaxHeight, available); return std::min(st::historyComposeFieldMaxHeight, available);
} }
void HistoryWidget::updateHistoryDownPosition() { void HistoryWidget::updateCornerButtonsPositions() {
// _historyDown is a child widget of _scroll, not me. const auto checkVisibility = [](CornerButton &button) {
auto top = anim::interpolate(0, _historyDown->height() + st::historyToDownPosition.y(), _historyDownShown.value(_historyDownIsShown ? 1. : 0.)); const auto shouldBeHidden = !button.shown
_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - top); && !button.animation.animating();
auto shouldBeHidden = !_historyDownIsShown && !_historyDownShown.animating(); if (shouldBeHidden != button.widget->isHidden()) {
if (shouldBeHidden != _historyDown->isHidden()) { button.widget->setVisible(!shouldBeHidden);
_historyDown->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);
} }
updateUnreadMentionsPosition(); {
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);
} }
void HistoryWidget::updateHistoryDownVisibility() { void HistoryWidget::updateHistoryDownVisibility() {
@ -5495,7 +5563,7 @@ void HistoryWidget::updateHistoryDownVisibility() {
const auto top = _list->itemTop(unread); const auto top = _list->itemTop(unread);
return (top >= _scroll->scrollTop() + _scroll->height()); return (top >= _scroll->scrollTop() + _scroll->height());
}; };
const auto historyDownIsVisible = [&] { updateCornerButtonVisibility(_historyDown, [&] {
if (!_list || _firstLoadRequest) { if (!_list || _firstLoadRequest) {
return false; return false;
} }
@ -5514,60 +5582,69 @@ void HistoryWidget::updateHistoryDownVisibility() {
return true; return true;
} }
return false; return false;
}; }());
auto historyDownIsShown = historyDownIsVisible(); }
if (_historyDownIsShown != historyDownIsShown) {
_historyDownIsShown = historyDownIsShown; void HistoryWidget::updateCornerButtonVisibility(
_historyDownShown.start([=] { updateHistoryDownPosition(); }, _historyDownIsShown ? 0. : 1., _historyDownIsShown ? 1. : 0., st::historyToDownDuration); 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::updateUnreadMentionsPosition() { void HistoryWidget::updateUnreadThingsVisibility() {
// _unreadMentions is a child widget of _scroll, not me. if (_a_show.animating()) {
auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.value(_unreadMentionsIsShown ? 1. : 0.)); return;
auto shift = anim::interpolate(0, _historyDown->height() + st::historyUnreadMentionsSkip, _historyDownShown.value(_historyDownIsShown ? 1. : 0.));
auto top = _scroll->height() - _unreadMentions->height() - st::historyToDownPosition.y() - shift;
_unreadMentions->moveToRight(right, top);
auto shouldBeHidden = !_unreadMentionsIsShown && !_unreadMentionsShown.animating();
if (shouldBeHidden != _unreadMentions->isHidden()) {
_unreadMentions->setVisible(!shouldBeHidden);
} }
}
void HistoryWidget::updateUnreadMentionsVisibility() { auto &unreadThings = session().api().unreadThings();
if (_a_show.animating()) return; unreadThings.preloadEnough(_history);
auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup()); const auto updateWithLoadedCount = [&](CornerButton &button, int count) {
if (showUnreadMentions) { updateCornerButtonVisibility(button, [&] {
session().api().preloadEnoughUnreadMentions(_history); if (!count
} || _firstLoadRequest
const auto unreadMentionsIsShown = [&] { || _voiceRecordBar->isLockPresent()) {
if (!showUnreadMentions || _firstLoadRequest) { return false;
return false;
}
if (_voiceRecordBar->isLockPresent()) {
return false;
}
if (!_history->getUnreadMentionsLoadedCount()) {
return false;
}
// If we have an unheard voice message with the mention
// and our message is the last one, we can't see the status
// (delivered/read) of this message.
// (Except for MacBooks with the TouchPad.)
if (_scroll->scrollTop() == _scroll->scrollTopMax()) {
if (const auto lastMessage = _history->lastMessage()) {
return !lastMessage->from()->isSelf();
} }
// If we have an unheard voice message with the mention
// and our message is the last one, we can't see the status
// (delivered/read) of this message.
// (Except for MacBooks with the TouchPad.)
if (_scroll->scrollTop() == _scroll->scrollTopMax()) {
if (const auto lastMessage = _history->lastMessage()) {
return !lastMessage->from()->isSelf();
}
}
return true;
}());
};
if (unreadThings.trackMentions(_peer)) {
if (const auto count = _history->unreadMentions().count(0)) {
_unreadMentions.widget->setUnreadCount(count);
} }
return true; updateWithLoadedCount(
}(); _unreadMentions,
if (unreadMentionsIsShown) { _history->unreadMentions().loadedCount());
_unreadMentions->setUnreadCount(_history->getUnreadMentionsCount()); } else {
updateCornerButtonVisibility(_unreadMentions, false);
} }
if (_unreadMentionsIsShown != unreadMentionsIsShown) {
_unreadMentionsIsShown = unreadMentionsIsShown; if (unreadThings.trackReactions(_peer)) {
_unreadMentionsShown.start([=] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration); if (const auto count = _history->unreadReactions().count(0)) {
_unreadReactions.widget->setUnreadCount(count);
}
updateWithLoadedCount(
_unreadReactions,
_history->unreadReactions().loadedCount());
} else {
updateCornerButtonVisibility(_unreadReactions, false);
} }
} }

View file

@ -242,11 +242,6 @@ public:
void applyCloudDraft(History *history); void applyCloudDraft(History *history);
void updateHistoryDownPosition();
void updateHistoryDownVisibility();
void updateUnreadMentionsPosition();
void updateUnreadMentionsVisibility();
void updateFieldSubmitSettings(); void updateFieldSubmitSettings();
void activate(); void activate();
@ -332,7 +327,15 @@ private:
}; };
using TextUpdateEvents = base::flags<TextUpdateEvent>; using TextUpdateEvents = base::flags<TextUpdateEvent>;
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; 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 checkSuggestToGigagroup(); void checkSuggestToGigagroup();
void initTabbedSelector(); void initTabbedSelector();
@ -384,6 +387,7 @@ private:
void recountChatWidth(); void recountChatWidth();
void historyDownClicked(); void historyDownClicked();
void showNextUnreadMention(); void showNextUnreadMention();
void showNextUnreadReaction();
void handlePeerUpdate(); void handlePeerUpdate();
void setMembersShowAreaActive(bool active); void setMembersShowAreaActive(bool active);
void handleHistoryChange(not_null<const History*> history); void handleHistoryChange(not_null<const History*> history);
@ -416,11 +420,15 @@ private:
void animationCallback(); void animationCallback();
void updateOverStates(QPoint pos); void updateOverStates(QPoint pos);
void chooseAttach(); void chooseAttach();
void historyDownAnimationFinish(); void cornerButtonsAnimationFinish();
void unreadMentionsAnimationFinish();
void sendButtonClicked(); void sendButtonClicked();
void newItemAdded(not_null<HistoryItem*> item); void newItemAdded(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 canSendFiles(not_null<const QMimeData*> data) const;
bool confirmSendingFiles( bool confirmSendingFiles(
const QStringList &files, const QStringList &files,
@ -694,13 +702,9 @@ private:
bool _synteticScrollEvent = false; bool _synteticScrollEvent = false;
Ui::Animations::Simple _scrollToAnimation; Ui::Animations::Simple _scrollToAnimation;
Ui::Animations::Simple _historyDownShown; CornerButton _historyDown;
bool _historyDownIsShown = false; CornerButton _unreadMentions;
object_ptr<Ui::HistoryDownButton> _historyDown; CornerButton _unreadReactions;
Ui::Animations::Simple _unreadMentionsShown;
bool _unreadMentionsIsShown = false;
object_ptr<Ui::HistoryDownButton> _unreadMentions;
const object_ptr<FieldAutocomplete> _fieldAutocomplete; const object_ptr<FieldAutocomplete> _fieldAutocomplete;
object_ptr<Support::Autocomplete> _supportAutocomplete; object_ptr<Support::Autocomplete> _supportAutocomplete;

View file

@ -121,7 +121,11 @@ historyUnreadMentions: TwoIconButton(historyToDown) {
iconAbove: icon {{ "history_unread_mention", historyToDownFg, point(16px, 16px) }}; iconAbove: icon {{ "history_unread_mention", historyToDownFg, point(16px, 16px) }};
iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver, point(16px, 16px) }}; iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver, point(16px, 16px) }};
} }
historyUnreadMentionsSkip: 4px; historyUnreadReactions: TwoIconButton(historyToDown) {
iconAbove: icon {{ "history_unread_reaction", historyToDownFg, point(16px, 16px) }};
iconAboveOver: icon {{ "history_unread_reaction", historyToDownFgOver, point(16px, 16px) }};
}
historyUnreadThingsSkip: 4px;
membersInnerWidth: 310px; membersInnerWidth: 310px;
membersInnerHeightMax: 360px; membersInnerHeightMax: 360px;

View file

@ -918,8 +918,8 @@ void Manager::notificationReplied(
history->session().api().sendMessage(std::move(message)); history->session().api().sendMessage(std::move(message));
const auto item = history->owner().message(history->peer, id.msgId); const auto item = history->owner().message(history->peer, id.msgId);
if (item && item->isUnreadMention() && !item->isUnreadMedia()) { if (item && item->isUnreadMention() && !item->isIncomingUnreadMedia()) {
history->session().api().markMediaRead(item); history->session().api().markContentsRead(item);
} }
} }