diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 1a44719e8..70dbef59d 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1086,8 +1086,8 @@ void History::newItemAdded(not_null item) { } item->contributeToSlowmode(); auto notification = ItemNotification{ - item, - ItemNotificationType::Message, + .item = item, + .type = ItemNotificationType::Message, }; if (item->showNotification()) { pushNotification(notification); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 3f8398a37..6de30b3eb 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -84,10 +84,13 @@ enum class ItemNotificationType { }; struct ItemNotification { not_null item; + UserData *reactionSender = nullptr; ItemNotificationType type = ItemNotificationType::Message; friend inline bool operator==(ItemNotification a, ItemNotification b) { - return (a.item == b.item) && (a.type == b.type); + return (a.item == b.item) + && (a.reactionSender == b.reactionSender) + && (a.type == b.type); } }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 09e0cd1b2..cb6050641 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kNotificationTextLimit = 255; +constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case. using ItemPreview = HistoryView::ItemPreview; @@ -178,6 +179,57 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { return flags; } +using OnStackUsers = std::array; +[[nodiscard]] OnStackUsers LookupRecentReactedUsers( + not_null item) { + auto result = OnStackUsers(); + auto index = 0; + for (const auto &[emoji, reactions] : item->recentReactions()) { + for (const auto &reaction : reactions) { + if (const auto user = reaction.peer->asUser()) { + result[index++] = user; + if (index == result.size()) { + return result; + } + } + } + } + return result; +} + +void CheckReactionNotificationSchedule( + not_null item, + const OnStackUsers &wasUsers) { + if (!item->hasUnreadReaction()) { + return; + } + for (const auto &[emoji, reactions] : item->recentReactions()) { + for (const auto &reaction : reactions) { + if (!reaction.unread) { + continue; + } + const auto user = reaction.peer->asUser(); + if (!user + || !user->isContact() + || ranges::contains(wasUsers, user)) { + continue; + } + using Status = PeerData::BlockStatus; + if (user->blockStatus() == Status::Unknown) { + user->updateFull(); + } + const auto notification = ItemNotification{ + .item = item, + .reactionSender = user, + .type = ItemNotificationType::Reaction, + }; + item->history()->pushNotification(notification); + Core::App().notifications().schedule(notification); + return; + } + } +} + } // namespace void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { @@ -846,6 +898,7 @@ void HistoryItem::toggleReaction(const QString &reaction) { } void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { + const auto wasRecentUsers = LookupRecentReactedUsers(this); const auto hadUnread = hasUnreadReaction(); const auto changed = changeReactions(reactions); if (!changed) { @@ -859,16 +912,11 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { // Call to addToUnreadThings may have read the reaction already. if (hasUnreadReaction()) { - const auto notification = ItemNotification{ - this, - ItemNotificationType::Reaction, - }; - history()->pushNotification(notification); - Core::App().notifications().schedule(notification); } } else if (!hasUnread && hadUnread) { markReactionsRead(); } + CheckReactionNotificationSchedule(this, wasRecentUsers); history()->owner().notifyItemDataChange(this); } @@ -932,21 +980,21 @@ QString HistoryItem::chosenReaction() const { return _reactions ? _reactions->chosen() : QString(); } -HistoryItemUnreadReaction HistoryItem::lookupUnreadReaction() const { +QString HistoryItem::lookupUnreadReaction(not_null from) const { if (!_reactions) { - return {}; + return QString(); } const auto recent = _reactions->recent(); for (const auto &[emoji, list] : _reactions->recent()) { const auto i = ranges::find( list, - true, - &Data::RecentReaction::unread); - if (i != end(list)) { - return { .from = i->peer, .emoji = emoji }; + from, + &Data::RecentReaction::peer); + if (i != end(list) && i->unread) { + return emoji; } } - return {}; + return QString(); } crl::time HistoryItem::lastReactionsRefreshTime() const { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 6af925ffd..fc629377e 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -63,15 +63,6 @@ enum class Context : char; class ElementDelegate; } // namespace HistoryView -struct HistoryItemUnreadReaction { - PeerData *from = nullptr; - QString emoji; - - explicit operator bool() const { - return (from != nullptr) && !emoji.isEmpty(); - } -}; - struct HiddenSenderInfo; class History; @@ -382,7 +373,8 @@ public: std::vector> &; [[nodiscard]] bool canViewReactions() const; [[nodiscard]] QString chosenReaction() const; - [[nodiscard]] HistoryItemUnreadReaction lookupUnreadReaction() const; + [[nodiscard]] QString lookupUnreadReaction( + not_null from) const; [[nodiscard]] crl::time lastReactionsRefreshTime() const; [[nodiscard]] bool hasDirectLink() const; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 265754957..c4945f70b 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -68,6 +68,13 @@ QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) { } // namespace +struct System::Waiter { + NotificationInHistoryKey key; + UserData *reactionSender = nullptr; + ItemNotificationType type = ItemNotificationType::Message; + crl::time when = 0; +}; + System::NotificationInHistoryKey::NotificationInHistoryKey( ItemNotification notification) : NotificationInHistoryKey(notification.item->id, notification.type) { @@ -155,24 +162,30 @@ System::SkipState System::skipNotification( && skipReactionNotification(item))) { return { SkipState::Skip }; } - const auto showForMuted = messageNotification - && item->out() - && item->isFromScheduled(); - auto result = computeSkipState(item, showForMuted); - if (showForMuted) { - result.alsoMuted = true; - } - if (messageNotification && item->isSilent()) { - result.silent = true; - } - return result; + return computeSkipState(notification); } System::SkipState System::computeSkipState( - not_null item, - bool showForMuted) const { + ItemNotification notification) const { + const auto type = notification.type; + const auto item = notification.item; const auto history = item->history(); - const auto notifyBy = item->specialNotificationPeer(); + const auto messageNotification = (type == ItemNotificationType::Message); + const auto withSilent = [&]( + SkipState::Value value, + bool forceSilent = false) { + return SkipState{ + .value = value, + .silent = (forceSilent + || (messageNotification && item->isSilent())), + }; + }; + const auto showForMuted = messageNotification + && item->out() + && item->isFromScheduled(); + const auto notifyBy = messageNotification + ? item->specialNotificationPeer() + : notification.reactionSender; if (Core::Quitting()) { return { SkipState::Skip }; } else if (!Core::App().settings().notifyFromAll() @@ -180,29 +193,36 @@ System::SkipState System::computeSkipState( return { SkipState::Skip }; } - history->owner().requestNotifySettings(history->peer); + if (messageNotification) { + history->owner().requestNotifySettings(history->peer); + } else if (notifyBy->blockStatus() == PeerData::BlockStatus::Unknown) { + notifyBy->updateFull(); + } if (notifyBy) { history->owner().requestNotifySettings(notifyBy); } - if (history->owner().notifyMuteUnknown(history->peer)) { + if (messageNotification + && history->owner().notifyMuteUnknown(history->peer)) { return { SkipState::Unknown }; - } else if (!history->owner().notifyIsMuted(history->peer)) { - return { SkipState::DontSkip }; + } else if (messageNotification + && !history->owner().notifyIsMuted(history->peer)) { + return withSilent(SkipState::DontSkip); } else if (!notifyBy) { - return { + return withSilent( showForMuted ? SkipState::DontSkip : SkipState::Skip, - showForMuted, - }; - } else if (history->owner().notifyMuteUnknown(notifyBy)) { - return { SkipState::Unknown, item->isSilent() }; - } else if (!history->owner().notifyIsMuted(notifyBy)) { - return { SkipState::DontSkip, item->isSilent() }; + showForMuted); + } else if (history->owner().notifyMuteUnknown(notifyBy) + || (!messageNotification + && notifyBy->blockStatus() == PeerData::BlockStatus::Unknown)) { + return withSilent(SkipState::Unknown); + } else if (!history->owner().notifyIsMuted(notifyBy) + && (messageNotification || !notifyBy->isBlocked())) { + return withSilent(SkipState::DontSkip); } else { - return { + return withSilent( showForMuted ? SkipState::DontSkip : SkipState::Skip, - showForMuted, - }; + showForMuted); } } @@ -248,7 +268,9 @@ void System::schedule(ItemNotification notification) { ? kMinimalForwardDelay : kMinimalDelay; const auto timing = countTiming(history, minimalDelay); - const auto notifyBy = item->specialNotificationPeer(); + const auto notifyBy = (type == ItemNotificationType::Message) + ? item->specialNotificationPeer() + : notification.reactionSender; if (!skip.silent) { _whenAlerts[history].emplace(timing.when, notifyBy); } @@ -265,9 +287,9 @@ void System::schedule(ItemNotification notification) { if (it == addTo.end() || it->second.when > timing.when) { addTo.emplace(history, Waiter{ .key = key, + .reactionSender = notification.reactionSender, + .type = notification.type, .when = timing.when, - .notifyBy = notifyBy, - .alsoMuted = skip.alsoMuted, }); } } @@ -367,41 +389,29 @@ void System::clearAllFast() { void System::checkDelayed() { for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) { - const auto history = i->first; - const auto peer = history->peer; - auto loaded = false; - auto muted = false; - if (!peer->owner().notifyMuteUnknown(peer)) { - if (!peer->owner().notifyIsMuted(peer)) { - loaded = true; - } else if (const auto from = i->second.notifyBy) { - if (!peer->owner().notifyMuteUnknown(from)) { - if (!peer->owner().notifyIsMuted(from)) { - loaded = true; - } else { - loaded = muted = true; - } - } - } else { - loaded = muted = true; + const auto remove = [&] { + const auto history = i->first; + const auto peer = history->peer; + const auto fullId = FullMsgId(peer->id, i->second.key.messageId); + const auto item = peer->owner().message(fullId); + if (!item) { + return true; } - } - if (loaded) { - const auto fullId = FullMsgId( - history->peer->id, - i->second.key.messageId); - if (const auto item = peer->owner().message(fullId)) { - if (!item->notificationReady()) { - loaded = false; - } - } else { - muted = true; - } - } - if (loaded) { - if (!muted || i->second.alsoMuted) { - _waiters.emplace(i->first, i->second); + const auto state = computeSkipState({ + .item = item, + .reactionSender = i->second.reactionSender, + .type = i->second.type, + }); + if (state.value == SkipState::Skip) { + return true; + } else if (state.value == SkipState::Unknown + || !item->notificationReady()) { + return false; } + _waiters.emplace(i->first, i->second); + return true; + }(); + if (remove) { i = _settingWaiters.erase(i); } else { ++i; @@ -657,14 +667,14 @@ void System::showNext() { const auto reactionNotification = (notify->type == ItemNotificationType::Reaction); const auto reaction = reactionNotification - ? notify->item->lookupUnreadReaction() - : HistoryItemUnreadReaction(); - if (!reactionNotification || reaction) { + ? notify->item->lookupUnreadReaction(notify->reactionSender) + : QString(); + if (!reactionNotification || !reaction.isEmpty()) { _manager->showNotification({ .item = notify->item, .forwardedCount = forwardedCount, - .reactionFrom = reaction.from, - .reactionEmoji = reaction.emoji, + .reactionFrom = notify->reactionSender, + .reactionEmoji = reaction, }); } } diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index 5f228e6c8..73022607d 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -103,6 +103,8 @@ public: } private: + struct Waiter; + struct SkipState { enum Value { Unknown, @@ -111,7 +113,6 @@ private: }; Value value = Value::Unknown; bool silent = false; - bool alsoMuted = false; }; struct NotificationInHistoryKey { NotificationInHistoryKey(ItemNotification notification); @@ -127,12 +128,6 @@ private: < std::pair(b.messageId, b.type); } }; - struct Waiter { - NotificationInHistoryKey key; - crl::time when = 0; - PeerData *notifyBy = nullptr; - bool alsoMuted = false; - }; struct Timing { crl::time delay = 0; crl::time when = 0; @@ -152,8 +147,7 @@ private: [[nodiscard]] SkipState skipNotification( ItemNotification notification) const; [[nodiscard]] SkipState computeSkipState( - not_null item, - bool showForMuted) const; + ItemNotification notification) const; [[nodiscard]] Timing countTiming( not_null history, crl::time minimalDelay) const;