| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "window/notifications_manager_default.h"
- #include "platform/platform_notifications_manager.h"
- #include "platform/platform_specific.h"
- #include "core/application.h"
- #include "core/ui_integration.h"
- #include "chat_helpers/message_field.h"
- #include "lang/lang_keys.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/platform/ui_platform_utility.h"
- #include "ui/text/text_options.h"
- #include "ui/text/text_utilities.h"
- #include "ui/emoji_config.h"
- #include "ui/empty_userpic.h"
- #include "ui/painter.h"
- #include "ui/power_saving.h"
- #include "ui/ui_utility.h"
- #include "data/data_session.h"
- #include "data/data_forum_topic.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "dialogs/ui/dialogs_layout.h"
- #include "window/window_controller.h"
- #include "storage/file_download.h"
- #include "main/main_session.h"
- #include "main/main_account.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/view/history_view_item_preview.h"
- #include "base/platform/base_platform_last_input.h"
- #include "base/call_delayed.h"
- #include "styles/style_dialogs.h"
- #include "styles/style_layers.h"
- #include "styles/style_window.h"
- #include <QtGui/QGuiApplication>
- #include <QtGui/QScreen>
- namespace Window {
- namespace Notifications {
- namespace Default {
- namespace {
- [[nodiscard]] QPoint notificationStartPosition() {
- const auto corner = Core::App().settings().notificationsCorner();
- const auto window = Core::App().activePrimaryWindow();
- const auto r = window
- ? window->widget()->desktopRect()
- : QGuiApplication::primaryScreen()->availableGeometry();
- const auto isLeft = Core::Settings::IsLeftCorner(corner);
- const auto isTop = Core::Settings::IsTopCorner(corner);
- const auto x = (isLeft == rtl())
- ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX)
- : (r.x() + st::notifyDeltaX);
- const auto y = isTop ? r.y() : (r.y() + r.height());
- return QPoint(x, y);
- }
- internal::Widget::Direction notificationShiftDirection() {
- auto isTop = Core::Settings::IsTopCorner(Core::App().settings().notificationsCorner());
- return isTop ? internal::Widget::Direction::Down : internal::Widget::Direction::Up;
- }
- } // namespace
- std::unique_ptr<Manager> Create(System *system) {
- return std::make_unique<Manager>(system);
- }
- Manager::Manager(System *system)
- : Notifications::Manager(system)
- , _inputCheckTimer([=] { checkLastInput(); }) {
- system->settingsChanged(
- ) | rpl::start_with_next([=](ChangeType change) {
- settingsChanged(change);
- }, _lifetime);
- }
- Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
- : history(fields.item->history())
- , topicRootId(fields.item->topicRootId())
- , peer(history->peer)
- , reaction(fields.reactionId)
- , author(!fields.reactionFrom
- ? fields.item->notificationHeader()
- : (fields.reactionFrom != peer)
- ? fields.reactionFrom->name()
- : QString())
- , item((fields.forwardedCount < 2) ? fields.item.get() : nullptr)
- , forwardedCount(fields.forwardedCount)
- , fromScheduled(reaction.empty() && (fields.item->out() || peer->isSelf())
- && fields.item->isFromScheduled()) {
- }
- QPixmap Manager::hiddenUserpicPlaceholder() const {
- if (_hiddenUserpicPlaceholder.isNull()) {
- const auto ratio = style::DevicePixelRatio();
- _hiddenUserpicPlaceholder = Ui::PixmapFromImage(
- LogoNoMargin().scaled(
- st::notifyPhotoSize * ratio,
- st::notifyPhotoSize * ratio,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation));
- _hiddenUserpicPlaceholder.setDevicePixelRatio(ratio);
- }
- return _hiddenUserpicPlaceholder;
- }
- bool Manager::hasReplyingNotification() const {
- for (const auto ¬ification : _notifications) {
- if (notification->isReplying()) {
- return true;
- }
- }
- return false;
- }
- void Manager::settingsChanged(ChangeType change) {
- if (change == ChangeType::Corner) {
- auto startPosition = notificationStartPosition();
- auto shiftDirection = notificationShiftDirection();
- for (const auto ¬ification : _notifications) {
- notification->updatePosition(startPosition, shiftDirection);
- }
- if (_hideAll) {
- _hideAll->updatePosition(startPosition, shiftDirection);
- }
- } else if (change == ChangeType::MaxCount) {
- int allow = Core::App().settings().notificationsCount();
- for (int i = _notifications.size(); i != 0;) {
- auto ¬ification = _notifications[--i];
- if (notification->isUnlinked()) continue;
- if (--allow < 0) {
- notification->unlinkHistory();
- }
- }
- if (allow > 0) {
- for (int i = 0; i != allow; ++i) {
- showNextFromQueue();
- }
- }
- } else if ((change == ChangeType::DemoIsShown)
- || (change == ChangeType::DemoIsHidden)) {
- _demoIsShown = (change == ChangeType::DemoIsShown);
- _demoMasterOpacity.start(
- [=] { demoMasterOpacityCallback(); },
- _demoIsShown ? 1. : 0.,
- _demoIsShown ? 0. : 1.,
- st::notifyFastAnim);
- }
- }
- void Manager::demoMasterOpacityCallback() {
- for (const auto ¬ification : _notifications) {
- notification->updateOpacity();
- }
- if (_hideAll) {
- _hideAll->updateOpacity();
- }
- }
- float64 Manager::demoMasterOpacity() const {
- return _demoMasterOpacity.value(_demoIsShown ? 0. : 1.);
- }
- void Manager::checkLastInput() {
- auto replying = hasReplyingNotification();
- auto waiting = false;
- const auto lastInputTime = base::Platform::LastUserInputTimeSupported()
- ? std::make_optional(Core::App().lastNonIdleTime())
- : std::nullopt;
- for (const auto ¬ification : _notifications) {
- if (!notification->checkLastInput(replying, lastInputTime)) {
- waiting = true;
- }
- }
- if (waiting) {
- _inputCheckTimer.callOnce(300);
- }
- }
- void Manager::startAllHiding() {
- if (!hasReplyingNotification()) {
- for (const auto ¬ification : _notifications) {
- notification->startHiding();
- }
- if (_hideAll && _queuedNotifications.size() < 2) {
- _hideAll->startHiding();
- }
- }
- }
- void Manager::stopAllHiding() {
- for (const auto ¬ification : _notifications) {
- notification->stopHiding();
- }
- if (_hideAll) {
- _hideAll->stopHiding();
- }
- }
- void Manager::showNextFromQueue() {
- auto guard = gsl::finally([this] {
- if (_positionsOutdated) {
- moveWidgets();
- }
- });
- if (_queuedNotifications.empty()) {
- return;
- }
- int count = Core::App().settings().notificationsCount();
- for (const auto ¬ification : _notifications) {
- if (notification->isUnlinked()) continue;
- --count;
- }
- if (count <= 0) {
- return;
- }
- auto startPosition = notificationStartPosition();
- auto startShift = 0;
- auto shiftDirection = notificationShiftDirection();
- do {
- auto queued = _queuedNotifications.front();
- _queuedNotifications.pop_front();
- subscribeToSession(&queued.history->session());
- _notifications.push_back(std::make_unique<Notification>(
- this,
- queued.history,
- queued.topicRootId,
- queued.peer,
- queued.author,
- queued.item,
- queued.reaction,
- queued.forwardedCount,
- queued.fromScheduled,
- startPosition,
- startShift,
- shiftDirection));
- --count;
- } while (count > 0 && !_queuedNotifications.empty());
- _positionsOutdated = true;
- checkLastInput();
- }
- void Manager::subscribeToSession(not_null<Main::Session*> session) {
- auto i = _subscriptions.find(session);
- if (i == _subscriptions.end()) {
- i = _subscriptions.emplace(session).first;
- session->account().sessionChanges(
- ) | rpl::start_with_next([=] {
- _subscriptions.remove(session);
- }, i->second.lifetime);
- } else if (i->second.subscription) {
- return;
- }
- session->downloaderTaskFinished(
- ) | rpl::start_with_next([=] {
- auto found = false;
- for (const auto ¬ification : _notifications) {
- if (const auto history = notification->maybeHistory()) {
- if (&history->session() == session) {
- notification->updatePeerPhoto();
- found = true;
- }
- }
- }
- if (!found) {
- _subscriptions[session].subscription.destroy();
- }
- }, i->second.subscription);
- }
- void Manager::moveWidgets() {
- auto shift = st::notifyDeltaY;
- int lastShift = 0, lastShiftCurrent = 0, count = 0;
- for (int i = _notifications.size(); i != 0;) {
- auto ¬ification = _notifications[--i];
- if (notification->isUnlinked()) continue;
- notification->changeShift(shift);
- shift += notification->height() + st::notifyDeltaY;
- lastShiftCurrent = notification->currentShift();
- lastShift = shift;
- ++count;
- }
- if (count > 1 || !_queuedNotifications.empty()) {
- if (!_hideAll) {
- _hideAll = std::make_unique<HideAllButton>(this, notificationStartPosition(), lastShiftCurrent, notificationShiftDirection());
- }
- _hideAll->changeShift(lastShift);
- _hideAll->stopHiding();
- } else if (_hideAll) {
- _hideAll->startHidingFast();
- }
- }
- void Manager::changeNotificationHeight(Notification *notification, int newHeight) {
- auto deltaHeight = newHeight - notification->height();
- if (!deltaHeight) return;
- notification->addToHeight(deltaHeight);
- auto it = std::find_if(_notifications.cbegin(), _notifications.cend(), [notification](auto &item) {
- return (item.get() == notification);
- });
- if (it != _notifications.cend()) {
- for (auto i = _notifications.cbegin(); i != it; ++i) {
- auto ¬ification = *i;
- if (notification->isUnlinked()) continue;
- notification->addToShift(deltaHeight);
- }
- }
- if (_hideAll) {
- _hideAll->addToShift(deltaHeight);
- }
- }
- void Manager::unlinkFromShown(Notification *remove) {
- if (remove) {
- if (remove->unlinkHistory()) {
- _positionsOutdated = true;
- }
- }
- showNextFromQueue();
- }
- void Manager::removeWidget(internal::Widget *remove) {
- if (remove == _hideAll.get()) {
- _hideAll.reset();
- } else if (remove) {
- const auto it = ranges::find(
- _notifications,
- remove,
- &std::unique_ptr<Notification>::get);
- if (it != end(_notifications)) {
- _notifications.erase(it);
- _positionsOutdated = true;
- }
- }
- showNextFromQueue();
- }
- void Manager::doShowNotification(NotificationFields &&fields) {
- _queuedNotifications.emplace_back(std::move(fields));
- showNextFromQueue();
- }
- void Manager::doClearAll() {
- _queuedNotifications.clear();
- for (const auto ¬ification : _notifications) {
- notification->unlinkHistory();
- }
- showNextFromQueue();
- }
- void Manager::doClearAllFast() {
- _queuedNotifications.clear();
- base::take(_notifications);
- base::take(_hideAll);
- }
- void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
- const auto history = topic->history();
- const auto topicRootId = topic->rootId();
- for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
- if (i->history == history && i->topicRootId == topicRootId) {
- i = _queuedNotifications.erase(i);
- } else {
- ++i;
- }
- }
- for (const auto ¬ification : _notifications) {
- if (notification->unlinkHistory(history, topicRootId)) {
- _positionsOutdated = true;
- }
- }
- showNextFromQueue();
- }
- void Manager::doClearFromHistory(not_null<History*> history) {
- for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
- if (i->history == history) {
- i = _queuedNotifications.erase(i);
- } else {
- ++i;
- }
- }
- for (const auto ¬ification : _notifications) {
- if (notification->unlinkHistory(history)) {
- _positionsOutdated = true;
- }
- }
- 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 ¬ification : _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);
- }), _queuedNotifications.cend());
- auto showNext = false;
- for (const auto ¬ification : _notifications) {
- if (notification->unlinkItem(item)) {
- showNext = true;
- }
- }
- if (showNext) {
- // This call invalidates _notifications iterators.
- showNextFromQueue();
- }
- }
- bool Manager::doSkipToast() const {
- return Platform::Notifications::SkipToastForCustom();
- }
- void Manager::doMaybePlaySound(Fn<void()> playSound) {
- Platform::Notifications::MaybePlaySoundForCustom(std::move(playSound));
- }
- void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
- Platform::Notifications::MaybeFlashBounceForCustom(std::move(flashBounce));
- }
- void Manager::doUpdateAll() {
- for (const auto ¬ification : _notifications) {
- notification->updateNotifyDisplay();
- }
- }
- Manager::~Manager() {
- clearAllFast();
- }
- namespace internal {
- Widget::Widget(
- not_null<Manager*> manager,
- QPoint startPosition,
- int shift,
- Direction shiftDirection)
- : _manager(manager)
- , _startPosition(startPosition)
- , _direction(shiftDirection)
- , _shift(shift)
- , _shiftAnimation([=](crl::time now) {
- return shiftAnimationCallback(now);
- }) {
- setWindowOpacity(0.);
- setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint)
- | Qt::WindowStaysOnTopHint
- | Qt::BypassWindowManagerHint
- | Qt::NoDropShadowWindowHint
- | Qt::Tool);
- setAttribute(Qt::WA_MacAlwaysShowToolWindow);
- setAttribute(Qt::WA_OpaquePaintEvent);
- Ui::Platform::InitOnTopPanel(this);
- _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
- }
- void Widget::opacityAnimationCallback() {
- updateOpacity();
- update();
- if (!_a_opacity.animating() && _hiding) {
- if (underMouse()) {
- // The notification is leaving from under the cursor, but in such case leave hook is not
- // triggered automatically. But we still want the manager to start hiding notifications
- // (see #28813).
- manager()->startAllHiding();
- }
- manager()->removeWidget(this); // Deletes `this`
- }
- }
- bool Widget::shiftAnimationCallback(crl::time now) {
- if (anim::Disabled()) {
- now += st::notifyFastAnim;
- }
- const auto dt = (now - _shiftAnimation.started())
- / float64(st::notifyFastAnim);
- if (dt >= 1.) {
- _shift.finish();
- } else {
- _shift.update(dt, anim::linear);
- }
- moveByShift();
- return (dt < 1.);
- }
- void Widget::hideSlow() {
- if (anim::Disabled()) {
- _hiding = true;
- base::call_delayed(
- st::notifySlowHide,
- this,
- [=, guard = _hidingDelayed.make_guard()] {
- if (guard && _hiding) {
- hideFast();
- }
- });
- } else {
- hideAnimated(st::notifySlowHide, anim::easeInCirc);
- }
- }
- void Widget::hideFast() {
- hideAnimated(st::notifyFastAnim, anim::linear);
- }
- void Widget::hideStop() {
- if (_hiding) {
- _hiding = false;
- _hidingDelayed = {};
- _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
- }
- }
- void Widget::hideAnimated(float64 duration, const anim::transition &func) {
- _hiding = true;
- // Stop the previous animation so as to make sure that the notification
- // is fully restored before hiding it again.
- // Relates to https://github.com/telegramdesktop/tdesktop/issues/28811.
- _a_opacity.stop();
- _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., duration, func);
- }
- void Widget::updateOpacity() {
- setWindowOpacity(_a_opacity.value(_hiding ? 0. : 1.) * _manager->demoMasterOpacity());
- }
- void Widget::changeShift(int top) {
- _shift.start(top);
- _shiftAnimation.start();
- }
- void Widget::updatePosition(QPoint startPosition, Direction shiftDirection) {
- _startPosition = startPosition;
- _direction = shiftDirection;
- moveByShift();
- }
- void Widget::addToHeight(int add) {
- auto newHeight = height() + add;
- auto newPosition = computePosition(newHeight);
- updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight);
- Ui::ForceFullRepaintSync(this);
- }
- void Widget::updateGeometry(int x, int y, int width, int height) {
- move(x, y);
- setFixedSize(width, height);
- update();
- }
- void Widget::addToShift(int add) {
- _shift.add(add);
- moveByShift();
- }
- void Widget::moveByShift() {
- move(computePosition(height()));
- }
- QPoint Widget::computePosition(int height) const {
- auto realShift = qRound(_shift.current());
- if (_direction == Direction::Up) {
- realShift = -realShift - height;
- }
- return QPoint(_startPosition.x(), _startPosition.y() + realShift);
- }
- Background::Background(QWidget *parent) : TWidget(parent) {
- setAttribute(Qt::WA_OpaquePaintEvent);
- }
- void Background::paintEvent(QPaintEvent *e) {
- Painter p(this);
- p.fillRect(rect(), st::notificationBg);
- p.fillRect(0, 0, st::notifyBorderWidth, height(), st::notifyBorder);
- p.fillRect(width() - st::notifyBorderWidth, 0, st::notifyBorderWidth, height(), st::notifyBorder);
- p.fillRect(st::notifyBorderWidth, height() - st::notifyBorderWidth, width() - 2 * st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
- }
- Notification::Notification(
- not_null<Manager*> manager,
- not_null<History*> history,
- MsgId topicRootId,
- not_null<PeerData*> peer,
- const QString &author,
- HistoryItem *item,
- const Data::ReactionId &reaction,
- int forwardedCount,
- bool fromScheduled,
- QPoint startPosition,
- int shift,
- Direction shiftDirection)
- : Widget(manager, startPosition, shift, shiftDirection)
- , _peer(peer)
- , _started(crl::now())
- , _history(history)
- , _topic(history->peer->forumTopicFor(topicRootId))
- , _topicRootId(topicRootId)
- , _userpicView(_peer->createUserpicView())
- , _author(author)
- , _reaction(reaction)
- , _item(item)
- , _forwardedCount(forwardedCount)
- , _fromScheduled(fromScheduled)
- , _close(this, st::notifyClose)
- , _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) {
- Lang::Updated(
- ) | rpl::start_with_next([=] {
- refreshLang();
- }, lifetime());
- if (_topic) {
- _topic->destroyed(
- ) | rpl::start_with_next([=] {
- unlinkHistory();
- }, lifetime());
- }
- auto position = computePosition(st::notifyMinHeight);
- updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight);
- _userpicLoaded = !Ui::PeerUserpicLoading(_userpicView);
- updateNotifyDisplay();
- _hideTimer.setSingleShot(true);
- connect(&_hideTimer, &QTimer::timeout, [=] { startHiding(); });
- _close->setClickedCallback([this] {
- unlinkHistoryInManager();
- });
- _close->setAcceptBoth(true);
- _close->moveToRight(st::notifyClosePos.x(), st::notifyClosePos.y());
- _close->show();
- _reply->setClickedCallback([this] {
- showReplyField();
- });
- _replyPadding = st::notifyMinHeight - st::notifyPhotoPos.y() - st::notifyPhotoSize;
- updateReplyGeometry();
- _reply->hide();
- prepareActionsCache();
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- updateNotifyDisplay();
- if (!_buttonsCache.isNull()) {
- prepareActionsCache();
- }
- update();
- if (_background) {
- _background->update();
- }
- }, lifetime());
- show();
- }
- void Notification::updateReplyGeometry() {
- _reply->moveToRight(_replyPadding, height() - _reply->height() - _replyPadding);
- }
- void Notification::refreshLang() {
- InvokeQueued(this, [this] { updateReplyGeometry(); });
- }
- void Notification::prepareActionsCache() {
- auto replyCache = Ui::GrabWidget(_reply);
- auto fadeWidth = st::notifyFadeRight.width();
- auto actionsTop = st::notifyTextTop + st::semiboldFont->height;
- auto replyRight = _replyPadding - st::notifyBorderWidth;
- auto actionsCacheWidth = _reply->width() + replyRight + fadeWidth;
- auto actionsCacheHeight = height() - actionsTop - st::notifyBorderWidth;
- auto actionsCacheImg = QImage(
- QSize(actionsCacheWidth, actionsCacheHeight)
- * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- actionsCacheImg.setDevicePixelRatio(style::DevicePixelRatio());
- actionsCacheImg.fill(Qt::transparent);
- {
- Painter p(&actionsCacheImg);
- st::notifyFadeRight.fill(p, style::rtlrect(0, 0, fadeWidth, actionsCacheHeight, actionsCacheWidth));
- p.fillRect(style::rtlrect(fadeWidth, 0, actionsCacheWidth - fadeWidth, actionsCacheHeight, actionsCacheWidth), st::notificationBg);
- p.drawPixmapRight(replyRight, _reply->y() - actionsTop, actionsCacheWidth, replyCache);
- }
- _buttonsCache = Ui::PixmapFromImage(std::move(actionsCacheImg));
- }
- bool Notification::checkLastInput(
- bool hasReplyingNotifications,
- std::optional<crl::time> lastInputTime) {
- if (!_waitingForInput) return true;
- using namespace Platform::Notifications;
- const auto waitForUserInput = WaitForInputForCustom()
- && lastInputTime.has_value()
- && (*lastInputTime <= _started);
- if (!waitForUserInput) {
- _waitingForInput = false;
- if (!hasReplyingNotifications) {
- _hideTimer.start(st::notifyWaitLongHide);
- }
- return true;
- }
- return false;
- }
- void Notification::replyResized() {
- changeHeight(st::notifyMinHeight + _replyArea->height() + st::notifyBorderWidth);
- }
- void Notification::replyCancel() {
- unlinkHistoryInManager();
- }
- void Notification::updateGeometry(int x, int y, int width, int height) {
- if (height > st::notifyMinHeight) {
- if (!_background) {
- _background.create(this);
- }
- _background->setGeometry(0, st::notifyMinHeight, width, height - st::notifyMinHeight);
- } else if (_background) {
- _background.destroy();
- }
- Widget::updateGeometry(x, y, width, height);
- }
- void Notification::paintEvent(QPaintEvent *e) {
- repaintText();
- Painter p(this);
- p.setClipRect(e->rect());
- p.drawImage(0, 0, _cache);
- auto buttonsTop = st::notifyTextTop + st::semiboldFont->height;
- if (a_actionsOpacity.animating()) {
- p.setOpacity(a_actionsOpacity.value(1.));
- p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
- } else if (_actionsVisible) {
- p.drawPixmapRight(st::notifyBorderWidth, buttonsTop, width(), _buttonsCache);
- }
- }
- void Notification::actionsOpacityCallback() {
- update();
- if (!a_actionsOpacity.animating() && _actionsVisible) {
- _reply->show();
- }
- }
- void Notification::customEmojiCallback() {
- if (_textsRepaintScheduled) {
- return;
- }
- _textsRepaintScheduled = true;
- crl::on_main(this, [=] { repaintText(); });
- }
- void Notification::repaintText() {
- if (!_textsRepaintScheduled) {
- return;
- }
- _textsRepaintScheduled = false;
- if (_cache.isNull()) {
- return;
- }
- Painter p(&_cache);
- const auto adjusted = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
- const auto skip = (adjusted - st::emojiSize + 1) / 2;
- const auto margin = QMargins{ skip, skip, skip, skip };
- p.fillRect(_titleRect.marginsAdded(margin), st::notificationBg);
- p.fillRect(_textRect.marginsAdded(margin), st::notificationBg);
- paintTitle(p);
- paintText(p);
- update();
- }
- void Notification::paintTitle(Painter &p) {
- p.setPen(st::dialogsNameFg);
- p.setFont(st::semiboldFont);
- _titleCache.draw(p, {
- .position = _titleRect.topLeft(),
- .availableWidth = _titleRect.width(),
- .palette = &st::dialogsTextPalette,
- .spoiler = Ui::Text::DefaultSpoilerCache(),
- .pausedEmoji = On(PowerSaving::kEmojiChat),
- .pausedSpoiler = On(PowerSaving::kChatSpoiler),
- .elisionLines = 1,
- });
- }
- void Notification::paintText(Painter &p) {
- p.setPen(st::dialogsTextFg);
- p.setFont(st::dialogsTextFont);
- _textCache.draw(p, {
- .position = _textRect.topLeft(),
- .availableWidth = _textRect.width(),
- .palette = &st::dialogsTextPalette,
- .spoiler = Ui::Text::DefaultSpoilerCache(),
- .pausedEmoji = On(PowerSaving::kEmojiChat),
- .pausedSpoiler = On(PowerSaving::kChatSpoiler),
- .elisionHeight = _textRect.height(),
- });
- }
- void Notification::updateNotifyDisplay() {
- if (!_history || (!_item && _forwardedCount < 2)) {
- return;
- }
- const auto options = manager()->getNotificationOptions(
- _item,
- (_reaction.empty()
- ? Data::ItemNotificationType::Message
- : Data::ItemNotificationType::Reaction));
- _hideReplyButton = options.hideReplyButton;
- int32 w = width(), h = height();
- auto img = QImage(
- size() * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- img.setDevicePixelRatio(style::DevicePixelRatio());
- img.fill(st::notificationBg->c);
- {
- Painter p(&img);
- p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder);
- if (!options.hideNameAndPhoto) {
- if (_fromScheduled && _history->peer->isSelf()) {
- Ui::EmptyUserpic::PaintSavedMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
- _userpicLoaded = true;
- } else if (_history->peer->isRepliesChat()) {
- Ui::EmptyUserpic::PaintRepliesMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
- _userpicLoaded = true;
- } else {
- _userpicView = _history->peer->createUserpicView();
- _history->peer->loadUserpic();
- _history->peer->paintUserpicLeft(p, _userpicView, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize);
- }
- } else {
- p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder());
- _userpicLoaded = true;
- }
- int32 itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width;
- QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::semiboldFont->height);
- const auto reminder = _fromScheduled && _history->peer->isSelf();
- if (!options.hideNameAndPhoto) {
- if (_fromScheduled) {
- static const auto emoji = Ui::Emoji::Find(QString::fromUtf8("\xF0\x9F\x93\x85"));
- const auto size = Ui::Emoji::GetSizeNormal()
- / style::DevicePixelRatio();
- const auto top = rectForName.top() + (st::semiboldFont->height - size) / 2;
- Ui::Emoji::Draw(p, emoji, Ui::Emoji::GetSizeNormal(), rectForName.left(), top);
- rectForName.setLeft(rectForName.left() + size + st::semiboldFont->spacew);
- }
- const auto chatTypeIcon = _topic
- ? nullptr
- : Dialogs::Ui::ChatTypeIcon(_history->peer);
- if (chatTypeIcon) {
- chatTypeIcon->paint(p, rectForName.topLeft(), w);
- rectForName.setLeft(rectForName.left()
- + chatTypeIcon->width()
- + st::dialogsChatTypeSkip);
- }
- }
- const auto composeText = !options.hideMessageText
- || (!_reaction.empty() && !options.hideNameAndPhoto);
- if (composeText) {
- auto old = base::take(_textCache);
- _textCache = Ui::Text::String(itemWidth);
- auto r = QRect(
- st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
- st::notifyItemTop + st::semiboldFont->height,
- itemWidth,
- 2 * st::dialogsTextFont->height);
- const auto text = !_reaction.empty()
- ? (!_author.isEmpty()
- ? Ui::Text::Colorized(_author).append(' ')
- : TextWithEntities()
- ).append(Manager::ComposeReactionNotification(
- _item,
- _reaction,
- options.hideMessageText))
- : _item
- ? _item->toPreview({
- .hideSender = reminder,
- .generateImages = false,
- .spoilerLoginCode = options.spoilerLoginCode,
- }).text
- : ((!_author.isEmpty()
- ? Ui::Text::Colorized(_author)
- : TextWithEntities()
- ).append(_forwardedCount > 1
- ? ('\n' + tr::lng_forward_messages(
- tr::now,
- lt_count,
- _forwardedCount))
- : QString()));
- const auto options = TextParseOptions{
- (TextParseColorized
- | TextParseMarkdown
- | (_forwardedCount > 1 ? TextParseMultiline : 0)),
- 0,
- 0,
- Qt::LayoutDirectionAuto,
- };
- const auto context = Core::TextContext({
- .session = &_history->session(),
- .repaint = [=] { customEmojiCallback(); },
- });
- _textCache.setMarkedText(
- st::dialogsTextStyle,
- text,
- options,
- context);
- _textRect = r;
- paintText(p);
- if (!_textCache.hasPersistentAnimation() && !_topic) {
- _textCache = Ui::Text::String();
- }
- } else {
- p.setFont(st::dialogsTextFont);
- p.setPen(st::dialogsTextFgService);
- p.drawText(
- st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
- st::notifyItemTop + st::semiboldFont->height + st::dialogsTextFont->ascent,
- st::dialogsTextFont->elided(
- tr::lng_notification_preview(tr::now),
- itemWidth));
- }
- const auto topicWithChat = [&]() -> TextWithEntities {
- const auto name = st::wrap_rtl(_history->peer->name());
- return _topic
- ? _topic->titleWithIcon().append(u" ("_q + name + ')')
- : TextWithEntities{ name };
- };
- auto title = options.hideNameAndPhoto
- ? TextWithEntities{ u"Telegram Desktop"_q }
- : reminder
- ? tr::lng_notification_reminder(tr::now, Ui::Text::WithEntities)
- : topicWithChat();
- const auto fullTitle = manager()->addTargetAccountName(
- std::move(title),
- &_history->session());
- const auto context = Core::TextContext({
- .session = &_history->session(),
- .repaint = [=] { customEmojiCallback(); },
- });
- _titleCache.setMarkedText(
- st::semiboldTextStyle,
- fullTitle,
- Ui::NameTextOptions(),
- context);
- _titleRect = rectForName;
- paintTitle(p);
- }
- _cache = std::move(img);
- if (!canReply()) {
- toggleActionButtons(false);
- }
- update();
- }
- void Notification::updatePeerPhoto() {
- if (_userpicLoaded) {
- return;
- }
- _userpicView = _peer->createUserpicView();
- if (Ui::PeerUserpicLoading(_userpicView)) {
- return;
- }
- _userpicLoaded = true;
- Painter p(&_cache);
- p.fillRect(
- style::rtlrect(
- QRect(
- st::notifyPhotoPos,
- QSize(st::notifyPhotoSize, st::notifyPhotoSize)),
- width()),
- st::notificationBg);
- _peer->paintUserpicLeft(
- p,
- _userpicView,
- st::notifyPhotoPos.x(),
- st::notifyPhotoPos.y(),
- width(),
- st::notifyPhotoSize);
- _userpicView = {};
- update();
- }
- bool Notification::unlinkItem(HistoryItem *deleted) {
- auto unlink = (_item && _item == deleted);
- if (unlink) {
- _item = nullptr;
- unlinkHistory();
- }
- return unlink;
- }
- bool Notification::canReply() const {
- return !_hideReplyButton
- && (_item != nullptr)
- && !Core::App().passcodeLocked()
- && (Core::App().settings().notifyView()
- <= Core::Settings::NotifyView::ShowPreview);
- }
- void Notification::unlinkHistoryInManager() {
- manager()->unlinkFromShown(this);
- }
- void Notification::toggleActionButtons(bool visible) {
- if (_actionsVisible != visible) {
- _actionsVisible = visible;
- a_actionsOpacity.start([this] { actionsOpacityCallback(); }, _actionsVisible ? 0. : 1., _actionsVisible ? 1. : 0., st::notifyActionsDuration);
- _reply->clearState();
- _reply->hide();
- }
- }
- void Notification::showReplyField() {
- if (!_item) {
- return;
- }
- raise();
- activateWindow();
- if (_replyArea) {
- _replyArea->setFocus();
- return;
- }
- stopHiding();
- _background.create(this);
- _background->setGeometry(0, st::notifyMinHeight, width(), st::notifySendReply.height + st::notifyBorderWidth);
- _background->show();
- _replyArea.create(
- this,
- st::notifyReplyArea,
- Ui::InputField::Mode::MultiLine,
- tr::lng_message_ph());
- _replyArea->resize(width() - st::notifySendReply.width - 2 * st::notifyBorderWidth, st::notifySendReply.height);
- _replyArea->moveToLeft(st::notifyBorderWidth, st::notifyMinHeight);
- _replyArea->show();
- _replyArea->setFocus();
- _replyArea->setMaxLength(MaxMessageSize);
- _replyArea->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
- InitMessageFieldHandlers({
- .session = &_item->history()->session(),
- .field = _replyArea.data(),
- });
- // Catch mouse press event to activate the window.
- QCoreApplication::instance()->installEventFilter(this);
- _replyArea->heightChanges(
- ) | rpl::start_with_next([=] {
- replyResized();
- }, _replyArea->lifetime());
- _replyArea->submits(
- ) | rpl::start_with_next([=] { sendReply(); }, _replyArea->lifetime());
- _replyArea->cancelled(
- ) | rpl::start_with_next([=] {
- replyCancel();
- }, _replyArea->lifetime());
- _replySend.create(this, st::notifySendReply);
- _replySend->moveToRight(st::notifyBorderWidth, st::notifyMinHeight);
- _replySend->show();
- _replySend->setClickedCallback([this] { sendReply(); });
- toggleActionButtons(false);
- replyResized();
- update();
- }
- void Notification::sendReply() {
- if (!_history) return;
- manager()->notificationReplied(
- myId(),
- _replyArea->getTextWithAppliedMarkdown());
- manager()->startAllHiding();
- }
- Notifications::Manager::NotificationId Notification::myId() const {
- if (!_history) {
- return {};
- }
- return { .contextId = {
- .sessionId = _history->session().uniqueId(),
- .peerId = _history->peer->id,
- .topicRootId = _topicRootId,
- }, .msgId = _item ? _item->id : ShowAtUnreadMsgId };
- }
- void Notification::changeHeight(int newHeight) {
- manager()->changeNotificationHeight(this, newHeight);
- }
- bool Notification::unlinkHistory(History *history, MsgId topicRootId) {
- const auto unlink = _history
- && (history == _history || !history)
- && (topicRootId == _topicRootId || !topicRootId);
- if (unlink) {
- hideFast();
- _history = nullptr;
- _topic = nullptr;
- _item = nullptr;
- }
- 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(QEnterEvent *e) {
- if (!_history) {
- return;
- }
- manager()->stopAllHiding();
- if (!_replyArea && canReply()) {
- toggleActionButtons(true);
- }
- }
- void Notification::leaveEventHook(QEvent *e) {
- if (!_history) {
- return;
- }
- manager()->startAllHiding();
- toggleActionButtons(false);
- }
- void Notification::startHiding() {
- if (!_history) return;
- hideSlow();
- }
- void Notification::mousePressEvent(QMouseEvent *e) {
- if (!_history) return;
- if (e->button() == Qt::RightButton) {
- unlinkHistoryInManager();
- } else {
- e->ignore();
- manager()->notificationActivated(myId());
- }
- }
- bool Notification::eventFilter(QObject *o, QEvent *e) {
- if (e->type() == QEvent::MouseButtonPress) {
- if (auto receiver = qobject_cast<QWidget*>(o)) {
- if (isAncestorOf(receiver)) {
- raise();
- activateWindow();
- }
- }
- }
- return false;
- }
- void Notification::stopHiding() {
- if (!_history) return;
- _hideTimer.stop();
- Widget::hideStop();
- }
- HideAllButton::HideAllButton(
- not_null<Manager*> manager,
- QPoint startPosition,
- int shift,
- Direction shiftDirection)
- : Widget(manager, startPosition, shift, shiftDirection) {
- setCursor(style::cur_pointer);
- auto position = computePosition(st::notifyHideAllHeight);
- updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight);
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- update();
- }, lifetime());
- show();
- }
- void HideAllButton::startHiding() {
- hideSlow();
- }
- void HideAllButton::startHidingFast() {
- hideFast();
- }
- void HideAllButton::stopHiding() {
- hideStop();
- }
- void HideAllButton::enterEventHook(QEnterEvent *e) {
- _mouseOver = true;
- update();
- }
- void HideAllButton::leaveEventHook(QEvent *e) {
- _mouseOver = false;
- update();
- }
- void HideAllButton::mousePressEvent(QMouseEvent *e) {
- _mouseDown = true;
- }
- void HideAllButton::mouseReleaseEvent(QMouseEvent *e) {
- auto mouseDown = base::take(_mouseDown);
- if (mouseDown && _mouseOver) {
- manager()->clearAll();
- }
- }
- void HideAllButton::paintEvent(QPaintEvent *e) {
- Painter p(this);
- p.setClipRect(e->rect());
- p.fillRect(rect(), _mouseOver ? st::lightButtonBgOver : st::lightButtonBg);
- p.fillRect(0, 0, width(), st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(0, height() - st::notifyBorderWidth, width(), st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder);
- p.fillRect(width() - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorderWidth, height() - 2 * st::notifyBorderWidth, st::notifyBorder);
- p.setFont(st::defaultLinkButton.font);
- p.setPen(_mouseOver ? st::lightButtonFgOver : st::lightButtonFg);
- p.drawText(rect(), tr::lng_notification_hide_all(tr::now), style::al_center);
- }
- } // namespace internal
- } // namespace Default
- } // namespace Notifications
- } // namespace Window
|