1138 lines
31 KiB
C++
1138 lines
31 KiB
C++
/*
|
|
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 "lang/lang_keys.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/input_fields.h"
|
|
#include "ui/platform/ui_platform_utility.h"
|
|
#include "ui/text/text_options.h"
|
|
#include "ui/emoji_config.h"
|
|
#include "ui/empty_userpic.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "dialogs/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 "base/platform/base_platform_last_input.h"
|
|
#include "base/call_delayed.h"
|
|
#include "facades.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 {
|
|
|
|
QPoint notificationStartPosition() {
|
|
const auto corner = Core::App().settings().notificationsCorner();
|
|
const auto window = Core::App().activeWindow();
|
|
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(
|
|
not_null<HistoryItem*> item,
|
|
int forwardedCount)
|
|
: history(item->history())
|
|
, peer(history->peer)
|
|
, author(item->notificationHeader())
|
|
, item((forwardedCount < 2) ? item.get() : nullptr)
|
|
, forwardedCount(forwardedCount)
|
|
, fromScheduled((item->out() || peer->isSelf()) && item->isFromScheduled()) {
|
|
}
|
|
|
|
QPixmap Manager::hiddenUserpicPlaceholder() const {
|
|
if (_hiddenUserpicPlaceholder.isNull()) {
|
|
_hiddenUserpicPlaceholder = Ui::PixmapFromImage(
|
|
Core::App().logoNoMargin().scaled(
|
|
st::notifyPhotoSize,
|
|
st::notifyPhotoSize,
|
|
Qt::IgnoreAspectRatio,
|
|
Qt::SmoothTransformation));
|
|
_hiddenUserpicPlaceholder.setDevicePixelRatio(cRetinaFactor());
|
|
}
|
|
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()) {
|
|
int notHidingCount = 0;
|
|
for_const (auto ¬ification, _notifications) {
|
|
if (notification->isShowing()) {
|
|
++notHidingCount;
|
|
} else {
|
|
notification->startHiding();
|
|
}
|
|
}
|
|
notHidingCount += _queuedNotifications.size();
|
|
if (_hideAll && notHidingCount < 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.peer,
|
|
queued.author,
|
|
queued.item,
|
|
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(
|
|
not_null<HistoryItem*> item,
|
|
int forwardedCount) {
|
|
_queuedNotifications.emplace_back(item, forwardedCount);
|
|
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::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::doSkipAudio() const {
|
|
return Platform::Notifications::SkipAudioForCustom();
|
|
}
|
|
|
|
bool Manager::doSkipToast() const {
|
|
return Platform::Notifications::SkipToastForCustom();
|
|
}
|
|
|
|
bool Manager::doSkipFlashBounce() const {
|
|
return Platform::Notifications::SkipFlashBounceForCustom();
|
|
}
|
|
|
|
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::destroyDelayed() {
|
|
hide();
|
|
if (_deleted) return;
|
|
_deleted = true;
|
|
|
|
// Ubuntu has a lag if a fully transparent widget is destroyed immediately.
|
|
base::call_delayed(1000, this, [this] {
|
|
manager()->removeWidget(this);
|
|
});
|
|
}
|
|
|
|
void Widget::opacityAnimationCallback() {
|
|
updateOpacity();
|
|
update();
|
|
if (!_a_opacity.animating() && _hiding) {
|
|
destroyDelayed();
|
|
}
|
|
}
|
|
|
|
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;
|
|
_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::Platform::UpdateOverlayed(this);
|
|
}
|
|
|
|
void Widget::updateGeometry(int x, int y, int width, int height) {
|
|
setGeometry(x, y, width, height);
|
|
setMinimumSize(QSize(width, height));
|
|
setMaximumSize(QSize(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,
|
|
not_null<PeerData*> peer,
|
|
const QString &author,
|
|
HistoryItem *item,
|
|
int forwardedCount,
|
|
bool fromScheduled,
|
|
QPoint startPosition,
|
|
int shift,
|
|
Direction shiftDirection)
|
|
: Widget(manager, startPosition, shift, shiftDirection)
|
|
, _peer(peer)
|
|
, _started(crl::now())
|
|
, _history(history)
|
|
, _userpicView(_peer->createUserpicView())
|
|
, _author(author)
|
|
, _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());
|
|
|
|
auto position = computePosition(st::notifyMinHeight);
|
|
updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight);
|
|
|
|
_userpicLoaded = !_userpicView || (_userpicView->image() != nullptr);
|
|
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::msgNameFont->height;
|
|
auto replyRight = _replyPadding - st::notifyBorderWidth;
|
|
auto actionsCacheWidth = _reply->width() + replyRight + fadeWidth;
|
|
auto actionsCacheHeight = height() - actionsTop - st::notifyBorderWidth;
|
|
auto actionsCacheImg = QImage(QSize(actionsCacheWidth, actionsCacheHeight) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
actionsCacheImg.setDevicePixelRatio(cRetinaFactor());
|
|
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;
|
|
|
|
const auto waitForUserInput = 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) {
|
|
Painter p(this);
|
|
p.setClipRect(e->rect());
|
|
p.drawPixmap(0, 0, _cache);
|
|
|
|
auto buttonsTop = st::notifyTextTop + st::msgNameFont->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::updateNotifyDisplay() {
|
|
if (!_history || (!_item && _forwardedCount < 2)) return;
|
|
|
|
const auto options = manager()->getNotificationOptions(_item);
|
|
_hideReplyButton = options.hideReplyButton;
|
|
|
|
int32 w = width(), h = height();
|
|
QImage img(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
img.setDevicePixelRatio(cRetinaFactor());
|
|
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::msgNameFont->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() / cIntRetinaFactor();
|
|
const auto top = rectForName.top() + (st::msgNameFont->height - size) / 2;
|
|
Ui::Emoji::Draw(p, emoji, Ui::Emoji::GetSizeNormal(), rectForName.left(), top);
|
|
rectForName.setLeft(rectForName.left() + size + st::msgNameFont->spacew);
|
|
}
|
|
if (const auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_history->peer, false, false)) {
|
|
chatTypeIcon->paint(p, rectForName.topLeft(), w);
|
|
rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
|
|
}
|
|
}
|
|
|
|
if (!options.hideMessageText) {
|
|
auto itemTextCache = Ui::Text::String(itemWidth);
|
|
auto r = QRect(
|
|
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
|
|
st::notifyItemTop + st::msgNameFont->height,
|
|
itemWidth,
|
|
2 * st::dialogsTextFont->height);
|
|
p.setTextPalette(st::dialogsTextPalette);
|
|
p.setPen(st::dialogsTextFg);
|
|
p.setFont(st::dialogsTextFont);
|
|
const auto text = _item
|
|
? _item->inDialogsText(reminder
|
|
? HistoryItem::DrawInDialog::WithoutSender
|
|
: HistoryItem::DrawInDialog::Normal)
|
|
: ((!_author.isEmpty()
|
|
? textcmdLink(1, _author)
|
|
: QString())
|
|
+ (_forwardedCount > 1
|
|
? ('\n' + tr::lng_forward_messages(
|
|
tr::now,
|
|
lt_count,
|
|
_forwardedCount))
|
|
: QString()));
|
|
const auto Options = TextParseOptions{
|
|
TextParseRichText
|
|
| (_forwardedCount > 1 ? TextParseMultiline : 0),
|
|
0,
|
|
0,
|
|
Qt::LayoutDirectionAuto,
|
|
};
|
|
itemTextCache.setText(st::dialogsTextStyle, text, Options);
|
|
itemTextCache.drawElided(
|
|
p,
|
|
r.left(),
|
|
r.top(),
|
|
r.width(),
|
|
r.height() / st::dialogsTextFont->height);
|
|
p.restoreTextPalette();
|
|
} else {
|
|
p.setFont(st::dialogsTextFont);
|
|
p.setPen(st::dialogsTextFgService);
|
|
p.drawText(
|
|
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
|
|
st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent,
|
|
st::dialogsTextFont->elided(
|
|
tr::lng_notification_preview(tr::now),
|
|
itemWidth));
|
|
}
|
|
|
|
p.setPen(st::dialogsNameFg);
|
|
Ui::Text::String titleText;
|
|
const auto title = options.hideNameAndPhoto
|
|
? qsl("Telegram Desktop")
|
|
: reminder
|
|
? tr::lng_notification_reminder(tr::now)
|
|
: _history->peer->nameText().toString();
|
|
const auto fullTitle = manager()->addTargetAccountName(
|
|
title,
|
|
&_history->session());
|
|
titleText.setText(st::msgNameStyle, fullTitle, Ui::NameTextOptions());
|
|
titleText.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
|
|
}
|
|
|
|
_cache = Ui::PixmapFromImage(std::move(img));
|
|
if (!canReply()) {
|
|
toggleActionButtons(false);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void Notification::updatePeerPhoto() {
|
|
if (_userpicLoaded) {
|
|
return;
|
|
}
|
|
_userpicView = _peer->createUserpicView();
|
|
if (_userpicView && !_userpicView->image()) {
|
|
return;
|
|
}
|
|
_userpicLoaded = true;
|
|
|
|
auto img = _cache.toImage();
|
|
{
|
|
Painter p(&img);
|
|
_peer->paintUserpicLeft(
|
|
p,
|
|
_userpicView,
|
|
st::notifyPhotoPos.x(),
|
|
st::notifyPhotoPos.y(),
|
|
width(),
|
|
st::notifyPhotoSize);
|
|
}
|
|
_cache = Ui::PixmapFromImage(std::move(img));
|
|
_userpicView = nullptr;
|
|
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);
|
|
_replyArea->setInstantReplaces(Ui::InstantReplaces::Default());
|
|
_replyArea->setInstantReplacesEnabled(
|
|
Core::App().settings().replaceEmojiValue());
|
|
_replyArea->setMarkdownReplacesEnabled(rpl::single(true));
|
|
|
|
// Catch mouse press event to activate the window.
|
|
QCoreApplication::instance()->installEventFilter(this);
|
|
connect(_replyArea, &Ui::InputField::resized, [=] { replyResized(); });
|
|
connect(_replyArea, &Ui::InputField::submitted, [=] { sendReply(); });
|
|
connect(_replyArea, &Ui::InputField::cancelled, [=] { replyCancel(); });
|
|
|
|
_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 { .full = {
|
|
.sessionId = _history->session().uniqueId(),
|
|
.peerId = _history->peer->id
|
|
}, .msgId = _item ? _item->id : ShowAtUnreadMsgId };
|
|
}
|
|
|
|
void Notification::changeHeight(int newHeight) {
|
|
manager()->changeNotificationHeight(this, newHeight);
|
|
}
|
|
|
|
bool Notification::unlinkHistory(History *history) {
|
|
const auto unlink = _history && (history == _history || !history);
|
|
if (unlink) {
|
|
hideFast();
|
|
_history = 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(QEvent *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);
|
|
hide();
|
|
createWinId();
|
|
|
|
style::PaletteChanged(
|
|
) | rpl::start_with_next([=] {
|
|
update();
|
|
}, lifetime());
|
|
|
|
show();
|
|
}
|
|
|
|
void HideAllButton::startHiding() {
|
|
hideSlow();
|
|
}
|
|
|
|
void HideAllButton::startHidingFast() {
|
|
hideFast();
|
|
}
|
|
|
|
void HideAllButton::stopHiding() {
|
|
hideStop();
|
|
}
|
|
|
|
void HideAllButton::enterEventHook(QEvent *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
|