One Window::Notifications system for all sessions.

This commit is contained in:
John Preston 2020-06-19 16:59:31 +04:00
parent 83538675ce
commit 997913be25
28 changed files with 578 additions and 297 deletions

View File

@ -2601,7 +2601,7 @@ void ApiWrap::applyNotifySettings(
}
} break;
}
_session->notifications().checkDelayed();
Core::App().notifications().checkDelayed();
}
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {

View File

@ -99,6 +99,7 @@ Application::Application(not_null<Launcher*> launcher)
, _fallbackProductionConfig(
std::make_unique<MTP::Config>(MTP::Environment::Production))
, _domain(std::make_unique<Main::Domain>(cDataFile()))
, _notifications(std::make_unique<Window::Notifications::System>())
, _langpack(std::make_unique<Lang::Instance>())
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
@ -116,6 +117,11 @@ Application::Application(not_null<Launcher*> launcher)
_shouldLockAt = 0;
}, _lifetime);
passcodeLockChanges(
) | rpl::start_with_next([=] {
_notifications->updateAll();
}, _lifetime);
_domain->activeSessionChanges(
) | rpl::start_with_next([=](Main::Session *session) {
if (session && !UpdaterDisabled()) { // #TODO multi someSessionValue

View File

@ -27,6 +27,12 @@ struct TermsLock;
class Controller;
} // namespace Window
namespace Window {
namespace Notifications {
class System;
} // namespace Notifications
} // namespace Window
namespace ChatHelpers {
class EmojiKeywords;
} // namespace ChatHelpers
@ -101,6 +107,9 @@ public:
[[nodiscard]] Ui::Animations::Manager &animationManager() const {
return *_animationsManager;
}
[[nodiscard]] Window::Notifications::System &notifications() const {
return *_notifications;
}
// Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const;
@ -294,6 +303,10 @@ private:
QPointer<Ui::BoxContent> _badProxyDisableBox;
const std::unique_ptr<Media::Audio::Instance> _audio;
// Notifications should be destroyed before _audio.
const std::unique_ptr<Window::Notifications::System> _notifications;
const QImage _logo;
const QImage _logoNoMargin;

View File

@ -165,7 +165,7 @@ void Histories::readInboxTill(
}
});
history->session().notifications().clearIncomingFromHistory(history);
Core::App().notifications().clearIncomingFromHistory(history);
const auto needsRequest = history->readInboxTillNeedsRequest(tillId);
if (!needsRequest && !force) {

View File

@ -1110,7 +1110,7 @@ void Session::setupUserIsContactViewer() {
Session::~Session() {
// Optimization: clear notifications before destroying items.
_session->notifications().clearAllFast();
Core::App().notifications().clearFromSession(_session);
clearLocalStorage();
}
@ -1980,7 +1980,7 @@ void Session::updateNotifySettingsLocal(not_null<PeerData*> peer) {
_mutedPeers.emplace(peer);
unmuteByFinishedDelayed(changesIn);
if (history) {
_session->notifications().clearIncomingFromHistory(history);
Core::App().notifications().clearIncomingFromHistory(history);
}
} else {
_mutedPeers.erase(peer);
@ -3488,7 +3488,7 @@ void Session::removeChatListEntry(Dialogs::Key key) {
}
}
if (const auto history = key.history()) {
session().notifications().clearFromHistory(history);
Core::App().notifications().clearFromHistory(history);
}
}

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/text/text_utilities.h"
@ -133,7 +134,7 @@ InnerWidget::InnerWidget(
subscribe(session().downloaderTaskFinished(), [=] { update(); });
subscribe(session().notifications().settingsChanged(), [=](
subscribe(Core::App().notifications().settingsChanged(), [=](
Window::Notifications::ChangeType change) {
if (change == Window::Notifications::ChangeType::CountMessages) {
// Folder rows change their unread badge with this setting.

View File

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/text_options.h"
#include "core/crash_reports.h"
#include "core/application.h"
#include "base/unixtime.h"
#include "styles/style_dialogs.h"
@ -671,7 +672,7 @@ void History::destroyMessage(not_null<HistoryItem*> item) {
}
owner().unregisterMessage(item);
session().notifications().clearFromItem(item);
Core::App().notifications().clearFromItem(item);
auto hack = std::unique_ptr<HistoryItem>(item.get());
const auto i = _messages.find(hack);
@ -1290,7 +1291,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
owner().notifyUnreadItemAdded(item);
const auto stillShow = item->showNotification();
if (stillShow) {
session().notifications().schedule(item);
Core::App().notifications().schedule(item);
if (!item->out() && item->unread()) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
@ -1741,7 +1742,7 @@ void History::inboxRead(MsgId upTo, std::optional<int> stillUnread) {
}
_firstUnreadView = nullptr;
session().notifications().clearIncomingFromHistory(this);
Core::App().notifications().clearIncomingFromHistory(this);
}
void History::inboxRead(not_null<const HistoryItem*> wasRead) {

View File

@ -2057,7 +2057,7 @@ void HistoryInner::checkHistoryActivation() {
adjustCurrent(_visibleAreaBottom);
if (_history->loadedAtBottom() && _visibleAreaBottom >= height()) {
// Clear possible scheduled messages notifications.
session().notifications().clearFromHistory(_history);
Core::App().notifications().clearFromHistory(_history);
}
if (_curHistory != _history || _history->isEmpty()) {
return;

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "core/application.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
#include "storage/storage_shared_media.h"
@ -355,7 +356,7 @@ bool HistoryService::updateDependent(bool force) {
updateDependentText();
}
if (force && gotDependencyItem) {
history()->session().notifications().checkDelayed();
Core::App().notifications().checkDelayed();
}
return (dependent->msg || !dependent->msgId);
}

View File

@ -2423,7 +2423,7 @@ void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> item) {
session().data().histories().readInboxOnNewMessage(item);
// Also clear possible scheduled messages notifications.
session().notifications().clearFromHistory(_history);
Core::App().notifications().clearFromHistory(_history);
}
void HistoryWidget::unreadCountUpdated() {

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_dc_options.h"
#include "storage/storage_domain.h"
#include "storage/localstorage.h"
#include "window/notifications_manager.h"
#include "facades.h"
namespace Main {
@ -47,22 +48,25 @@ Storage::StartResult Domain::start(const QByteArray &passcode) {
}
void Domain::finish() {
_activeIndex = -1;
_accountToActivate = -1;
_active = nullptr;
base::take(_accounts);
}
void Domain::accountAddedInStorage(
int index,
std::unique_ptr<Account> account) {
Expects(account != nullptr);
Expects(!_accounts.contains(index));
void Domain::accountAddedInStorage(AccountWithIndex accountWithIndex) {
Expects(accountWithIndex.account != nullptr);
if (_accounts.empty()) {
_activeIndex = index;
for (const auto &[index, _] : _accounts) {
if (index == accountWithIndex.index) {
Unexpected("Repeated account index.");
}
}
_accounts.emplace(index, std::move(account));
};
_accounts.push_back(std::move(accountWithIndex));
}
void Domain::activateFromStorage(int index) {
_accountToActivate = index;
}
void Domain::resetWithForgottenPasscode() {
if (_accounts.empty()) {
@ -78,15 +82,19 @@ void Domain::resetWithForgottenPasscode() {
void Domain::activateAfterStarting() {
Expects(started());
auto toActivate = _accounts.front().account.get();
for (const auto &[index, account] : _accounts) {
if (index == _accountToActivate) {
toActivate = account.get();
}
watchSession(account.get());
}
activate(_activeIndex);
activate(toActivate);
removePasscodeIfEmpty();
}
const base::flat_map<int, std::unique_ptr<Account>> &Domain::accounts() const {
const std::vector<Domain::AccountWithIndex> &Domain::accounts() const {
return _accounts;
}
@ -94,12 +102,6 @@ rpl::producer<Account*> Domain::activeValue() const {
return _active.value();
}
int Domain::activeIndex() const {
Expects(_accounts.contains(_activeIndex));
return _activeIndex;
}
Account &Domain::active() const {
Expects(!_accounts.empty());
@ -170,8 +172,8 @@ void Domain::scheduleUpdateUnreadBadge() {
}));
}
int Domain::add(MTP::Environment environment) {
Expects(_active.current() != nullptr);
not_null<Main::Account*> Domain::add(MTP::Environment environment) {
Expects(started());
static const auto cloneConfig = [](const MTP::Config &config) {
return std::make_unique<MTP::Config>(config);
@ -193,16 +195,17 @@ int Domain::add(MTP::Environment environment) {
: std::make_unique<MTP::Config>(environment);
}();
auto index = 0;
while (_accounts.contains(index)) {
while (ranges::contains(_accounts, index, &AccountWithIndex::index)) {
++index;
}
const auto account = _accounts.emplace(
index,
std::make_unique<Account>(this, _dataName, index)
).first->second.get();
_accounts.push_back(AccountWithIndex{
.index = index,
.account = std::make_unique<Account>(this, _dataName, index)
});
const auto account = _accounts.back().account.get();
_local->startAdded(account, std::move(config));
watchSession(account);
return index;
return account;
}
void Domain::watchSession(not_null<Account*> account) {
@ -237,8 +240,8 @@ void Domain::activateAuthedAccount() {
return;
}
for (auto i = _accounts.begin(); i != _accounts.end(); ++i) {
if (i->second->sessionExists()) {
activate(i->first);
if (i->account->sessionExists()) {
activate(i->account.get());
return;
}
}
@ -264,12 +267,12 @@ void Domain::removeRedundantAccounts() {
const auto was = _accounts.size();
activateAuthedAccount();
for (auto i = _accounts.begin(); i != _accounts.end();) {
if (i->second.get() == _active.current()
|| i->second->sessionExists()) {
if (i->account.get() == _active.current()
|| i->account->sessionExists()) {
++i;
continue;
}
checkForLastProductionConfig(i->second.get());
checkForLastProductionConfig(i->account.get());
i = _accounts.erase(i);
}
@ -293,13 +296,20 @@ void Domain::checkForLastProductionConfig(
Core::App().refreshFallbackProductionConfig(mtp->config());
}
void Domain::activate(int index) {
Expects(_accounts.contains(index));
void Domain::activate(not_null<Main::Account*> account) {
if (_active.current() == account.get()) {
return;
}
const auto i = ranges::find(_accounts, account.get(), [](
const AccountWithIndex &value) {
return value.account.get();
});
Assert(i != end(_accounts));
const auto changed = (_accountToActivate != i->index);
const auto changed = (_activeIndex != index);
_activeLifetime.destroy();
_activeIndex = index;
_active = _accounts.find(index)->second.get();
_accountToActivate = i->index;
_active = account.get();
_active.current()->sessionValue(
) | rpl::start_to_stream(_activeSessions, _activeLifetime);

View File

@ -25,6 +25,11 @@ class Session;
class Domain final {
public:
struct AccountWithIndex {
int index = 0;
std::unique_ptr<Account> account;
};
static constexpr auto kMaxAccounts = 3;
explicit Domain(const QString &dataName);
@ -40,11 +45,10 @@ public:
}
[[nodiscard]] auto accounts() const
-> const base::flat_map<int, std::unique_ptr<Account>> &;
-> const std::vector<AccountWithIndex> &;
[[nodiscard]] rpl::producer<Account*> activeValue() const;
// Expects(started());
[[nodiscard]] int activeIndex() const;
[[nodiscard]] Account &active() const;
[[nodiscard]] rpl::producer<not_null<Account*>> activeChanges() const;
@ -56,11 +60,12 @@ public:
[[nodiscard]] rpl::producer<> unreadBadgeChanges() const;
void notifyUnreadBadgeChanged();
[[nodiscard]] int add(MTP::Environment environment);
void activate(int index);
[[nodiscard]] not_null<Main::Account*> add(MTP::Environment environment);
void activate(not_null<Main::Account*> account);
// Interface for Storage::Domain.
void accountAddedInStorage(int index, std::unique_ptr<Account> account);
void accountAddedInStorage(AccountWithIndex accountWithIndex);
void activateFromStorage(int index);
private:
void activateAfterStarting();
@ -76,9 +81,9 @@ private:
const QString _dataName;
const std::unique_ptr<Storage::Domain> _local;
base::flat_map<int, std::unique_ptr<Account>> _accounts;
std::vector<AccountWithIndex> _accounts;
rpl::variable<Account*> _active = nullptr;
int _activeIndex = 0;
int _accountToActivate = -1;
bool _writeAccountsScheduled = false;
rpl::event_stream<Session*> _activeSessions;

View File

@ -25,7 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
//#include "platform/platform_specific.h"
@ -75,7 +74,6 @@ Session::Session(
, _downloader(std::make_unique<Storage::DownloadManagerMtproto>(_api.get()))
, _uploader(std::make_unique<Storage::Uploader>(_api.get()))
, _storage(std::make_unique<Storage::Facade>())
, _notifications(std::make_unique<Window::Notifications::System>(this))
, _changes(std::make_unique<Data::Changes>(this))
, _data(std::make_unique<Data::Session>(this))
, _user(_data->processUser(user))
@ -85,11 +83,6 @@ Session::Session(
, _saveSettingsTimer([=] { saveSettings(); }) {
Expects(_settings != nullptr);
Core::App().lockChanges(
) | rpl::start_with_next([=] {
notifications().updateAll();
}, _lifetime);
subscribe(Global::RefConnectionTypeChanged(), [=] {
_api->refreshTopPromotion();
});

View File

@ -42,9 +42,6 @@ class Domain;
} // namespace Storage
namespace Window {
namespace Notifications {
class System;
} // namespace Notifications
class SessionController;
} // namespace Window
@ -106,9 +103,6 @@ public:
[[nodiscard]] Stickers::DicePacks &diceStickersPacks() const {
return *_diceStickersPacks;
}
[[nodiscard]] Window::Notifications::System &notifications() const {
return *_notifications;
}
[[nodiscard]] Data::Changes &changes() const {
return *_changes;
}
@ -168,9 +162,8 @@ private:
const std::unique_ptr<Storage::DownloadManagerMtproto> _downloader;
const std::unique_ptr<Storage::Uploader> _uploader;
const std::unique_ptr<Storage::Facade> _storage;
const std::unique_ptr<Window::Notifications::System> _notifications;
// _data depends on _downloader / _uploader / _notifications.
// _data depends on _downloader / _uploader.
const std::unique_ptr<Data::Changes> _changes;
const std::unique_ptr<Data::Session> _data;
const not_null<UserData*> _user;

View File

@ -814,7 +814,7 @@ void MainWindow::toggleDisplayNotifyFromTray() {
}
account().session().saveSettings();
using Change = Window::Notifications::ChangeType;
auto &changes = account().session().notifications().settingsChanged();
auto &changes = Core::App().notifications().settingsChanged();
changes.notify(Change::DesktopEnabled);
if (soundNotifyChanged) {
changes.notify(Change::SoundEnabled);

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/core_settings.h"
#include "history/history.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
#include <QtCore/QVersionNumber>
@ -201,7 +202,7 @@ QString GetImageKey(const QVersionNumber &specificationVersion) {
return QString();
}
}
} // namespace
NotificationData::NotificationData(
const base::weak_ptr<Manager> &manager,
@ -210,6 +211,7 @@ NotificationData::NotificationData(
const QString &msg,
PeerId peerId,
MsgId msgId,
UserId selfId,
bool hideReplyButton)
: _dbusConnection(QDBusConnection::sessionBus())
, _manager(manager)
@ -217,7 +219,8 @@ NotificationData::NotificationData(
, _imageKey(GetImageKey(ParseSpecificationVersion(
GetServerInformation())))
, _peerId(peerId)
, _msgId(msgId) {
, _msgId(msgId)
, _selfId(selfId) {
const auto capabilities = GetCapabilities();
if (capabilities.contains(qsl("body-markup"))) {
@ -374,11 +377,16 @@ void NotificationData::setImage(const QString &imagePath) {
_hints[_imageKey] = QVariant::fromValue(imageData);
}
NotificationData::NotificationId NotificationData::myId() const {
return { .peerId = _peerId, .msgId = _msgId, .selfId = _selfId };
}
void NotificationData::notificationClosed(uint id) {
if (id == _notificationId) {
const auto manager = _manager;
const auto my = myId();
crl::on_main(manager, [=] {
manager->clearNotification(_peerId, _msgId);
manager->clearNotification(my);
});
}
}
@ -391,13 +399,15 @@ void NotificationData::actionInvoked(uint id, const QString &actionName) {
if (actionName == qsl("default")
|| actionName == qsl("mail-reply-sender")) {
const auto manager = _manager;
const auto my = myId();
crl::on_main(manager, [=] {
manager->notificationActivated(_peerId, _msgId);
manager->notificationActivated(my);
});
} else if (actionName == qsl("mail-mark-read")) {
const auto manager = _manager;
const auto my = myId();
crl::on_main(manager, [=] {
manager->notificationReplied(_peerId, _msgId, {});
manager->notificationReplied(my, {});
});
}
}
@ -405,8 +415,9 @@ void NotificationData::actionInvoked(uint id, const QString &actionName) {
void NotificationData::notificationReplied(uint id, const QString &text) {
if (id == _notificationId) {
const auto manager = _manager;
const auto my = myId();
crl::on_main(manager, [=] {
manager->notificationReplied(_peerId, _msgId, { text, {} });
manager->notificationReplied(my, { text, {} });
});
}
}
@ -530,6 +541,9 @@ void Manager::Private::showNotification(
bool hideReplyButton) {
if (!Supported()) return;
const auto peerId = peer->id;
const auto selfId = peer->session().userId();
const auto key = FullPeer{ peerId, selfId };
auto notification = std::make_shared<NotificationData>(
_manager,
title,
@ -537,32 +551,38 @@ void Manager::Private::showNotification(
msg,
peer->id,
msgId,
peer->session().userId(),
hideReplyButton);
if (!hideNameAndPhoto) {
const auto key = peer->userpicUniqueKey(userpicView);
notification->setImage(_cachedUserpics.get(key, peer, userpicView));
const auto userpicKey = peer->userpicUniqueKey(userpicView);
notification->setImage(
_cachedUserpics.get(userpicKey, peer, userpicView));
}
auto i = _notifications.find(peer->id);
auto i = _notifications.find(key);
if (i != _notifications.cend()) {
auto j = i->find(msgId);
if (j != i->cend()) {
auto oldNotification = j.value();
i->erase(j);
auto j = i->second.find(msgId);
if (j != i->second.end()) {
auto oldNotification = j->second;
i->second.erase(j);
oldNotification->close();
i = _notifications.find(peer->id);
i = _notifications.find(key);
}
}
if (i == _notifications.cend()) {
i = _notifications.insert(peer->id, QMap<MsgId, Notification>());
i = _notifications.emplace(
key,
base::flat_map<MsgId, Notification>()).first;
}
_notifications[peer->id].insert(msgId, notification);
i->second.emplace(msgId, notification);
if (!notification->show()) {
i = _notifications.find(peer->id);
i = _notifications.find(key);
if (i != _notifications.cend()) {
i->remove(msgId);
if (i->isEmpty()) _notifications.erase(i);
i->second.remove(msgId);
if (i->empty()) {
_notifications.erase(i);
}
}
}
}
@ -570,9 +590,8 @@ void Manager::Private::showNotification(
void Manager::Private::clearAll() {
if (!Supported()) return;
auto temp = base::take(_notifications);
for_const (auto &notifications, temp) {
for_const (auto notification, notifications) {
for (const auto &[key, notifications] : base::take(_notifications)) {
for (const auto &[msgId, notification] : notifications) {
notification->close();
}
}
@ -581,24 +600,43 @@ void Manager::Private::clearAll() {
void Manager::Private::clearFromHistory(not_null<History*> history) {
if (!Supported()) return;
auto i = _notifications.find(history->peer->id);
const auto key = FullPeer{
history->peer->id,
history->session().userId()
};
auto i = _notifications.find(key);
if (i != _notifications.cend()) {
auto temp = base::take(i.value());
const auto temp = base::take(i->second);
_notifications.erase(i);
for_const (auto notification, temp) {
for (const auto &[msgId, notification] : temp) {
notification->close();
}
}
}
void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) {
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
if (!Supported()) return;
auto i = _notifications.find(peerId);
const auto selfId = session->userId();
for (auto i = _notifications.begin(); i != _notifications.end();) {
if (i->first.second == selfId) {
const auto temp = base::take(i->second);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
notification->close();
}
}
}
}
void Manager::Private::clearNotification(NotificationId id) {
if (!Supported()) return;
auto i = _notifications.find(FullPeer{ id.peerId, id.selfId });
if (i != _notifications.cend()) {
i.value().remove(msgId);
if (i.value().isEmpty()) {
if (i->second.remove(id.msgId) && i->second.empty()) {
_notifications.erase(i);
}
}
@ -613,8 +651,8 @@ Manager::Manager(not_null<Window::Notifications::System*> system)
, _private(std::make_unique<Private>(this, Private::Type::Rounded)) {
}
void Manager::clearNotification(PeerId peerId, MsgId msgId) {
_private->clearNotification(peerId, msgId);
void Manager::clearNotification(NotificationId id) {
_private->clearNotification(id);
}
Manager::~Manager() = default;
@ -646,6 +684,10 @@ void Manager::doClearAllFast() {
void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}
void Manager::doClearFromSession(not_null<Main::Session*> session) {
_private->clearFromSession(session);
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
} // namespace Notifications

View File

@ -24,6 +24,8 @@ class NotificationData : public QObject {
Q_OBJECT
public:
using NotificationId = Window::Notifications::Manager::NotificationId;
NotificationData(
const base::weak_ptr<Manager> &manager,
const QString &title,
@ -31,6 +33,7 @@ public:
const QString &msg,
PeerId peerId,
MsgId msgId,
UserId selfId,
bool hideReplyButton);
NotificationData(const NotificationData &other) = delete;
@ -50,6 +53,8 @@ public:
};
private:
[[nodiscard]] NotificationId myId() const;
QDBusConnection _dbusConnection;
base::weak_ptr<Manager> _manager;
@ -59,9 +64,10 @@ private:
QVariantMap _hints;
QString _imageKey;
uint _notificationId;
PeerId _peerId;
MsgId _msgId;
uint _notificationId = 0;
PeerId _peerId = 0;
MsgId _msgId = 0;
UserId _selfId = 0;
private slots:
void notificationClosed(uint id);
@ -84,7 +90,7 @@ class Manager
, public base::has_weak_ptr {
public:
Manager(not_null<Window::Notifications::System*> system);
void clearNotification(PeerId peerId, MsgId msgId);
void clearNotification(NotificationId id);
~Manager();
protected:
@ -99,6 +105,7 @@ protected:
bool hideReplyButton) override;
void doClearAllFast() override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
private:
class Private;
@ -122,13 +129,15 @@ public:
bool hideReplyButton);
void clearAll();
void clearFromHistory(not_null<History*> history);
void clearNotification(PeerId peerId, MsgId msgId);
void clearFromSession(not_null<Main::Session*> session);
void clearNotification(NotificationId id);
~Private();
private:
using Notifications = QMap<PeerId, QMap<MsgId, Notification>>;
Notifications _notifications;
base::flat_map<
FullPeer,
base::flat_map<MsgId, Notification>> _notifications;
Window::Notifications::CachedUserpics _cachedUserpics;
base::weak_ptr<Manager> _manager;

View File

@ -30,6 +30,7 @@ protected:
bool hideReplyButton) override;
void doClearAllFast() override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
private:
class Private;

View File

@ -173,6 +173,7 @@ public:
bool hideReplyButton);
void clearAll();
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void updateDelegate();
~Private();
@ -328,6 +329,10 @@ void Manager::Private::clearFromHistory(not_null<History*> history) {
putClearTask(ClearFromHistory { history->peer->id });
}
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
putClearTask(); // #TODO multi
}
void Manager::Private::updateDelegate() {
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
[center setDelegate:_delegate];
@ -377,5 +382,9 @@ void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}
void Manager::doClearFromSession(not_null<Main::Session*> session) {
_private->clearFromSession(session);
}
} // namespace Notifications
} // namespace Platform

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include <Shobjidl.h>
@ -208,13 +209,17 @@ class ToastEventHandler final : public Implements<
DesktopToastDismissedEventHandler,
DesktopToastFailedEventHandler> {
public:
using NotificationId = Manager::NotificationId;
// We keep a weak pointer to a member field of native notifications manager.
ToastEventHandler(
const std::shared_ptr<Manager*> &guarded,
const PeerId &peer,
MsgId msg)
PeerId peer,
MsgId msg,
UserId selfId)
: _peerId(peer)
, _msgId(msg)
, _selfId(selfId)
, _weak(guarded) {
}
@ -227,8 +232,9 @@ public:
// DesktopToastActivatedEventHandler
IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) {
performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) {
manager->notificationActivated(peerId, msgId);
const auto my = myId();
performOnMainQueue([my](Manager *manager) {
manager->notificationActivated(my);
});
return S_OK;
}
@ -243,8 +249,9 @@ public:
case ToastDismissalReason_UserCanceled:
case ToastDismissalReason_TimedOut:
default:
performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) {
manager->clearNotification(peerId, msgId);
const auto my = myId();
performOnMainQueue([my](Manager *manager) {
manager->clearNotification(my);
});
break;
}
@ -254,8 +261,9 @@ public:
// DesktopToastFailedEventHandler
IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) {
performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) {
manager->clearNotification(peerId, msgId);
const auto my = myId();
performOnMainQueue([my](Manager *manager) {
manager->clearNotification(my);
});
return S_OK;
}
@ -293,9 +301,14 @@ public:
}
private:
[[nodiscard]] NotificationId myId() const {
return { .peerId = _peerId, .msgId = _msgId, .selfId = _selfId };
}
ULONG _refCount = 0;
PeerId _peerId = 0;
MsgId _msgId = 0;
UserId _selfId = 0;
std::weak_ptr<Manager*> _weak;
};
@ -353,9 +366,10 @@ public:
bool hideReplyButton);
void clearAll();
void clearFromHistory(not_null<History*> history);
void beforeNotificationActivated(PeerId peerId, MsgId msgId);
void afterNotificationActivated(PeerId peerId, MsgId msgId);
void clearNotification(PeerId peerId, MsgId msgId);
void clearFromSession(not_null<Main::Session*> session);
void beforeNotificationActivated(NotificationId id);
void afterNotificationActivated(NotificationId id);
void clearNotification(NotificationId id);
~Private();
@ -376,7 +390,7 @@ private:
ComPtr<IToastNotification> p;
};
QMap<PeerId, QMap<MsgId, NotificationPtr>> _notifications;
base::flat_map<FullPeer, base::flat_map<MsgId, NotificationPtr>> _notifications;
};
@ -414,8 +428,8 @@ void Manager::Private::clearAll() {
if (!_notifier) return;
auto temp = base::take(_notifications);
for_const (auto &notifications, temp) {
for_const (auto &notification, notifications) {
for (const auto &[key, notifications] : base::take(_notifications)) {
for (const auto &[msgId, notification] : notifications) {
_notifier->Hide(notification.p.Get());
}
}
@ -424,32 +438,51 @@ void Manager::Private::clearAll() {
void Manager::Private::clearFromHistory(not_null<History*> history) {
if (!_notifier) return;
auto i = _notifications.find(history->peer->id);
auto i = _notifications.find(FullPeer{
history->peer->id,
history->session().userId()
});
if (i != _notifications.cend()) {
auto temp = base::take(i.value());
auto temp = base::take(i->second);
_notifications.erase(i);
for_const (auto &notification, temp) {
for (const auto &[msgId, notification] : temp) {
_notifier->Hide(notification.p.Get());
}
}
}
void Manager::Private::beforeNotificationActivated(PeerId peerId, MsgId msgId) {
clearNotification(peerId, msgId);
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
if (!_notifier) return;
const auto selfId = session->userId();
for (auto i = _notifications.begin(); i != _notifications.end();) {
if (i->first.selfId == selfId) {
const auto temp = base::take(i->second);
_notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
_notifier->Hide(notification.p.Get());
}
}
}
}
void Manager::Private::afterNotificationActivated(PeerId peerId, MsgId msgId) {
void Manager::Private::beforeNotificationActivated(NotificationId id) {
clearNotification(id);
}
void Manager::Private::afterNotificationActivated(NotificationId id) {
if (auto window = App::wnd()) {
SetForegroundWindow(window->psHwnd());
}
}
void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) {
auto i = _notifications.find(peerId);
void Manager::Private::clearNotification(NotificationId id) {
auto i = _notifications.find(FullPeer{ id.peerId, id.selfId });
if (i != _notifications.cend()) {
i.value().remove(msgId);
if (i.value().isEmpty()) {
i->second.remove(id.msgId);
if (i->second.empty()) {
_notifications.erase(i);
}
}
@ -481,10 +514,10 @@ bool Manager::Private::showNotification(
hr = SetAudioSilent(toastXml.Get());
if (!SUCCEEDED(hr)) return false;
const auto key = hideNameAndPhoto
const auto userpicKey = hideNameAndPhoto
? InMemoryKey()
: peer->userpicUniqueKey(userpicView);
const auto userpicPath = _cachedUserpics.get(key, peer, userpicView);
const auto userpicPath = _cachedUserpics.get(userpicKey, peer, userpicView);
const auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString();
hr = SetImageSrc(userpicPathWide.c_str(), toastXml.Get());
@ -533,7 +566,11 @@ bool Manager::Private::showNotification(
if (!SUCCEEDED(hr)) return false;
EventRegistrationToken activatedToken, dismissedToken, failedToken;
ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler(_guarded, peer->id, msgId));
ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler(
_guarded,
peer->id,
msgId,
peer->session().userId()));
hr = toast->add_Activated(eventHandler.Get(), &activatedToken);
if (!SUCCEEDED(hr)) return false;
@ -544,26 +581,34 @@ bool Manager::Private::showNotification(
hr = toast->add_Failed(eventHandler.Get(), &failedToken);
if (!SUCCEEDED(hr)) return false;
auto i = _notifications.find(peer->id);
const auto key = FullPeer{
peer->id,
peer->session().userId()
};
auto i = _notifications.find(key);
if (i != _notifications.cend()) {
auto j = i->find(msgId);
if (j != i->cend()) {
ComPtr<IToastNotification> notify = j->p;
i->erase(j);
auto j = i->second.find(msgId);
if (j != i->second.end()) {
ComPtr<IToastNotification> notify = j->second.p;
i->second.erase(j);
_notifier->Hide(notify.Get());
i = _notifications.find(peer->id);
i = _notifications.find(key);
}
}
if (i == _notifications.cend()) {
i = _notifications.insert(peer->id, QMap<MsgId, NotificationPtr>());
i = _notifications.emplace(
key,
base::flat_map<MsgId, NotificationPtr>()).first;
}
hr = _notifier->Show(toast.Get());
if (!SUCCEEDED(hr)) {
i = _notifications.find(peer->id);
if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i);
i = _notifications.find(key);
if (i != _notifications.cend() && i->second.empty()) {
_notifications.erase(i);
}
return false;
}
_notifications[peer->id].insert(msgId, toast);
i->second.emplace(msgId, toast);
return true;
}
@ -576,8 +621,8 @@ bool Manager::init() {
return _private->init();
}
void Manager::clearNotification(PeerId peerId, MsgId msgId) {
_private->clearNotification(peerId, msgId);
void Manager::clearNotification(NotificationId id) {
_private->clearNotification(id);
}
Manager::~Manager() = default;
@ -610,12 +655,16 @@ void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}
void Manager::onBeforeNotificationActivated(PeerId peerId, MsgId msgId) {
_private->beforeNotificationActivated(peerId, msgId);
void Manager::doClearFromSession(not_null<Main::Session*> session) {
_private->clearFromSession(session);
}
void Manager::onAfterNotificationActivated(PeerId peerId, MsgId msgId) {
_private->afterNotificationActivated(peerId, msgId);
void Manager::onBeforeNotificationActivated(NotificationId id) {
_private->beforeNotificationActivated(id);
}
void Manager::onAfterNotificationActivated(NotificationId id) {
_private->afterNotificationActivated(id);
}
#endif // !__MINGW32__

View File

@ -13,15 +13,14 @@ namespace Platform {
namespace Notifications {
#ifndef __MINGW32__
class Manager : public Window::Notifications::NativeManager {
public:
Manager(Window::Notifications::System *system);
~Manager();
bool init();
void clearNotification(PeerId peerId, MsgId msgId);
~Manager();
void clearNotification(NotificationId id);
protected:
void doShowNativeNotification(
@ -35,8 +34,9 @@ protected:
bool hideReplyButton) override;
void doClearAllFast() override;
void doClearFromHistory(not_null<History*> history) override;
void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override;
void onAfterNotificationActivated(PeerId peerId, MsgId msgId) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void onBeforeNotificationActivated(NotificationId id) override;
void onAfterNotificationActivated(NotificationId id) override;
private:
class Private;

View File

@ -150,11 +150,8 @@ auto GenerateCodes() {
SessionController *window) {
crl::on_main(&Core::App(), [=] {
const auto &list = Core::App().domain().accounts();
const auto j = list.find(i);
if (j != list.end() && !Core::App().locked()) {
if (&Core::App().activeAccount() != j->second.get()) {
Core::App().domain().activate(i);
}
if (i < list.size()) {
Core::App().domain().activate(list[i].account.get());
}
});
});

View File

@ -191,7 +191,7 @@ void NotificationsCount::setCount(int count) {
if (count != Core::App().settings().notificationsCount()) {
Core::App().settings().setNotificationsCount(count);
Core::App().saveSettingsDelayed();
_controller->session().notifications().settingsChanged().notify(
Core::App().notifications().settingsChanged().notify(
ChangeType::MaxCount);
}
}
@ -355,7 +355,7 @@ void NotificationsCount::setOverCorner(ScreenCorner corner) {
_isOverCorner = true;
setCursor(style::cur_pointer);
Global::SetNotificationsDemoIsShown(true);
_controller->session().notifications().settingsChanged().notify(
Core::App().notifications().settingsChanged().notify(
ChangeType::DemoIsShown);
}
_overCorner = corner;
@ -391,7 +391,7 @@ void NotificationsCount::clearOverCorner() {
_isOverCorner = false;
setCursor(style::cur_default);
Global::SetNotificationsDemoIsShown(false);
_controller->session().notifications().settingsChanged().notify(ChangeType::DemoIsShown);
Core::App().notifications().settingsChanged().notify(ChangeType::DemoIsShown);
for_const (const auto &samples, _cornerSamples) {
for_const (const auto widget, samples) {
@ -418,7 +418,7 @@ void NotificationsCount::mouseReleaseEvent(QMouseEvent *e) {
if (_chosenCorner != Core::App().settings().notificationsCorner()) {
Core::App().settings().setNotificationsCorner(_chosenCorner);
Core::App().saveSettingsDelayed();
_controller->session().notifications().settingsChanged().notify(
Core::App().notifications().settingsChanged().notify(
ChangeType::Corner);
}
}
@ -685,7 +685,7 @@ void SetupNotificationsContent(
using Change = Window::Notifications::ChangeType;
const auto changed = [=](Change change) {
Core::App().saveSettingsDelayed();
session->notifications().settingsChanged().notify(change);
Core::App().notifications().settingsChanged().notify(change);
};
desktop->checkedChanges(
) | rpl::filter([](bool checked) {
@ -758,7 +758,7 @@ void SetupNotificationsContent(
}, count->lifetime());
base::ObservableViewer(
session->notifications().settingsChanged()
Core::App().notifications().settingsChanged()
) | rpl::start_with_next([=](Change change) {
if (change == Change::DesktopEnabled) {
desktop->setChecked(Core::App().settings().desktopNotify());
@ -785,7 +785,7 @@ void SetupNotificationsContent(
}) | rpl::start_with_next([=](bool checked) {
Core::App().settings().setNativeNotifications(checked);
Core::App().saveSettingsDelayed();
session->notifications().createManager();
Core::App().notifications().createManager();
if (advancedSlide) {
advancedSlide->toggle(

View File

@ -87,7 +87,7 @@ void Domain::startWithSingleAccount(
generateLocalKey();
account->start(account->prepareToStart(_localKey));
}
_owner->accountAddedInStorage(0, std::move(account));
_owner->accountAddedInStorage({ .account = std::move(account) });
writeAccounts();
}
@ -170,6 +170,7 @@ Domain::StartModernResult Domain::startModern(
auto tried = base::flat_set<int>();
auto users = base::flat_set<UserId>();
auto active = 0;
for (auto i = 0; i != count; ++i) {
auto index = qint32();
info.stream >> index;
@ -184,12 +185,22 @@ Domain::StartModernResult Domain::startModern(
const auto userId = account->willHaveUserId();
if (!users.contains(userId)
&& (userId != 0 || (users.empty() && i + 1 == count))) {
if (users.empty()) {
active = index;
}
account->start(std::move(config));
_owner->accountAddedInStorage(index, std::move(account));
_owner->accountAddedInStorage({
.index = index,
.account = std::move(account)
});
users.emplace(userId);
}
}
}
if (!info.stream.atEnd()) {
info.stream >> active;
}
_owner->activateFromStorage(active);
Ensures(!users.empty());
return StartModernResult::Success;
@ -208,18 +219,21 @@ void Domain::writeAccounts() {
key.writeData(_passcodeKeyEncrypted);
const auto &list = _owner->accounts();
const auto active = _owner->activeIndex();
auto keySize = sizeof(qint32) + sizeof(qint32) * list.size();
const auto active = &_owner->active();
auto activeIndex = -1;
EncryptedDescriptor keyData(keySize);
keyData.stream << qint32(list.size());
keyData.stream << qint32(active);
for (const auto &[index, account] : list) {
if (index != active) {
keyData.stream << qint32(index);
if (active == account.get()) {
activeIndex = index;
}
keyData.stream << qint32(index);
}
keyData.stream << qint32(activeIndex);
key.writeEncrypted(keyData, _localKey);
}

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "main/main_domain.h"
#include "facades.h"
@ -47,9 +48,8 @@ constexpr auto kSystemAlertDuration = crl::time(0);
} // namespace
System::System(not_null<Main::Session*> session)
: _session(session)
, _waitTimer([=] { showNext(); })
System::System()
: _waitTimer([=] { showNext(); })
, _waitForAllGroupedTimer([=] { showGrouped(); }) {
createManager();
@ -73,6 +73,16 @@ void System::createManager() {
}
}
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;
}
System::SkipState System::skipNotification(
not_null<HistoryItem*> item) const {
const auto history = item->history();
@ -135,19 +145,23 @@ void System::schedule(not_null<HistoryItem*> item) {
auto when = ms + delay;
if (!skip.silent) {
_whenAlerts[history].insert(when, notifyBy);
_whenAlerts[history].emplace(when, notifyBy);
}
if (Core::App().settings().desktopNotify()
&& !Platform::Notifications::SkipToast()) {
auto &whenMap = _whenMaps[history];
if (whenMap.constFind(item->id) == whenMap.cend()) {
whenMap.insert(item->id, when);
if (whenMap.find(item->id) == whenMap.end()) {
whenMap.emplace(item->id, when);
}
auto &addTo = ready ? _waiters : _settingWaiters;
const auto it = addTo.constFind(history);
if (it == addTo.cend() || it->when > when) {
addTo.insert(history, Waiter(item->id, when, notifyBy));
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
});
}
}
if (ready) {
@ -161,7 +175,7 @@ void System::clearAll() {
_manager->clearAll();
for (auto i = _whenMaps.cbegin(), e = _whenMaps.cend(); i != e; ++i) {
i.key()->clearNotifications();
i->first->clearNotifications();
}
_whenMaps.clear();
_whenAlerts.clear();
@ -182,6 +196,35 @@ void System::clearFromHistory(not_null<History*> history) {
showNext();
}
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);
}
void System::clearIncomingFromHistory(not_null<History*> history) {
_manager->clearFromHistory(history);
history->clearIncomingNotifications();
@ -203,14 +246,14 @@ void System::clearAllFast() {
void System::checkDelayed() {
for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
const auto history = i.key();
const auto history = i->first;
const auto peer = history->peer;
auto loaded = false;
auto muted = false;
if (!peer->owner().notifyMuteUnknown(peer)) {
if (!peer->owner().notifyIsMuted(peer)) {
loaded = true;
} else if (const auto from = i.value().notifyBy) {
} else if (const auto from = i->second.notifyBy) {
if (!peer->owner().notifyMuteUnknown(from)) {
if (!peer->owner().notifyIsMuted(from)) {
loaded = true;
@ -225,7 +268,7 @@ void System::checkDelayed() {
if (loaded) {
const auto fullId = FullMsgId(
history->channelId(),
i.value().msg);
i->second.msg);
if (const auto item = peer->owner().message(fullId)) {
if (!item->notificationReady()) {
loaded = false;
@ -236,7 +279,7 @@ void System::checkDelayed() {
}
if (loaded) {
if (!muted) {
_waiters.insert(i.key(), i.value());
_waiters.emplace(i->first, i->second);
}
i = _settingWaiters.erase(i);
} else {
@ -248,11 +291,13 @@ void System::checkDelayed() {
}
void System::showGrouped() {
if (const auto lastItem = session().data().message(_lastHistoryItemId)) {
_waitForAllGroupedTimer.cancel();
_manager->showNotification(lastItem, _lastForwardedCount);
_lastForwardedCount = 0;
_lastHistoryItemId = FullMsgId();
if (const auto session = findSession(_lastHistorySelfId)) {
if (const auto lastItem = session->data().message(_lastHistoryItemId)) {
_waitForAllGroupedTimer.cancel();
_manager->showNotification(lastItem, _lastForwardedCount);
_lastForwardedCount = 0;
_lastHistoryItemId = FullMsgId();
}
}
}
@ -275,12 +320,12 @@ void System::showNext() {
bool alert = false;
int32 now = base::unixtime::now();
for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
while (!i.value().isEmpty() && i.value().begin().key() <= ms) {
const auto peer = i.key()->peer;
while (!i->second.empty() && i->second.begin()->first <= ms) {
const auto peer = i->first->peer;
const auto peerUnknown = peer->owner().notifyMuteUnknown(peer);
const auto peerAlert = !peerUnknown
&& !peer->owner().notifyIsMuted(peer);
const auto from = i.value().begin().value();
const auto from = i->second.begin()->second;
const auto fromUnknown = (!from
|| peer->owner().notifyMuteUnknown(from));
const auto fromAlert = !fromUnknown
@ -288,16 +333,16 @@ void System::showNext() {
if (peerAlert || fromAlert) {
alert = true;
}
while (!i.value().isEmpty()
&& i.value().begin().key() <= ms + kMinimalAlertDelay) {
i.value().erase(i.value().begin());
while (!i->second.empty()
&& i->second.begin()->first <= ms + kMinimalAlertDelay) {
i->second.erase(i->second.begin());
}
}
if (i.value().isEmpty()) {
if (i->second.empty()) {
i = _whenAlerts.erase(i);
} else {
if (!nextAlert || nextAlert > i.value().begin().key()) {
nextAlert = i.value().begin().key();
if (!nextAlert || nextAlert > i->second.begin()->first) {
nextAlert = i->second.begin()->first;
}
++i;
}
@ -320,7 +365,7 @@ void System::showNext() {
}
}
if (_waiters.isEmpty() || !settings.desktopNotify() || Platform::Notifications::SkipToast()) {
if (_waiters.empty() || !settings.desktopNotify() || Platform::Notifications::SkipToast()) {
if (nextAlert) {
_waitTimer.callOnce(nextAlert - ms);
}
@ -332,8 +377,8 @@ void System::showNext() {
HistoryItem *notifyItem = nullptr;
History *notifyHistory = nullptr;
for (auto i = _waiters.begin(); i != _waiters.end();) {
History *history = i.key();
if (history->currentNotification() && history->currentNotification()->id != i.value().msg) {
const auto history = i->first;
if (history->currentNotification() && history->currentNotification()->id != i->second.msg) {
auto j = _whenMaps.find(history);
if (j == _whenMaps.end()) {
history->clearNotifications();
@ -341,10 +386,10 @@ void System::showNext() {
continue;
}
do {
auto k = j.value().constFind(history->currentNotification()->id);
if (k != j.value().cend()) {
i.value().msg = k.key();
i.value().when = k.value();
auto k = j->second.find(history->currentNotification()->id);
if (k != j->second.cend()) {
i->second.msg = k->first;
i->second.when = k->second;
break;
}
history->skipNotification();
@ -355,7 +400,7 @@ void System::showNext() {
i = _waiters.erase(i);
continue;
}
auto when = i.value().when;
auto when = i->second.when;
if (!notifyItem || next > when) {
next = when;
notifyItem = history->currentNotification();
@ -390,12 +435,15 @@ void System::showNext() {
break;
}
j.value().remove((groupedItem ? groupedItem : notifyItem)->id);
j->second.remove((groupedItem ? groupedItem : notifyItem)->id);
do {
const auto k = j.value().constFind(history->currentNotification()->id);
if (k != j.value().cend()) {
const auto k = j->second.find(history->currentNotification()->id);
if (k != j->second.cend()) {
nextNotify = history->currentNotification();
_waiters.insert(notifyHistory, Waiter(k.key(), k.value(), 0));
_waiters.emplace(notifyHistory, Waiter{
.msg = k->first,
.when = k->second
});
break;
}
history->skipNotification();
@ -482,8 +530,8 @@ void System::updateAll() {
_manager->updateAll();
}
Manager::DisplayOptions Manager::getNotificationOptions(HistoryItem *item) {
const auto hideEverything = Core::App().locked()
Manager::DisplayOptions Manager::GetNotificationOptions(HistoryItem *item) {
const auto hideEverything = Core::App().passcodeLocked()
|| Global::ScreenIsLocked();
const auto view = Core::App().settings().notifyView();
@ -499,20 +547,26 @@ Manager::DisplayOptions Manager::getNotificationOptions(HistoryItem *item) {
return result;
}
void Manager::notificationActivated(PeerId peerId, MsgId msgId) {
onBeforeNotificationActivated(peerId, msgId);
if (auto window = App::wnd()) {
auto history = system()->session().data().history(peerId);
window->showFromTray();
window->reActivateWindow();
if (Core::App().locked()) {
window->setInnerFocus();
system()->clearAll();
} else {
openNotificationMessage(history, msgId);
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);
}
}
}
onAfterNotificationActivated(peerId, msgId);
onAfterNotificationActivated(id);
}
void Manager::openNotificationMessage(
@ -548,20 +602,29 @@ void Manager::openNotificationMessage(
}
void Manager::notificationReplied(
PeerId peerId,
MsgId msgId,
NotificationId id,
const TextWithTags &reply) {
if (!peerId) return;
if (!id.selfId || !id.peerId) {
return;
}
const auto history = system()->session().data().history(peerId);
const auto session = system()->findSession(id.selfId);
if (!session) {
return;
}
const auto history = session->data().history(id.peerId);
auto message = Api::MessageToSend(history);
message.textWithTags = reply;
message.action.replyTo = (msgId > 0 && !history->peer->isUser()) ? msgId : 0;
message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
? id.msgId
: 0;
message.action.clearDraft = false;
history->session().api().sendMessage(std::move(message));
const auto item = history->owner().message(history->channelId(), msgId);
const auto item = history->owner().message(
history->channelId(),
id.msgId);
if (item && item->isUnreadMention() && !item->isUnreadMedia()) {
history->session().api().markMediaRead(item);
}
@ -570,7 +633,7 @@ void Manager::notificationReplied(
void NativeManager::doShowNotification(
not_null<HistoryItem*> item,
int forwardedCount) {
const auto options = getNotificationOptions(item);
const auto options = GetNotificationOptions(item);
const auto peer = item->history()->peer;
const auto scheduled = !options.hideNameAndPhoto

View File

@ -64,7 +64,10 @@ class Manager;
class System final : private base::Subscriber {
public:
explicit System(not_null<Main::Session*> session);
System();
~System();
[[nodiscard]] Main::Session *findSession(UserId selfId) const;
void createManager();
@ -72,6 +75,7 @@ public:
void schedule(not_null<HistoryItem*> item);
void clearFromHistory(not_null<History*> history);
void clearIncomingFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void clearFromItem(not_null<HistoryItem*> item);
void clearAll();
void clearAllFast();
@ -81,12 +85,6 @@ public:
return _settingsChanged;
}
Main::Session &session() const {
return *_session;
}
~System();
private:
struct SkipState {
enum Value {
@ -97,34 +95,29 @@ private:
Value value = Value::Unknown;
bool silent = false;
};
struct Waiter {
MsgId msg;
crl::time when;
PeerData *notifyBy = nullptr;
};
SkipState skipNotification(not_null<HistoryItem*> item) const;
[[nodiscard]] SkipState skipNotification(
not_null<HistoryItem*> item) const;
void showNext();
void showGrouped();
void ensureSoundCreated();
not_null<Main::Session*> _session;
base::flat_map<
not_null<History*>,
base::flat_map<MsgId, crl::time>> _whenMaps;
QMap<History*, QMap<MsgId, crl::time>> _whenMaps;
struct Waiter {
Waiter(MsgId msg, crl::time when, PeerData *notifyBy)
: msg(msg)
, when(when)
, notifyBy(notifyBy) {
}
MsgId msg;
crl::time when;
PeerData *notifyBy;
};
using Waiters = QMap<History*, Waiter>;
Waiters _waiters;
Waiters _settingWaiters;
base::flat_map<not_null<History*>, Waiter> _waiters;
base::flat_map<not_null<History*>, Waiter> _settingWaiters;
base::Timer _waitTimer;
base::Timer _waitForAllGroupedTimer;
QMap<History*, QMap<crl::time, PeerData*>> _whenAlerts;
base::flat_map<not_null<History*>, base::flat_map<crl::time, PeerData*>> _whenAlerts;
std::unique_ptr<Manager> _manager;
@ -133,12 +126,28 @@ private:
std::unique_ptr<Media::Audio::Track> _soundTrack;
int _lastForwardedCount = 0;
UserId _lastHistorySelfId = 0;
FullMsgId _lastHistoryItemId;
};
class Manager {
public:
struct NotificationId {
PeerId peerId = 0;
MsgId msgId = 0;
UserId selfId = 0;
};
struct FullPeer {
PeerId peerId = 0;
UserId selfId = 0;
friend inline bool operator<(const FullPeer &a, const FullPeer &b) {
return std::tie(a.peerId, a.selfId)
< std::tie(b.peerId, b.selfId);
}
};
explicit Manager(not_null<System*> system) : _system(system) {
}
@ -162,19 +171,20 @@ public:
void clearFromHistory(not_null<History*> history) {
doClearFromHistory(history);
}
void clearFromSession(not_null<Main::Session*> session) {
doClearFromSession(session);
}
void notificationActivated(PeerId peerId, MsgId msgId);
void notificationReplied(
PeerId peerId,
MsgId msgId,
const TextWithTags &reply);
void notificationActivated(NotificationId id);
void notificationReplied(NotificationId id, const TextWithTags &reply);
struct DisplayOptions {
bool hideNameAndPhoto;
bool hideMessageText;
bool hideReplyButton;
bool hideNameAndPhoto = false;
bool hideMessageText = false;
bool hideReplyButton = false;
};
static DisplayOptions getNotificationOptions(HistoryItem *item);
[[nodiscard]] static DisplayOptions GetNotificationOptions(
HistoryItem *item);
virtual ~Manager() = default;
@ -191,9 +201,10 @@ protected:
virtual void doClearAllFast() = 0;
virtual void doClearFromItem(not_null<HistoryItem*> item) = 0;
virtual void doClearFromHistory(not_null<History*> history) = 0;
virtual void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) {
virtual void doClearFromSession(not_null<Main::Session*> session) = 0;
virtual void onBeforeNotificationActivated(NotificationId id) {
}
virtual void onAfterNotificationActivated(PeerId peerId, MsgId msgId) {
virtual void onAfterNotificationActivated(NotificationId id) {
}
private:

View File

@ -67,11 +67,6 @@ std::unique_ptr<Manager> Create(System *system) {
Manager::Manager(System *system)
: Notifications::Manager(system)
, _inputCheckTimer([=] { checkLastInput(); }) {
subscribe(system->session().downloaderTaskFinished(), [this] {
for (const auto &notification : _notifications) {
notification->updatePeerPhoto();
}
});
subscribe(system->settingsChanged(), [this](ChangeType change) {
settingsChanged(change);
});
@ -212,7 +207,8 @@ void Manager::showNextFromQueue() {
auto queued = _queuedNotifications.front();
_queuedNotifications.pop_front();
auto notification = std::make_unique<Notification>(
subscribeToSession(&queued.history->session());
_notifications.push_back(std::make_unique<Notification>(
this,
queued.history,
queued.peer,
@ -222,8 +218,7 @@ void Manager::showNextFromQueue() {
queued.fromScheduled,
startPosition,
startShift,
shiftDirection);
_notifications.push_back(std::move(notification));
shiftDirection));
--count;
} while (count > 0 && !_queuedNotifications.empty());
@ -231,6 +226,32 @@ void Manager::showNextFromQueue() {
checkLastInput();
}
void Manager::subscribeToSession(not_null<Main::Session*> session) {
auto i = _subscriptions.find(session);
if (i == _subscriptions.end()) {
i = _subscriptions.emplace(session, base::Subscription()).first;
session->lifetime().add([=] {
_subscriptions.remove(session);
});
} else if (i->second) {
return;
}
i->second = session->downloaderTaskFinished().add_subscription([=] {
auto found = false;
for (const auto &notification : _notifications) {
if (const auto history = notification->maybeHistory()) {
if (&history->session() == session) {
notification->updatePeerPhoto();
found = true;
}
}
}
if (!found) {
_subscriptions[session].destroy();
}
});
}
void Manager::moveWidgets() {
auto shift = st::notifyDeltaY;
int lastShift = 0, lastShiftCurrent = 0, count = 0;
@ -334,7 +355,7 @@ void Manager::doClearFromHistory(not_null<History*> history) {
++i;
}
}
for_const (auto &notification, _notifications) {
for (const auto &notification : _notifications) {
if (notification->unlinkHistory(history)) {
_positionsOutdated = true;
}
@ -342,6 +363,22 @@ void Manager::doClearFromHistory(not_null<History*> history) {
showNextFromQueue();
}
void Manager::doClearFromSession(not_null<Main::Session*> session) {
for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
if (&i->history->session() == session) {
i = _queuedNotifications.erase(i);
} else {
++i;
}
}
for (const auto &notification : _notifications) {
if (notification->unlinkSession(session)) {
_positionsOutdated = true;
}
}
showNextFromQueue();
}
void Manager::doClearFromItem(not_null<HistoryItem*> item) {
_queuedNotifications.erase(std::remove_if(_queuedNotifications.begin(), _queuedNotifications.end(), [&](auto &queued) {
return (queued.item == item);
@ -678,7 +715,7 @@ void Notification::actionsOpacityCallback() {
void Notification::updateNotifyDisplay() {
if (!_history || (!_item && _forwardedCount < 2)) return;
const auto options = Manager::getNotificationOptions(_item);
const auto options = Manager::GetNotificationOptions(_item);
_hideReplyButton = options.hideReplyButton;
int32 w = width(), h = height();
@ -832,7 +869,7 @@ bool Notification::unlinkItem(HistoryItem *deleted) {
bool Notification::canReply() const {
return !_hideReplyButton
&& (_item != nullptr)
&& !Core::App().locked()
&& !Core::App().passcodeLocked()
&& (Core::App().settings().notifyView() <= dbinvShowPreview);
}
@ -901,16 +938,23 @@ void Notification::showReplyField() {
void Notification::sendReply() {
if (!_history) return;
auto peerId = _history->peer->id;
auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
manager()->notificationReplied(
peerId,
msgId,
myId(),
_replyArea->getTextWithAppliedMarkdown());
manager()->startAllHiding();
}
Notifications::Manager::NotificationId Notification::myId() const {
if (!_history) {
return {};
}
const auto selfId = _history->session().userId();
const auto peerId = _history->peer->id;
const auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
return { .peerId = peerId, .msgId = msgId, .selfId = selfId };
}
void Notification::changeHeight(int newHeight) {
manager()->changeNotificationHeight(this, newHeight);
}
@ -925,6 +969,16 @@ bool Notification::unlinkHistory(History *history) {
return unlink;
}
bool Notification::unlinkSession(not_null<Main::Session*> session) {
const auto unlink = _history && (&_history->session() == session);
if (unlink) {
hideFast();
_history = nullptr;
_item = nullptr;
}
return unlink;
}
void Notification::enterEventHook(QEvent *e) {
if (!_history) return;
manager()->stopAllHiding();
@ -951,9 +1005,7 @@ void Notification::mousePressEvent(QMouseEvent *e) {
unlinkHistoryInManager();
} else {
e->ignore();
auto peerId = _history->peer->id;
auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
manager()->notificationActivated(peerId, msgId);
manager()->notificationActivated(myId());
}
}

View File

@ -38,19 +38,18 @@ class HideAllButton;
class Manager;
std::unique_ptr<Manager> Create(System *system);
class Manager : public Notifications::Manager, private base::Subscriber {
class Manager final : public Notifications::Manager, private base::Subscriber {
public:
Manager(System *system);
~Manager();
template <typename Method>
void enumerateNotifications(Method method) {
for_const (auto &notification, _notifications) {
for (const auto &notification : _notifications) {
method(notification);
}
}
~Manager();
private:
friend class internal::Notification;
friend class internal::HideAllButton;
@ -58,7 +57,7 @@ private:
using Notification = internal::Notification;
using HideAllButton = internal::HideAllButton;
QPixmap hiddenUserpicPlaceholder() const;
[[nodiscard]] QPixmap hiddenUserpicPlaceholder() const;
void doUpdateAll() override;
void doShowNotification(
@ -67,6 +66,7 @@ private:
void doClearAll() override;
void doClearAllFast() override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void doClearFromItem(not_null<HistoryItem*> item) override;
void showNextFromQueue();
@ -86,7 +86,12 @@ private:
bool hasReplyingNotification() const;
void subscribeToSession(not_null<Main::Session*> session);
std::vector<std::unique_ptr<Notification>> _notifications;
base::flat_map<
not_null<Main::Session*>,
base::Subscription> _subscriptions;
std::unique_ptr<HideAllButton> _hideAll;
@ -181,7 +186,7 @@ protected:
};
class Notification : public Widget {
class Notification final : public Widget {
public:
Notification(
not_null<Manager*> manager,
@ -207,10 +212,14 @@ public:
bool isReplying() const {
return _replyArea && !isUnlinked();
}
[[nodiscard]] History *maybeHistory() const {
return _history;
}
// Called only by Manager.
bool unlinkItem(HistoryItem *del);
bool unlinkHistory(History *history = nullptr);
bool unlinkSession(not_null<Main::Session*> session);
bool checkLastInput(bool hasReplyingNotifications);
protected:
@ -236,6 +245,8 @@ private:
void updateGeometry(int x, int y, int width, int height) override;
void actionsOpacityCallback();
[[nodiscard]] Notifications::Manager::NotificationId myId() const;
const not_null<PeerData*> _peer;
QPixmap _cache;