1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "window/notifications_manager.h"
9
10 #include "platform/platform_notifications_manager.h"
11 #include "window/notifications_manager_default.h"
12 #include "media/audio/media_audio_track.h"
13 #include "media/audio/media_audio.h"
14 #include "mtproto/mtproto_config.h"
15 #include "history/history.h"
16 #include "history/history_item_components.h"
17 #include "lang/lang_keys.h"
18 #include "data/data_session.h"
19 #include "data/data_channel.h"
20 #include "data/data_user.h"
21 #include "base/unixtime.h"
22 #include "window/window_controller.h"
23 #include "window/window_session_controller.h"
24 #include "core/application.h"
25 #include "mainwindow.h"
26 #include "api/api_updates.h"
27 #include "apiwrap.h"
28 #include "main/main_account.h"
29 #include "main/main_session.h"
30 #include "main/main_domain.h"
31 #include "facades.h"
32 #include "app.h"
33
34 #include <QtGui/QWindow>
35
36 namespace Window {
37 namespace Notifications {
38 namespace {
39
40 // not more than one sound in 500ms from one peer - grouping
41 constexpr auto kMinimalAlertDelay = crl::time(500);
42 constexpr auto kWaitingForAllGroupedDelay = crl::time(1000);
43
44 #ifdef Q_OS_MAC
45 constexpr auto kSystemAlertDuration = crl::time(1000);
46 #else // !Q_OS_MAC
47 constexpr auto kSystemAlertDuration = crl::time(0);
48 #endif // Q_OS_MAC
49
50 } // namespace
51
System()52 System::System()
53 : _waitTimer([=] { showNext(); })
__anoncbf258090302null54 , _waitForAllGroupedTimer([=] { showGrouped(); }) {
55 settingsChanged(
__anoncbf258090402(ChangeType type) 56 ) | rpl::start_with_next([=](ChangeType type) {
57 if (type == ChangeType::DesktopEnabled) {
58 clearAll();
59 } else if (type == ChangeType::ViewParams) {
60 updateAll();
61 } else if (type == ChangeType::IncludeMuted
62 || type == ChangeType::CountMessages) {
63 Core::App().domain().notifyUnreadBadgeChanged();
64 }
65 }, lifetime());
66 }
67
createManager()68 void System::createManager() {
69 Platform::Notifications::Create(this);
70 }
71
setManager(std::unique_ptr<Manager> manager)72 void System::setManager(std::unique_ptr<Manager> manager) {
73 _manager = std::move(manager);
74 if (!_manager) {
75 _manager = std::make_unique<Default::Manager>(this);
76 }
77 }
78
managerType() const79 std::optional<ManagerType> System::managerType() const {
80 if (_manager) {
81 return _manager->type();
82 }
83 return std::nullopt;
84 }
85
findSession(uint64 sessionId) const86 Main::Session *System::findSession(uint64 sessionId) const {
87 for (const auto &[index, account] : Core::App().domain().accounts()) {
88 if (const auto session = account->maybeSession()) {
89 if (session->uniqueId() == sessionId) {
90 return session;
91 }
92 }
93 }
94 return nullptr;
95 }
96
skipNotification(not_null<HistoryItem * > item) const97 System::SkipState System::skipNotification(
98 not_null<HistoryItem*> item) const {
99 const auto history = item->history();
100 const auto notifyBy = item->specialNotificationPeer();
101 if (App::quitting()
102 || !history->currentNotification()
103 || item->skipNotification()) {
104 return { SkipState::Skip };
105 } else if (!Core::App().settings().notifyFromAll()
106 && &history->session().account() != &Core::App().domain().active()) {
107 return { SkipState::Skip };
108 }
109
110 history->owner().requestNotifySettings(history->peer);
111 if (notifyBy) {
112 history->owner().requestNotifySettings(notifyBy);
113 }
114
115 const auto scheduled = item->out() && item->isFromScheduled();
116 if (history->owner().notifyMuteUnknown(history->peer)) {
117 return { SkipState::Unknown, item->isSilent() };
118 } else if (!history->owner().notifyIsMuted(history->peer)) {
119 return { SkipState::DontSkip, item->isSilent() };
120 } else if (!notifyBy) {
121 return {
122 scheduled ? SkipState::DontSkip : SkipState::Skip,
123 item->isSilent() || scheduled
124 };
125 } else if (history->owner().notifyMuteUnknown(notifyBy)) {
126 return { SkipState::Unknown, item->isSilent() };
127 } else if (!history->owner().notifyIsMuted(notifyBy)) {
128 return { SkipState::DontSkip, item->isSilent() };
129 } else {
130 return {
131 scheduled ? SkipState::DontSkip : SkipState::Skip,
132 item->isSilent() || scheduled
133 };
134 }
135 }
136
schedule(not_null<HistoryItem * > item)137 void System::schedule(not_null<HistoryItem*> item) {
138 Expects(_manager != nullptr);
139
140 const auto history = item->history();
141 const auto skip = skipNotification(item);
142 if (skip.value == SkipState::Skip) {
143 history->popNotification(item);
144 return;
145 }
146 const auto notifyBy = item->specialNotificationPeer();
147 const auto ready = (skip.value != SkipState::Unknown)
148 && item->notificationReady();
149
150 auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100;
151 const auto t = base::unixtime::now();
152 const auto ms = crl::now();
153 const auto &updates = history->session().updates();
154 const auto &config = history->session().serverConfig();
155 const bool isOnline = updates.lastWasOnline();
156 const auto otherNotOld = ((cOtherOnline() * 1000LL) + config.onlineCloudTimeout > t * 1000LL);
157 const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - updates.lastSetOnline()) > t * 1000LL);
158 if (!isOnline && otherNotOld && otherLaterThanMe) {
159 delay = config.notifyCloudDelay;
160 } else if (cOtherOnline() >= t) {
161 delay = config.notifyDefaultDelay;
162 }
163
164 auto when = ms + delay;
165 if (!skip.silent) {
166 _whenAlerts[history].emplace(when, notifyBy);
167 }
168 if (Core::App().settings().desktopNotify()
169 && !_manager->skipToast()) {
170 auto &whenMap = _whenMaps[history];
171 if (whenMap.find(item->id) == whenMap.end()) {
172 whenMap.emplace(item->id, when);
173 }
174
175 auto &addTo = ready ? _waiters : _settingWaiters;
176 const auto it = addTo.find(history);
177 if (it == addTo.end() || it->second.when > when) {
178 addTo.emplace(history, Waiter{
179 .msg = item->id,
180 .when = when,
181 .notifyBy = notifyBy
182 });
183 }
184 }
185 if (ready) {
186 if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) {
187 _waitTimer.callOnce(delay);
188 }
189 }
190 }
191
clearAll()192 void System::clearAll() {
193 if (_manager) {
194 _manager->clearAll();
195 }
196
197 for (auto i = _whenMaps.cbegin(), e = _whenMaps.cend(); i != e; ++i) {
198 i->first->clearNotifications();
199 }
200 _whenMaps.clear();
201 _whenAlerts.clear();
202 _waiters.clear();
203 _settingWaiters.clear();
204 }
205
clearFromHistory(not_null<History * > history)206 void System::clearFromHistory(not_null<History*> history) {
207 if (_manager) {
208 _manager->clearFromHistory(history);
209 }
210
211 history->clearNotifications();
212 _whenMaps.remove(history);
213 _whenAlerts.remove(history);
214 _waiters.remove(history);
215 _settingWaiters.remove(history);
216
217 _waitTimer.cancel();
218 showNext();
219 }
220
clearFromSession(not_null<Main::Session * > session)221 void System::clearFromSession(not_null<Main::Session*> session) {
222 if (_manager) {
223 _manager->clearFromSession(session);
224 }
225
226 for (auto i = _whenMaps.begin(); i != _whenMaps.end();) {
227 const auto history = i->first;
228 if (&history->session() != session) {
229 ++i;
230 continue;
231 }
232 history->clearNotifications();
233 i = _whenMaps.erase(i);
234 _whenAlerts.remove(history);
235 _waiters.remove(history);
236 _settingWaiters.remove(history);
237 }
238 const auto clearFrom = [&](auto &map) {
239 for (auto i = map.begin(); i != map.end();) {
240 if (&i->first->session() == session) {
241 i = map.erase(i);
242 } else {
243 ++i;
244 }
245 }
246 };
247 clearFrom(_whenAlerts);
248 clearFrom(_waiters);
249 clearFrom(_settingWaiters);
250 }
251
clearIncomingFromHistory(not_null<History * > history)252 void System::clearIncomingFromHistory(not_null<History*> history) {
253 if (_manager) {
254 _manager->clearFromHistory(history);
255 }
256 history->clearIncomingNotifications();
257 _whenAlerts.remove(history);
258 }
259
clearFromItem(not_null<HistoryItem * > item)260 void System::clearFromItem(not_null<HistoryItem*> item) {
261 if (_manager) {
262 _manager->clearFromItem(item);
263 }
264 }
265
clearAllFast()266 void System::clearAllFast() {
267 if (_manager) {
268 _manager->clearAllFast();
269 }
270
271 _whenMaps.clear();
272 _whenAlerts.clear();
273 _waiters.clear();
274 _settingWaiters.clear();
275 }
276
checkDelayed()277 void System::checkDelayed() {
278 for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
279 const auto history = i->first;
280 const auto peer = history->peer;
281 auto loaded = false;
282 auto muted = false;
283 if (!peer->owner().notifyMuteUnknown(peer)) {
284 if (!peer->owner().notifyIsMuted(peer)) {
285 loaded = true;
286 } else if (const auto from = i->second.notifyBy) {
287 if (!peer->owner().notifyMuteUnknown(from)) {
288 if (!peer->owner().notifyIsMuted(from)) {
289 loaded = true;
290 } else {
291 loaded = muted = true;
292 }
293 }
294 } else {
295 loaded = muted = true;
296 }
297 }
298 if (loaded) {
299 const auto fullId = FullMsgId(
300 history->channelId(),
301 i->second.msg);
302 if (const auto item = peer->owner().message(fullId)) {
303 if (!item->notificationReady()) {
304 loaded = false;
305 }
306 } else {
307 muted = true;
308 }
309 }
310 if (loaded) {
311 if (!muted) {
312 _waiters.emplace(i->first, i->second);
313 }
314 i = _settingWaiters.erase(i);
315 } else {
316 ++i;
317 }
318 }
319 _waitTimer.cancel();
320 showNext();
321 }
322
showGrouped()323 void System::showGrouped() {
324 Expects(_manager != nullptr);
325
326 if (const auto session = findSession(_lastHistorySessionId)) {
327 if (const auto lastItem = session->data().message(_lastHistoryItemId)) {
328 _waitForAllGroupedTimer.cancel();
329 _manager->showNotification(lastItem, _lastForwardedCount);
330 _lastForwardedCount = 0;
331 _lastHistoryItemId = FullMsgId();
332 _lastHistorySessionId = 0;
333 }
334 }
335 }
336
showNext()337 void System::showNext() {
338 Expects(_manager != nullptr);
339
340 if (App::quitting()) {
341 return;
342 }
343
344 const auto isSameGroup = [=](HistoryItem *item) {
345 if (!_lastHistorySessionId || !_lastHistoryItemId || !item) {
346 return false;
347 } else if (item->history()->session().uniqueId()
348 != _lastHistorySessionId) {
349 return false;
350 }
351 const auto lastItem = item->history()->owner().message(
352 _lastHistoryItemId);
353 if (lastItem) {
354 return (lastItem->groupId() == item->groupId())
355 || (lastItem->author() == item->author());
356 }
357 return false;
358 };
359
360 auto ms = crl::now(), nextAlert = crl::time(0);
361 bool alert = false;
362 for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
363 while (!i->second.empty() && i->second.begin()->first <= ms) {
364 const auto peer = i->first->peer;
365 const auto peerUnknown = peer->owner().notifyMuteUnknown(peer);
366 const auto peerAlert = !peerUnknown
367 && !peer->owner().notifyIsMuted(peer);
368 const auto from = i->second.begin()->second;
369 const auto fromUnknown = (!from
370 || peer->owner().notifyMuteUnknown(from));
371 const auto fromAlert = !fromUnknown
372 && !peer->owner().notifyIsMuted(from);
373 if (peerAlert || fromAlert) {
374 alert = true;
375 }
376 while (!i->second.empty()
377 && i->second.begin()->first <= ms + kMinimalAlertDelay) {
378 i->second.erase(i->second.begin());
379 }
380 }
381 if (i->second.empty()) {
382 i = _whenAlerts.erase(i);
383 } else {
384 if (!nextAlert || nextAlert > i->second.begin()->first) {
385 nextAlert = i->second.begin()->first;
386 }
387 ++i;
388 }
389 }
390 const auto &settings = Core::App().settings();
391 if (alert) {
392 if (settings.flashBounceNotify() && !_manager->skipFlashBounce()) {
393 if (const auto window = Core::App().activeWindow()) {
394 if (const auto handle = window->widget()->windowHandle()) {
395 handle->alert(kSystemAlertDuration);
396 // (handle, SLOT(_q_clearAlert())); in the future.
397 }
398 }
399 }
400 if (settings.soundNotify() && !_manager->skipAudio()) {
401 ensureSoundCreated();
402 _soundTrack->playOnce();
403 Media::Player::mixer()->suppressAll(_soundTrack->getLengthMs());
404 Media::Player::mixer()->faderOnTimer();
405 }
406 }
407
408 if (_waiters.empty() || !settings.desktopNotify() || _manager->skipToast()) {
409 if (nextAlert) {
410 _waitTimer.callOnce(nextAlert - ms);
411 }
412 return;
413 }
414
415 while (true) {
416 auto next = 0LL;
417 HistoryItem *notifyItem = nullptr;
418 History *notifyHistory = nullptr;
419 for (auto i = _waiters.begin(); i != _waiters.end();) {
420 const auto history = i->first;
421 if (history->currentNotification() && history->currentNotification()->id != i->second.msg) {
422 auto j = _whenMaps.find(history);
423 if (j == _whenMaps.end()) {
424 history->clearNotifications();
425 i = _waiters.erase(i);
426 continue;
427 }
428 do {
429 auto k = j->second.find(history->currentNotification()->id);
430 if (k != j->second.cend()) {
431 i->second.msg = k->first;
432 i->second.when = k->second;
433 break;
434 }
435 history->skipNotification();
436 } while (history->currentNotification());
437 }
438 if (!history->currentNotification()) {
439 _whenMaps.remove(history);
440 i = _waiters.erase(i);
441 continue;
442 }
443 auto when = i->second.when;
444 if (!notifyItem || next > when) {
445 next = when;
446 notifyItem = history->currentNotification();
447 notifyHistory = history;
448 }
449 ++i;
450 }
451 if (notifyItem) {
452 if (next > ms) {
453 if (nextAlert && nextAlert < next) {
454 next = nextAlert;
455 nextAlert = 0;
456 }
457 _waitTimer.callOnce(next - ms);
458 break;
459 } else {
460 const auto isForwarded = notifyItem->Has<HistoryMessageForwarded>();
461 const auto isAlbum = notifyItem->groupId();
462
463 auto groupedItem = (isForwarded || isAlbum) ? notifyItem : nullptr; // forwarded and album notify grouping
464 auto forwardedCount = isForwarded ? 1 : 0;
465
466 const auto history = notifyItem->history();
467 const auto j = _whenMaps.find(history);
468 if (j == _whenMaps.cend()) {
469 history->clearNotifications();
470 } else {
471 auto nextNotify = (HistoryItem*)nullptr;
472 do {
473 history->skipNotification();
474 if (!history->hasNotification()) {
475 break;
476 }
477
478 j->second.remove((groupedItem ? groupedItem : notifyItem)->id);
479 do {
480 const auto k = j->second.find(history->currentNotification()->id);
481 if (k != j->second.cend()) {
482 nextNotify = history->currentNotification();
483 _waiters.emplace(notifyHistory, Waiter{
484 .msg = k->first,
485 .when = k->second
486 });
487 break;
488 }
489 history->skipNotification();
490 } while (history->hasNotification());
491 if (nextNotify) {
492 if (groupedItem) {
493 const auto canNextBeGrouped = (isForwarded && nextNotify->Has<HistoryMessageForwarded>())
494 || (isAlbum && nextNotify->groupId());
495 const auto nextItem = canNextBeGrouped ? nextNotify : nullptr;
496 if (nextItem
497 && qAbs(int64(nextItem->date()) - int64(groupedItem->date())) < 2) {
498 if (isForwarded
499 && groupedItem->author() == nextItem->author()) {
500 ++forwardedCount;
501 groupedItem = nextItem;
502 continue;
503 }
504 if (isAlbum
505 && groupedItem->groupId() == nextItem->groupId()) {
506 groupedItem = nextItem;
507 continue;
508 }
509 }
510 }
511 nextNotify = nullptr;
512 }
513 } while (nextNotify);
514 }
515
516 if (!_lastHistoryItemId && groupedItem) {
517 _lastHistorySessionId = groupedItem->history()->session().uniqueId();
518 _lastHistoryItemId = groupedItem->fullId();
519 }
520
521 // If the current notification is grouped.
522 if (isAlbum || isForwarded) {
523 // If the previous notification is grouped
524 // then reset the timer.
525 if (_waitForAllGroupedTimer.isActive()) {
526 _waitForAllGroupedTimer.cancel();
527 // If this is not the same group
528 // then show the previous group immediately.
529 if (!isSameGroup(groupedItem)) {
530 showGrouped();
531 }
532 }
533 // We have to wait until all the messages in this group are loaded.
534 _lastForwardedCount += forwardedCount;
535 _lastHistorySessionId = groupedItem->history()->session().uniqueId();
536 _lastHistoryItemId = groupedItem->fullId();
537 _waitForAllGroupedTimer.callOnce(kWaitingForAllGroupedDelay);
538 } else {
539 // If the current notification is not grouped
540 // then there is no reason to wait for the timer
541 // to show the previous notification.
542 showGrouped();
543 _manager->showNotification(notifyItem, forwardedCount);
544 }
545
546 if (!history->hasNotification()) {
547 _waiters.remove(history);
548 _whenMaps.remove(history);
549 continue;
550 }
551 }
552 } else {
553 break;
554 }
555 }
556 if (nextAlert) {
557 _waitTimer.callOnce(nextAlert - ms);
558 }
559 }
560
ensureSoundCreated()561 void System::ensureSoundCreated() {
562 if (_soundTrack) {
563 return;
564 }
565
566 _soundTrack = Media::Audio::Current().createTrack();
567 _soundTrack->fillFromFile(
568 Core::App().settings().getSoundPath(qsl("msg_incoming")));
569 }
570
updateAll()571 void System::updateAll() {
572 if (_manager) {
573 _manager->updateAll();
574 }
575 }
576
settingsChanged() const577 rpl::producer<ChangeType> System::settingsChanged() const {
578 return _settingsChanged.events();
579 }
580
notifySettingsChanged(ChangeType type)581 void System::notifySettingsChanged(ChangeType type) {
582 return _settingsChanged.fire(std::move(type));
583 }
584
getNotificationOptions(HistoryItem * item) const585 Manager::DisplayOptions Manager::getNotificationOptions(
586 HistoryItem *item) const {
587 const auto hideEverything = Core::App().passcodeLocked()
588 || forceHideDetails();
589
590 const auto view = Core::App().settings().notifyView();
591 DisplayOptions result;
592 result.hideNameAndPhoto = hideEverything
593 || (view > Core::Settings::NotifyView::ShowName);
594 result.hideMessageText = hideEverything
595 || (view > Core::Settings::NotifyView::ShowPreview);
596 result.hideMarkAsRead = result.hideMessageText
597 || !item
598 || ((item->out() || item->history()->peer->isSelf())
599 && item->isFromScheduled());
600 result.hideReplyButton = result.hideMarkAsRead
601 || !item->history()->peer->canWrite()
602 || item->history()->peer->isBroadcast()
603 || (item->history()->peer->slowmodeSecondsLeft() > 0);
604 return result;
605 }
606
addTargetAccountName(const QString & title,not_null<Main::Session * > session)607 QString Manager::addTargetAccountName(
608 const QString &title,
609 not_null<Main::Session*> session) {
610 const auto add = [&] {
611 for (const auto &[index, account] : Core::App().domain().accounts()) {
612 if (const auto other = account->maybeSession()) {
613 if (other != session) {
614 return true;
615 }
616 }
617 }
618 return false;
619 }();
620 return add
621 ? (title
622 + accountNameSeparator()
623 + (session->user()->username.isEmpty()
624 ? session->user()->name
625 : session->user()->username))
626 : title;
627 }
628
accountNameSeparator()629 QString Manager::accountNameSeparator() {
630 return QString::fromUtf8(" \xE2\x9E\x9C ");
631 }
632
notificationActivated(NotificationId id,const TextWithTags & reply)633 void Manager::notificationActivated(
634 NotificationId id,
635 const TextWithTags &reply) {
636 onBeforeNotificationActivated(id);
637 if (const auto session = system()->findSession(id.full.sessionId)) {
638 if (session->windows().empty()) {
639 Core::App().domain().activate(&session->account());
640 }
641 if (!session->windows().empty()) {
642 const auto window = session->windows().front();
643 const auto history = session->data().history(id.full.peerId);
644 if (!reply.text.isEmpty()) {
645 const auto replyToId = (id.msgId > 0
646 && !history->peer->isUser())
647 ? id.msgId
648 : 0;
649 auto draft = std::make_unique<Data::Draft>(
650 reply,
651 replyToId,
652 MessageCursor{
653 int(reply.text.size()),
654 int(reply.text.size()),
655 QFIXED_MAX,
656 },
657 Data::PreviewState::Allowed);
658 history->setLocalDraft(std::move(draft));
659 }
660 window->widget()->showFromTray();
661 window->widget()->reActivateWindow();
662 if (Core::App().passcodeLocked()) {
663 window->widget()->setInnerFocus();
664 system()->clearAll();
665 } else {
666 openNotificationMessage(history, id.msgId);
667 }
668 onAfterNotificationActivated(id, window);
669 }
670 }
671 }
672
openNotificationMessage(not_null<History * > history,MsgId messageId)673 void Manager::openNotificationMessage(
674 not_null<History*> history,
675 MsgId messageId) {
676 const auto openExactlyMessage = [&] {
677 if (history->peer->isUser() || history->peer->isChannel()) {
678 return false;
679 }
680 const auto item = history->owner().message(
681 history->channelId(),
682 messageId);
683 if (!item || !item->isRegular() || !item->mentionsMe()) {
684 return false;
685 }
686 return true;
687 }();
688 if (openExactlyMessage) {
689 Ui::showPeerHistory(history, messageId);
690 } else {
691 Ui::showPeerHistory(history, ShowAtUnreadMsgId);
692 }
693 system()->clearFromHistory(history);
694 }
695
notificationReplied(NotificationId id,const TextWithTags & reply)696 void Manager::notificationReplied(
697 NotificationId id,
698 const TextWithTags &reply) {
699 if (!id.full.sessionId || !id.full.peerId) {
700 return;
701 }
702
703 const auto session = system()->findSession(id.full.sessionId);
704 if (!session) {
705 return;
706 }
707 const auto history = session->data().history(id.full.peerId);
708
709 auto message = Api::MessageToSend(history);
710 message.textWithTags = reply;
711 message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
712 ? id.msgId
713 : 0;
714 message.action.clearDraft = false;
715 history->session().api().sendMessage(std::move(message));
716
717 const auto item = history->owner().message(
718 history->channelId(),
719 id.msgId);
720 if (item && item->isUnreadMention() && !item->isUnreadMedia()) {
721 history->session().api().markMediaRead(item);
722 }
723 }
724
doShowNotification(not_null<HistoryItem * > item,int forwardedCount)725 void NativeManager::doShowNotification(
726 not_null<HistoryItem*> item,
727 int forwardedCount) {
728 const auto options = getNotificationOptions(item);
729
730 const auto peer = item->history()->peer;
731 const auto scheduled = !options.hideNameAndPhoto
732 && (item->out() || peer->isSelf())
733 && item->isFromScheduled();
734 const auto title = options.hideNameAndPhoto
735 ? qsl("Telegram Desktop")
736 : (scheduled && peer->isSelf())
737 ? tr::lng_notification_reminder(tr::now)
738 : peer->name;
739 const auto fullTitle = addTargetAccountName(title, &peer->session());
740 const auto subtitle = options.hideNameAndPhoto
741 ? QString()
742 : item->notificationHeader();
743 const auto text = options.hideMessageText
744 ? tr::lng_notification_preview(tr::now)
745 : (forwardedCount < 2
746 ? (item->groupId()
747 ? tr::lng_in_dlg_album(tr::now)
748 : item->notificationText())
749 : tr::lng_forward_messages(tr::now, lt_count, forwardedCount));
750
751 // #TODO optimize
752 auto userpicView = item->history()->peer->createUserpicView();
753 doShowNativeNotification(
754 item->history()->peer,
755 userpicView,
756 item->id,
757 scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
758 subtitle,
759 text,
760 options);
761 }
762
forceHideDetails() const763 bool NativeManager::forceHideDetails() const {
764 return Core::App().screenIsLocked();
765 }
766
767 System::~System() = default;
768
WrapFromScheduled(const QString & text)769 QString WrapFromScheduled(const QString &text) {
770 return QString::fromUtf8("\xF0\x9F\x93\x85 ") + text;
771 }
772
773 } // namespace Notifications
774 } // namespace Window
775