mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Reaction notifications only from non-blocked contacts.
This commit is contained in:
parent
6a9c5818ba
commit
63bf564757
6 changed files with 151 additions and 104 deletions
|
@ -1086,8 +1086,8 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
item->contributeToSlowmode();
|
item->contributeToSlowmode();
|
||||||
auto notification = ItemNotification{
|
auto notification = ItemNotification{
|
||||||
item,
|
.item = item,
|
||||||
ItemNotificationType::Message,
|
.type = ItemNotificationType::Message,
|
||||||
};
|
};
|
||||||
if (item->showNotification()) {
|
if (item->showNotification()) {
|
||||||
pushNotification(notification);
|
pushNotification(notification);
|
||||||
|
|
|
@ -84,10 +84,13 @@ enum class ItemNotificationType {
|
||||||
};
|
};
|
||||||
struct ItemNotification {
|
struct ItemNotification {
|
||||||
not_null<HistoryItem*> item;
|
not_null<HistoryItem*> item;
|
||||||
|
UserData *reactionSender = nullptr;
|
||||||
ItemNotificationType type = ItemNotificationType::Message;
|
ItemNotificationType type = ItemNotificationType::Message;
|
||||||
|
|
||||||
friend inline bool operator==(ItemNotification a, ItemNotification b) {
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kNotificationTextLimit = 255;
|
constexpr auto kNotificationTextLimit = 255;
|
||||||
|
constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case.
|
||||||
|
|
||||||
using ItemPreview = HistoryView::ItemPreview;
|
using ItemPreview = HistoryView::ItemPreview;
|
||||||
|
|
||||||
|
@ -178,6 +179,57 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using OnStackUsers = std::array<UserData*, kMaxUnreadReactions>;
|
||||||
|
[[nodiscard]] OnStackUsers LookupRecentReactedUsers(
|
||||||
|
not_null<HistoryItem*> 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<HistoryItem*> 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
|
} // namespace
|
||||||
|
|
||||||
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
||||||
|
@ -846,6 +898,7 @@ void HistoryItem::toggleReaction(const QString &reaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
||||||
|
const auto wasRecentUsers = LookupRecentReactedUsers(this);
|
||||||
const auto hadUnread = hasUnreadReaction();
|
const auto hadUnread = hasUnreadReaction();
|
||||||
const auto changed = changeReactions(reactions);
|
const auto changed = changeReactions(reactions);
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
|
@ -859,16 +912,11 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
||||||
|
|
||||||
// Call to addToUnreadThings may have read the reaction already.
|
// Call to addToUnreadThings may have read the reaction already.
|
||||||
if (hasUnreadReaction()) {
|
if (hasUnreadReaction()) {
|
||||||
const auto notification = ItemNotification{
|
|
||||||
this,
|
|
||||||
ItemNotificationType::Reaction,
|
|
||||||
};
|
|
||||||
history()->pushNotification(notification);
|
|
||||||
Core::App().notifications().schedule(notification);
|
|
||||||
}
|
}
|
||||||
} else if (!hasUnread && hadUnread) {
|
} else if (!hasUnread && hadUnread) {
|
||||||
markReactionsRead();
|
markReactionsRead();
|
||||||
}
|
}
|
||||||
|
CheckReactionNotificationSchedule(this, wasRecentUsers);
|
||||||
history()->owner().notifyItemDataChange(this);
|
history()->owner().notifyItemDataChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,21 +980,21 @@ QString HistoryItem::chosenReaction() const {
|
||||||
return _reactions ? _reactions->chosen() : QString();
|
return _reactions ? _reactions->chosen() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItemUnreadReaction HistoryItem::lookupUnreadReaction() const {
|
QString HistoryItem::lookupUnreadReaction(not_null<UserData*> from) const {
|
||||||
if (!_reactions) {
|
if (!_reactions) {
|
||||||
return {};
|
return QString();
|
||||||
}
|
}
|
||||||
const auto recent = _reactions->recent();
|
const auto recent = _reactions->recent();
|
||||||
for (const auto &[emoji, list] : _reactions->recent()) {
|
for (const auto &[emoji, list] : _reactions->recent()) {
|
||||||
const auto i = ranges::find(
|
const auto i = ranges::find(
|
||||||
list,
|
list,
|
||||||
true,
|
from,
|
||||||
&Data::RecentReaction::unread);
|
&Data::RecentReaction::peer);
|
||||||
if (i != end(list)) {
|
if (i != end(list) && i->unread) {
|
||||||
return { .from = i->peer, .emoji = emoji };
|
return emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time HistoryItem::lastReactionsRefreshTime() const {
|
crl::time HistoryItem::lastReactionsRefreshTime() const {
|
||||||
|
|
|
@ -63,15 +63,6 @@ enum class Context : char;
|
||||||
class ElementDelegate;
|
class ElementDelegate;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
struct HistoryItemUnreadReaction {
|
|
||||||
PeerData *from = nullptr;
|
|
||||||
QString emoji;
|
|
||||||
|
|
||||||
explicit operator bool() const {
|
|
||||||
return (from != nullptr) && !emoji.isEmpty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HiddenSenderInfo;
|
struct HiddenSenderInfo;
|
||||||
class History;
|
class History;
|
||||||
|
|
||||||
|
@ -382,7 +373,8 @@ public:
|
||||||
std::vector<Data::RecentReaction>> &;
|
std::vector<Data::RecentReaction>> &;
|
||||||
[[nodiscard]] bool canViewReactions() const;
|
[[nodiscard]] bool canViewReactions() const;
|
||||||
[[nodiscard]] QString chosenReaction() const;
|
[[nodiscard]] QString chosenReaction() const;
|
||||||
[[nodiscard]] HistoryItemUnreadReaction lookupUnreadReaction() const;
|
[[nodiscard]] QString lookupUnreadReaction(
|
||||||
|
not_null<UserData*> from) const;
|
||||||
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
|
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
|
||||||
|
|
||||||
[[nodiscard]] bool hasDirectLink() const;
|
[[nodiscard]] bool hasDirectLink() const;
|
||||||
|
|
|
@ -68,6 +68,13 @@ QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
struct System::Waiter {
|
||||||
|
NotificationInHistoryKey key;
|
||||||
|
UserData *reactionSender = nullptr;
|
||||||
|
ItemNotificationType type = ItemNotificationType::Message;
|
||||||
|
crl::time when = 0;
|
||||||
|
};
|
||||||
|
|
||||||
System::NotificationInHistoryKey::NotificationInHistoryKey(
|
System::NotificationInHistoryKey::NotificationInHistoryKey(
|
||||||
ItemNotification notification)
|
ItemNotification notification)
|
||||||
: NotificationInHistoryKey(notification.item->id, notification.type) {
|
: NotificationInHistoryKey(notification.item->id, notification.type) {
|
||||||
|
@ -155,24 +162,30 @@ System::SkipState System::skipNotification(
|
||||||
&& skipReactionNotification(item))) {
|
&& skipReactionNotification(item))) {
|
||||||
return { SkipState::Skip };
|
return { SkipState::Skip };
|
||||||
}
|
}
|
||||||
const auto showForMuted = messageNotification
|
return computeSkipState(notification);
|
||||||
&& item->out()
|
|
||||||
&& item->isFromScheduled();
|
|
||||||
auto result = computeSkipState(item, showForMuted);
|
|
||||||
if (showForMuted) {
|
|
||||||
result.alsoMuted = true;
|
|
||||||
}
|
|
||||||
if (messageNotification && item->isSilent()) {
|
|
||||||
result.silent = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
System::SkipState System::computeSkipState(
|
System::SkipState System::computeSkipState(
|
||||||
not_null<HistoryItem*> item,
|
ItemNotification notification) const {
|
||||||
bool showForMuted) const {
|
const auto type = notification.type;
|
||||||
|
const auto item = notification.item;
|
||||||
const auto history = item->history();
|
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()) {
|
if (Core::Quitting()) {
|
||||||
return { SkipState::Skip };
|
return { SkipState::Skip };
|
||||||
} else if (!Core::App().settings().notifyFromAll()
|
} else if (!Core::App().settings().notifyFromAll()
|
||||||
|
@ -180,29 +193,36 @@ System::SkipState System::computeSkipState(
|
||||||
return { SkipState::Skip };
|
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) {
|
if (notifyBy) {
|
||||||
history->owner().requestNotifySettings(notifyBy);
|
history->owner().requestNotifySettings(notifyBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (history->owner().notifyMuteUnknown(history->peer)) {
|
if (messageNotification
|
||||||
|
&& history->owner().notifyMuteUnknown(history->peer)) {
|
||||||
return { SkipState::Unknown };
|
return { SkipState::Unknown };
|
||||||
} else if (!history->owner().notifyIsMuted(history->peer)) {
|
} else if (messageNotification
|
||||||
return { SkipState::DontSkip };
|
&& !history->owner().notifyIsMuted(history->peer)) {
|
||||||
|
return withSilent(SkipState::DontSkip);
|
||||||
} else if (!notifyBy) {
|
} else if (!notifyBy) {
|
||||||
return {
|
return withSilent(
|
||||||
showForMuted ? SkipState::DontSkip : SkipState::Skip,
|
showForMuted ? SkipState::DontSkip : SkipState::Skip,
|
||||||
showForMuted,
|
showForMuted);
|
||||||
};
|
} else if (history->owner().notifyMuteUnknown(notifyBy)
|
||||||
} else if (history->owner().notifyMuteUnknown(notifyBy)) {
|
|| (!messageNotification
|
||||||
return { SkipState::Unknown, item->isSilent() };
|
&& notifyBy->blockStatus() == PeerData::BlockStatus::Unknown)) {
|
||||||
} else if (!history->owner().notifyIsMuted(notifyBy)) {
|
return withSilent(SkipState::Unknown);
|
||||||
return { SkipState::DontSkip, item->isSilent() };
|
} else if (!history->owner().notifyIsMuted(notifyBy)
|
||||||
|
&& (messageNotification || !notifyBy->isBlocked())) {
|
||||||
|
return withSilent(SkipState::DontSkip);
|
||||||
} else {
|
} else {
|
||||||
return {
|
return withSilent(
|
||||||
showForMuted ? SkipState::DontSkip : SkipState::Skip,
|
showForMuted ? SkipState::DontSkip : SkipState::Skip,
|
||||||
showForMuted,
|
showForMuted);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +268,9 @@ void System::schedule(ItemNotification notification) {
|
||||||
? kMinimalForwardDelay
|
? kMinimalForwardDelay
|
||||||
: kMinimalDelay;
|
: kMinimalDelay;
|
||||||
const auto timing = countTiming(history, minimalDelay);
|
const auto timing = countTiming(history, minimalDelay);
|
||||||
const auto notifyBy = item->specialNotificationPeer();
|
const auto notifyBy = (type == ItemNotificationType::Message)
|
||||||
|
? item->specialNotificationPeer()
|
||||||
|
: notification.reactionSender;
|
||||||
if (!skip.silent) {
|
if (!skip.silent) {
|
||||||
_whenAlerts[history].emplace(timing.when, notifyBy);
|
_whenAlerts[history].emplace(timing.when, notifyBy);
|
||||||
}
|
}
|
||||||
|
@ -265,9 +287,9 @@ void System::schedule(ItemNotification notification) {
|
||||||
if (it == addTo.end() || it->second.when > timing.when) {
|
if (it == addTo.end() || it->second.when > timing.when) {
|
||||||
addTo.emplace(history, Waiter{
|
addTo.emplace(history, Waiter{
|
||||||
.key = key,
|
.key = key,
|
||||||
|
.reactionSender = notification.reactionSender,
|
||||||
|
.type = notification.type,
|
||||||
.when = timing.when,
|
.when = timing.when,
|
||||||
.notifyBy = notifyBy,
|
|
||||||
.alsoMuted = skip.alsoMuted,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,41 +389,29 @@ void System::clearAllFast() {
|
||||||
|
|
||||||
void System::checkDelayed() {
|
void System::checkDelayed() {
|
||||||
for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
|
for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
|
||||||
const auto history = i->first;
|
const auto remove = [&] {
|
||||||
const auto peer = history->peer;
|
const auto history = i->first;
|
||||||
auto loaded = false;
|
const auto peer = history->peer;
|
||||||
auto muted = false;
|
const auto fullId = FullMsgId(peer->id, i->second.key.messageId);
|
||||||
if (!peer->owner().notifyMuteUnknown(peer)) {
|
const auto item = peer->owner().message(fullId);
|
||||||
if (!peer->owner().notifyIsMuted(peer)) {
|
if (!item) {
|
||||||
loaded = true;
|
return 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 state = computeSkipState({
|
||||||
if (loaded) {
|
.item = item,
|
||||||
const auto fullId = FullMsgId(
|
.reactionSender = i->second.reactionSender,
|
||||||
history->peer->id,
|
.type = i->second.type,
|
||||||
i->second.key.messageId);
|
});
|
||||||
if (const auto item = peer->owner().message(fullId)) {
|
if (state.value == SkipState::Skip) {
|
||||||
if (!item->notificationReady()) {
|
return true;
|
||||||
loaded = false;
|
} else if (state.value == SkipState::Unknown
|
||||||
}
|
|| !item->notificationReady()) {
|
||||||
} else {
|
return false;
|
||||||
muted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loaded) {
|
|
||||||
if (!muted || i->second.alsoMuted) {
|
|
||||||
_waiters.emplace(i->first, i->second);
|
|
||||||
}
|
}
|
||||||
|
_waiters.emplace(i->first, i->second);
|
||||||
|
return true;
|
||||||
|
}();
|
||||||
|
if (remove) {
|
||||||
i = _settingWaiters.erase(i);
|
i = _settingWaiters.erase(i);
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
|
@ -657,14 +667,14 @@ void System::showNext() {
|
||||||
const auto reactionNotification
|
const auto reactionNotification
|
||||||
= (notify->type == ItemNotificationType::Reaction);
|
= (notify->type == ItemNotificationType::Reaction);
|
||||||
const auto reaction = reactionNotification
|
const auto reaction = reactionNotification
|
||||||
? notify->item->lookupUnreadReaction()
|
? notify->item->lookupUnreadReaction(notify->reactionSender)
|
||||||
: HistoryItemUnreadReaction();
|
: QString();
|
||||||
if (!reactionNotification || reaction) {
|
if (!reactionNotification || !reaction.isEmpty()) {
|
||||||
_manager->showNotification({
|
_manager->showNotification({
|
||||||
.item = notify->item,
|
.item = notify->item,
|
||||||
.forwardedCount = forwardedCount,
|
.forwardedCount = forwardedCount,
|
||||||
.reactionFrom = reaction.from,
|
.reactionFrom = notify->reactionSender,
|
||||||
.reactionEmoji = reaction.emoji,
|
.reactionEmoji = reaction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Waiter;
|
||||||
|
|
||||||
struct SkipState {
|
struct SkipState {
|
||||||
enum Value {
|
enum Value {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -111,7 +113,6 @@ private:
|
||||||
};
|
};
|
||||||
Value value = Value::Unknown;
|
Value value = Value::Unknown;
|
||||||
bool silent = false;
|
bool silent = false;
|
||||||
bool alsoMuted = false;
|
|
||||||
};
|
};
|
||||||
struct NotificationInHistoryKey {
|
struct NotificationInHistoryKey {
|
||||||
NotificationInHistoryKey(ItemNotification notification);
|
NotificationInHistoryKey(ItemNotification notification);
|
||||||
|
@ -127,12 +128,6 @@ private:
|
||||||
< std::pair(b.messageId, b.type);
|
< std::pair(b.messageId, b.type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Waiter {
|
|
||||||
NotificationInHistoryKey key;
|
|
||||||
crl::time when = 0;
|
|
||||||
PeerData *notifyBy = nullptr;
|
|
||||||
bool alsoMuted = false;
|
|
||||||
};
|
|
||||||
struct Timing {
|
struct Timing {
|
||||||
crl::time delay = 0;
|
crl::time delay = 0;
|
||||||
crl::time when = 0;
|
crl::time when = 0;
|
||||||
|
@ -152,8 +147,7 @@ private:
|
||||||
[[nodiscard]] SkipState skipNotification(
|
[[nodiscard]] SkipState skipNotification(
|
||||||
ItemNotification notification) const;
|
ItemNotification notification) const;
|
||||||
[[nodiscard]] SkipState computeSkipState(
|
[[nodiscard]] SkipState computeSkipState(
|
||||||
not_null<HistoryItem*> item,
|
ItemNotification notification) const;
|
||||||
bool showForMuted) const;
|
|
||||||
[[nodiscard]] Timing countTiming(
|
[[nodiscard]] Timing countTiming(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
crl::time minimalDelay) const;
|
crl::time minimalDelay) const;
|
||||||
|
|
Loading…
Add table
Reference in a new issue