From e9c79886d20760b682b59ad3f6ff8fe46737fca3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Jan 2022 19:01:40 +0300 Subject: [PATCH] Track unread mentions and unread reactions the same way. --- Telegram/CMakeLists.txt | 4 + .../icons/dialogs/dialogs_reaction.png | Bin 0 -> 513 bytes .../icons/dialogs/dialogs_reaction@2x.png | Bin 0 -> 905 bytes .../icons/dialogs/dialogs_reaction@3x.png | Bin 0 -> 1341 bytes .../icons/history_unread_reaction.png | Bin 0 -> 977 bytes .../icons/history_unread_reaction@2x.png | Bin 0 -> 1900 bytes .../icons/history_unread_reaction@3x.png | Bin 0 -> 2806 bytes .../SourceFiles/api/api_unread_things.cpp | 138 +++++++++ Telegram/SourceFiles/api/api_unread_things.h | 44 +++ Telegram/SourceFiles/api/api_updates.cpp | 25 +- Telegram/SourceFiles/apiwrap.cpp | 68 +---- Telegram/SourceFiles/apiwrap.h | 15 +- .../chat_helpers/send_context_menu.cpp | 4 + .../chat_helpers/send_context_menu.h | 4 + Telegram/SourceFiles/data/data_changes.h | 23 +- .../SourceFiles/data/data_chat_filters.cpp | 5 +- Telegram/SourceFiles/data/data_session.cpp | 9 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 5 +- Telegram/SourceFiles/history/history.cpp | 140 +++------ Telegram/SourceFiles/history/history.h | 42 +-- .../history/history_inner_widget.cpp | 5 +- Telegram/SourceFiles/history/history_item.cpp | 36 ++- Telegram/SourceFiles/history/history_item.h | 13 +- .../SourceFiles/history/history_message.cpp | 22 +- .../SourceFiles/history/history_message.h | 2 +- .../history/history_unread_things.cpp | 190 +++++++++++++ .../history/history_unread_things.h | 131 +++++++++ .../SourceFiles/history/history_widget.cpp | 265 +++++++++++------- Telegram/SourceFiles/history/history_widget.h | 32 ++- Telegram/SourceFiles/ui/chat/chat.style | 6 +- .../window/notifications_manager.cpp | 4 +- 31 files changed, 885 insertions(+), 347 deletions(-) create mode 100644 Telegram/Resources/icons/dialogs/dialogs_reaction.png create mode 100644 Telegram/Resources/icons/dialogs/dialogs_reaction@2x.png create mode 100644 Telegram/Resources/icons/dialogs/dialogs_reaction@3x.png create mode 100644 Telegram/Resources/icons/history_unread_reaction.png create mode 100644 Telegram/Resources/icons/history_unread_reaction@2x.png create mode 100644 Telegram/Resources/icons/history_unread_reaction@3x.png create mode 100644 Telegram/SourceFiles/api/api_unread_things.cpp create mode 100644 Telegram/SourceFiles/api/api_unread_things.h create mode 100644 Telegram/SourceFiles/history/history_unread_things.cpp create mode 100644 Telegram/SourceFiles/history/history_unread_things.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 559738afd4..fee34723f8 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/icons/dialogs/dialogs_reaction.png b/Telegram/Resources/icons/dialogs/dialogs_reaction.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae8b8f16f307899fb5cdff685b96632f827885e GIT binary patch literal 513 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1SJ0&Eu0Oc7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzScmXql4N|C(AFvFlEZft?F~mYJ zIYFY)!Na3t_Uzkd&*mO#;mpa&QE54FWo7XC`2Fu*zyAI7^mN_mZ3P7d45rVYJ+rc~ z`0(k|sgoxY%}-ywdiC<<&o?$EJDg2SOmsNAyZrsLCr|EdOg?^VYxd;Hll|vdG{&Yo zIv)J~{=T!bbHBVj-<3HtXa4;E|9|c8Z)+nrKl}Ol`L12N_SgSsyVc{mdCi)f=xAw| z&o3`8Z@e|3anGJV%l+rSdHeRE)l3B)HMM2h;p;RsHGSt;6kc59T2oU~AS%$dZ=YR2 z@pjF-iS@aIksStXbFB$8&RYYinp62z8LSzAl!T?f3Tl`+PDM41$S`t0Oih z9lgK59_XuMhnfUhPXGJ!bMoA|f6vdiFAywoD=7E?^!S8Yjud8DrY{@@Th^{!d(l+k zGcz;uMAQF2J{~@LH1+AJsn5^Nbq;Hro12ux)Wg3(b-G?GQ(Jmy=+(8++m|d^vSIZx kWiFqoOiWA_B0P1P3>-)H2KfX&oDK?pPgg&ebxsLQ0LwPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91CZGcV1ONa40RR91CIA2c0EF@&TL1t8^GQTOR9Fe^mOn`9Q54556%+*t z4e}2b7$jn(P*K?CP@51$xJc!oiv&7li(3uR*5o2WL(r5o6gU(uVpK>FB$x<_{lg3z zMAP?u@4$~A`uypySHlAxe%y2JIp^a&_nvdFK+ya3GSJJw|C)iZv9Xnv6}epggA3^E z>vKAtX0sU#u~_`{^welHZf|dghK7EMy0EYS#^>kf+S(czXJ=;+@w>mj*XeXk)amKz zk&%%mC2vuy)t1X;FzWR>s7j^s=H})Pw2{eV*nrK=P3ZIayhtPhW@>6G5D3ti2URAM zA+a+vGwrCkTyAu9lp=;gA^IzGb92z7QYnZ&pN|L6VzGRDd|X{!4G#}9RAhK{b(OtV zEX!y#A}(@3NWb3?@$~c*;?dC&59I9ZEQNS@c;KcMi$#;kWVhSN{@~yMm|!pnu~aJc z_xJOlZf$LGQ0M38X%^qd$H(>cbrK7O0vI-%jYO-}$^!}0?d@&yY+5T$)BF27O|^av z1_LUpQmJ6dAgpa5y;DM_|H4A`yt^=jUA^vFF)rmi(Kjq~!i4lSyDUHa1{}Bi$7eMktK8 zw>LI58jW(3m|3I&5vXjiqunB5hX%$_(O2*8?wFt*2ZMuy*a9-+cMVKVPNKFt0$L3; z8qMe)698@035eU%qar)G1HN24ITCK**{kJj7`Q_!Mbt&yMZnqm= zu~-ZS@pzm!j~pf@CZ3<4+cDVs;R29KrH6-yr1W~d|HLC_JV8!QP8bX`Kx0B{6$*tH z7Z;=qhr{2%qY&7t#l^+_{e9#O{T+R3XJ=<%U;wjtoe*z#cNh3?Lvjbga|w0DZPT0W fWuTXV&KdXxOc9)ECse@T00000NkvXXu0mjfK(3H( literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/dialogs/dialogs_reaction@3x.png b/Telegram/Resources/icons/dialogs/dialogs_reaction@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bb50125502685247c39d0072a78bcdc43ac1ea6f GIT binary patch literal 1341 zcmV-D1;YA?P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91IiLdo1ONa40RR91IRF3v0Jo8fpa1{_r%6OXRA>e5SV<^vaTI=Jo|1VM zk}@PKSqLR$AsdtslC>cVGHfg?h%zKY*oc%Q#6k(hLZ&D(3&~i<%Gm2<#ZiZ0$yS}~-4-c1?>4#C!cXoD0 zvSwyxtgNg+n46pXSL&btH#awtk&!FuYv8}(XJut2Q^L?E_vz^=IXPL#7at$bfeQ-@G{jE4F*i3iAzq;!930%<-d zY-D7F6jfDK%qk-H<>e(MC50&i0|Png`T3bvCuR#fGcqz5tk8fy+Le%WY;4Tb)Kt1$ zR4Y9_ortj(Sr&|lhzR)M#3PZvqoYHiL=M9yj~B``a^=3gy=7%(QMSv=OU_eJP(aSP zxjAxfZEZ2Ba-*W6f;nJ_%f)i(?d=ufAvZfa8}y5di|ij57$8A4H8t#XcXwB=k|S$t zYdIqfak+VUc|tI3$So==Lgm&RvLjcz9rX5yRi# zA5yHXt??uTYiMXtC{8;}qFfPu!Xcb_M6tQKN&d;nNo`UY9$#PImX?4Hdz1*B6m;v8t35|QmAkUCLUiTj?I`0H8eC_U0vlwF!ae4Vx-&Dl%dt+ zkj%_XNy>DByuH15cXv4#43;ZwUWEYZHZ3o_cMd5mEYvB3orY{_8|dPqqoX00hdez! zr>3U-{QL$72id92KoSIATwFjaWpHqCP?qXnl!u4M_VzYEJq#SY=>3Eq1$JLwA938? z-cmUBlYgwbEU~&0{^;l^Dk@5NWTSWHn~biUl6-WU;8?b>u%LjuyF2m7&$S}{`1lx; z0sFRiE?J`+8ympe*w|nZQ{dIrm3Rh8T9E?HVN6U65r}7$I7)MXW-W!F7n2w(3FhqV zyuQA!V0LPROiWA=UukJ64~2w;ND?6(CyOpI6OQ|TG7aV|AwkeJN8bUf#}q_ zySve*d_z~IlR$N<6;LanRzR(QS^>2JvMcZ#>E}v;AHqW000000NkvXXu0mjfLO4$w literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/history_unread_reaction.png b/Telegram/Resources/icons/history_unread_reaction.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6386dd28405d2a3607b2140dad416470768b0e GIT binary patch literal 977 zcmV;?11|iDP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917@z|H1ONa40RR917ytkO0OB=utpET6J4r-AR5%gMluIb=Q5?sI8SmG^ zhzVPdk|?v9m1IFF(s-qi#U!y|Fr((-u_Iz(DN0c@@>ofvJZqX*culgA1|ez|h~a*w zbN|g4_s;4r?pd7w_xJjq|M@+RO!mJm`OhDUh=}Ou=;-Y1%+Jq1Iy!oNef{YZN~6(e zwzs!$Z*Q-zuD+xN&d$yjVPRom$;rv=p;)rAvR+_)zxsFoSeW#?iUvqF-=TN5TMiPz%w&5;L*`hu*qcd5#fsP z_V&i%-QC^b-Q8Wdv|24($H&L<@$m|U!e+BAEiK_+SXhY9#l;1o`cgutM&KybC>DqX{ZebLqK3KWOVSMKQMH4b%Dpm#uVr0=dG=+l#?HZ2u$upQGy91 znPdZ_qodiG0d}QQso+DsU?oQb0|Shb(P%^&Vv!N7bQwFfW2fW&{tSnh1B=Dt*Zx5C zvD@wDV44;q#RWGsG<eZq8Q`^YI2;^6PcS7EjvOJ< zoRr8P9E8+rwaewgkM;TaDF8bKEh;LaW5nad`uIS&pCA@hugmI>kWFHOGyb*UjcB1$>+00000NkvXXu0mjf9u~<_ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/history_unread_reaction@2x.png b/Telegram/Resources/icons/history_unread_reaction@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b4581adb3edeafcac4910a9d3daa4cf81844800a GIT binary patch literal 1900 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 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 &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) { + 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) { + 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, 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, 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 diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h new file mode 100644 index 0000000000..4f6f423645 --- /dev/null +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -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 api); + + [[nodiscard]] bool trackMentions(PeerData *peer) const; + [[nodiscard]] bool trackReactions(PeerData *peer) const; + + void preloadEnough(History *history); + + void mediaAndMentionsRead( + const base::flat_set &readIds, + ChannelData *channel = nullptr); + +private: + void preloadEnoughMentions(not_null history); + void preloadEnoughReactions(not_null history); + + void requestMentions(not_null history, int loaded); + void requestReactions(not_null history, int loaded); + + const not_null _api; + + base::flat_map, mtpRequestId> _mentionsRequests; + base::flat_map, mtpRequestId> _reactionsRequests; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 4f138a510d..9359a0d73b 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -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(); + auto unknownReadIds = base::flat_set(); 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(); + auto unknownReadIds = base::flat_set(); 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. diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index b9f47daf3a..da47b0efd0 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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 session) , _confirmPhone(std::make_unique(this)) , _peerPhoto(std::make_unique(this)) , _polls(std::make_unique(this)) -, _chatParticipants(std::make_unique(this)) { +, _chatParticipants(std::make_unique(this)) +, _unreadThings(std::make_unique(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 peer, const QString &error) { } } -void ApiWrap::markMediaRead( +void ApiWrap::markContentsRead( const base::flat_set> &items) { auto markedIds = QVector(); auto channelMarkedIds = base::flat_map< @@ -1295,12 +1294,7 @@ void ApiWrap::markMediaRead( QVector>(); 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 item) { - if ((!item->isUnreadMedia() || item->out()) - && !item->isUnreadMention()) { - return; - } - item->markMediaRead(); - if (!item->isRegular()) { +void ApiWrap::markContentsRead(not_null item) { + if (!item->markContentsRead() || !item->isRegular()) { return; } const auto ids = MTP_vector(1, MTP_int(item->id)); @@ -2910,45 +2899,6 @@ void ApiWrap::jumpToHistoryDate(not_null peer, const QDate &date) { } } -void ApiWrap::preloadEnoughUnreadMentions(not_null 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 &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 peer, Storage::SharedMediaType type) { @@ -4146,3 +4096,7 @@ Api::Polls &ApiWrap::polls() { Api::ChatParticipants &ApiWrap::chatParticipants() { return *_chatParticipants; } + +Api::UnreadThings &ApiWrap::unreadThings() { + return *_unreadThings; +} diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7376729182..f26ea4dd58 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -67,6 +67,7 @@ class ConfirmPhone; class PeerPhoto; class Polls; class ChatParticipants; +class UnreadThings; namespace details { @@ -206,8 +207,9 @@ public: FnMut)> done, Fn fail = nullptr); - void markMediaRead(const base::flat_set> &items); - void markMediaRead(not_null item); + void markContentsRead( + const base::flat_set> &items); + void markContentsRead(not_null item); void deleteAllFromParticipant( not_null channel, @@ -250,11 +252,6 @@ public: void jumpToDate(Dialogs::Key chat, const QDate &date); - void preloadEnoughUnreadMentions(not_null history); - void checkForUnreadMentions( - const base::flat_set &possiblyReadMentions, - ChannelData *channel = nullptr); - using SliceType = Data::LoadDirection; void requestSharedMedia( not_null 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, mtpRequestId> _unreadMentionsRequests; - base::flat_set, SharedMediaType, @@ -636,6 +632,7 @@ private: const std::unique_ptr _peerPhoto; const std::unique_ptr _polls; const std::unique_ptr _chatParticipants; + const std::unique_ptr _unreadThings; mtpRequestId _wallPaperRequestId = 0; QString _wallPaperSlug; diff --git a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp index 16ac9378fd..93ae5f1e1b 100644 --- a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp +++ b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp @@ -183,7 +183,11 @@ void SetupUnreadMentionsMenu( } return base::EventFilterResult::Continue; }); +} +void SetupUnreadReactionsMenu( + not_null button, + Fn currentPeer) { } } // namespace SendMenu diff --git a/Telegram/SourceFiles/chat_helpers/send_context_menu.h b/Telegram/SourceFiles/chat_helpers/send_context_menu.h index 1b79feffb7..3cddc9a113 100644 --- a/Telegram/SourceFiles/chat_helpers/send_context_menu.h +++ b/Telegram/SourceFiles/chat_helpers/send_context_menu.h @@ -54,4 +54,8 @@ void SetupUnreadMentionsMenu( not_null button, Fn currentPeer); +void SetupUnreadReactionsMenu( + not_null button, + Fn currentPeer); + } // namespace SendMenu diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 9b64579adc..ee9e58d44f 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 93628a42cf..68812aaa83 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -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) 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()))) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 9cae7df26f..9ed93566c3 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1303,7 +1303,14 @@ void Session::photoLoadFail( void Session::markMediaRead(not_null 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>(); + items.reserve(i->second.size()); + for (const auto &item : i->second) { + if (item->isUnreadMention() || item->isIncomingUnreadMedia()) { + items.emplace(item); + } + } + _session->api().markContentsRead(items); } } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 7eaf3aa08d..8fa508f5d7 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -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 diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index cba7892120..e3b4af378f 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -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 item) { @@ -691,106 +692,40 @@ not_null 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*)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 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::migrateToOrMe() const { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 122b22e464..3f8398a37c 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -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 &&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; friend inline constexpr auto is_flag_type(Flag) { @@ -622,12 +611,11 @@ private: std::optional _inboxReadBefore; std::optional _outboxReadBefore; std::optional _unreadCount; - std::optional _unreadMentionsCount; - base::flat_set _unreadMentions; std::optional _lastMessage; std::optional _lastServerMessage; base::flat_set> _clientSideMessages; std::unordered_set> _messages; + std::unique_ptr _unreadThings; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 53687c4644..9eae92ee37 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 48e51581dd..40a694c467 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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, diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index b446071b57..83f13960c9 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include -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; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index ab004089e2..edb10a470d 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -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()) { changeReplyToTopCounter(reply, -1); diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index eb02f09062..ca31472e53 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -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; diff --git a/Telegram/SourceFiles/history/history_unread_things.cpp b/Telegram/SourceFiles/history/history_unread_things.cpp new file mode 100644 index 0000000000..663ce371dc --- /dev/null +++ b/Telegram/SourceFiles/history/history_unread_things.cpp @@ -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(); + }, [&](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(); + 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 diff --git a/Telegram/SourceFiles/history/history_unread_things.h b/Telegram/SourceFiles/history/history_unread_things.h new file mode 100644 index 0000000000..3c0d0ac059 --- /dev/null +++ b/Telegram/SourceFiles/history/history_unread_things.h @@ -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 _count; + base::flat_set _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, + std::unique_ptr &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; + std::unique_ptr &_data; + Type _type = Type::Mentions; + bool _known = false; + +}; + +} // namespace HistoryUnreadThings diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1dde33297d..1f075ddcd9 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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(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 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); } } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index d5da88bbcb..a11749841d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -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; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; + struct CornerButton { + template + CornerButton(Args &&...args) : widget(std::forward(args)...) { + } + Ui::Animations::Simple animation; + bool shown = false; + object_ptr widget; + }; void 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 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 item); + void updateCornerButtonsPositions(); + void updateHistoryDownVisibility(); + void updateUnreadThingsVisibility(); + void updateCornerButtonVisibility(CornerButton &button, bool shown); + bool canSendFiles(not_null 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 _historyDown; - - Ui::Animations::Simple _unreadMentionsShown; - bool _unreadMentionsIsShown = false; - object_ptr _unreadMentions; + CornerButton _historyDown; + CornerButton _unreadMentions; + CornerButton _unreadReactions; const object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 3af40d1ad3..b3f71d6de2 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -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; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index e985be5341..630bd1f22c 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -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); } }