Notify about published scheduled messages.

This commit is contained in:
John Preston 2019-08-28 17:24:12 +03:00
parent 3b76a908a4
commit 0a4f91a53d
21 changed files with 423 additions and 179 deletions

View file

@ -1188,6 +1188,14 @@ rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const {
return _viewLayoutChanges.events(); return _viewLayoutChanges.events();
} }
void Session::notifyUnreadItemAdded(not_null<HistoryItem*> item) {
_unreadItemAdded.fire_copy(item);
}
rpl::producer<not_null<HistoryItem*>> Session::unreadItemAdded() const {
return _unreadItemAdded.events();
}
void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) { void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) {
const auto list = messagesListForInsert(channel); const auto list = messagesListForInsert(channel);
auto i = list->find(wasId); auto i = list->find(wasId);

View file

@ -192,6 +192,8 @@ public:
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const; [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const;
void notifyViewLayoutChange(not_null<const ViewElement*> view); void notifyViewLayoutChange(not_null<const ViewElement*> view);
[[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const; [[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const;
void notifyUnreadItemAdded(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> unreadItemAdded() const;
void requestItemRepaint(not_null<const HistoryItem*> item); void requestItemRepaint(not_null<const HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const; [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
void requestViewRepaint(not_null<const ViewElement*> view); void requestViewRepaint(not_null<const ViewElement*> view);
@ -841,6 +843,7 @@ private:
rpl::event_stream<IdChange> _itemIdChanges; rpl::event_stream<IdChange> _itemIdChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges; rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges; rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges;
rpl::event_stream<not_null<HistoryItem*>> _unreadItemAdded;
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest; rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;

View file

@ -1242,19 +1242,28 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
from->madeAction(item->date()); from->madeAction(item->date());
} }
item->contributeToSlowmode(); item->contributeToSlowmode();
if (item->out()) { if (item->showNotification()) {
_notifications.push_back(item);
owner().notifyUnreadItemAdded(item);
const auto stillShow = item->showNotification();
if (stillShow) {
session().notifications().schedule(item);
if (!item->out() && item->unread()) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
} else {
session().api().requestDialogEntry(this);
}
}
}
} else if (item->out()) {
destroyUnreadBar(); destroyUnreadBar();
if (!item->unread()) {
outboxRead(item);
}
} else if (item->unread()) {
if (!isChannel() || peer->asChannel()->amIn()) {
_notifications.push_back(item);
App::main()->newUnreadMsg(this, item);
}
} else { } else {
inboxRead(item); inboxRead(item);
} }
if (item->out() && !item->unread()) {
outboxRead(item);
}
if (!folderKnown()) { if (!folderKnown()) {
session().api().requestDialogEntry(this); session().api().requestDialogEntry(this);
} }

View file

@ -367,6 +367,12 @@ void HistoryItem::addLogEntryOriginal(
content); content);
} }
PeerData *HistoryItem::specialNotificationPeer() const {
return (mentionsMe() && !_history->peer->isUser())
? from().get()
: nullptr;
}
UserData *HistoryItem::viaBot() const { UserData *HistoryItem::viaBot() const {
if (const auto via = Get<HistoryMessageVia>()) { if (const auto via = Get<HistoryMessageVia>()) {
return via->bot; return via->bot;
@ -383,7 +389,22 @@ UserData *HistoryItem::getMessageBot() const {
bot = history()->peer->asUser(); bot = history()->peer->asUser();
} }
return (bot && bot->isBot()) ? bot : nullptr; return (bot && bot->isBot()) ? bot : nullptr;
}; }
bool HistoryItem::isHistoryEntry() const {
return IsServerMsgId(id)
|| (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
}
bool HistoryItem::isFromScheduled() const {
return isHistoryEntry()
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
}
bool HistoryItem::isScheduled() const {
return !isHistoryEntry()
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
}
void HistoryItem::destroy() { void HistoryItem::destroy() {
_history->owner().destroyMessage(this); _history->owner().destroyMessage(this);
@ -737,6 +758,14 @@ bool HistoryItem::unread() const {
return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread); return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread);
} }
bool HistoryItem::showNotification() const {
const auto channel = _history->peer->asChannel();
if (channel && !channel->amIn()) {
return false;
}
return out() ? isFromScheduled() : unread();
}
void HistoryItem::markClientSideAsRead() { void HistoryItem::markClientSideAsRead() {
_clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread; _clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread;
} }

View file

@ -75,28 +75,20 @@ public:
virtual MsgId dependencyMsgId() const { virtual MsgId dependencyMsgId() const {
return 0; return 0;
} }
virtual bool notificationReady() const { [[nodiscard]] virtual bool notificationReady() const {
return true; return true;
} }
[[nodiscard]] PeerData *specialNotificationPeer() const;
virtual void applyGroupAdminChanges( virtual void applyGroupAdminChanges(
const base::flat_set<UserId> &changes) { const base::flat_set<UserId> &changes) {
} }
UserData *viaBot() const; [[nodiscard]] UserData *viaBot() const;
UserData *getMessageBot() const; [[nodiscard]] UserData *getMessageBot() const;
[[nodiscard]] bool isHistoryEntry() const;
[[nodiscard]] bool isFromScheduled() const;
[[nodiscard]] bool isScheduled() const;
[[nodiscard]] bool isHistoryEntry() const {
return IsServerMsgId(id)
|| (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
}
[[nodiscard]] bool isFromScheduled() const {
return isHistoryEntry()
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
}
[[nodiscard]] bool isScheduled() const {
return !isHistoryEntry()
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
}
void addLogEntryOriginal( void addLogEntryOriginal(
WebPageId localId, WebPageId localId,
const QString &label, const QString &label,
@ -123,6 +115,7 @@ public:
return _flags & MTPDmessage::Flag::f_out; return _flags & MTPDmessage::Flag::f_out;
} }
[[nodiscard]] bool unread() const; [[nodiscard]] bool unread() const;
[[nodiscard]] bool showNotification() const;
void markClientSideAsRead(); void markClientSideAsRead();
[[nodiscard]] bool mentionsMe() const; [[nodiscard]] bool mentionsMe() const;
[[nodiscard]] bool isUnreadMention() const; [[nodiscard]] bool isUnreadMention() const;

View file

@ -1302,7 +1302,12 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
} }
QString HistoryMessage::notificationHeader() const { QString HistoryMessage::notificationHeader() const {
return (!_history->peer->isUser() && !isPost()) ? from()->name : QString(); if (out() && isFromScheduled()) {
return tr::lng_from_you(tr::now);
} else if (!_history->peer->isUser() && !isPost()) {
return App::peerName(from());
}
return QString();
} }
std::unique_ptr<HistoryView::Element> HistoryMessage::createView( std::unique_ptr<HistoryView::Element> HistoryMessage::createView(

View file

@ -455,6 +455,12 @@ HistoryWidget::HistoryWidget(
update(); update();
} }
}); });
session().data().unreadItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
unreadMessageAdded(item);
}, lifetime());
session().data().itemRemoved( session().data().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) { ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
itemRemoved(item); itemRemoved(item);
@ -2225,34 +2231,28 @@ void HistoryWidget::destroyUnreadBar() {
if (_migrated) _migrated->destroyUnreadBar(); if (_migrated) _migrated->destroyUnreadBar();
} }
void HistoryWidget::newUnreadMsg( void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> item) {
not_null<History*> history, if (_history != item->history()) {
not_null<HistoryItem*> item) { return;
if (_history == history) { }
// If we get here in non-resized state we can't rely on results of
// doWeReadServerHistory() and mark chat as read.
// If we receive N messages being not at bottom:
// - on first message we set unreadcount += 1, firstUnreadMessage.
// - on second we get wrong doWeReadServerHistory() and read both.
session().data().sendHistoryChangeNotifications();
if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) { // If we get here in non-resized state we can't rely on results of
destroyUnreadBar(); // doWeReadServerHistory() and mark chat as read.
} // If we receive N messages being not at bottom:
if (App::wnd()->doWeReadServerHistory()) { // - on first message we set unreadcount += 1, firstUnreadMessage.
if (item->isUnreadMention() && !item->isUnreadMedia()) { // - on second we get wrong doWeReadServerHistory() and read both.
session().api().markMediaRead(item); session().data().sendHistoryChangeNotifications();
}
session().api().readServerHistoryForce(history); if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) {
return; destroyUnreadBar();
}
} }
session().notifications().schedule(history, item); if (!App::wnd()->doWeReadServerHistory()) {
if (history->unreadCountKnown()) { return;
history->setUnreadCount(history->unreadCount() + 1);
} else {
session().api().requestDialogEntry(history);
} }
if (item->isUnreadMention() && !item->isUnreadMedia()) {
session().api().markMediaRead(item);
}
session().api().readServerHistoryForce(_history);
} }
void HistoryWidget::historyToDown(History *history) { void HistoryWidget::historyToDown(History *history) {

View file

@ -124,9 +124,6 @@ public:
void firstLoadMessages(); void firstLoadMessages();
void delayedShowAt(MsgId showAtMsgId); void delayedShowAt(MsgId showAtMsgId);
void newUnreadMsg(
not_null<History*> history,
not_null<HistoryItem*> item);
void historyToDown(History *history); void historyToDown(History *history);
QRect historyRect() const; QRect historyRect() const;
@ -423,6 +420,7 @@ private:
void historyDownAnimationFinish(); void historyDownAnimationFinish();
void unreadMentionsAnimationFinish(); void unreadMentionsAnimationFinish();
void sendButtonClicked(); void sendButtonClicked();
void unreadMessageAdded(not_null<HistoryItem*> item);
bool canSendFiles(not_null<const QMimeData*> data) const; bool canSendFiles(not_null<const QMimeData*> data) const;
bool confirmSendingFiles( bool confirmSendingFiles(

View file

@ -2198,12 +2198,6 @@ void MainWidget::dialogsToUp() {
_dialogs->jumpToTop(); _dialogs->jumpToTop();
} }
void MainWidget::newUnreadMsg(
not_null<History*> history,
not_null<HistoryItem*> item) {
_history->newUnreadMsg(history, item);
}
void MainWidget::markActiveHistoryAsRead() { void MainWidget::markActiveHistoryAsRead() {
if (const auto activeHistory = _history->history()) { if (const auto activeHistory = _history->history()) {
session().api().readServerHistory(activeHistory); session().api().readServerHistory(activeHistory);

View file

@ -146,9 +146,6 @@ public:
bool deleteChannelFailed(const RPCError &error); bool deleteChannelFailed(const RPCError &error);
void historyToDown(History *hist); void historyToDown(History *hist);
void dialogsToUp(); void dialogsToUp();
void newUnreadMsg(
not_null<History*> history,
not_null<HistoryItem*> item);
void markActiveHistoryAsRead(); void markActiveHistoryAsRead();
PeerData *peer(); PeerData *peer();

View file

@ -304,7 +304,14 @@ public:
void init(Manager *manager); void init(Manager *manager);
void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); void showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton);
void clearAll(); void clearAll();
void clearFromHistory(History *history); void clearFromHistory(History *history);
void clearNotification(PeerId peerId, MsgId msgId); void clearNotification(PeerId peerId, MsgId msgId);
@ -384,7 +391,14 @@ QString Manager::Private::escapeNotificationText(const QString &text) const {
return _markupSupported ? escapeHtml(text) : text; return _markupSupported ? escapeHtml(text) : text;
} }
void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { void Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
auto titleText = escapeNotificationText(title); auto titleText = escapeNotificationText(title);
auto subtitleText = escapeNotificationText(subtitle); auto subtitleText = escapeNotificationText(subtitle);
auto msgText = escapeNotificationText(msg); auto msgText = escapeNotificationText(msg);
@ -537,8 +551,22 @@ bool Manager::hasActionsSupport() const {
Manager::~Manager() = default; Manager::~Manager() = default;
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { void Manager::doShowNativeNotification(
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
_private->showNotification(
peer,
msgId,
title,
subtitle,
msg,
hideNameAndPhoto,
hideReplyButton);
} }
void Manager::doClearAllFast() { void Manager::doClearAllFast() {

View file

@ -36,7 +36,14 @@ public:
~Manager(); ~Manager();
protected: protected:
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) override;
void doClearAllFast() override; void doClearAllFast() override;
void doClearFromHistory(History *history) override; void doClearFromHistory(History *history) override;

View file

@ -22,7 +22,14 @@ public:
~Manager(); ~Manager();
protected: protected:
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) override;
void doClearAllFast() override; void doClearAllFast() override;
void doClearFromHistory(History *history) override; void doClearFromHistory(History *history) override;

View file

@ -160,7 +160,14 @@ class Manager::Private : public QObject, private base::Subscriber {
public: public:
Private(Manager *manager); Private(Manager *manager);
void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); void showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton);
void clearAll(); void clearAll();
void clearFromHistory(History *history); void clearFromHistory(History *history);
void updateDelegate(); void updateDelegate();
@ -208,7 +215,14 @@ Manager::Private::Private(Manager *manager)
}); });
} }
void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { void Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
@autoreleasepool { @autoreleasepool {
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease]; NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
@ -329,8 +343,22 @@ Manager::Manager(Window::Notifications::System *system) : NativeManager(system)
Manager::~Manager() = default; Manager::~Manager() = default;
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { void Manager::doShowNativeNotification(
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
_private->showNotification(
peer,
msgId,
title,
subtitle,
msg,
hideNameAndPhoto,
hideReplyButton);
} }
void Manager::doClearAllFast() { void Manager::doClearAllFast() {

View file

@ -342,7 +342,14 @@ public:
explicit Private(Manager *instance, Type type); explicit Private(Manager *instance, Type type);
bool init(); bool init();
bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton); bool showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton);
void clearAll(); void clearAll();
void clearFromHistory(History *history); void clearFromHistory(History *history);
void beforeNotificationActivated(PeerId peerId, MsgId msgId); void beforeNotificationActivated(PeerId peerId, MsgId msgId);
@ -447,13 +454,24 @@ void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) {
} }
} }
bool Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { bool Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
if (!_notificationManager || !_notifier || !_notificationFactory) return false; if (!_notificationManager || !_notifier || !_notificationFactory) return false;
ComPtr<IXmlDocument> toastXml; ComPtr<IXmlDocument> toastXml;
bool withSubtitle = !subtitle.isEmpty(); bool withSubtitle = !subtitle.isEmpty();
HRESULT hr = _notificationManager->GetTemplateContent(withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml); HRESULT hr = _notificationManager->GetTemplateContent(
(withSubtitle
? ToastTemplateType_ToastImageAndText04
: ToastTemplateType_ToastImageAndText02),
&toastXml);
if (!SUCCEEDED(hr)) return false; if (!SUCCEEDED(hr)) return false;
hr = SetAudioSilent(toastXml.Get()); hr = SetAudioSilent(toastXml.Get());
@ -560,8 +578,22 @@ void Manager::clearNotification(PeerId peerId, MsgId msgId) {
Manager::~Manager() = default; Manager::~Manager() = default;
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { void Manager::doShowNativeNotification(
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton); not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
_private->showNotification(
peer,
msgId,
title,
subtitle,
msg,
hideNameAndPhoto,
hideReplyButton);
} }
void Manager::doClearAllFast() { void Manager::doClearAllFast() {

View file

@ -23,7 +23,14 @@ public:
~Manager(); ~Manager();
protected: protected:
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override; void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) override;
void doClearAllFast() override; void doClearAllFast() override;
void doClearFromHistory(History *history) override; void doClearFromHistory(History *history) override;
void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override; void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override;

View file

@ -3322,7 +3322,9 @@ IsolatedEmoji String::toIsolatedEmoji() const {
auto index = 0; auto index = 0;
for (const auto &block : _blocks) { for (const auto &block : _blocks) {
const auto type = block->type(); const auto type = block->type();
if (type == TextBlockTEmoji) { if (block->lnkIndex()) {
return IsolatedEmoji();
} else if (type == TextBlockTEmoji) {
result.items[index++] = static_cast<EmojiBlock*>(block.get())->emoji; result.items[index++] = static_cast<EmojiBlock*>(block.get())->emoji;
} else if (type != TextBlockTSkip) { } else if (type != TextBlockTSkip) {
return IsolatedEmoji(); return IsolatedEmoji();

View file

@ -61,45 +61,58 @@ void System::createManager() {
} }
} }
void System::schedule( System::SkipState System::skipNotification(
not_null<History*> history, not_null<HistoryItem*> item) const {
not_null<HistoryItem*> item) { const auto history = item->history();
if (App::quitting() const auto notifyBy = item->specialNotificationPeer();
|| !history->currentNotification() if (App::quitting() || !history->currentNotification()) {
|| !Main::Session::Exists()) return; return { SkipState::Skip };
}
const auto notifyBy = (!history->peer->isUser() && item->mentionsMe()) const auto scheduled = item->out() && item->isFromScheduled();
? item->from().get()
: nullptr;
history->owner().requestNotifySettings(history->peer); history->owner().requestNotifySettings(history->peer);
if (notifyBy) { if (notifyBy) {
history->owner().requestNotifySettings(notifyBy); history->owner().requestNotifySettings(notifyBy);
} }
auto haveSetting = !history->owner().notifyMuteUnknown(history->peer);
if (haveSetting && history->owner().notifyIsMuted(history->peer)) { if (history->owner().notifyMuteUnknown(history->peer)) {
if (notifyBy) { return { SkipState::Unknown, item->isSilent() };
haveSetting = !history->owner().notifyMuteUnknown(notifyBy); } else if (!history->owner().notifyIsMuted(history->peer)) {
if (haveSetting) { return { SkipState::DontSkip, item->isSilent() };
if (history->owner().notifyIsMuted(notifyBy)) { } else if (!notifyBy) {
history->popNotification(item); return {
return; scheduled ? SkipState::DontSkip : SkipState::Skip,
} item->isSilent() || scheduled
} };
} else { } else if (history->owner().notifyMuteUnknown(notifyBy)) {
history->popNotification(item); return { SkipState::Unknown, item->isSilent() };
return; } else if (!history->owner().notifyIsMuted(notifyBy)) {
} return { SkipState::DontSkip, item->isSilent() };
} else {
return {
scheduled ? SkipState::DontSkip : SkipState::Skip,
item->isSilent() || scheduled
};
} }
if (!item->notificationReady()) { }
haveSetting = false;
void System::schedule(not_null<HistoryItem*> item) {
const auto history = item->history();
const auto skip = skipNotification(item);
if (skip.value == SkipState::Skip) {
history->popNotification(item);
return;
} }
const auto notifyBy = item->specialNotificationPeer();
const auto ready = (skip.value != SkipState::Unknown)
&& item->notificationReady();
auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100; auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100;
auto t = base::unixtime::now(); const auto t = base::unixtime::now();
auto ms = crl::now(); const auto ms = crl::now();
bool isOnline = App::main()->lastWasOnline(), otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL); const bool isOnline = App::main()->lastWasOnline();
bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL); const auto otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL);
const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL);
if (!isOnline && otherNotOld && otherLaterThanMe) { if (!isOnline && otherNotOld && otherLaterThanMe) {
delay = Global::NotifyCloudDelay(); delay = Global::NotifyCloudDelay();
} else if (cOtherOnline() >= t) { } else if (cOtherOnline() >= t) {
@ -107,7 +120,7 @@ void System::schedule(
} }
auto when = ms + delay; auto when = ms + delay;
if (!item->isSilent()) { if (!skip.silent) {
_whenAlerts[history].insert(when, notifyBy); _whenAlerts[history].insert(when, notifyBy);
} }
if (Global::DesktopNotify() && !Platform::Notifications::SkipToast()) { if (Global::DesktopNotify() && !Platform::Notifications::SkipToast()) {
@ -116,13 +129,13 @@ void System::schedule(
whenMap.insert(item->id, when); whenMap.insert(item->id, when);
} }
auto &addTo = haveSetting ? _waiters : _settingWaiters; auto &addTo = ready ? _waiters : _settingWaiters;
auto it = addTo.constFind(history); const auto it = addTo.constFind(history);
if (it == addTo.cend() || it->when > when) { if (it == addTo.cend() || it->when > when) {
addTo.insert(history, Waiter(item->id, when, notifyBy)); addTo.insert(history, Waiter(item->id, when, notifyBy));
} }
} }
if (haveSetting) { if (ready) {
if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) { if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) {
_waitTimer.callOnce(delay); _waitTimer.callOnce(delay);
} }
@ -524,9 +537,12 @@ void Manager::notificationReplied(
} }
} }
void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) { void NativeManager::doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) {
const auto options = getNotificationOptions(item); const auto options = getNotificationOptions(item);
const auto scheduled = (item->out() && item->isFromScheduled());
const auto title = options.hideNameAndPhoto ? qsl("Telegram Desktop") : item->history()->peer->name; const auto title = options.hideNameAndPhoto ? qsl("Telegram Desktop") : item->history()->peer->name;
const auto subtitle = options.hideNameAndPhoto ? QString() : item->notificationHeader(); const auto subtitle = options.hideNameAndPhoto ? QString() : item->notificationHeader();
const auto text = options.hideMessageText const auto text = options.hideMessageText
@ -539,7 +555,7 @@ void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
item->history()->peer, item->history()->peer,
item->id, item->id,
title, title,
subtitle, scheduled ? WrapFromScheduled(subtitle) : subtitle,
text, text,
options.hideNameAndPhoto, options.hideNameAndPhoto,
options.hideReplyButton); options.hideReplyButton);
@ -547,5 +563,9 @@ void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
System::~System() = default; System::~System() = default;
QString WrapFromScheduled(const QString &text) {
return QString::fromUtf8("\xF0\x9F\x93\x85 ") + text;
}
} // namespace Notifications } // namespace Notifications
} // namespace Window } // namespace Window

View file

@ -62,7 +62,7 @@ public:
void createManager(); void createManager();
void checkDelayed(); void checkDelayed();
void schedule(not_null<History*> history, not_null<HistoryItem*> item); void schedule(not_null<HistoryItem*> item);
void clearFromHistory(History *history); void clearFromHistory(History *history);
void clearFromItem(HistoryItem *item); void clearFromItem(HistoryItem *item);
void clearAll(); void clearAll();
@ -80,6 +80,18 @@ public:
~System(); ~System();
private: private:
struct SkipState {
enum Value {
Unknown,
Skip,
DontSkip
};
Value value = Value::Unknown;
bool silent = false;
};
SkipState skipNotification(not_null<HistoryItem*> item) const;
void showNext(); void showNext();
void showGrouped(); void showGrouped();
void ensureSoundCreated(); void ensureSoundCreated();
@ -122,7 +134,9 @@ public:
explicit Manager(not_null<System*> system) : _system(system) { explicit Manager(not_null<System*> system) : _system(system) {
} }
void showNotification(HistoryItem *item, int forwardedCount) { void showNotification(
not_null<HistoryItem*> item,
int forwardedCount) {
doShowNotification(item, forwardedCount); doShowNotification(item, forwardedCount);
} }
void updateAll() { void updateAll() {
@ -162,7 +176,9 @@ protected:
} }
virtual void doUpdateAll() = 0; virtual void doUpdateAll() = 0;
virtual void doShowNotification(HistoryItem *item, int forwardedCount) = 0; virtual void doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) = 0;
virtual void doClearAll() = 0; virtual void doClearAll() = 0;
virtual void doClearAllFast() = 0; virtual void doClearAllFast() = 0;
virtual void doClearFromItem(HistoryItem *item) = 0; virtual void doClearFromItem(HistoryItem *item) = 0;
@ -193,11 +209,22 @@ protected:
} }
void doClearFromItem(HistoryItem *item) override { void doClearFromItem(HistoryItem *item) override {
} }
void doShowNotification(HistoryItem *item, int forwardedCount) override; void doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) override;
virtual void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) = 0; virtual void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) = 0;
}; };
QString WrapFromScheduled(const QString &text);
} // namespace Notifications } // namespace Notifications
} // namespace Window } // namespace Window

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/text_options.h"
#include "dialogs/dialogs_layout.h" #include "dialogs/dialogs_layout.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
@ -66,13 +67,14 @@ Manager::Manager(System *system)
} }
Manager::QueuedNotification::QueuedNotification( Manager::QueuedNotification::QueuedNotification(
not_null<HistoryItem*> item not_null<HistoryItem*> item,
, int forwardedCount) int forwardedCount)
: history(item->history()) : history(item->history())
, peer(history->peer) , peer(history->peer)
, author((!peer->isUser() && !item->isPost()) ? item->author().get() : nullptr) , author(item->notificationHeader())
, item((forwardedCount < 2) ? item.get() : nullptr) , item((forwardedCount < 2) ? item.get() : nullptr)
, forwardedCount(forwardedCount) { , forwardedCount(forwardedCount)
, fromScheduled(item->out() && item->isFromScheduled()) {
} }
QPixmap Manager::hiddenUserpicPlaceholder() const { QPixmap Manager::hiddenUserpicPlaceholder() const {
@ -206,7 +208,10 @@ void Manager::showNextFromQueue() {
queued.author, queued.author,
queued.item, queued.item,
queued.forwardedCount, queued.forwardedCount,
startPosition, startShift, shiftDirection); queued.fromScheduled,
startPosition,
startShift,
shiftDirection);
_notifications.push_back(std::move(notification)); _notifications.push_back(std::move(notification));
--count; --count;
} while (count > 0 && !_queuedNotifications.empty()); } while (count > 0 && !_queuedNotifications.empty());
@ -289,7 +294,9 @@ void Manager::removeWidget(internal::Widget *remove) {
showNextFromQueue(); showNextFromQueue();
} }
void Manager::doShowNotification(HistoryItem *item, int forwardedCount) { void Manager::doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) {
_queuedNotifications.emplace_back(item, forwardedCount); _queuedNotifications.emplace_back(item, forwardedCount);
showNextFromQueue(); showNextFromQueue();
} }
@ -353,7 +360,12 @@ Manager::~Manager() {
namespace internal { namespace internal {
Widget::Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : TWidget(nullptr) Widget::Widget(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection)
: TWidget(nullptr)
, _manager(manager) , _manager(manager)
, _startPosition(startPosition) , _startPosition(startPosition)
, _direction(shiftDirection) , _direction(shiftDirection)
@ -501,12 +513,13 @@ void Background::paintEvent(QPaintEvent *e) {
} }
Notification::Notification( Notification::Notification(
Manager *manager, not_null<Manager*> manager,
History *history, not_null<History*> history,
PeerData *peer, not_null<PeerData*> peer,
PeerData *author, const QString &author,
HistoryItem *msg, HistoryItem *item,
int forwardedCount, int forwardedCount,
bool fromScheduled,
QPoint startPosition, QPoint startPosition,
int shift, int shift,
Direction shiftDirection) Direction shiftDirection)
@ -515,8 +528,9 @@ Notification::Notification(
, _history(history) , _history(history)
, _peer(peer) , _peer(peer)
, _author(author) , _author(author)
, _item(msg) , _item(item)
, _forwardedCount(forwardedCount) , _forwardedCount(forwardedCount)
, _fromScheduled(fromScheduled)
, _close(this, st::notifyClose) , _close(this, st::notifyClose)
, _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) { , _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) {
subscribe(Lang::Current().updated(), [this] { refreshLang(); }); subscribe(Lang::Current().updated(), [this] { refreshLang(); });
@ -683,35 +697,53 @@ void Notification::updateNotifyDisplay() {
} }
if (!options.hideMessageText) { if (!options.hideMessageText) {
const HistoryItem *textCachedFor = nullptr; auto itemTextCache = Ui::Text::String(itemWidth);
Ui::Text::String itemTextCache(itemWidth); auto r = QRect(
QRect r(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height, itemWidth, 2 * st::dialogsTextFont->height); st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
if (_item) { st::notifyItemTop + st::msgNameFont->height,
auto active = false, selected = false; itemWidth,
_item->drawInDialog( 2 * st::dialogsTextFont->height);
p, p.setTextPalette(st::dialogsTextPalette);
r, p.setPen(st::dialogsTextFg);
active, p.setFont(st::dialogsTextFont);
selected, const auto text = _item
HistoryItem::DrawInDialog::Normal, ? _item->inDialogsText(HistoryItem::DrawInDialog::Normal)
textCachedFor, : ((!_author.isEmpty()
itemTextCache); ? textcmdLink(1, _author)
} else if (_forwardedCount > 1) { : QString())
p.setFont(st::dialogsTextFont); + (_forwardedCount > 1
if (_author) { ? ('\n' + tr::lng_forward_messages(
itemTextCache.setText(st::dialogsTextStyle, _author->name); tr::now,
p.setPen(st::dialogsTextFgService); lt_count,
itemTextCache.drawElided(p, r.left(), r.top(), r.width()); _forwardedCount))
r.setTop(r.top() + st::dialogsTextFont->height); : QString()));
} const auto Options = TextParseOptions{
p.setPen(st::dialogsTextFg); TextParseRichText
p.drawText(r.left(), r.top() + st::dialogsTextFont->ascent, tr::lng_forward_messages(tr::now, lt_count, _forwardedCount)); | (_forwardedCount > 1 ? TextParseMultiline : 0),
} 0,
0,
Qt::LayoutDirectionAuto,
};
itemTextCache.setText(
st::dialogsTextStyle,
_fromScheduled ? WrapFromScheduled(text) : text,
Options);
itemTextCache.drawElided(
p,
r.left(),
r.top(),
r.width(),
r.height() / st::dialogsTextFont->height);
p.restoreTextPalette();
} else { } else {
static QString notifyText = st::dialogsTextFont->elided(tr::lng_notification_preview(tr::now), itemWidth);
p.setFont(st::dialogsTextFont); p.setFont(st::dialogsTextFont);
p.setPen(st::dialogsTextFgService); p.setPen(st::dialogsTextFgService);
p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText); p.drawText(
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent,
st::dialogsTextFont->elided(
tr::lng_notification_preview(tr::now),
itemWidth));
} }
p.setPen(st::dialogsNameFg); p.setPen(st::dialogsNameFg);
@ -839,7 +871,7 @@ void Notification::changeHeight(int newHeight) {
} }
bool Notification::unlinkHistory(History *history) { bool Notification::unlinkHistory(History *history) {
auto unlink = _history && (history == _history || !history); const auto unlink = _history && (history == _history || !history);
if (unlink) { if (unlink) {
hideFast(); hideFast();
_history = nullptr; _history = nullptr;
@ -897,7 +929,12 @@ void Notification::stopHiding() {
Widget::hideStop(); Widget::hideStop();
} }
HideAllButton::HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : Widget(manager, startPosition, shift, shiftDirection) { HideAllButton::HideAllButton(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection)
: Widget(manager, startPosition, shift, shiftDirection) {
setCursor(style::cur_pointer); setCursor(style::cur_pointer);
auto position = computePosition(st::notifyHideAllHeight); auto position = computePosition(st::notifyHideAllHeight);

View file

@ -52,7 +52,9 @@ private:
QPixmap hiddenUserpicPlaceholder() const; QPixmap hiddenUserpicPlaceholder() const;
void doUpdateAll() override; void doUpdateAll() override;
void doShowNotification(HistoryItem *item, int forwardedCount) override; void doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) override;
void doClearAll() override; void doClearAll() override;
void doClearAllFast() override; void doClearAllFast() override;
void doClearFromHistory(History *history) override; void doClearFromHistory(History *history) override;
@ -87,9 +89,10 @@ private:
not_null<History*> history; not_null<History*> history;
not_null<PeerData*> peer; not_null<PeerData*> peer;
PeerData *author; QString author;
HistoryItem *item; HistoryItem *item = nullptr;
int forwardedCount; int forwardedCount = 0;
bool fromScheduled = false;
}; };
std::deque<QueuedNotification> _queuedNotifications; std::deque<QueuedNotification> _queuedNotifications;
@ -107,7 +110,11 @@ public:
Up, Up,
Down, Down,
}; };
Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection); Widget(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection);
bool isShowing() const { bool isShowing() const {
return _a_opacity.animating() && !_hiding; return _a_opacity.animating() && !_hiding;
@ -131,7 +138,7 @@ protected:
virtual void updateGeometry(int x, int y, int width, int height); virtual void updateGeometry(int x, int y, int width, int height);
protected: protected:
Manager *manager() const { not_null<Manager*> manager() const {
return _manager; return _manager;
} }
@ -142,7 +149,7 @@ private:
void hideAnimated(float64 duration, const anim::transition &func); void hideAnimated(float64 duration, const anim::transition &func);
bool shiftAnimationCallback(crl::time now); bool shiftAnimationCallback(crl::time now);
Manager *_manager = nullptr; const not_null<Manager*> _manager;
bool _hiding = false; bool _hiding = false;
bool _deleted = false; bool _deleted = false;
@ -168,12 +175,13 @@ protected:
class Notification : public Widget { class Notification : public Widget {
public: public:
Notification( Notification(
Manager *manager, not_null<Manager*> manager,
History *history, not_null<History*> history,
PeerData *peer, not_null<PeerData*> peer,
PeerData *author, const QString &author,
HistoryItem *item, HistoryItem *item,
int forwardedCount, int forwardedCount,
bool fromScheduled,
QPoint startPosition, QPoint startPosition,
int shift, int shift,
Direction shiftDirection); Direction shiftDirection);
@ -228,11 +236,12 @@ private:
crl::time _started; crl::time _started;
History *_history; History *_history = nullptr;
PeerData *_peer; PeerData *_peer = nullptr;
PeerData *_author; QString _author;
HistoryItem *_item; HistoryItem *_item = nullptr;
int _forwardedCount; int _forwardedCount = 0;
bool _fromScheduled = false;
object_ptr<Ui::IconButton> _close; object_ptr<Ui::IconButton> _close;
object_ptr<Ui::RoundButton> _reply; object_ptr<Ui::RoundButton> _reply;
object_ptr<Background> _background = { nullptr }; object_ptr<Background> _background = { nullptr };
@ -250,7 +259,11 @@ private:
class HideAllButton : public Widget { class HideAllButton : public Widget {
public: public:
HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection); HideAllButton(
not_null<Manager*> manager,
QPoint startPosition,
int shift,
Direction shiftDirection);
void startHiding(); void startHiding();
void startHidingFast(); void startHidingFast();