2016-10-02 09:30:28 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-10-02 09:30:28 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-10-02 09:30:28 +00:00
|
|
|
*/
|
2016-10-02 13:54:27 +00:00
|
|
|
#include "window/notifications_manager.h"
|
2016-10-02 09:30:28 +00:00
|
|
|
|
|
|
|
#include "platform/platform_notifications_manager.h"
|
2016-10-02 13:54:27 +00:00
|
|
|
#include "window/notifications_manager_default.h"
|
2019-02-13 12:36:59 +00:00
|
|
|
#include "media/audio/media_audio_track.h"
|
|
|
|
#include "media/audio/media_audio.h"
|
2020-06-17 09:36:25 +00:00
|
|
|
#include "mtproto/mtproto_config.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "history/history_item_components.h"
|
2019-04-15 11:54:03 +00:00
|
|
|
//#include "history/feed/history_feed_section.h" // #feed
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2018-04-09 17:48:29 +00:00
|
|
|
#include "data/data_session.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_channel.h"
|
2020-06-19 13:34:43 +00:00
|
|
|
#include "data/data_user.h"
|
2019-07-10 17:28:33 +00:00
|
|
|
#include "base/unixtime.h"
|
2019-06-06 10:21:40 +00:00
|
|
|
#include "window/window_session_controller.h"
|
2019-01-21 13:42:21 +00:00
|
|
|
#include "core/application.h"
|
2016-10-02 16:32:46 +00:00
|
|
|
#include "mainwindow.h"
|
2020-06-11 09:41:03 +00:00
|
|
|
#include "api/api_updates.h"
|
2017-03-04 19:36:59 +00:00
|
|
|
#include "apiwrap.h"
|
2020-06-19 12:59:31 +00:00
|
|
|
#include "main/main_account.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2020-06-18 18:04:16 +00:00
|
|
|
#include "main/main_domain.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "facades.h"
|
|
|
|
#include "app.h"
|
2016-10-02 09:30:28 +00:00
|
|
|
|
2020-05-12 10:04:53 +00:00
|
|
|
#include <QtGui/QWindow>
|
|
|
|
|
2016-10-02 09:30:28 +00:00
|
|
|
namespace Window {
|
|
|
|
namespace Notifications {
|
2017-12-04 17:46:03 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// not more than one sound in 500ms from one peer - grouping
|
2019-02-19 06:57:53 +00:00
|
|
|
constexpr auto kMinimalAlertDelay = crl::time(500);
|
2019-03-10 07:42:25 +00:00
|
|
|
constexpr auto kWaitingForAllGroupedDelay = crl::time(1000);
|
2017-12-04 17:46:03 +00:00
|
|
|
|
2020-05-14 22:10:27 +00:00
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
constexpr auto kSystemAlertDuration = crl::time(1000);
|
2020-05-20 08:49:41 +00:00
|
|
|
#else // !Q_OS_MAC
|
2020-05-14 22:10:27 +00:00
|
|
|
constexpr auto kSystemAlertDuration = crl::time(0);
|
|
|
|
#endif // Q_OS_MAC
|
|
|
|
|
2017-12-04 17:46:03 +00:00
|
|
|
} // namespace
|
2016-10-02 09:30:28 +00:00
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
System::System()
|
|
|
|
: _waitTimer([=] { showNext(); })
|
2019-03-10 07:42:25 +00:00
|
|
|
, _waitForAllGroupedTimer([=] { showGrouped(); }) {
|
2017-03-04 19:36:59 +00:00
|
|
|
createManager();
|
|
|
|
|
2019-01-18 11:26:43 +00:00
|
|
|
subscribe(settingsChanged(), [=](ChangeType type) {
|
2017-03-04 19:36:59 +00:00
|
|
|
if (type == ChangeType::DesktopEnabled) {
|
|
|
|
App::wnd()->updateTrayMenu();
|
|
|
|
clearAll();
|
|
|
|
} else if (type == ChangeType::ViewParams) {
|
|
|
|
updateAll();
|
2018-12-04 10:32:06 +00:00
|
|
|
} else if (type == ChangeType::IncludeMuted
|
|
|
|
|| type == ChangeType::CountMessages) {
|
2020-06-18 18:04:16 +00:00
|
|
|
Core::App().domain().notifyUnreadBadgeChanged();
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::createManager() {
|
|
|
|
_manager = Platform::Notifications::Create(this);
|
|
|
|
if (!_manager) {
|
|
|
|
_manager = std::make_unique<Default::Manager>(this);
|
|
|
|
}
|
2016-10-02 09:30:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
Main::Session *System::findSession(UserId selfId) const {
|
|
|
|
for (const auto &[index, account] : Core::App().domain().accounts()) {
|
|
|
|
if (account->sessionExists()
|
|
|
|
&& account->session().userId() == selfId) {
|
|
|
|
return &account->session();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-08-28 14:24:12 +00:00
|
|
|
System::SkipState System::skipNotification(
|
|
|
|
not_null<HistoryItem*> item) const {
|
|
|
|
const auto history = item->history();
|
|
|
|
const auto notifyBy = item->specialNotificationPeer();
|
|
|
|
if (App::quitting() || !history->currentNotification()) {
|
|
|
|
return { SkipState::Skip };
|
|
|
|
}
|
|
|
|
const auto scheduled = item->out() && item->isFromScheduled();
|
2017-03-04 19:36:59 +00:00
|
|
|
|
2019-07-24 11:13:51 +00:00
|
|
|
history->owner().requestNotifySettings(history->peer);
|
2018-04-09 17:48:29 +00:00
|
|
|
if (notifyBy) {
|
2019-07-24 11:13:51 +00:00
|
|
|
history->owner().requestNotifySettings(notifyBy);
|
2018-04-09 17:48:29 +00:00
|
|
|
}
|
2019-08-28 14:24:12 +00:00
|
|
|
|
|
|
|
if (history->owner().notifyMuteUnknown(history->peer)) {
|
|
|
|
return { SkipState::Unknown, item->isSilent() };
|
|
|
|
} else if (!history->owner().notifyIsMuted(history->peer)) {
|
|
|
|
return { SkipState::DontSkip, item->isSilent() };
|
|
|
|
} else if (!notifyBy) {
|
|
|
|
return {
|
|
|
|
scheduled ? SkipState::DontSkip : SkipState::Skip,
|
|
|
|
item->isSilent() || scheduled
|
|
|
|
};
|
|
|
|
} else if (history->owner().notifyMuteUnknown(notifyBy)) {
|
|
|
|
return { SkipState::Unknown, item->isSilent() };
|
|
|
|
} else if (!history->owner().notifyIsMuted(notifyBy)) {
|
|
|
|
return { SkipState::DontSkip, item->isSilent() };
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
scheduled ? SkipState::DontSkip : SkipState::Skip,
|
|
|
|
item->isSilent() || scheduled
|
|
|
|
};
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
2019-08-28 14:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
2019-08-28 14:24:12 +00:00
|
|
|
const auto notifyBy = item->specialNotificationPeer();
|
|
|
|
const auto ready = (skip.value != SkipState::Unknown)
|
|
|
|
&& item->notificationReady();
|
2017-03-04 19:36:59 +00:00
|
|
|
|
2018-04-09 17:48:29 +00:00
|
|
|
auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100;
|
2019-08-28 14:24:12 +00:00
|
|
|
const auto t = base::unixtime::now();
|
|
|
|
const auto ms = crl::now();
|
2020-06-11 09:41:03 +00:00
|
|
|
const auto &updates = history->session().updates();
|
2020-06-17 09:36:25 +00:00
|
|
|
const auto &config = history->session().serverConfig();
|
2020-06-11 09:41:03 +00:00
|
|
|
const bool isOnline = updates.lastWasOnline();
|
2020-06-17 09:36:25 +00:00
|
|
|
const auto otherNotOld = ((cOtherOnline() * 1000LL) + config.onlineCloudTimeout > t * 1000LL);
|
2020-06-11 09:41:03 +00:00
|
|
|
const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - updates.lastSetOnline()) > t * 1000LL);
|
2017-03-04 19:36:59 +00:00
|
|
|
if (!isOnline && otherNotOld && otherLaterThanMe) {
|
2020-06-17 09:36:25 +00:00
|
|
|
delay = config.notifyCloudDelay;
|
2017-03-04 19:36:59 +00:00
|
|
|
} else if (cOtherOnline() >= t) {
|
2020-06-17 09:36:25 +00:00
|
|
|
delay = config.notifyDefaultDelay;
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto when = ms + delay;
|
2019-08-28 14:24:12 +00:00
|
|
|
if (!skip.silent) {
|
2020-06-19 12:59:31 +00:00
|
|
|
_whenAlerts[history].emplace(when, notifyBy);
|
2019-07-26 16:09:42 +00:00
|
|
|
}
|
2020-06-18 18:04:16 +00:00
|
|
|
if (Core::App().settings().desktopNotify()
|
|
|
|
&& !Platform::Notifications::SkipToast()) {
|
2017-03-04 19:36:59 +00:00
|
|
|
auto &whenMap = _whenMaps[history];
|
2020-06-19 12:59:31 +00:00
|
|
|
if (whenMap.find(item->id) == whenMap.end()) {
|
|
|
|
whenMap.emplace(item->id, when);
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 14:24:12 +00:00
|
|
|
auto &addTo = ready ? _waiters : _settingWaiters;
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto it = addTo.find(history);
|
|
|
|
if (it == addTo.end() || it->second.when > when) {
|
|
|
|
addTo.emplace(history, Waiter{
|
|
|
|
.msg = item->id,
|
|
|
|
.when = when,
|
|
|
|
.notifyBy = notifyBy
|
|
|
|
});
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-28 14:24:12 +00:00
|
|
|
if (ready) {
|
2017-03-04 19:36:59 +00:00
|
|
|
if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) {
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.callOnce(delay);
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::clearAll() {
|
|
|
|
_manager->clearAll();
|
|
|
|
|
|
|
|
for (auto i = _whenMaps.cbegin(), e = _whenMaps.cend(); i != e; ++i) {
|
2020-06-19 12:59:31 +00:00
|
|
|
i->first->clearNotifications();
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
_whenMaps.clear();
|
|
|
|
_whenAlerts.clear();
|
|
|
|
_waiters.clear();
|
|
|
|
_settingWaiters.clear();
|
|
|
|
}
|
|
|
|
|
2019-08-30 14:06:21 +00:00
|
|
|
void System::clearFromHistory(not_null<History*> history) {
|
2017-03-04 19:36:59 +00:00
|
|
|
_manager->clearFromHistory(history);
|
|
|
|
|
|
|
|
history->clearNotifications();
|
|
|
|
_whenMaps.remove(history);
|
|
|
|
_whenAlerts.remove(history);
|
|
|
|
_waiters.remove(history);
|
|
|
|
_settingWaiters.remove(history);
|
|
|
|
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.cancel();
|
2017-03-04 19:36:59 +00:00
|
|
|
showNext();
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
void System::clearFromSession(not_null<Main::Session*> session) {
|
|
|
|
_manager->clearFromSession(session);
|
|
|
|
|
|
|
|
for (auto i = _whenMaps.begin(), e = _whenMaps.end(); i != e;) {
|
|
|
|
const auto history = i->first;
|
|
|
|
if (&history->session() == session) {
|
|
|
|
history->clearNotifications();
|
|
|
|
i = _whenMaps.erase(i);
|
|
|
|
_whenAlerts.remove(history);
|
|
|
|
_waiters.remove(history);
|
|
|
|
_settingWaiters.remove(history);
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto clearFrom = [&](auto &map) {
|
|
|
|
for (auto i = map.begin(); i != map.end();) {
|
|
|
|
if (&i->first->session() == session) {
|
|
|
|
i = map.erase(i);
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
clearFrom(_whenAlerts);
|
|
|
|
clearFrom(_waiters);
|
|
|
|
clearFrom(_settingWaiters);
|
|
|
|
}
|
|
|
|
|
2019-08-30 14:06:21 +00:00
|
|
|
void System::clearIncomingFromHistory(not_null<History*> history) {
|
|
|
|
_manager->clearFromHistory(history);
|
|
|
|
history->clearIncomingNotifications();
|
|
|
|
_whenAlerts.remove(history);
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::clearFromItem(not_null<HistoryItem*> item) {
|
2017-03-04 19:36:59 +00:00
|
|
|
_manager->clearFromItem(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::clearAllFast() {
|
|
|
|
_manager->clearAllFast();
|
|
|
|
|
|
|
|
_whenMaps.clear();
|
|
|
|
_whenAlerts.clear();
|
|
|
|
_waiters.clear();
|
|
|
|
_settingWaiters.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::checkDelayed() {
|
|
|
|
for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto history = i->first;
|
2017-12-04 17:46:03 +00:00
|
|
|
const auto peer = history->peer;
|
|
|
|
auto loaded = false;
|
|
|
|
auto muted = false;
|
2019-07-24 11:13:51 +00:00
|
|
|
if (!peer->owner().notifyMuteUnknown(peer)) {
|
|
|
|
if (!peer->owner().notifyIsMuted(peer)) {
|
2017-03-04 19:36:59 +00:00
|
|
|
loaded = true;
|
2020-06-19 12:59:31 +00:00
|
|
|
} else if (const auto from = i->second.notifyBy) {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (!peer->owner().notifyMuteUnknown(from)) {
|
|
|
|
if (!peer->owner().notifyIsMuted(from)) {
|
2017-03-04 19:36:59 +00:00
|
|
|
loaded = true;
|
|
|
|
} else {
|
|
|
|
loaded = muted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
loaded = muted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (loaded) {
|
2017-12-04 17:46:03 +00:00
|
|
|
const auto fullId = FullMsgId(
|
|
|
|
history->channelId(),
|
2020-06-19 12:59:31 +00:00
|
|
|
i->second.msg);
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto item = peer->owner().message(fullId)) {
|
2017-03-04 19:36:59 +00:00
|
|
|
if (!item->notificationReady()) {
|
|
|
|
loaded = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
muted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (loaded) {
|
|
|
|
if (!muted) {
|
2020-06-19 12:59:31 +00:00
|
|
|
_waiters.emplace(i->first, i->second);
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
i = _settingWaiters.erase(i);
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.cancel();
|
2017-03-04 19:36:59 +00:00
|
|
|
showNext();
|
|
|
|
}
|
|
|
|
|
2019-03-10 07:42:25 +00:00
|
|
|
void System::showGrouped() {
|
2020-06-19 12:59:31 +00:00
|
|
|
if (const auto session = findSession(_lastHistorySelfId)) {
|
|
|
|
if (const auto lastItem = session->data().message(_lastHistoryItemId)) {
|
|
|
|
_waitForAllGroupedTimer.cancel();
|
|
|
|
_manager->showNotification(lastItem, _lastForwardedCount);
|
|
|
|
_lastForwardedCount = 0;
|
|
|
|
_lastHistoryItemId = FullMsgId();
|
|
|
|
}
|
2019-03-10 07:42:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-04 19:36:59 +00:00
|
|
|
void System::showNext() {
|
|
|
|
if (App::quitting()) return;
|
|
|
|
|
2019-03-10 07:42:25 +00:00
|
|
|
const auto isSameGroup = [=](HistoryItem *item) {
|
|
|
|
if (!_lastHistoryItemId || !item) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto lastItem = item->history()->owner().message(
|
|
|
|
_lastHistoryItemId);
|
|
|
|
if (lastItem) {
|
2019-03-10 07:42:25 +00:00
|
|
|
return (lastItem->groupId() == item->groupId() || lastItem->author() == item->author());
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2019-03-01 14:41:10 +00:00
|
|
|
auto ms = crl::now(), nextAlert = crl::time(0);
|
2017-03-04 19:36:59 +00:00
|
|
|
bool alert = false;
|
2019-07-10 17:28:33 +00:00
|
|
|
int32 now = base::unixtime::now();
|
2017-03-04 19:36:59 +00:00
|
|
|
for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
|
2020-06-19 12:59:31 +00:00
|
|
|
while (!i->second.empty() && i->second.begin()->first <= ms) {
|
|
|
|
const auto peer = i->first->peer;
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto peerUnknown = peer->owner().notifyMuteUnknown(peer);
|
2018-04-09 17:48:29 +00:00
|
|
|
const auto peerAlert = !peerUnknown
|
2019-07-24 11:13:51 +00:00
|
|
|
&& !peer->owner().notifyIsMuted(peer);
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto from = i->second.begin()->second;
|
2018-04-09 17:48:29 +00:00
|
|
|
const auto fromUnknown = (!from
|
2019-07-24 11:13:51 +00:00
|
|
|
|| peer->owner().notifyMuteUnknown(from));
|
2018-04-09 17:48:29 +00:00
|
|
|
const auto fromAlert = !fromUnknown
|
2019-07-24 11:13:51 +00:00
|
|
|
&& !peer->owner().notifyIsMuted(from);
|
2017-12-04 17:46:03 +00:00
|
|
|
if (peerAlert || fromAlert) {
|
2017-03-04 19:36:59 +00:00
|
|
|
alert = true;
|
|
|
|
}
|
2020-06-19 12:59:31 +00:00
|
|
|
while (!i->second.empty()
|
|
|
|
&& i->second.begin()->first <= ms + kMinimalAlertDelay) {
|
|
|
|
i->second.erase(i->second.begin());
|
2017-12-04 17:46:03 +00:00
|
|
|
}
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
2020-06-19 12:59:31 +00:00
|
|
|
if (i->second.empty()) {
|
2017-03-04 19:36:59 +00:00
|
|
|
i = _whenAlerts.erase(i);
|
|
|
|
} else {
|
2020-06-19 12:59:31 +00:00
|
|
|
if (!nextAlert || nextAlert > i->second.begin()->first) {
|
|
|
|
nextAlert = i->second.begin()->first;
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
2020-06-18 18:04:16 +00:00
|
|
|
const auto &settings = Core::App().settings();
|
2017-03-04 19:36:59 +00:00
|
|
|
if (alert) {
|
2020-06-18 18:04:16 +00:00
|
|
|
if (settings.flashBounceNotify() && !Platform::Notifications::SkipFlashBounce()) {
|
2020-05-12 10:04:53 +00:00
|
|
|
if (const auto widget = App::wnd()) {
|
|
|
|
if (const auto window = widget->windowHandle()) {
|
2020-05-14 22:10:27 +00:00
|
|
|
window->alert(kSystemAlertDuration);
|
2020-05-12 10:04:53 +00:00
|
|
|
// (window, SLOT(_q_clearAlert())); in the future.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-18 18:04:16 +00:00
|
|
|
if (settings.soundNotify() && !Platform::Notifications::SkipAudio()) {
|
2017-05-03 13:01:15 +00:00
|
|
|
ensureSoundCreated();
|
|
|
|
_soundTrack->playOnce();
|
|
|
|
emit Media::Player::mixer()->suppressAll(_soundTrack->getLengthMs());
|
|
|
|
emit Media::Player::mixer()->faderOnTimer();
|
|
|
|
}
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
if (_waiters.empty() || !settings.desktopNotify() || Platform::Notifications::SkipToast()) {
|
2017-03-04 19:36:59 +00:00
|
|
|
if (nextAlert) {
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.callOnce(nextAlert - ms);
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
auto next = 0LL;
|
|
|
|
HistoryItem *notifyItem = nullptr;
|
|
|
|
History *notifyHistory = nullptr;
|
|
|
|
for (auto i = _waiters.begin(); i != _waiters.end();) {
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto history = i->first;
|
|
|
|
if (history->currentNotification() && history->currentNotification()->id != i->second.msg) {
|
2017-03-04 19:36:59 +00:00
|
|
|
auto j = _whenMaps.find(history);
|
|
|
|
if (j == _whenMaps.end()) {
|
|
|
|
history->clearNotifications();
|
|
|
|
i = _waiters.erase(i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
do {
|
2020-06-19 12:59:31 +00:00
|
|
|
auto k = j->second.find(history->currentNotification()->id);
|
|
|
|
if (k != j->second.cend()) {
|
|
|
|
i->second.msg = k->first;
|
|
|
|
i->second.when = k->second;
|
2017-03-04 19:36:59 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
history->skipNotification();
|
|
|
|
} while (history->currentNotification());
|
|
|
|
}
|
|
|
|
if (!history->currentNotification()) {
|
|
|
|
_whenMaps.remove(history);
|
|
|
|
i = _waiters.erase(i);
|
|
|
|
continue;
|
|
|
|
}
|
2020-06-19 12:59:31 +00:00
|
|
|
auto when = i->second.when;
|
2017-03-04 19:36:59 +00:00
|
|
|
if (!notifyItem || next > when) {
|
|
|
|
next = when;
|
|
|
|
notifyItem = history->currentNotification();
|
|
|
|
notifyHistory = history;
|
|
|
|
}
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
if (notifyItem) {
|
|
|
|
if (next > ms) {
|
|
|
|
if (nextAlert && nextAlert < next) {
|
|
|
|
next = nextAlert;
|
|
|
|
nextAlert = 0;
|
|
|
|
}
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.callOnce(next - ms);
|
2017-03-04 19:36:59 +00:00
|
|
|
break;
|
|
|
|
} else {
|
2019-03-03 09:32:01 +00:00
|
|
|
const auto isForwarded = notifyItem->Has<HistoryMessageForwarded>();
|
|
|
|
const auto isAlbum = notifyItem->groupId();
|
2017-03-04 19:36:59 +00:00
|
|
|
|
2019-03-03 09:32:01 +00:00
|
|
|
auto groupedItem = (isForwarded || isAlbum) ? notifyItem : nullptr; // forwarded and album notify grouping
|
|
|
|
auto forwardedCount = isForwarded ? 1 : 0;
|
|
|
|
|
|
|
|
const auto history = notifyItem->history();
|
|
|
|
const auto j = _whenMaps.find(history);
|
2017-03-04 19:36:59 +00:00
|
|
|
if (j == _whenMaps.cend()) {
|
|
|
|
history->clearNotifications();
|
|
|
|
} else {
|
2017-04-30 12:44:17 +00:00
|
|
|
auto nextNotify = (HistoryItem*)nullptr;
|
2017-03-04 19:36:59 +00:00
|
|
|
do {
|
|
|
|
history->skipNotification();
|
|
|
|
if (!history->hasNotification()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
j->second.remove((groupedItem ? groupedItem : notifyItem)->id);
|
2017-03-04 19:36:59 +00:00
|
|
|
do {
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto k = j->second.find(history->currentNotification()->id);
|
|
|
|
if (k != j->second.cend()) {
|
2017-03-04 19:36:59 +00:00
|
|
|
nextNotify = history->currentNotification();
|
2020-06-19 12:59:31 +00:00
|
|
|
_waiters.emplace(notifyHistory, Waiter{
|
|
|
|
.msg = k->first,
|
|
|
|
.when = k->second
|
|
|
|
});
|
2017-03-04 19:36:59 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
history->skipNotification();
|
|
|
|
} while (history->hasNotification());
|
|
|
|
if (nextNotify) {
|
2019-03-03 09:32:01 +00:00
|
|
|
if (groupedItem) {
|
|
|
|
const auto canNextBeGrouped = (isForwarded && nextNotify->Has<HistoryMessageForwarded>())
|
|
|
|
|| (isAlbum && nextNotify->groupId());
|
|
|
|
const auto nextItem = canNextBeGrouped ? nextNotify : nullptr;
|
|
|
|
if (nextItem
|
|
|
|
&& qAbs(int64(nextItem->date()) - int64(groupedItem->date())) < 2) {
|
|
|
|
if (isForwarded
|
|
|
|
&& groupedItem->author() == nextItem->author()) {
|
|
|
|
++forwardedCount;
|
|
|
|
groupedItem = nextItem;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (isAlbum
|
|
|
|
&& groupedItem->groupId() == nextItem->groupId()) {
|
|
|
|
groupedItem = nextItem;
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-03 09:32:01 +00:00
|
|
|
nextNotify = nullptr;
|
2017-03-04 19:36:59 +00:00
|
|
|
}
|
|
|
|
} while (nextNotify);
|
|
|
|
}
|
|
|
|
|
2019-03-10 07:42:25 +00:00
|
|
|
if (!_lastHistoryItemId && groupedItem) {
|
|
|
|
_lastHistoryItemId = groupedItem->fullId();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the current notification is grouped.
|
|
|
|
if (isAlbum || isForwarded) {
|
|
|
|
// If the previous notification is grouped
|
|
|
|
// then reset the timer.
|
|
|
|
if (_waitForAllGroupedTimer.isActive()) {
|
|
|
|
_waitForAllGroupedTimer.cancel();
|
|
|
|
// If this is not the same group
|
|
|
|
// then show the previous group immediately.
|
|
|
|
if (!isSameGroup(groupedItem)) {
|
|
|
|
showGrouped();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We have to wait until all the messages in this group are loaded.
|
|
|
|
_lastForwardedCount += forwardedCount;
|
|
|
|
_lastHistoryItemId = groupedItem->fullId();
|
|
|
|
_waitForAllGroupedTimer.callOnce(kWaitingForAllGroupedDelay);
|
|
|
|
} else {
|
|
|
|
// If the current notification is not grouped
|
|
|
|
// then there is no reason to wait for the timer
|
|
|
|
// to show the previous notification.
|
|
|
|
showGrouped();
|
|
|
|
_manager->showNotification(notifyItem, forwardedCount);
|
|
|
|
}
|
2017-03-04 19:36:59 +00:00
|
|
|
|
|
|
|
if (!history->hasNotification()) {
|
|
|
|
_waiters.remove(history);
|
|
|
|
_whenMaps.remove(history);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nextAlert) {
|
2019-01-18 11:26:43 +00:00
|
|
|
_waitTimer.callOnce(nextAlert - ms);
|
2016-10-02 09:30:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-03 13:01:15 +00:00
|
|
|
void System::ensureSoundCreated() {
|
|
|
|
if (_soundTrack) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_soundTrack = Media::Audio::Current().createTrack();
|
2018-01-04 10:22:53 +00:00
|
|
|
_soundTrack->fillFromFile(
|
2020-06-18 18:04:16 +00:00
|
|
|
Core::App().settings().getSoundPath(qsl("msg_incoming")));
|
2017-05-03 13:01:15 +00:00
|
|
|
}
|
|
|
|
|
2017-03-04 19:36:59 +00:00
|
|
|
void System::updateAll() {
|
|
|
|
_manager->updateAll();
|
2016-10-02 09:30:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
Manager::DisplayOptions Manager::GetNotificationOptions(HistoryItem *item) {
|
|
|
|
const auto hideEverything = Core::App().passcodeLocked()
|
2018-06-03 13:30:40 +00:00
|
|
|
|| Global::ScreenIsLocked();
|
2016-10-08 08:38:53 +00:00
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
const auto view = Core::App().settings().notifyView();
|
2016-10-08 08:38:53 +00:00
|
|
|
DisplayOptions result;
|
2020-06-18 18:04:16 +00:00
|
|
|
result.hideNameAndPhoto = hideEverything || (view > dbinvShowName);
|
|
|
|
result.hideMessageText = hideEverything || (view > dbinvShowPreview);
|
2019-07-18 08:37:10 +00:00
|
|
|
result.hideReplyButton = result.hideMessageText
|
|
|
|
|| !item
|
2019-08-28 18:20:49 +00:00
|
|
|
|| ((item->out() || item->history()->peer->isSelf())
|
|
|
|
&& item->isFromScheduled())
|
2019-07-18 08:37:10 +00:00
|
|
|
|| !item->history()->peer->canWrite()
|
|
|
|
|| (item->history()->peer->slowmodeSecondsLeft() > 0);
|
2016-10-08 08:38:53 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-06-19 13:34:43 +00:00
|
|
|
QString Manager::addTargetAccountName(
|
|
|
|
const QString &title,
|
|
|
|
not_null<Main::Session*> session) {
|
|
|
|
return (Core::App().domain().accounts().size() > 1)
|
2020-06-19 17:26:29 +00:00
|
|
|
? (title
|
|
|
|
+ accountNameSeparator()
|
|
|
|
+ (session->user()->username.isEmpty()
|
|
|
|
? session->user()->name
|
|
|
|
: session->user()->username))
|
2020-06-19 13:34:43 +00:00
|
|
|
: title;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Manager::accountNameSeparator() {
|
|
|
|
return QString::fromUtf8(" \xE2\x9E\x9C ");
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
void Manager::notificationActivated(NotificationId id) {
|
|
|
|
onBeforeNotificationActivated(id);
|
|
|
|
if (const auto session = system()->findSession(id.selfId)) {
|
|
|
|
if (session->windows().empty()) {
|
|
|
|
Core::App().domain().activate(&session->account());
|
|
|
|
}
|
|
|
|
if (!session->windows().empty()) {
|
|
|
|
const auto window = session->windows().front();
|
|
|
|
const auto history = session->data().history(id.peerId);
|
|
|
|
window->widget()->showFromTray();
|
|
|
|
window->widget()->reActivateWindow();
|
|
|
|
if (Core::App().passcodeLocked()) {
|
|
|
|
window->widget()->setInnerFocus();
|
|
|
|
system()->clearAll();
|
|
|
|
} else {
|
|
|
|
openNotificationMessage(history, id.msgId);
|
|
|
|
}
|
2016-10-02 16:32:46 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-19 12:59:31 +00:00
|
|
|
onAfterNotificationActivated(id);
|
2016-10-02 16:32:46 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 11:10:40 +00:00
|
|
|
void Manager::openNotificationMessage(
|
|
|
|
not_null<History*> history,
|
|
|
|
MsgId messageId) {
|
|
|
|
const auto openExactlyMessage = [&] {
|
|
|
|
if (history->peer->isUser()
|
|
|
|
|| history->peer->isChannel()
|
|
|
|
|| !IsServerMsgId(messageId)) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto item = history->owner().message(history->channelId(), messageId);
|
2018-02-12 11:10:40 +00:00
|
|
|
if (!item || !item->mentionsMe()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}();
|
2019-04-15 11:54:03 +00:00
|
|
|
//const auto messageFeed = [&] { // #feed
|
|
|
|
// if (const auto channel = history->peer->asChannel()) {
|
|
|
|
// return channel->feed();
|
|
|
|
// }
|
|
|
|
// return (Data::Feed*)nullptr;
|
|
|
|
//}();
|
2018-02-12 11:10:40 +00:00
|
|
|
if (openExactlyMessage) {
|
|
|
|
Ui::showPeerHistory(history, messageId);
|
2019-04-15 11:54:03 +00:00
|
|
|
//} else if (messageFeed) { // #feed
|
2019-06-06 10:21:40 +00:00
|
|
|
// App::wnd()->sessionController()->showSection(
|
2019-04-15 11:54:03 +00:00
|
|
|
// HistoryFeed::Memento(messageFeed));
|
2018-02-12 11:10:40 +00:00
|
|
|
} else {
|
|
|
|
Ui::showPeerHistory(history, ShowAtUnreadMsgId);
|
|
|
|
}
|
|
|
|
system()->clearFromHistory(history);
|
|
|
|
}
|
|
|
|
|
2017-12-07 13:02:24 +00:00
|
|
|
void Manager::notificationReplied(
|
2020-06-19 12:59:31 +00:00
|
|
|
NotificationId id,
|
2018-05-24 13:03:21 +00:00
|
|
|
const TextWithTags &reply) {
|
2020-06-19 12:59:31 +00:00
|
|
|
if (!id.selfId || !id.peerId) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-02 17:06:34 +00:00
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto session = system()->findSession(id.selfId);
|
|
|
|
if (!session) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto history = session->data().history(id.peerId);
|
2016-10-02 17:06:34 +00:00
|
|
|
|
2019-08-12 12:11:34 +00:00
|
|
|
auto message = Api::MessageToSend(history);
|
2018-05-24 13:03:21 +00:00
|
|
|
message.textWithTags = reply;
|
2020-06-19 12:59:31 +00:00
|
|
|
message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
|
|
|
|
? id.msgId
|
|
|
|
: 0;
|
2019-08-12 12:11:34 +00:00
|
|
|
message.action.clearDraft = false;
|
2019-07-18 08:37:10 +00:00
|
|
|
history->session().api().sendMessage(std::move(message));
|
2019-07-04 11:56:28 +00:00
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto item = history->owner().message(
|
|
|
|
history->channelId(),
|
|
|
|
id.msgId);
|
2019-07-04 11:56:28 +00:00
|
|
|
if (item && item->isUnreadMention() && !item->isUnreadMedia()) {
|
2019-07-18 08:37:10 +00:00
|
|
|
history->session().api().markMediaRead(item);
|
2019-07-04 11:56:28 +00:00
|
|
|
}
|
2016-10-02 17:06:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 14:24:12 +00:00
|
|
|
void NativeManager::doShowNotification(
|
|
|
|
not_null<HistoryItem*> item,
|
|
|
|
int forwardedCount) {
|
2020-06-19 12:59:31 +00:00
|
|
|
const auto options = GetNotificationOptions(item);
|
2018-01-14 16:02:25 +00:00
|
|
|
|
2019-08-28 18:20:49 +00:00
|
|
|
const auto peer = item->history()->peer;
|
|
|
|
const auto scheduled = !options.hideNameAndPhoto
|
|
|
|
&& (item->out() || peer->isSelf())
|
|
|
|
&& item->isFromScheduled();
|
|
|
|
const auto title = options.hideNameAndPhoto
|
|
|
|
? qsl("Telegram Desktop")
|
|
|
|
: (scheduled && peer->isSelf())
|
|
|
|
? tr::lng_notification_reminder(tr::now)
|
2019-09-13 06:06:02 +00:00
|
|
|
: peer->name;
|
2020-06-19 13:34:43 +00:00
|
|
|
const auto fullTitle = addTargetAccountName(title, &peer->session());
|
2019-08-28 18:20:49 +00:00
|
|
|
const auto subtitle = options.hideNameAndPhoto
|
|
|
|
? QString()
|
|
|
|
: item->notificationHeader();
|
2018-01-14 16:02:25 +00:00
|
|
|
const auto text = options.hideMessageText
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_notification_preview(tr::now)
|
2018-01-14 16:02:25 +00:00
|
|
|
: (forwardedCount < 2
|
2019-08-28 18:20:49 +00:00
|
|
|
? (item->groupId()
|
|
|
|
? tr::lng_in_dlg_album(tr::now)
|
|
|
|
: item->notificationText())
|
2019-06-19 16:39:25 +00:00
|
|
|
: tr::lng_forward_messages(tr::now, lt_count, forwardedCount));
|
2018-01-14 16:02:25 +00:00
|
|
|
|
2020-05-29 16:55:01 +00:00
|
|
|
// #TODO optimize
|
|
|
|
auto userpicView = item->history()->peer->createUserpicView();
|
2018-01-14 16:02:25 +00:00
|
|
|
doShowNativeNotification(
|
|
|
|
item->history()->peer,
|
2020-05-29 16:55:01 +00:00
|
|
|
userpicView,
|
2018-01-14 16:02:25 +00:00
|
|
|
item->id,
|
2020-06-19 13:34:43 +00:00
|
|
|
scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
|
2019-08-28 18:20:49 +00:00
|
|
|
subtitle,
|
2018-01-14 16:02:25 +00:00
|
|
|
text,
|
|
|
|
options.hideNameAndPhoto,
|
|
|
|
options.hideReplyButton);
|
2016-10-02 09:30:28 +00:00
|
|
|
}
|
|
|
|
|
2017-05-03 13:01:15 +00:00
|
|
|
System::~System() = default;
|
|
|
|
|
2019-08-28 14:24:12 +00:00
|
|
|
QString WrapFromScheduled(const QString &text) {
|
|
|
|
return QString::fromUtf8("\xF0\x9F\x93\x85 ") + text;
|
|
|
|
}
|
|
|
|
|
2016-10-02 09:30:28 +00:00
|
|
|
} // namespace Notifications
|
|
|
|
} // namespace Window
|