shift-shell/components/mobileshell/shellutil.cpp
Micah Stanley eba6074161 notifications: Implement popup notifications
This merge request implements a more mobile optimized solution for popup notification.
- 

The current controls are:
- Swipe up to move the notification to the notification center.
- Swipe left/right to dismiss the notification entirely.
- If multiple popup notifications are grouped together, tap on the bottom area to view them in a expanded view.

What still needs to be done:
- ~~For notification without a default action, tapping on them should probably open up the associated app.~~ Note: I think I will add this in a separate merge request as it probably should be the case regardless if the notification is a popup
- ~~Swiping down on a notification currently does nothing. Maybe we should map this to a notification action?~~ Note: I have some ideas I will try later, though for now, I will leave this action blank
- ~~The expanded view of notifications should be able to be dismissed by swiping up/down on the top/bottom of the list.~~ Note: Added
- Investigate further into how to remove the current desktop popup notifications.
- ~~Code clean up.~~ Note: The code is at least a bit better

Single popup notification:

![notification_1](/uploads/63d12be6da1dd2676de17940dcadbdfa/notification_1.gif)

Multiple popup notifications:

![notification_2](/uploads/907a14b772f66f46040c28342f4dcf02/notification_2.gif)

Multiple popup notifications in the expanded view:

![notification_3](/uploads/9a7cd09a6bb8a0f7ee70e5bcf7c29c6b/notification_3.gif)

Any feedback would be greatly appreciated.
2024-11-07 16:13:06 +00:00

148 lines
4.5 KiB
C++

/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2018 Bhushan Shah <bshah@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "shellutil.h"
#include <KConfigGroup>
#include <KFileUtils>
#include <KLocalizedString>
#include <KNotification>
#include <KNotificationJobUiDelegate>
#include <QQuickWindow>
#include <QDBusPendingReply>
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QProcess>
#include <QTextDocumentFragment>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
#define FORMAT24H "HH:mm:ss"
ShellUtil::ShellUtil(QObject *parent) : QObject{parent}, m_localeConfig {
KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig) } {
}
void ShellUtil::stackItemBefore(QQuickItem *item1, QQuickItem *item2)
{
if (!item1 || !item2 || item1 == item2 || item1->parentItem() != item2->parentItem()) {
return;
}
item1->stackBefore(item2);
}
void ShellUtil::stackItemAfter(QQuickItem *item1, QQuickItem *item2)
{
if (!item1 || !item2 || item1 == item2 || item1->parentItem() != item2->parentItem()) {
return;
}
item1->stackAfter(item2);
}
void ShellUtil::executeCommand(const QString &command)
{
qWarning() << "Executing" << command;
const QStringList commandAndArguments = QProcess::splitCommand(command);
QProcess::startDetached(commandAndArguments.front(), commandAndArguments.mid(1));
}
bool ShellUtil::isSystem24HourFormat()
{
// only load the config watcher if this function is actually used once
if (!m_localeConfigWatcher) {
m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig);
// watch for changes to locale config, to update 12/24 hour time
connect(m_localeConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group) -> void {
if (group.name() == "Locale") {
// we have to reparse for new changes (from system settings)
m_localeConfig->reparseConfiguration();
Q_EMIT isSystem24HourFormatChanged();
}
});
}
KConfigGroup localeSettings = KConfigGroup(m_localeConfig, "Locale");
QString timeFormat = localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H));
return timeFormat == QStringLiteral(FORMAT24H);
}
void ShellUtil::launchApp(const QString &storageId)
{
KService::Ptr service = KService::serviceByStorageId(storageId);
if (!service) {
qWarning() << "Could not find" << storageId;
return;
}
auto job = new KIO::ApplicationLauncherJob(service, this);
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
job->start();
}
void ShellUtil::setInputTransparent(QQuickWindow *window, bool transparent) {
if (window) {
Qt::WindowFlags flags = window->flags();
if (transparent) {
flags |= Qt::WindowTransparentForInput;
} else {
flags &= ~Qt::WindowTransparentForInput;
}
window->setFlags(flags);
}
}
void ShellUtil::setInputRegion(QWindow *window, const QRect &region) {
auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
if (!waylandWindow) {
qWarning() << "Failed to retrieve Wayland window handle.";
return;
}
auto waylandDisplay = dynamic_cast<QtWaylandClient::QWaylandDisplay *>(waylandWindow->display());
if (!waylandDisplay) {
qWarning() << "Failed to retrieve Wayland display.";
return;
}
wl_compositor *compositorResource = static_cast<wl_compositor *>(waylandDisplay->compositor()->object());
if (!compositorResource) {
qWarning() << "Failed to retrieve compositor.";
return;
}
wl_surface *surface = waylandWindow->wlSurface();
if (!surface) {
qWarning() << "Failed to retrieve Wayland surface.";
return;
}
if (region.isEmpty()) {
wl_surface_set_input_region(surface, nullptr);
} else {
wl_region *inputRegion = wl_compositor_create_region(compositorResource);
wl_region_add(inputRegion, region.x(), region.y(), region.width(), region.height());
wl_surface_set_input_region(surface, inputRegion);
wl_region_destroy(inputRegion);
}
wl_surface_commit(surface);
}
QString ShellUtil::toPlainText(QString htmlString) {
return QTextDocumentFragment::fromHtml(htmlString).toPlainText();
}