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_toggling_media.cpp
api/api_toggling_media.h
api/api_unread_things.cpp
api/api_unread_things.h
api/api_updates.cpp
api/api_updates.h
api/api_user_privacy.cpp
@ -686,6 +688,8 @@ PRIVATE
history/history_message.h
history/history_service.cpp
history/history_service.h
history/history_unread_things.cpp
history/history_unread_things.h
history/history_widget.cpp
history/history_widget.h
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_text_entities.h"
#include "api/api_user_privacy.h"
#include "api/api_unread_things.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "mtproto/mtp_instance.h"
@ -1178,25 +1179,29 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
case mtpc_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) {
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()) {
item->markMediaRead();
item->markMediaAndMentionRead();
_session->data().requestItemRepaint(item);
if (item->out()
&& item->history()->peer->isUser()
&& !requestingDifference()) {
item->history()->peer->asUser()->madeAction(base::unixtime::now());
item->history()->peer->asUser()->madeAction(
base::unixtime::now());
}
}
} else {
// Perhaps it was an unread mention!
possiblyReadMentions.insert(msgId.v);
unknownReadIds.insert(msgId.v);
}
}
session().api().checkForUnreadMentions(possiblyReadMentions);
session().api().unreadThings().mediaAndMentionsRead(unknownReadIds);
} break;
case mtpc_updateReadHistoryInbox: {
@ -1565,19 +1570,21 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
return;
}
auto possiblyReadMentions = base::flat_set<MsgId>();
auto unknownReadIds = base::flat_set<MsgId>();
for (const auto &msgId : d.vmessages().v) {
if (auto item = session().data().message(channel->id, msgId.v)) {
if (item->isUnreadMedia() || item->isUnreadMention()) {
item->markMediaRead();
item->markMediaAndMentionRead();
session().data().requestItemRepaint(item);
}
} else {
// 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;
// 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_views.h"
#include "api/api_confirm_phone.h"
#include "api/api_unread_things.h"
#include "data/stickers/data_stickers.h"
#include "data/data_drafts.h"
#include "data/data_changes.h"
@ -96,9 +97,6 @@ constexpr auto kSaveCloudDraftTimeout = 1000;
constexpr auto kTopPromotionInterval = TimeId(60 * 60);
constexpr auto kTopPromotionMinDelay = TimeId(10);
constexpr auto kSmallDelayMs = 5;
constexpr auto kUnreadMentionsPreloadIfLess = 5;
constexpr auto kUnreadMentionsFirstRequestLimit = 10;
constexpr auto kUnreadMentionsNextRequestLimit = 100;
constexpr auto kSharedMediaLimit = 100;
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
@ -142,7 +140,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
, _peerPhoto(std::make_unique<Api::PeerPhoto>(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, [=] {
// You can't use _session->lifetime() in the constructor,
// 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) {
auto markedIds = QVector<MTPint>();
auto channelMarkedIds = base::flat_map<
@ -1295,12 +1294,7 @@ void ApiWrap::markMediaRead(
QVector<MTPint>>();
markedIds.reserve(items.size());
for (const auto &item : items) {
if ((!item->isUnreadMedia() || item->out())
&& !item->isUnreadMention()) {
continue;
}
item->markMediaRead();
if (!item->isRegular()) {
if (!item->markContentsRead() || !item->isRegular()) {
continue;
}
if (const auto channel = item->history()->peer->asChannel()) {
@ -1324,13 +1318,8 @@ void ApiWrap::markMediaRead(
}
}
void ApiWrap::markMediaRead(not_null<HistoryItem*> item) {
if ((!item->isUnreadMedia() || item->out())
&& !item->isUnreadMention()) {
return;
}
item->markMediaRead();
if (!item->isRegular()) {
void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
if (!item->markContentsRead() || !item->isRegular()) {
return;
}
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(
not_null<PeerData*> peer,
Storage::SharedMediaType type) {
@ -4146,3 +4096,7 @@ Api::Polls &ApiWrap::polls() {
Api::ChatParticipants &ApiWrap::chatParticipants() {
return *_chatParticipants;
}
Api::UnreadThings &ApiWrap::unreadThings() {
return *_unreadThings;
}

View File

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

View File

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

View File

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

View File

@ -116,18 +116,19 @@ struct HistoryUpdate {
TopPromoted = (1U << 2),
Folder = (1U << 3),
UnreadMentions = (1U << 4),
ClientSideMessages = (1U << 5),
ChatOccupied = (1U << 6),
MessageSent = (1U << 7),
ScheduledSent = (1U << 8),
ForwardDraft = (1U << 9),
OutboxRead = (1U << 10),
BotKeyboard = (1U << 11),
CloudDraft = (1U << 12),
LocalDraftSet = (1U << 13),
PinnedMessages = (1U << 14),
UnreadReactions = (1U << 5),
ClientSideMessages = (1U << 6),
ChatOccupied = (1U << 7),
MessageSent = (1U << 8),
ScheduledSent = (1U << 9),
ForwardDraft = (1U << 10),
OutboxRead = (1U << 11),
BotKeyboard = (1U << 12),
CloudDraft = (1U << 13),
LocalDraftSet = (1U << 14),
PinnedMessages = (1U << 15),
LastUsedBit = (1U << 14),
LastUsedBit = (1U << 15),
};
using Flags = base::flags<Flag>;
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_histories.h"
#include "dialogs/dialogs_main_list.h"
#include "history/history_unread_things.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "apiwrap.h"
@ -202,13 +203,13 @@ bool ChatFilter::contains(not_null<History*> history) const {
|| ((_flags & flag)
&& (!(_flags & Flag::NoMuted)
|| !history->mute()
|| (history->hasUnreadMentions()
|| (history->unreadMentions().has()
&& history->folderKnown()
&& !history->folder()))
&& (!(_flags & Flag::NoRead)
|| history->unreadCount()
|| history->unreadMark()
|| history->hasUnreadMentions()
|| history->unreadMentions().has()
|| history->fakeUnreadWhileOpened())
&& (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder())))

View File

@ -1303,7 +1303,14 @@ void Session::photoLoadFail(
void Session::markMediaRead(not_null<const DocumentData*> document) {
const auto i = _documentItems.find(document);
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 "history/view/history_view_send_action.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.h"
#include "history/history.h"
@ -778,7 +779,7 @@ void RowPainter::paint(
: QDateTime();
}();
const auto displayMentionBadge = history
? history->hasUnreadMentions()
? history->unreadMentions().has()
: false;
const auto displayUnreadCounter = [&] {
if (displayMentionBadge
@ -941,7 +942,7 @@ void RowPainter::paint(
const auto unreadMuted = history->chatListMutedBadge();
const auto mentionMuted = (history->folder() != nullptr);
const auto displayMentionBadge = displayUnreadInfo
&& history->hasUnreadMentions();
&& history->unreadMentions().has();
const auto displayUnreadCounter = (unreadCount > 0);
const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_service.h"
#include "history/history_item_components.h"
#include "history/history_inner_widget.h"
#include "history/history_unread_things.h"
#include "dialogs/dialogs_indexed_list.h"
#include "data/stickers/data_stickers.h"
#include "data/data_drafts.h"
@ -128,11 +129,11 @@ void History::popNotification(ItemNotification notification) {
}
bool History::hasPendingResizedItems() const {
return _flags & Flag::f_has_pending_resized_items;
return _flags & Flag::HasPendingResizedItems;
}
void History::setHasPendingResizedItems() {
_flags |= Flag::f_has_pending_resized_items;
_flags |= Flag::HasPendingResizedItems;
}
void History::itemRemoved(not_null<HistoryItem*> item) {
@ -691,106 +692,40 @@ not_null<HistoryItem*> History::addNewLocalMessage(
true);
}
void History::setUnreadMentionsCount(int count) {
const auto had = _unreadMentionsCount && (*_unreadMentionsCount > 0);
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();
}
void History::setUnreadThingsKnown() {
_flags &= ~Flag::UnreadThingsKnown;
}
bool History::addToUnreadMentions(
MsgId msgId,
UnreadMentionType type) {
if (peer->isChannel() && !peer->isMegagroup()) {
return false;
}
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;
HistoryUnreadThings::Proxy History::unreadMentions() {
return {
this,
_unreadThings,
HistoryUnreadThings::Type::Mentions,
!!(_flags & Flag::UnreadThingsKnown),
};
switch (result.type()) {
case mtpc_messages_messages: {
auto &d = result.c_messages_messages();
messages = getMessages(d);
count = messages->size();
} break;
}
case mtpc_messages_messagesSlice: {
auto &d = result.c_messages_messagesSlice();
messages = getMessages(d);
count = d.vcount().v;
} break;
HistoryUnreadThings::ConstProxy History::unreadMentions() const {
return {
_unreadThings ? &_unreadThings->mentions : nullptr,
!!(_flags & Flag::UnreadThingsKnown),
};
}
case mtpc_messages_channelMessages: {
LOG(("API Error: unexpected messages.channelMessages! (History::addUnreadMentionsSlice)"));
auto &d = result.c_messages_channelMessages();
messages = getMessages(d);
count = d.vcount().v;
} break;
HistoryUnreadThings::Proxy History::unreadReactions() {
return {
this,
_unreadThings,
HistoryUnreadThings::Type::Reactions,
!!(_flags & Flag::UnreadThingsKnown),
};
}
case mtpc_messages_messagesNotModified: {
LOG(("API Error: received messages.messagesNotModified! (History::addUnreadMentionsSlice)"));
} break;
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);
HistoryUnreadThings::ConstProxy History::unreadReactions() const {
return {
_unreadThings ? &_unreadThings->reactions : nullptr,
!!(_flags & Flag::UnreadThingsKnown),
};
}
not_null<HistoryItem*> History::addNewToBack(
@ -1368,7 +1303,7 @@ void History::addItemsToLists(
markupSenders = &peer->asChannel()->mgInfo->markupSenders;
}
for (const auto &item : ranges::views::reverse(items)) {
item->addToUnreadMentions(UnreadMentionType::Existing);
item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
if (item->from()->id) {
if (lastAuthors) { // chats
if (auto user = item->from()->asUser()) {
@ -1433,7 +1368,7 @@ void History::checkAddAllToUnreadMentions() {
for (const auto &block : blocks) {
for (const auto &message : block->messages) {
const auto item = message->data();
item->addToUnreadMentions(UnreadMentionType::Existing);
item->addToUnreadThings(HistoryUnreadThings::AddType::Existing);
}
}
}
@ -1754,7 +1689,7 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
&& (!inChatList()
|| (!unreadCount()
&& !unreadMark()
&& !hasUnreadMentions())))) {
&& !unreadMentions().has())))) {
return;
}
_fakeUnreadWhileOpened = enabled;
@ -2601,7 +2536,8 @@ void History::applyDialog(
data.vread_outbox_max_id().v);
applyDialogTopMessage(data.vtop_message().v);
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 pts = data.vpts()) {
channel->ptsReceived(pts->v);
@ -2832,7 +2768,7 @@ void History::resizeToWidth(int newWidth) {
if (!resizeAllItems && !hasPendingResizedItems()) {
return;
}
_flags &= ~(Flag::f_has_pending_resized_items);
_flags &= ~(Flag::HasPendingResizedItems);
_width = newWidth;
int y = 0;
@ -2845,7 +2781,7 @@ void History::resizeToWidth(int newWidth) {
void History::forceFullResize() {
_width = 0;
_flags |= Flag::f_has_pending_resized_items;
_flags |= Flag::HasPendingResizedItems;
}
not_null<History*> History::migrateToOrMe() const {

View File

@ -27,6 +27,13 @@ class HistoryService;
struct HistoryMessageMarkupData;
class HistoryMainElementDelegateMixin;
namespace HistoryUnreadThings {
enum class AddType;
struct All;
class Proxy;
class ConstProxy;
} // namespace HistoryUnreadThings
namespace Main {
class Session;
} // namespace Main
@ -71,11 +78,6 @@ enum class NewMessageType {
Existing,
};
enum class UnreadMentionType {
New, // when new message is added to history
Existing, // when some messages slice was received
};
enum class ItemNotificationType {
Message,
Reaction,
@ -333,25 +335,11 @@ public:
void clearLastKeyboard();
int getUnreadMentionsLoadedCount() const {
return _unreadMentions.size();
}
MsgId getMinLoadedUnreadMention() const {
return _unreadMentions.empty() ? 0 : _unreadMentions.front();
}
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);
void setUnreadThingsKnown();
[[nodiscard]] HistoryUnreadThings::Proxy unreadMentions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const;
[[nodiscard]] HistoryUnreadThings::Proxy unreadReactions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
Data::Draft *draft(Data::DraftKey key) const;
void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft);
@ -493,7 +481,8 @@ private:
friend class HistoryBlock;
enum class Flag {
f_has_pending_resized_items = (1 << 0),
HasPendingResizedItems = (1 << 0),
UnreadThingsKnown = (1 << 1),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) {
@ -622,12 +611,11 @@ private:
std::optional<MsgId> _inboxReadBefore;
std::optional<MsgId> _outboxReadBefore;
std::optional<int> _unreadCount;
std::optional<int> _unreadMentionsCount;
base::flat_set<MsgId> _unreadMentions;
std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage;
base::flat_set<not_null<HistoryItem*>> _clientSideMessages;
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
// 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()) {
session().api().views().scheduleIncrement(item);
}
if (item->isUnreadMention() && !item->isUnreadMedia()) {
if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readMentions.insert(item);
_widget->enqueueMessageHighlight(view);
}
@ -1051,7 +1052,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
}
if (!readMentions.empty() && _widget->doWeReadMentions()) {
session().api().markMediaRead(readMentions);
session().api().markContentsRead(readMentions);
}
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_item_preview.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/history_item_components.h"
#include "history/history_service.h"
#include "history/history_message.h"
#include "history/history_unread_things.h"
#include "history/history.h"
#include "mtproto/mtproto_config.h"
#include "media/clip/media_clip_reader.h"
@ -332,7 +333,11 @@ bool HistoryItem::hasUnreadMediaFlag() 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 {
@ -356,15 +361,34 @@ bool HistoryItem::isUnreadMedia() const {
return false;
}
void HistoryItem::markMediaRead() {
bool HistoryItem::isIncomingUnreadMedia() const {
return !out() && isUnreadMedia();
}
void HistoryItem::markMediaAndMentionRead() {
_flags &= ~MessageFlag::MediaIsUnread;
if (mentionsMe()) {
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) {
const auto changed = (isPinned() != pinned);
if (pinned) {
@ -526,7 +550,7 @@ void HistoryItem::clearMainView() {
_mainView = nullptr;
}
void HistoryItem::addToUnreadMentions(UnreadMentionType type) {
void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
}
void HistoryItem::applyEditionToHistoryCleared() {
@ -592,7 +616,7 @@ void HistoryItem::applySentMessage(
void HistoryItem::indexAsNewItem() {
if (isRegular()) {
addToUnreadMentions(UnreadMentionType::New);
addToUnreadThings(HistoryUnreadThings::AddType::New);
if (const auto types = sharedMediaTypes()) {
_history->session().storage().add(Storage::SharedMediaAddNew(
_history->peer->id,

View File

@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <any>
enum class UnreadMentionType;
struct HistoryMessageReplyMarkup;
class ReplyKeyboard;
class HistoryMessage;
@ -50,6 +49,10 @@ namespace Window {
class SessionController;
} // namespace Window
namespace HistoryUnreadThings {
enum class AddType;
} // namespace HistoryUnreadThings
namespace HistoryView {
struct TextState;
struct StateRequest;
@ -140,9 +143,13 @@ public:
void markClientSideAsRead();
[[nodiscard]] bool mentionsMe() const;
[[nodiscard]] bool isUnreadMention() const;
[[nodiscard]] bool hasUnreadReaction() const;
[[nodiscard]] bool isUnreadMedia() const;
[[nodiscard]] bool isIncomingUnreadMedia() const;
[[nodiscard]] bool hasUnreadMediaFlag() const;
void markMediaRead();
void markReactionsRead();
void markMediaAndMentionRead();
bool markContentsRead();
void setIsPinned(bool isPinned);
// For edit media in history_message.
@ -274,7 +281,7 @@ public:
virtual void contributeToSlowmode(TimeId realDate = 0) {
}
virtual void addToUnreadMentions(UnreadMentionType type);
virtual void addToUnreadThings(HistoryUnreadThings::AddType type);
virtual void destroyHistoryEntry() {
}
[[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_location_manager.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_context_menu.h" // CopyPostLink.
#include "history/view/history_view_spoiler_click_handler.h"
@ -1529,19 +1530,32 @@ void HistoryMessage::contributeToSlowmode(TimeId realDate) {
}
}
void HistoryMessage::addToUnreadMentions(UnreadMentionType type) {
if (isRegular() && isUnreadMention()) {
if (history()->addToUnreadMentions(id, type)) {
void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) {
if (!isRegular()) {
return;
}
if (isUnreadMention()) {
if (history()->unreadMentions().add(id, type)) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::UnreadMentions);
}
}
if (hasUnreadReaction()) {
if (history()->unreadReactions().add(id, type)) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::UnreadReactions);
}
}
}
void HistoryMessage::destroyHistoryEntry() {
if (isUnreadMention()) {
history()->eraseFromUnreadMentions(id);
history()->unreadMentions().erase(id);
}
if (hasUnreadReaction()) {
history()->unreadReactions().erase(id);
}
if (const auto reply = Get<HistoryMessageReply>()) {
changeReplyToTopCounter(reply, -1);

View File

@ -175,7 +175,7 @@ public:
void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override;
void contributeToSlowmode(TimeId realDate = 0) override;
void addToUnreadMentions(UnreadMentionType type) override;
void addToUnreadThings(HistoryUnreadThings::AddType type) override;
void destroyHistoryEntry() 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_text_entities.h"
#include "api/api_send_progress.h"
#include "api/api_unread_things.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/delete_messages_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_inner_widget.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_ttl_button.h"
#include "history/view/history_view_service_message.h"
@ -215,6 +217,9 @@ HistoryWidget::HistoryWidget(
, _unreadMentions(
_scroll,
controller->chatStyle()->value(lifetime(), st::historyUnreadMentions))
, _unreadReactions(
_scroll,
controller->chatStyle()->value(lifetime(), st::historyUnreadReactions))
, _fieldAutocomplete(this, controller)
, _supportAutocomplete(session().supportMode()
? object_ptr<Support::Autocomplete>(this, &session())
@ -278,8 +283,13 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
_historyDown->addClickHandler([=] { historyDownClicked(); });
_unreadMentions->addClickHandler([=] { showNextUnreadMention(); });
_historyDown.widget->addClickHandler([=] { historyDownClicked(); });
_unreadMentions.widget->addClickHandler([=] {
showNextUnreadMention();
});
_unreadReactions.widget->addClickHandler([=] {
showNextUnreadReaction();
});
_fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
_send->addClickHandler([=] { sendButtonClicked(); });
@ -353,9 +363,13 @@ HistoryWidget::HistoryWidget(
_scroll->updateBars();
}, lifetime());
_historyDown->installEventFilter(this);
_unreadMentions->installEventFilter(this);
SendMenu::SetupUnreadMentionsMenu(_unreadMentions.data(), [=] {
_historyDown.widget->installEventFilter(this);
_unreadMentions.widget->installEventFilter(this);
_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;
});
@ -562,6 +576,7 @@ HistoryWidget::HistoryWidget(
| HistoryUpdateFlag::BotKeyboard
| HistoryUpdateFlag::CloudDraft
| HistoryUpdateFlag::UnreadMentions
| HistoryUpdateFlag::UnreadReactions
| HistoryUpdateFlag::UnreadView
| HistoryUpdateFlag::TopPromoted
| HistoryUpdateFlag::ClientSideMessages
@ -591,8 +606,9 @@ HistoryWidget::HistoryWidget(
if (flags & HistoryUpdateFlag::ClientSideMessages) {
updateSendButtonType();
}
if (flags & HistoryUpdateFlag::UnreadMentions) {
updateUnreadMentionsVisibility();
if ((flags & HistoryUpdateFlag::UnreadMentions)
|| (flags & HistoryUpdateFlag::UnreadReactions)) {
updateUnreadThingsVisibility();
}
if (flags & HistoryUpdateFlag::UnreadView) {
unreadCountUpdated();
@ -907,7 +923,7 @@ void HistoryWidget::initVoiceRecordBar() {
_voiceRecordBar->lockShowStarts(
) | rpl::start_with_next([=] {
updateHistoryDownVisibility();
updateUnreadMentionsVisibility();
updateUnreadThingsVisibility();
}, lifetime());
_voiceRecordBar->updateSendButtonTypeRequests(
@ -2459,7 +2475,7 @@ void HistoryWidget::updateControlsVisibility() {
_topBar->setVisible(_peer != nullptr);
}
updateHistoryDownVisibility();
updateUnreadMentionsVisibility();
updateUnreadThingsVisibility();
if (!_history || _a_show.animating()) {
hideChildWidgets();
return;
@ -2744,7 +2760,7 @@ void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
destroyUnreadBar();
if (doWeReadServerHistory()) {
if (item->isUnreadMention() && !item->isUnreadMedia()) {
session().api().markMediaRead(item);
session().api().markContentsRead(item);
}
session().data().histories().readInboxOnNewMessage(item);
@ -2769,7 +2785,7 @@ void HistoryWidget::unreadCountUpdated() {
});
} else {
updateHistoryDownVisibility();
_historyDown->setUnreadCount(_history->chatListUnreadCount());
_historyDown.widget->setUnreadCount(_history->chatListUnreadCount());
}
}
@ -3243,7 +3259,7 @@ void HistoryWidget::preloadHistoryIfNeeded() {
}
updateHistoryDownVisibility();
updateUnreadMentionsVisibility();
updateUnreadThingsVisibility();
if (!_scrollToAnimation.animating()) {
preloadHistoryByScroll();
checkReplyReturns();
@ -3332,7 +3348,7 @@ void HistoryWidget::historyDownClicked() {
}
void HistoryWidget::showNextUnreadMention() {
const auto msgId = _history->getMinLoadedUnreadMention();
const auto msgId = _history->unreadMentions().minLoaded();
const auto already = (_showAtMsgId == msgId);
// Mark mention voice/video message as read.
@ -3354,6 +3370,12 @@ void HistoryWidget::showNextUnreadMention() {
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() {
Expects(_history != nullptr);
@ -3698,8 +3720,7 @@ void HistoryWidget::showAnimated(
_preserveScrollTop = true;
show();
_topBar->finishAnimating();
historyDownAnimationFinish();
unreadMentionsAnimationFinish();
cornerButtonsAnimationFinish();
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
@ -3732,8 +3753,7 @@ void HistoryWidget::showAnimated(
void HistoryWidget::animationCallback() {
update();
if (!_a_show.animating()) {
historyDownAnimationFinish();
unreadMentionsAnimationFinish();
cornerButtonsAnimationFinish();
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
@ -3808,22 +3828,20 @@ void HistoryWidget::checkSuggestToGigagroup() {
}
void HistoryWidget::finishAnimating() {
if (!_a_show.animating()) return;
if (!_a_show.animating()) {
return;
}
_a_show.stop();
_topShadow->setVisible(_peer != nullptr);
_topBar->setVisible(_peer != nullptr);
historyDownAnimationFinish();
unreadMentionsAnimationFinish();
cornerButtonsAnimationFinish();
}
void HistoryWidget::historyDownAnimationFinish() {
_historyDownShown.stop();
updateHistoryDownPosition();
}
void HistoryWidget::unreadMentionsAnimationFinish() {
_unreadMentionsShown.stop();
updateUnreadMentionsPosition();
void HistoryWidget::cornerButtonsAnimationFinish() {
_historyDown.animation.stop();
_unreadMentions.animation.stop();
_unreadReactions.animation.stop();
updateCornerButtonsPositions();
}
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 TWidget::eventFilter(obj, e);
@ -4946,7 +4967,7 @@ void HistoryWidget::updateControlsGeometry() {
updateFieldSize();
updateHistoryDownPosition();
updateCornerButtonsPositions();
if (_membersDropdown) {
_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
@ -5156,16 +5177,7 @@ void HistoryWidget::updateHistoryGeometry(
if (_supportAutocomplete) {
_supportAutocomplete->setBoundings(_scroll->geometry());
}
if (!_historyDownShown.animating()) {
// _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());
}
}
updateCornerButtonsPositions();
controller()->floatPlayerAreaUpdated();
}
@ -5470,15 +5482,71 @@ int HistoryWidget::computeMaxFieldHeight() const {
return std::min(st::historyComposeFieldMaxHeight, available);
}
void HistoryWidget::updateHistoryDownPosition() {
// _historyDown is a child widget of _scroll, not me.
auto top = anim::interpolate(0, _historyDown->height() + st::historyToDownPosition.y(), _historyDownShown.value(_historyDownIsShown ? 1. : 0.));
_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - top);
auto shouldBeHidden = !_historyDownIsShown && !_historyDownShown.animating();
if (shouldBeHidden != _historyDown->isHidden()) {
_historyDown->setVisible(!shouldBeHidden);
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);
}
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() {
@ -5495,7 +5563,7 @@ void HistoryWidget::updateHistoryDownVisibility() {
const auto top = _list->itemTop(unread);
return (top >= _scroll->scrollTop() + _scroll->height());
};
const auto historyDownIsVisible = [&] {
updateCornerButtonVisibility(_historyDown, [&] {
if (!_list || _firstLoadRequest) {
return false;
}
@ -5514,60 +5582,69 @@ void HistoryWidget::updateHistoryDownVisibility() {
return true;
}
return false;
};
auto historyDownIsShown = historyDownIsVisible();
if (_historyDownIsShown != historyDownIsShown) {
_historyDownIsShown = historyDownIsShown;
_historyDownShown.start([=] { updateHistoryDownPosition(); }, _historyDownIsShown ? 0. : 1., _historyDownIsShown ? 1. : 0., st::historyToDownDuration);
}());
}
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::updateUnreadMentionsPosition() {
// _unreadMentions is a child widget of _scroll, not me.
auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.value(_unreadMentionsIsShown ? 1. : 0.));
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::updateUnreadThingsVisibility() {
if (_a_show.animating()) {
return;
}
}
void HistoryWidget::updateUnreadMentionsVisibility() {
if (_a_show.animating()) return;
auto &unreadThings = session().api().unreadThings();
unreadThings.preloadEnough(_history);
auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup());
if (showUnreadMentions) {
session().api().preloadEnoughUnreadMentions(_history);
}
const auto unreadMentionsIsShown = [&] {
if (!showUnreadMentions || _firstLoadRequest) {
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();
const auto updateWithLoadedCount = [&](CornerButton &button, int count) {
updateCornerButtonVisibility(button, [&] {
if (!count
|| _firstLoadRequest
|| _voiceRecordBar->isLockPresent()) {
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();
}
}
return true;
}());
};
if (unreadThings.trackMentions(_peer)) {
if (const auto count = _history->unreadMentions().count(0)) {
_unreadMentions.widget->setUnreadCount(count);
}
return true;
}();
if (unreadMentionsIsShown) {
_unreadMentions->setUnreadCount(_history->getUnreadMentionsCount());
updateWithLoadedCount(
_unreadMentions,
_history->unreadMentions().loadedCount());
} else {
updateCornerButtonVisibility(_unreadMentions, false);
}
if (_unreadMentionsIsShown != unreadMentionsIsShown) {
_unreadMentionsIsShown = unreadMentionsIsShown;
_unreadMentionsShown.start([=] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration);
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);
}
}

View File

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

View File

@ -121,7 +121,11 @@ historyUnreadMentions: TwoIconButton(historyToDown) {
iconAbove: icon {{ "history_unread_mention", historyToDownFg, 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;
membersInnerHeightMax: 360px;

View File

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