Bug#1109732: unblock: plasma-mobile/6.3.6-1
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: plasma-mobile@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Control: affects -1 + src:plasma-mobile
User: release.debian.org@packages.debian.org
Usertags: unblock
Dear Release Team,
please unblock package plasma-mobile.
[ Reason ]
It contains the following changes:
* New upstream release (6.3.6).
- Envmanager: Use BorderlessMaximizedWindows option.
- Shell: Fix Compact applet display popup when fullRepresentation is null.
- Homescreens/folio: Block propagation if edit mode is active to avoid
widget to trigger event.
- Load minimized Quicksettings as Async.
- Paginatemodel: Avoid to disconnect if model is the same because it's not
reconnected after.
- Fix mistake on paginatemodel.
- Quicksettings: Use synchrone component loading instead of asynchrone to
avoid concurrent bug.
- Envmanager: Write options as immutable, and add kdeglobals.
- Envmanager: Add overlay configs through XDG_CONFIG_DIRS.
- Taskswitcher: Fix task close icon colour in light mode.
- Folio: Ensure y anim signals don't get emitted if value didn't change.
- Notifications: Fix do-not-disturb.
- Notifications: Don't factor action drawer state in expiry.
- Notifications: do not expire notificationa after invoking action.
* Backport upstream commits:
- bin: Don't force QT_QPA_PLATFORM=wayland so apps that prefer x11 can use
it similar to what the desktop session does. [8b8c9585]
- applicationlistmodel: Space out application list refreshes to avoid log
spam and freezes. [7d9054f7]
- Disables unexpected gesture navigation activating the task switcher when
Action Drawer, Notification Popup Drawer, or VolumeOSD are visible
[013d0345]
- GestureNavigation: Fix Action Drawer State being out of Sync on First
Start. [9707f503]
- folio: Fix recurring AppDelegate null errors in the logs. [c5f0d15b]
- notification list: fix notification scrolling within action drawer and
lockscreen. [e4323f4e]
- statusbar: Don't show internet icon and network loading indicator at
same time. [23797660]
- modem manager plugin: Make calls nonblocking to not freeze UI when
called. [6e0dca97]
- Folio: Fix Settings Component drawer bug where it can end up displayed
at the same time as the application drawer. [a452bc30]
* Relax inter-plasma versioned dependency constraint so we can upload
only 6.3.6 packages that have actual code changes.
[ Tests ]
Tested basic session feature on a laptop: app launcher, app switcher /
closing, desktop shortcuts, various configuration panels including
network.
[ Risks ]
Only contains upstream point release for the Plasma 6.3 branch and
backports of upstream commits. More fixes could easily be backported or
the changes reverted.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
Thanks !
unblock plasma-mobile/6.3.6-1
diff -Nru plasma-mobile-6.3.5/bin/startplasmamobile.in plasma-mobile-6.3.6/bin/startplasmamobile.in
--- plasma-mobile-6.3.5/bin/startplasmamobile.in 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/bin/startplasmamobile.in 2025-07-08 13:47:14.000000000 +0200
@@ -17,6 +17,9 @@
export PLASMA_INTEGRATION_USE_PORTAL=1
export PLASMA_PLATFORM=phone:handset
+# Set ~/.config/plasma-mobile/... as location for default mobile configs (i.e. envmanager generated)
+export XDG_CONFIG_DIRS="$HOME/.config/plasma-mobile:/etc/xdg:$XDG_CONFIG_DIRS"
+
# if coredumpd knows about the dumps, make sure drkonqi catches them
if grep -q '/systemd-coredump' /proc/sys/kernel/core_pattern
then
diff -Nru plasma-mobile-6.3.5/CMakeLists.txt plasma-mobile-6.3.6/CMakeLists.txt
--- plasma-mobile-6.3.5/CMakeLists.txt 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/CMakeLists.txt 2025-07-08 13:47:14.000000000 +0200
@@ -7,10 +7,10 @@
cmake_minimum_required(VERSION 3.24)
project(plasma-mobile)
-set(PROJECT_VERSION "6.3.5")
+set(PROJECT_VERSION "6.3.6")
set(PROJECT_VERSION_MAJOR 6)
-set(PROJECT_DEP_VERSION "6.3.5")
+set(PROJECT_DEP_VERSION "6.3.6")
set(QT_MIN_VERSION "6.7.0")
set(KF6_MIN_VERSION "6.10.0")
set(KDE_COMPILERSETTINGS_LEVEL "5.82")
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/actiondrawer/quicksettings/QuickSettings.qml plasma-mobile-6.3.6/components/mobileshell/qml/actiondrawer/quicksettings/QuickSettings.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/actiondrawer/quicksettings/QuickSettings.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/mobileshell/qml/actiondrawer/quicksettings/QuickSettings.qml 2025-07-08 13:47:14.000000000 +0200
@@ -96,28 +96,12 @@
sourceModel: root.quickSettingsModel
pageSize: Math.min(root.pageSize, root.minimizedColumns) // HACK: just root.minimizedColumns appears to end up with an empty model?
}
- delegate: MobileShell.BaseItem {
+ delegate: Loader {
required property var modelData
- implicitHeight: root.minimizedRowHeight
- implicitWidth: root.minimizedColumnWidth
- horizontalPadding: (width - Kirigami.Units.gridUnit * 3) / 2
- verticalPadding: (height - Kirigami.Units.gridUnit * 3) / 2
-
- contentItem: QuickSettingsMinimizedDelegate {
- restrictedPermissions: actionDrawer.restrictedPermissions
-
- text: modelData.text
- status: modelData.status
- icon: modelData.icon
- enabled: modelData.enabled
- settingsCommand: modelData.settingsCommand
- toggleFunction: modelData.toggle
+ asynchronous: true
- onCloseRequested: {
- actionDrawer.close();
- }
- }
+ sourceComponent: quickSettingComponentMinimized
}
}
}
@@ -163,7 +147,7 @@
asynchronous: true
- sourceComponent: quickSettingComponent
+ sourceComponent: quickSettingComponentFull
}
}
}
@@ -211,9 +195,36 @@
}
}
- // Quick setting component
+ // Quick setting component minimized
+ Component {
+ id: quickSettingComponentMinimized
+
+ MobileShell.BaseItem {
+ implicitHeight: root.minimizedRowHeight
+ implicitWidth: root.minimizedColumnWidth
+ horizontalPadding: (width - Kirigami.Units.gridUnit * 3) / 2
+ verticalPadding: (height - Kirigami.Units.gridUnit * 3) / 2
+
+ contentItem: QuickSettingsMinimizedDelegate {
+ restrictedPermissions: actionDrawer.restrictedPermissions
+
+ text: modelData.text
+ status: modelData.status
+ icon: modelData.icon
+ enabled: modelData.enabled
+ settingsCommand: modelData.settingsCommand
+ toggleFunction: modelData.toggle
+
+ onCloseRequested: {
+ actionDrawer.close();
+ }
+ }
+ }
+ }
+
+ // Quick setting component full
Component {
- id: quickSettingComponent
+ id: quickSettingComponentFull
MobileShell.BaseItem {
height: root.rowHeight
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/NotificationPopupProvider.qml plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/NotificationPopupProvider.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/NotificationPopupProvider.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/NotificationPopupProvider.qml 2025-07-08 13:47:14.000000000 +0200
@@ -62,10 +62,15 @@
}
}
+ // TODO use pulseaudio-qt for this once it becomes a framework
+ property QtObject __pulseAudio: Loader {
+ source: "PulseAudio.qml"
+ }
+
property bool inhibited: false
onInhibitedChanged: {
- var pa = pulseAudio.item;
+ var pa = __pulseAudio.item;
if (!pa) {
return;
}
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/NotificationPopup.qml plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/NotificationPopup.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/NotificationPopup.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/NotificationPopup.qml 2025-07-08 13:47:14.000000000 +0200
@@ -47,7 +47,6 @@
onTriggered: {
visible = true;
updateNotificationPopups();
- checkActionDrawerOpened();
}
}
@@ -70,7 +69,7 @@
}
return true;
}
- onTriggered: notificationPopup.closePopup()
+ onTriggered: notificationPopup.closePopup(popupIndex);
}
// the value of how much time is left, normalized from 1 to 0
@@ -172,9 +171,6 @@
popupNotifications.currentPopupIndex = popupIndex;
}
}
- // if the action drawer opens, it is best to dismiss all popup notifications
- onIsActionDrawerOpenChanged: checkActionDrawerOpened()
-
property bool isActionDrawerOpen: MobileShellState.ShellDBusClient.isActionDrawerOpen
property bool waiting: true
@@ -206,16 +202,6 @@
return model.timeout;
}
- // check if the action drawer is opened and the popup is fully created
- // if so, close the popup with a scale effect
- function checkActionDrawerOpened() {
- if (isActionDrawerOpen && popupNotifications.objectAt(popupIndex)) {
- notificationPopup.expired();
- keyboardInteractivity = LayerShell.Window.KeyboardInteractivityNone;
- notificationItem.state = "closeWithScale";
- }
- }
-
// show the top most notification in the list and move the rest to the popup drawer
function updateNotificationPopups() {
if (popupCount != 1) {
@@ -447,8 +433,8 @@
preventDismissTimeout = true;
if (dismissTimeout) {
notificationPopup.dismissClicked();
- } else if (!isActionDrawerOpen) {
- notificationPopup.expired();
+ } else {
+ notificationPopup.expired();
}
}
}
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/PulseAudio.qml plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/PulseAudio.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/notificationpopup/PulseAudio.qml 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/components/mobileshell/qml/notificationpopup/PulseAudio.qml 2025-07-08 13:47:14.000000000 +0200
@@ -0,0 +1,39 @@
+/*
+ SPDX-FileCopyrightText: 2017, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+import QtQuick 2.2
+
+import org.kde.plasma.private.volume 0.1
+
+QtObject {
+ id: pulseAudio
+
+ readonly property string notificationStreamId: "sink-input-by-media-role:event"
+
+ property QtObject notificationStream
+
+ property QtObject instantiator: Instantiator {
+ model: StreamRestoreModel {}
+
+ delegate: QtObject {
+ readonly property string name: Name
+ readonly property bool muted: Muted
+
+ function mute() {
+ Muted = true
+ }
+ function unmute() {
+ Muted = false
+ }
+ }
+
+ onObjectAdded: (index, object) => {
+ if (object.name === notificationStreamId) {
+ notificationStream = object;
+ }
+ }
+ }
+}
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/widgets/notifications/BaseNotificationItem.qml plasma-mobile-6.3.6/components/mobileshell/qml/widgets/notifications/BaseNotificationItem.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/widgets/notifications/BaseNotificationItem.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/mobileshell/qml/widgets/notifications/BaseNotificationItem.qml 2025-07-08 13:47:14.000000000 +0200
@@ -158,7 +158,6 @@
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName, NotificationManager.Close); // notification closes
}
}
- expire();
}
if (notificationItem.requestToInvoke) {
diff -Nru plasma-mobile-6.3.5/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml plasma-mobile-6.3.6/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml
--- plasma-mobile-6.3.5/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml 2025-07-08 13:47:14.000000000 +0200
@@ -15,6 +15,7 @@
import org.kde.plasma.plasma5support 2.0 as P5Support
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
+import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.components 3.0 as PlasmaComponents3
@@ -103,13 +104,12 @@
if (doNotDisturbModeEnabled) {
notificationSettings.defaults();
} else {
+ // We just have a global toggle, so set it to a really long time (in this case, a year)
var until = new Date();
-
until.setFullYear(until.getFullYear() + 1);
notificationSettings.notificationsInhibitedUntil = until;
}
-
notificationSettings.save();
}
@@ -120,6 +120,17 @@
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_notifications");
}
+ // Implement listening to system "do not disturb" requests
+ Connections {
+ target: MobileShellState.ShellDBusClient
+
+ function onDoNotDisturbChanged() {
+ if (root.doNotDisturbModeEnabled !== MobileShellState.ShellDBusClient.doNotDisturb) {
+ root.toggleDoNotDisturbMode();
+ }
+ }
+ }
+
P5Support.DataSource {
id: timeDataSource
engine: "time"
diff -Nru plasma-mobile-6.3.5/components/quicksettingsplugin/paginatemodel.cpp plasma-mobile-6.3.6/components/quicksettingsplugin/paginatemodel.cpp
--- plasma-mobile-6.3.5/components/quicksettingsplugin/paginatemodel.cpp 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/quicksettingsplugin/paginatemodel.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -73,41 +73,43 @@
void PaginateModel::setSourceModel(QAbstractItemModel *model)
{
+ if (model == d->m_sourceModel) {
+ return;
+ }
+
if (d->m_sourceModel) {
disconnect(d->m_sourceModel, nullptr, this, nullptr);
}
- if (model != d->m_sourceModel) {
- beginResetModel();
- d->m_sourceModel = model;
- if (model) {
- connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &PaginateModel::_k_sourceRowsAboutToBeInserted);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::_k_sourceRowsInserted);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PaginateModel::_k_sourceRowsAboutToBeRemoved);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::_k_sourceRowsRemoved);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &PaginateModel::_k_sourceRowsAboutToBeMoved);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsMoved, this, &PaginateModel::_k_sourceRowsMoved);
-
- connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, &PaginateModel::_k_sourceColumnsAboutToBeInserted);
- connect(d->m_sourceModel, &QAbstractItemModel::columnsInserted, this, &PaginateModel::_k_sourceColumnsInserted);
- connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, &PaginateModel::_k_sourceColumnsAboutToBeRemoved);
- connect(d->m_sourceModel, &QAbstractItemModel::columnsRemoved, this, &PaginateModel::_k_sourceColumnsRemoved);
- connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this, &PaginateModel::_k_sourceColumnsAboutToBeMoved);
- connect(d->m_sourceModel, &QAbstractItemModel::columnsMoved, this, &PaginateModel::_k_sourceColumnsMoved);
-
- connect(d->m_sourceModel, &QAbstractItemModel::dataChanged, this, &PaginateModel::_k_sourceDataChanged);
- connect(d->m_sourceModel, &QAbstractItemModel::headerDataChanged, this, &PaginateModel::_k_sourceHeaderDataChanged);
-
- connect(d->m_sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &PaginateModel::_k_sourceModelAboutToBeReset);
- connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::_k_sourceModelReset);
-
- connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::pageCountChanged);
- connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::pageCountChanged);
- connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::pageCountChanged);
- }
- endResetModel();
- Q_EMIT sourceModelChanged();
+ beginResetModel();
+ d->m_sourceModel = model;
+ if (model) {
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &PaginateModel::_k_sourceRowsAboutToBeInserted);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::_k_sourceRowsInserted);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PaginateModel::_k_sourceRowsAboutToBeRemoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::_k_sourceRowsRemoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &PaginateModel::_k_sourceRowsAboutToBeMoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsMoved, this, &PaginateModel::_k_sourceRowsMoved);
+
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, &PaginateModel::_k_sourceColumnsAboutToBeInserted);
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsInserted, this, &PaginateModel::_k_sourceColumnsInserted);
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, &PaginateModel::_k_sourceColumnsAboutToBeRemoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsRemoved, this, &PaginateModel::_k_sourceColumnsRemoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this, &PaginateModel::_k_sourceColumnsAboutToBeMoved);
+ connect(d->m_sourceModel, &QAbstractItemModel::columnsMoved, this, &PaginateModel::_k_sourceColumnsMoved);
+
+ connect(d->m_sourceModel, &QAbstractItemModel::dataChanged, this, &PaginateModel::_k_sourceDataChanged);
+ connect(d->m_sourceModel, &QAbstractItemModel::headerDataChanged, this, &PaginateModel::_k_sourceHeaderDataChanged);
+
+ connect(d->m_sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &PaginateModel::_k_sourceModelAboutToBeReset);
+ connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::_k_sourceModelReset);
+
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::pageCountChanged);
+ connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::pageCountChanged);
+ connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::pageCountChanged);
}
+ endResetModel();
+ Q_EMIT sourceModelChanged();
}
QHash<int, QByteArray> PaginateModel::roleNames() const
@@ -377,7 +379,7 @@
if (canSizeChange()) {
endRemoveRows();
} else {
- beginResetModel();
+ endResetModel();
}
}
diff -Nru plasma-mobile-6.3.5/components/quicksettingsplugin/quicksettingsmodel.cpp plasma-mobile-6.3.6/components/quicksettingsplugin/quicksettingsmodel.cpp
--- plasma-mobile-6.3.5/components/quicksettingsplugin/quicksettingsmodel.cpp 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/quicksettingsplugin/quicksettingsmodel.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -82,6 +82,50 @@
return QVariant::fromValue<QObject *>(obj);
}
+QuickSetting *QuickSettingsModel::loadQuickSettingComponent(KPluginMetaData metaData)
+{
+ // Load KPackage
+ const KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML", QFileInfo(metaData.fileName()).path());
+ if (!package.isValid()) {
+ return nullptr;
+ }
+
+ // Create translation context
+ QQmlEngine *engine = qmlEngine(this);
+ KLocalizedContext *i18nContext = new KLocalizedContext(engine);
+ i18nContext->setTranslationDomain(QLatin1String("plasma_") + metaData.pluginId());
+ engine->rootContext()->setContextObject(i18nContext);
+
+ // Create component synchronously
+ QQmlComponent component(engine, package.fileUrl("mainscript"));
+ if (component.isError()) {
+ qWarning() << "Unable to load quick setting element:" << metaData.pluginId();
+ for (auto error : component.errors()) {
+ qWarning() << error;
+ }
+ return nullptr;
+ }
+
+ // Create object
+ QObject *object = component.create(engine->rootContext());
+ if (!object) {
+ qWarning() << "Unable to create quick setting element:" << metaData.pluginId();
+ return nullptr;
+ }
+
+ auto createdSetting = qobject_cast<QuickSetting *>(object);
+ if (createdSetting && createdSetting->isAvailable()) {
+ // Connect availability signal
+ connect(createdSetting, &QuickSetting::availableChanged, this, [this, metaData, createdSetting]() {
+ availabilityChanged(metaData, createdSetting);
+ });
+ return createdSetting;
+ } else {
+ object->deleteLater();
+ return nullptr;
+ }
+}
+
void QuickSettingsModel::loadQuickSettings()
{
if (!m_loaded) {
@@ -96,10 +140,12 @@
m_quickSettings.clear();
m_quickSettingsMetaData.clear();
- // Loop through enabled quick settings and start loading them
+ // Loop through enabled quick settings and load them synchronously
for (const auto &metaData : m_savedQuickSettings->enabledQuickSettingsModel()->list()) {
- // false - Ensure row insertion signals aren't emitted (since we are resetting the model)
- loadQuickSetting(metaData, false);
+ if (auto *setting = loadQuickSettingComponent(metaData)) {
+ m_quickSettings.append(setting);
+ m_quickSettingsMetaData.append(metaData);
+ }
}
endResetModel();
@@ -112,31 +158,8 @@
return;
}
- // Load KPackage
- const KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML", QFileInfo(metaData.fileName()).path());
- if (!package.isValid()) {
- return;
- }
-
- // Create translation context
- QQmlEngine *engine = qmlEngine(this);
- KLocalizedContext *i18nContext = new KLocalizedContext(engine);
- i18nContext->setTranslationDomain(QLatin1String("plasma_") + metaData.pluginId());
- engine->rootContext()->setContextObject(i18nContext);
-
- QQmlComponent *component = new QQmlComponent(engine, this);
-
- // Load QML from KPackage async
- component->loadUrl(package.fileUrl("mainscript"), QQmlComponent::Asynchronous);
-
- if (component->isLoading()) {
- // Listen to load completion
- connect(component, &QQmlComponent::statusChanged, this, [this, metaData, component, engine]() {
- afterQuickSettingLoad(engine, metaData, component, true);
- });
- } else {
- // Only emit insertion signal if we aren't resetting the model
- afterQuickSettingLoad(engine, metaData, component, emitInsertSignal);
+ if (auto *setting = loadQuickSettingComponent(metaData)) {
+ insertQuickSettingToModel(metaData, setting, emitInsertSignal);
}
}
@@ -151,41 +174,6 @@
}
}
-void QuickSettingsModel::afterQuickSettingLoad(QQmlEngine *engine, KPluginMetaData metaData, QQmlComponent *component, bool emitInsertSignal)
-{
- // Create quicksetting component
- QObject *object = component->create(engine->rootContext());
- if (!object) {
- qWarning() << "Unable to load quick setting element:" << metaData.pluginId();
- component->deleteLater();
- return;
- }
-
- if (component->isError()) {
- // Print errors
- qWarning() << "Unable to load quick setting element:" << metaData.pluginId();
- for (auto error : component->errors()) {
- qWarning() << error;
- }
-
- component->deleteLater();
- } else if (component->isReady()) {
- component->deleteLater();
-
- auto createdSetting = qobject_cast<QuickSetting *>(object);
-
- // Connect availability signal to insert/remove quicksetting into model
- connect(createdSetting, &QuickSetting::availableChanged, this, [this, metaData, createdSetting]() {
- availabilityChanged(metaData, createdSetting);
- });
-
- // Add quicksetting to model if available
- if (createdSetting->isAvailable()) {
- insertQuickSettingToModel(metaData, createdSetting, emitInsertSignal);
- }
- }
-}
-
void QuickSettingsModel::insertQuickSettingToModel(KPluginMetaData metaData, QuickSetting *quickSetting, bool emitInsertSignal)
{
// Insert into correct position based on the saved quick settings order
diff -Nru plasma-mobile-6.3.5/components/quicksettingsplugin/quicksettingsmodel.h plasma-mobile-6.3.6/components/quicksettingsplugin/quicksettingsmodel.h
--- plasma-mobile-6.3.5/components/quicksettingsplugin/quicksettingsmodel.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/components/quicksettingsplugin/quicksettingsmodel.h 2025-07-08 13:47:14.000000000 +0200
@@ -12,9 +12,11 @@
#include "savedquicksettings.h"
#include "savedquicksettingsmodel.h"
+#include <KPluginMetaData>
#include <QAbstractListModel>
#include <QQmlComponent>
#include <QQmlListProperty>
+#include <qqmlregistration.h>
class QuickSettingsModel : public QAbstractListModel, public QQmlParserStatus
{
@@ -44,8 +46,8 @@
void loadQuickSetting(KPluginMetaData metaData, bool emitInsertSignal);
void removeQuickSetting(int index);
- void afterQuickSettingLoad(QQmlEngine *engine, KPluginMetaData metaData, QQmlComponent *component, bool emitInsertSignal);
void insertQuickSettingToModel(KPluginMetaData metaData, QuickSetting *quickSetting, bool emitInsertSignal);
+ QuickSetting *loadQuickSettingComponent(KPluginMetaData metaData);
bool m_loaded{false};
diff -Nru plasma-mobile-6.3.5/containments/homescreens/folio/homescreenstate.cpp plasma-mobile-6.3.6/containments/homescreens/folio/homescreenstate.cpp
--- plasma-mobile-6.3.5/containments/homescreens/folio/homescreenstate.cpp 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/homescreens/folio/homescreenstate.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -564,6 +564,16 @@
return m_appDrawerOpenProgress;
}
+void HomeScreenState::setAppDrawerOpenProgress(qreal appDrawerOpenProgress)
+{
+ if (appDrawerOpenProgress == m_appDrawerOpenProgress) {
+ return;
+ }
+
+ m_appDrawerOpenProgress = appDrawerOpenProgress;
+ Q_EMIT appDrawerOpenProgressChanged();
+}
+
qreal HomeScreenState::appDrawerY()
{
return m_appDrawerY;
@@ -571,10 +581,12 @@
void HomeScreenState::setAppDrawerY(qreal appDrawerY)
{
- m_appDrawerY = appDrawerY;
- m_appDrawerOpenProgress = 1 - qBound(0.0, m_appDrawerY, APP_DRAWER_OPEN_DIST) / APP_DRAWER_OPEN_DIST;
- Q_EMIT appDrawerYChanged();
- Q_EMIT appDrawerOpenProgressChanged();
+ if (m_appDrawerY != appDrawerY) {
+ m_appDrawerY = appDrawerY;
+ Q_EMIT appDrawerYChanged();
+ }
+
+ setAppDrawerOpenProgress(1 - qBound(0.0, m_appDrawerY, APP_DRAWER_OPEN_DIST) / APP_DRAWER_OPEN_DIST);
}
qreal HomeScreenState::searchWidgetOpenProgress()
@@ -582,17 +594,29 @@
return m_searchWidgetOpenProgress;
}
+void HomeScreenState::setSearchWidgetOpenProgress(qreal progress)
+{
+ if (m_searchWidgetOpenProgress == progress) {
+ return;
+ }
+
+ m_searchWidgetOpenProgress = progress;
+ Q_EMIT searchWidgetOpenProgressChanged();
+}
+
qreal HomeScreenState::searchWidgetY()
{
- return m_searchWidgetOpenProgress;
+ return m_searchWidgetY;
}
void HomeScreenState::setSearchWidgetY(qreal searchWidgetY)
{
- m_searchWidgetY = searchWidgetY;
- m_searchWidgetOpenProgress = 1 - qBound(0.0, m_searchWidgetY, SEARCH_WIDGET_OPEN_DIST) / SEARCH_WIDGET_OPEN_DIST;
- Q_EMIT searchWidgetYChanged();
- Q_EMIT searchWidgetOpenProgressChanged();
+ if (m_searchWidgetY != searchWidgetY) {
+ m_searchWidgetY = searchWidgetY;
+ Q_EMIT searchWidgetYChanged();
+ }
+
+ setSearchWidgetOpenProgress(1 - qBound(0.0, m_searchWidgetY, SEARCH_WIDGET_OPEN_DIST) / SEARCH_WIDGET_OPEN_DIST);
}
qreal HomeScreenState::delegateDragX()
diff -Nru plasma-mobile-6.3.5/containments/homescreens/folio/homescreenstate.h plasma-mobile-6.3.6/containments/homescreens/folio/homescreenstate.h
--- plasma-mobile-6.3.5/containments/homescreens/folio/homescreenstate.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/homescreens/folio/homescreenstate.h 2025-07-08 13:47:14.000000000 +0200
@@ -346,6 +346,9 @@
void setViewState(ViewState viewState);
void setSwipeState(SwipeState swipeState);
+ void setAppDrawerOpenProgress(qreal progress);
+ void setSearchWidgetOpenProgress(qreal progress);
+
void startDelegateDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY);
void cancelAppDrawerAnimations();
diff -Nru plasma-mobile-6.3.5/containments/homescreens/folio/package/contents/ui/HomeScreen.qml plasma-mobile-6.3.6/containments/homescreens/folio/package/contents/ui/HomeScreen.qml
--- plasma-mobile-6.3.5/containments/homescreens/folio/package/contents/ui/HomeScreen.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/homescreens/folio/package/contents/ui/HomeScreen.qml 2025-07-08 13:47:14.000000000 +0200
@@ -464,8 +464,7 @@
function onSearchWidgetOpenProgressChanged() {
if (homeScreenState.searchWidgetOpenProgress === 1.0) {
searchWidget.requestFocus();
- } else {
- // TODO this gets called a lot, can we have a more performant way?
+ } else if (!root.activeFocus) {
root.forceActiveFocus();
}
}
diff -Nru plasma-mobile-6.3.5/containments/homescreens/folio/widgetcontainer.cpp plasma-mobile-6.3.6/containments/homescreens/folio/widgetcontainer.cpp
--- plasma-mobile-6.3.5/containments/homescreens/folio/widgetcontainer.cpp 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/homescreens/folio/widgetcontainer.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -57,6 +57,11 @@
switch (event->type()) {
case QEvent::MouseButtonPress: {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
+
+ if (!validMouseEvent(me)) {
+ return true;
+ }
+
if (me->buttons() & Qt::LeftButton) {
mousePressEvent(me);
}
@@ -64,11 +69,21 @@
}
case QEvent::MouseMove: {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
+
+ if (!validMouseEvent(me)) {
+ return true;
+ }
+
mouseMoveEvent(me);
break;
}
case QEvent::MouseButtonRelease: {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
+
+ if (!validMouseEvent(me)) {
+ return true;
+ }
+
mouseReleaseEvent(me);
break;
}
@@ -129,3 +144,22 @@
setEditMode(false);
}
}
+
+bool WidgetContainer::validMouseEvent(QMouseEvent *event)
+{
+ bool synthesized = event->source() == Qt::MouseEventSynthesizedByQt || event->source() == Qt::MouseEventSynthesizedBySystem;
+
+ // Don't need to block propagated events
+ if (!synthesized || event->type() != QEvent::MouseButtonRelease) {
+ return true;
+ }
+
+ // If edit mode is not enabled, let the event propagate
+ if (!m_editMode) {
+ return true;
+ } else {
+ // If edit mode is enabled, block the event propagation and handle it only on the WidgetContainer
+ mouseReleaseEvent(event);
+ return false;
+ }
+}
diff -Nru plasma-mobile-6.3.5/containments/homescreens/folio/widgetcontainer.h plasma-mobile-6.3.6/containments/homescreens/folio/widgetcontainer.h
--- plasma-mobile-6.3.5/containments/homescreens/folio/widgetcontainer.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/homescreens/folio/widgetcontainer.h 2025-07-08 13:47:14.000000000 +0200
@@ -37,6 +37,8 @@
void onActiveFocusChanged(bool activeFocus);
private:
+ bool validMouseEvent(QMouseEvent *event);
+
bool m_pressed{false};
bool m_editMode{false};
QTimer *m_pressAndHoldTimer{nullptr};
diff -Nru plasma-mobile-6.3.5/containments/panel/package/contents/ui/main.qml plasma-mobile-6.3.6/containments/panel/package/contents/ui/main.qml
--- plasma-mobile-6.3.5/containments/panel/package/contents/ui/main.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/containments/panel/package/contents/ui/main.qml 2025-07-08 13:47:14.000000000 +0200
@@ -108,12 +108,6 @@
function onCloseActionDrawerRequested() {
drawer.actionDrawer.close();
}
-
- function onDoNotDisturbChanged() {
- if (drawer.actionDrawer.notificationsWidget.doNotDisturbModeEnabled !== MobileShellState.ShellDBusClient.doNotDisturb) {
- drawer.actionDrawer.notificationsWidget.toggleDoNotDisturbMode();
- }
- }
}
Binding {
diff -Nru plasma-mobile-6.3.5/debian/changelog plasma-mobile-6.3.6/debian/changelog
--- plasma-mobile-6.3.5/debian/changelog 2025-05-19 01:24:48.000000000 +0200
+++ plasma-mobile-6.3.6/debian/changelog 2025-07-22 17:56:43.000000000 +0200
@@ -1,3 +1,50 @@
+plasma-mobile (6.3.6-1) unstable; urgency=medium
+
+ * Team upload.
+
+ [ Aurélien COUDERC ]
+ * New upstream release (6.3.6).
+ - Envmanager: Use BorderlessMaximizedWindows option.
+ - Shell: Fix Compact applet display popup when fullRepresentation is null.
+ - Homescreens/folio: Block propagation if edit mode is active to avoid
+ widget to trigger event.
+ - Load minimized Quicksettings as Async.
+ - Paginatemodel: Avoid to disconnect if model is the same because it's not
+ reconnected after.
+ - Fix mistake on paginatemodel.
+ - Quicksettings: Use synchrone component loading instead of asynchrone to
+ avoid concurrent bug.
+ - Envmanager: Write options as immutable, and add kdeglobals.
+ - Envmanager: Add overlay configs through XDG_CONFIG_DIRS.
+ - Taskswitcher: Fix task close icon colour in light mode.
+ - Folio: Ensure y anim signals don't get emitted if value didn't change.
+ - Notifications: Fix do-not-disturb.
+ - Notifications: Don't factor action drawer state in expiry.
+ - Notifications: do not expire notificationa after invoking action.
+ * Backport upstream commits:
+ - bin: Don't force QT_QPA_PLATFORM=wayland so apps that prefer x11 can use
+ it similar to what the desktop session does. [8b8c9585]
+ - applicationlistmodel: Space out application list refreshes to avoid log
+ spam and freezes. [7d9054f7]
+ - Disables unexpected gesture navigation activating the task switcher when
+ Action Drawer, Notification Popup Drawer, or VolumeOSD are visible
+ [013d0345]
+ - GestureNavigation: Fix Action Drawer State being out of Sync on First
+ Start. [9707f503]
+ - folio: Fix recurring AppDelegate null errors in the logs. [c5f0d15b]
+ - notification list: fix notification scrolling within action drawer and
+ lockscreen. [e4323f4e]
+ - statusbar: Don't show internet icon and network loading indicator at
+ same time. [23797660]
+ - modem manager plugin: Make calls nonblocking to not freeze UI when
+ called. [6e0dca97]
+ - Folio: Fix Settings Component drawer bug where it can end up displayed
+ at the same time as the application drawer. [a452bc30]
+ * Relax inter-plasma versioned dependency constraint so we can upload
+ only 6.3.6 packages that have actual code changes.
+
+ -- Aurélien COUDERC <coucouf@debian.org> Tue, 22 Jul 2025 17:56:43 +0200
+
plasma-mobile (6.3.5-1) unstable; urgency=medium
* Team upload.
diff -Nru plasma-mobile-6.3.5/debian/patches/relax-interplasma-versioned-deps.patch plasma-mobile-6.3.6/debian/patches/relax-interplasma-versioned-deps.patch
--- plasma-mobile-6.3.5/debian/patches/relax-interplasma-versioned-deps.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/relax-interplasma-versioned-deps.patch 2025-07-20 14:41:25.000000000 +0200
@@ -0,0 +1,11 @@
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -10,7 +10,7 @@
+ set(PROJECT_VERSION "6.3.6")
+ set(PROJECT_VERSION_MAJOR 6)
+
+-set(PROJECT_DEP_VERSION "6.3.6")
++set(PROJECT_DEP_VERSION "6.3.4")
+ set(QT_MIN_VERSION "6.7.0")
+ set(KF6_MIN_VERSION "6.10.0")
+ set(KDE_COMPILERSETTINGS_LEVEL "5.82")
diff -Nru plasma-mobile-6.3.5/debian/patches/series plasma-mobile-6.3.6/debian/patches/series
--- plasma-mobile-6.3.5/debian/patches/series 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/series 2025-07-20 14:40:59.000000000 +0200
@@ -0,0 +1,10 @@
+relax-interplasma-versioned-deps.patch
+upstream_8b8c9585_bin-Don-t-force-QT-QPA-PLATFORM-wayland.patch
+upstream_7d9054f7_applicationlistmodel-Space-out-application-list-refreshes.patch
+upstream_013d0345_Gesture-Navigation-Disable-Gestures-When-Action-Drawer-Notification-Popup-Drawer-or-VolumeOSD-are-Visible.patch
+upstream_9707f503_GestureNavigation-Fix-Action-Drawer-State-being-out-of-Sync-on-First-Start.patch
+upstream_c5f0d15b_folio-Fix-AppDelegate-null-errors.patch
+upstream_e4323f4e_notification-list-fix-notification-scrolling-within-action-drawer-and-lockscreen.patch
+upstream_23797660_statusbar-Don-t-show-internet-icon-and-loading-indicator-at-same-time.patch
+upstream_6e0dca97_mmplugin-Make-calls-nonblocking-to-not-freeze-UI-when-called.patch
+upstream_a452bc30_Folio-Settings-Component-Bugfix.patch
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_013d0345_Gesture-Navigation-Disable-Gestures-When-Action-Drawer-Notification-Popup-Drawer-or-VolumeOSD-are-Visible.patch plasma-mobile-6.3.6/debian/patches/upstream_013d0345_Gesture-Navigation-Disable-Gestures-When-Action-Drawer-Notification-Popup-Drawer-or-VolumeOSD-are-Visible.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_013d0345_Gesture-Navigation-Disable-Gestures-When-Action-Drawer-Notification-Popup-Drawer-or-VolumeOSD-are-Visible.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_013d0345_Gesture-Navigation-Disable-Gestures-When-Action-Drawer-Notification-Popup-Drawer-or-VolumeOSD-are-Visible.patch 2025-07-19 18:23:35.000000000 +0200
@@ -0,0 +1,323 @@
+From 013d034516fa1164a39ae607096603a3890e86f0 Mon Sep 17 00:00:00 2001
+From: Micah Stanley <stanleymicah@proton.me>
+Date: Wed, 19 Mar 2025 01:21:46 +0000
+Subject: [PATCH] Gesture Navigation: Disable Gestures When Action Drawer,
+ Notification Popup Drawer, or VolumeOSD are Visible
+
+Disables gesture navigation when Action Drawer, Notification Popup Drawer, or VolumeOSD are visible as activating the task switcher in these situation can feel a bit weird.
+
+Note:
+~~Draft for in till I can get side loading working again so I can test it on device.~~
+
+Still can't seem to get side loading working quite yet. Though, I did run the test I could from my laptop and it does seems to be working fine, so I will be removing this from draft.
+---
+ .../NotificationPopupManager.qml | 6 ++
+ .../mobileshell/qml/volumeosd/VolumeOSD.qml | 8 ++-
+ .../mobileshellstate/shelldbusclient.cpp | 70 ++++++++++++++++---
+ components/mobileshellstate/shelldbusclient.h | 14 ++++
+ .../mobileshellstate/shelldbusobject.cpp | 26 +++++++
+ components/mobileshellstate/shelldbusobject.h | 10 +++
+ .../package/contents/ui/main.qml | 3 +-
+ 7 files changed, 126 insertions(+), 11 deletions(-)
+
+diff --git a/components/mobileshell/qml/notificationpopup/NotificationPopupManager.qml b/components/mobileshell/qml/notificationpopup/NotificationPopupManager.qml
+index 3ade5132a..7eb4cb2a5 100644
+--- a/components/mobileshell/qml/notificationpopup/NotificationPopupManager.qml
++++ b/components/mobileshell/qml/notificationpopup/NotificationPopupManager.qml
+@@ -68,6 +68,12 @@ Window {
+
+ Component.onCompleted: ShellUtil.setInputTransparent(notificationPopupManager, true)
+
++ Binding {
++ target: MobileShellState.ShellDBusClient
++ property: "isNotificationPopupDrawerOpen"
++ value: popupDrawerOpened
++ }
++
+ // Update the window touch region to encapsulate the notification area or the whole screen depending on the 'popupDrawerOpened' state
+ function updateTouchArea() {
+ ShellUtil.setInputTransparent(notificationPopupManager, false);
+diff --git a/components/mobileshell/qml/volumeosd/VolumeOSD.qml b/components/mobileshell/qml/volumeosd/VolumeOSD.qml
+index c4294f172..31c766d5c 100644
+--- a/components/mobileshell/qml/volumeosd/VolumeOSD.qml
++++ b/components/mobileshell/qml/volumeosd/VolumeOSD.qml
+@@ -31,7 +31,7 @@ Window {
+
+ LayerShell.Window.scope: "overlay"
+ LayerShell.Window.anchors: LayerShell.Window.AnchorTop
+- LayerShell.Window.layer: LayerShell.Window.LayerTop
++ LayerShell.Window.layer: LayerShell.Window.LayerOverlay
+ LayerShell.Window.exclusionZone: -1
+
+ readonly property color backgroundColor: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95), 1.05)
+@@ -65,6 +65,12 @@ Window {
+ visible = false;
+ }
+
++ Binding {
++ target: MobileShellState.ShellDBusClient
++ property: "isVolumeOSDOpen"
++ value: window.visible
++ }
++
+ Flickable {
+ id: flickable
+ anchors.fill: parent
+diff --git a/components/mobileshellstate/shelldbusclient.cpp b/components/mobileshellstate/shelldbusclient.cpp
+index 2f33f5d84..1281b3963 100644
+--- a/components/mobileshellstate/shelldbusclient.cpp
++++ b/components/mobileshellstate/shelldbusclient.cpp
+@@ -8,22 +8,28 @@
+ ShellDBusClient::ShellDBusClient(QObject *parent)
+ : QObject{parent}
+ , m_interface{new OrgKdePlasmashellInterface{QStringLiteral("org.kde.plasmashell"), QStringLiteral("/Mobile"), QDBusConnection::sessionBus(), this}}
+- , m_watcher{new QDBusServiceWatcher(QStringLiteral("org.kde.plasmashell"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this)}
+ , m_connected{false}
+ {
+- if (m_interface->isValid()) {
+- connectSignals();
+- }
+-
+- connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() -> void {
++ // Check if the service is already running
++ if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.plasmashell"))) {
+ m_connected = true;
+ if (m_interface->isValid()) {
+ connectSignals();
+ }
+- });
++ }
+
+- connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() -> void {
+- m_connected = false;
++ connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, [this](const QString &service, const QString &oldOwner, const QString &newOwner) {
++ Q_UNUSED(oldOwner);
++ if (service == QStringLiteral("org.kde.plasmashell")) {
++ if (!newOwner.isEmpty() && !m_connected) {
++ m_connected = true;
++ if (m_interface->isValid()) {
++ connectSignals();
++ }
++ } else if (newOwner.isEmpty() && m_connected) {
++ m_connected = false;
++ }
++ }
+ });
+ }
+
+@@ -31,6 +37,8 @@ void ShellDBusClient::connectSignals()
+ {
+ connect(m_interface, &OrgKdePlasmashellInterface::panelStateChanged, this, &ShellDBusClient::updatePanelState);
+ connect(m_interface, &OrgKdePlasmashellInterface::isActionDrawerOpenChanged, this, &ShellDBusClient::updateIsActionDrawerOpen);
++ connect(m_interface, &OrgKdePlasmashellInterface::isVolumeOSDOpenChanged, this, &ShellDBusClient::updateIsVolumeOSDOpen);
++ connect(m_interface, &OrgKdePlasmashellInterface::isNotificationPopupDrawerOpenChanged, this, &ShellDBusClient::updateIsNotificationPopupDrawerOpen);
+ connect(m_interface, &OrgKdePlasmashellInterface::doNotDisturbChanged, this, &ShellDBusClient::updateDoNotDisturb);
+ connect(m_interface, &OrgKdePlasmashellInterface::isTaskSwitcherVisibleChanged, this, &ShellDBusClient::updateIsTaskSwitcherVisible);
+ connect(m_interface, &OrgKdePlasmashellInterface::openActionDrawerRequested, this, &ShellDBusClient::openActionDrawerRequested);
+@@ -78,6 +86,26 @@ void ShellDBusClient::setIsActionDrawerOpen(bool value)
+ m_interface->setIsActionDrawerOpen(value);
+ }
+
++bool ShellDBusClient::isVolumeOSDOpen() const
++{
++ return m_isVolumeOSDOpen;
++}
++
++void ShellDBusClient::setIsVolumeOSDOpen(bool value)
++{
++ m_interface->setIsVolumeOSDOpen(value);
++}
++
++bool ShellDBusClient::isNotificationPopupDrawerOpen() const
++{
++ return m_isNotificationPopupDrawerOpen;
++}
++
++void ShellDBusClient::setIsNotificationPopupDrawerOpen(bool value)
++{
++ m_interface->setIsNotificationPopupDrawerOpen(value);
++}
++
+ void ShellDBusClient::openActionDrawer()
+ {
+ m_interface->openActionDrawer();
+@@ -160,6 +188,30 @@ void ShellDBusClient::updateIsActionDrawerOpen()
+ });
+ }
+
++void ShellDBusClient::updateIsVolumeOSDOpen()
++{
++ auto reply = m_interface->isVolumeOSDOpen();
++ auto watcher = new QDBusPendingCallWatcher(reply, this);
++
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
++ QDBusPendingReply<bool> reply = *watcher;
++ m_isVolumeOSDOpen = reply.argumentAt<0>();
++ Q_EMIT isVolumeOSDOpenChanged();
++ });
++}
++
++void ShellDBusClient::updateIsNotificationPopupDrawerOpen()
++{
++ auto reply = m_interface->isNotificationPopupDrawerOpen();
++ auto watcher = new QDBusPendingCallWatcher(reply, this);
++
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
++ QDBusPendingReply<bool> reply = *watcher;
++ m_isNotificationPopupDrawerOpen = reply.argumentAt<0>();
++ Q_EMIT isNotificationPopupDrawerOpenChanged();
++ });
++}
++
+ void ShellDBusClient::updateIsTaskSwitcherVisible()
+ {
+ auto reply = m_interface->isTaskSwitcherVisible();
+diff --git a/components/mobileshellstate/shelldbusclient.h b/components/mobileshellstate/shelldbusclient.h
+index 329061418..e3818aab9 100644
+--- a/components/mobileshellstate/shelldbusclient.h
++++ b/components/mobileshellstate/shelldbusclient.h
+@@ -18,6 +18,8 @@ class ShellDBusClient : public QObject
+
+ Q_PROPERTY(bool doNotDisturb READ doNotDisturb WRITE setDoNotDisturb NOTIFY doNotDisturbChanged)
+ Q_PROPERTY(bool isActionDrawerOpen READ isActionDrawerOpen WRITE setIsActionDrawerOpen NOTIFY isActionDrawerOpenChanged)
++ Q_PROPERTY(bool isVolumeOSDOpen READ isVolumeOSDOpen WRITE setIsVolumeOSDOpen NOTIFY isVolumeOSDOpenChanged)
++ Q_PROPERTY(bool isNotificationPopupDrawerOpen READ isNotificationPopupDrawerOpen WRITE setIsNotificationPopupDrawerOpen NOTIFY isNotificationPopupDrawerOpenChanged)
+ Q_PROPERTY(bool isTaskSwitcherVisible READ isTaskSwitcherVisible NOTIFY isTaskSwitcherVisibleChanged)
+ Q_PROPERTY(QString panelState READ panelState WRITE setPanelState NOTIFY panelStateChanged)
+
+@@ -30,6 +32,12 @@ public:
+ bool isActionDrawerOpen() const;
+ void setIsActionDrawerOpen(bool value);
+
++ bool isVolumeOSDOpen() const;
++ void setIsVolumeOSDOpen(bool value);
++
++ bool isNotificationPopupDrawerOpen() const;
++ void setIsNotificationPopupDrawerOpen(bool value);
++
+ bool isTaskSwitcherVisible() const;
+
+ QString panelState() const;
+@@ -50,6 +58,8 @@ public:
+ Q_SIGNALS:
+ void panelStateChanged();
+ void isActionDrawerOpenChanged();
++ void isVolumeOSDOpenChanged();
++ void isNotificationPopupDrawerOpenChanged();
+ void doNotDisturbChanged();
+ void isTaskSwitcherVisibleChanged();
+ void openActionDrawerRequested();
+@@ -62,6 +72,8 @@ Q_SIGNALS:
+ private Q_SLOTS:
+ void updateDoNotDisturb();
+ void updateIsActionDrawerOpen();
++ void updateIsVolumeOSDOpen();
++ void updateIsNotificationPopupDrawerOpen();
+ void updateIsTaskSwitcherVisible();
+ void updatePanelState();
+
+@@ -75,6 +87,8 @@ private:
+
+ bool m_doNotDisturb = false;
+ bool m_isActionDrawerOpen = false;
++ bool m_isVolumeOSDOpen = false;
++ bool m_isNotificationPopupDrawerOpen = false;
+ bool m_isTaskSwitcherVisible = false;
+
+ bool m_connected = false;
+diff --git a/components/mobileshellstate/shelldbusobject.cpp b/components/mobileshellstate/shelldbusobject.cpp
+index 8df038db3..3697cb0fb 100644
+--- a/components/mobileshellstate/shelldbusobject.cpp
++++ b/components/mobileshellstate/shelldbusobject.cpp
+@@ -66,6 +66,32 @@ void ShellDBusObject::setIsActionDrawerOpen(bool value)
+ }
+ }
+
++bool ShellDBusObject::isVolumeOSDOpen()
++{
++ return m_isVolumeOSDOpen;
++}
++
++void ShellDBusObject::setIsVolumeOSDOpen(bool value)
++{
++ if (value != m_isVolumeOSDOpen) {
++ m_isVolumeOSDOpen = value;
++ Q_EMIT isVolumeOSDOpenChanged();
++ }
++}
++
++bool ShellDBusObject::isNotificationPopupDrawerOpen()
++{
++ return m_isNotificationPopupDrawerOpen;
++}
++
++void ShellDBusObject::setIsNotificationPopupDrawerOpen(bool value)
++{
++ if (value != m_isNotificationPopupDrawerOpen) {
++ m_isNotificationPopupDrawerOpen = value;
++ Q_EMIT isNotificationPopupDrawerOpenChanged();
++ }
++}
++
+ bool ShellDBusObject::isTaskSwitcherVisible()
+ {
+ return m_isTaskSwitcherVisible;
+diff --git a/components/mobileshellstate/shelldbusobject.h b/components/mobileshellstate/shelldbusobject.h
+index 7f8cf7e03..5d0055ce8 100644
+--- a/components/mobileshellstate/shelldbusobject.h
++++ b/components/mobileshellstate/shelldbusobject.h
+@@ -28,6 +28,8 @@ public:
+ Q_SIGNALS:
+ Q_SCRIPTABLE void doNotDisturbChanged();
+ Q_SCRIPTABLE void isActionDrawerOpenChanged();
++ Q_SCRIPTABLE void isVolumeOSDOpenChanged();
++ Q_SCRIPTABLE void isNotificationPopupDrawerOpenChanged();
+ Q_SCRIPTABLE void panelStateChanged();
+ Q_SCRIPTABLE void isTaskSwitcherVisibleChanged();
+ Q_SCRIPTABLE void openActionDrawerRequested();
+@@ -45,6 +47,12 @@ public Q_SLOTS:
+ Q_SCRIPTABLE bool isActionDrawerOpen();
+ Q_SCRIPTABLE void setIsActionDrawerOpen(bool value);
+
++ Q_SCRIPTABLE bool isVolumeOSDOpen();
++ Q_SCRIPTABLE void setIsVolumeOSDOpen(bool value);
++
++ Q_SCRIPTABLE bool isNotificationPopupDrawerOpen();
++ Q_SCRIPTABLE void setIsNotificationPopupDrawerOpen(bool value);
++
+ Q_SCRIPTABLE QString panelState();
+ Q_SCRIPTABLE void setPanelState(QString state);
+
+@@ -68,6 +76,8 @@ private:
+
+ bool m_doNotDisturb{false};
+ bool m_isActionDrawerOpen{false};
++ bool m_isVolumeOSDOpen{false};
++ bool m_isNotificationPopupDrawerOpen{false};
+ bool m_isTaskSwitcherVisible{false};
+
+ QString m_panelState{};
+diff --git a/kwin/mobiletaskswitcher/package/contents/ui/main.qml b/kwin/mobiletaskswitcher/package/contents/ui/main.qml
+index 98fa1fa7e..7015b58a0 100644
+--- a/kwin/mobiletaskswitcher/package/contents/ui/main.qml
++++ b/kwin/mobiletaskswitcher/package/contents/ui/main.qml
+@@ -7,6 +7,7 @@ import org.kde.kwin
+
+ import org.kde.plasma.private.mobileshell.taskswitcherplugin as TaskSwitcherPlugin
+ import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
++import org.kde.plasma.private.mobileshell.state as MobileShellState
+
+ SceneEffect {
+ id: root
+@@ -28,7 +29,7 @@ SceneEffect {
+ TaskSwitcherPlugin.MobileTaskSwitcherState {
+ id: taskSwitcherState
+
+- gestureEnabled: !ShellSettings.Settings.navigationPanelEnabled
++ gestureEnabled: !ShellSettings.Settings.navigationPanelEnabled && !MobileShellState.ShellDBusClient.isActionDrawerOpen && !MobileShellState.ShellDBusClient.isVolumeOSDOpen && !MobileShellState.ShellDBusClient.isNotificationPopupDrawerOpen
+
+ Component.onCompleted: {
+ // Initialize with effect
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_23797660_statusbar-Don-t-show-internet-icon-and-loading-indicator-at-same-time.patch plasma-mobile-6.3.6/debian/patches/upstream_23797660_statusbar-Don-t-show-internet-icon-and-loading-indicator-at-same-time.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_23797660_statusbar-Don-t-show-internet-icon-and-loading-indicator-at-same-time.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_23797660_statusbar-Don-t-show-internet-icon-and-loading-indicator-at-same-time.patch 2025-07-19 19:44:47.000000000 +0200
@@ -0,0 +1,46 @@
+From 23797660ac98872dc4e7aca6b0ad7236c149699d Mon Sep 17 00:00:00 2001
+From: Devin Lin <espidev@gmail.com>
+Date: Sun, 6 Apr 2025 00:36:54 -0400
+Subject: [PATCH] statusbar: Don't show internet icon and loading indicator at
+ same time
+
+Ensure that the internet icon and the loading indicator do not show at the same time.
+---
+ .../qml/statusbar/indicators/InternetIndicator.qml | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/components/mobileshell/qml/statusbar/indicators/InternetIndicator.qml b/components/mobileshell/qml/statusbar/indicators/InternetIndicator.qml
+index 3efa39b92..1f7e3ff22 100644
+--- a/components/mobileshell/qml/statusbar/indicators/InternetIndicator.qml
++++ b/components/mobileshell/qml/statusbar/indicators/InternetIndicator.qml
+@@ -12,7 +12,7 @@ import QtQuick.Controls as QQC2
+ import org.kde.plasma.networkmanagement as PlasmaNM
+ import org.kde.kirigami as Kirigami
+
+-Kirigami.Icon {
++Item {
+ id: connectionIcon
+
+ // data
+@@ -40,9 +40,16 @@ Kirigami.Icon {
+ id: connectionIconProvider
+ }
+
+- // implementation
+- source: icon
++ // Internet icon, only show while visible
++ Kirigami.Icon {
++ id: internetIcon
+
++ anchors.fill: parent
++ visible: !connectingIndicator.visible
++ source: connectionIcon.icon
++ }
++
++ // Connecting indicator
+ QQC2.BusyIndicator {
+ id: connectingIndicator
+
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_6e0dca97_mmplugin-Make-calls-nonblocking-to-not-freeze-UI-when-called.patch plasma-mobile-6.3.6/debian/patches/upstream_6e0dca97_mmplugin-Make-calls-nonblocking-to-not-freeze-UI-when-called.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_6e0dca97_mmplugin-Make-calls-nonblocking-to-not-freeze-UI-when-called.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_6e0dca97_mmplugin-Make-calls-nonblocking-to-not-freeze-UI-when-called.patch 2025-07-19 19:46:49.000000000 +0200
@@ -0,0 +1,185 @@
+From 6e0dca97028b8c269510904e3f4eafdb12f400d0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Luis=20B=C3=BCchi?= <luis.buechi@server23.cc>
+Date: Fri, 25 Apr 2025 10:12:10 +0200
+Subject: [PATCH] mmplugin: Make calls nonblocking to not freeze UI when called
+
+replaces the blocking `reply.waitForFinished` calls with QCoro asynchronous await calls.
+---
+ components/mmplugin/CMakeLists.txt | 1 +
+ components/mmplugin/signalindicator.cpp | 53 ++++++++++++-------------
+ components/mmplugin/signalindicator.h | 9 +++--
+ 3 files changed, 31 insertions(+), 32 deletions(-)
+
+diff --git a/components/mmplugin/CMakeLists.txt b/components/mmplugin/CMakeLists.txt
+index ec4fb882b..9b739fbb4 100644
+--- a/components/mmplugin/CMakeLists.txt
++++ b/components/mmplugin/CMakeLists.txt
+@@ -14,6 +14,7 @@ target_link_libraries(ppc-mmqmlplugin PRIVATE
+ KF6::NetworkManagerQt
+ KF6::CoreAddons
+ KF6::I18n
++ QCoro::DBus
+ )
+
+
+diff --git a/components/mmplugin/signalindicator.cpp b/components/mmplugin/signalindicator.cpp
+index 226d733b4..0ff27e092 100644
+--- a/components/mmplugin/signalindicator.cpp
++++ b/components/mmplugin/signalindicator.cpp
+@@ -8,6 +8,7 @@
+ #include <NetworkManagerQt/Manager>
+ #include <NetworkManagerQt/Settings>
+ #include <NetworkManagerQt/Utils>
++#include <QDBusReply>
+
+ #include <KUser>
+
+@@ -197,11 +198,11 @@ void SignalIndicator::refreshProfiles()
+ Q_EMIT profileListChanged();
+ }
+
+-void SignalIndicator::activateProfile(const QString &connectionUni)
++QCoro::Task<void> SignalIndicator::activateProfile(const QString &connectionUni)
+ {
+ if (!m_nmModem) {
+ qWarning() << "Cannot activate profile since there is no NetworkManager modem";
+- return;
++ co_return;
+ }
+
+ qDebug() << QStringLiteral("Activating profile on modem") << m_nmModem->uni() << QStringLiteral("for connection") << connectionUni << ".";
+@@ -220,24 +221,23 @@ void SignalIndicator::activateProfile(const QString &connectionUni)
+
+ if (!con) {
+ qDebug() << QStringLiteral("Connection") << connectionUni << QStringLiteral("not found.");
+- return;
++ co_return;
+ }
+
+ // activate connection manually
+ // despite the documentation saying otherwise, activateConnection seems to need the DBus path, not uuid of the connection
+- QDBusPendingReply<QDBusObjectPath> reply = NetworkManager::activateConnection(con->path(), m_nmModem->uni(), {});
+- reply.waitForFinished();
+- if (reply.isError()) {
++ QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::activateConnection(con->path(), m_nmModem->uni(), {});
++ if (!reply.isValid()) {
+ qWarning() << QStringLiteral("Error activating connection:") << reply.error().message();
+- return;
++ co_return;
+ }
+ }
+
+-void SignalIndicator::addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType)
++QCoro::Task<void> SignalIndicator::addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType)
+ {
+ if (!m_nmModem) {
+ qWarning() << "Cannot add profile since there is no NetworkManager modem";
+- return;
++ co_return;
+ }
+
+ NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Gsm)};
+@@ -255,47 +255,45 @@ void SignalIndicator::addProfile(const QString &name, const QString &apn, const
+
+ gsmSetting->setInitialized(true);
+
+- QDBusPendingReply<QDBusObjectPath> reply = NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), {});
+- reply.waitForFinished();
+- if (reply.isError()) {
++ QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), {});
++ if (!reply.isValid()) {
+ qWarning() << "Error adding connection:" << reply.error().message();
+ } else {
+ qDebug() << "Successfully added a new connection" << name << "with APN" << apn << ".";
+ }
+ }
+
+-void SignalIndicator::removeProfile(const QString &connectionUni)
++QCoro::Task<void> SignalIndicator::removeProfile(const QString &connectionUni)
+ {
+ NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
+ if (!con) {
+ qWarning() << "Could not find connection" << connectionUni << "to update!";
+- return;
++ co_return;
+ }
+
+- QDBusPendingReply reply = con->remove();
+- reply.waitForFinished();
+- if (reply.isError()) {
++ QDBusPendingReply reply = co_await con->remove();
++ if (!reply.isValid()) {
+ qWarning() << "Error removing connection" << reply.error().message();
+ }
+ }
+
+-void SignalIndicator::updateProfile(const QString &connectionUni,
+- const QString &name,
+- const QString &apn,
+- const QString &username,
+- const QString &password,
+- const QString &networkType)
++QCoro::Task<void> SignalIndicator::updateProfile(const QString &connectionUni,
++ const QString &name,
++ const QString &apn,
++ const QString &username,
++ const QString &password,
++ const QString &networkType)
+ {
+ NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
+ if (!con) {
+ qWarning() << "Could not find connection" << connectionUni << "to update!";
+- return;
++ co_return;
+ }
+
+ NetworkManager::ConnectionSettings::Ptr conSettings = con->settings();
+ if (!conSettings) {
+ qWarning() << "Could not find connection settings for" << connectionUni << "to update!";
+- return;
++ co_return;
+ }
+
+ conSettings->setId(name);
+@@ -309,9 +307,8 @@ void SignalIndicator::updateProfile(const QString &connectionUni,
+
+ gsmSetting->setInitialized(true);
+
+- QDBusPendingReply reply = con->update(conSettings->toMap());
+- reply.waitForFinished();
+- if (reply.isError()) {
++ QDBusPendingReply reply = co_await con->update(conSettings->toMap());
++ if (!reply.isValid()) {
+ qWarning() << "Error updating connection settings for" << connectionUni << ":" << reply.error().message() << ".";
+ } else {
+ qDebug() << "Successfully updated connection settings" << connectionUni << ".";
+diff --git a/components/mmplugin/signalindicator.h b/components/mmplugin/signalindicator.h
+index b17ba7834..77af2cda9 100644
+--- a/components/mmplugin/signalindicator.h
++++ b/components/mmplugin/signalindicator.h
+@@ -9,6 +9,7 @@
+
+ #include <NetworkManagerQt/Connection>
+ #include <NetworkManagerQt/ModemDevice>
++#include <QCoroDBusPendingReply>
+
+ #include <QObject>
+ #include <qqmlregistration.h>
+@@ -53,10 +54,10 @@ public:
+ // connection profiles
+ QList<ProfileSettings *> &profileList();
+ void refreshProfiles();
+- Q_INVOKABLE void activateProfile(const QString &connectionUni);
+- Q_INVOKABLE void addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType);
+- Q_INVOKABLE void removeProfile(const QString &connectionUni);
+- Q_INVOKABLE void updateProfile(const QString &connectionUni,
++ Q_INVOKABLE QCoro::Task<void> activateProfile(const QString &connectionUni);
++ Q_INVOKABLE QCoro::Task<void> addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType);
++ Q_INVOKABLE QCoro::Task<void> removeProfile(const QString &connectionUni);
++ Q_INVOKABLE QCoro::Task<void> updateProfile(const QString &connectionUni,
+ const QString &name,
+ const QString &apn,
+ const QString &username,
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_7d9054f7_applicationlistmodel-Space-out-application-list-refreshes.patch plasma-mobile-6.3.6/debian/patches/upstream_7d9054f7_applicationlistmodel-Space-out-application-list-refreshes.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_7d9054f7_applicationlistmodel-Space-out-application-list-refreshes.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_7d9054f7_applicationlistmodel-Space-out-application-list-refreshes.patch 2025-07-19 18:07:59.000000000 +0200
@@ -0,0 +1,152 @@
+From 7d9054f74ee2d2fba63c442f444fc8a276ca0f1d Mon Sep 17 00:00:00 2001
+From: Devin Lin <espidev@gmail.com>
+Date: Thu, 13 Feb 2025 11:39:28 -0500
+Subject: [PATCH] applicationlistmodel: Space out application list refreshes
+
+Use the same behavior as kickoff on desktop, to limit application list
+refreshes to once every 100ms.
+
+https://invent.kde.org/plasma/plasma-mobile/-/issues/440
+---
+ .../folio/applicationlistmodel.cpp | 23 +++++++++++++------
+ .../homescreens/folio/applicationlistmodel.h | 2 ++
+ .../halcyon/plugin/applicationlistmodel.cpp | 23 +++++++++++++------
+ .../halcyon/plugin/applicationlistmodel.h | 2 ++
+ 4 files changed, 36 insertions(+), 14 deletions(-)
+
+diff --git a/containments/homescreens/folio/applicationlistmodel.cpp b/containments/homescreens/folio/applicationlistmodel.cpp
+index 4a5178765..c39532394 100644
+--- a/containments/homescreens/folio/applicationlistmodel.cpp
++++ b/containments/homescreens/folio/applicationlistmodel.cpp
+@@ -18,10 +18,19 @@
+ #include <KSharedConfig>
+ #include <KSycoca>
+
++#include <chrono>
++
++using namespace std::chrono_literals;
++
+ ApplicationListModel::ApplicationListModel(HomeScreen *parent)
+ : QAbstractListModel(parent)
++ , m_reloadAppsTimer{new QTimer{this}}
+ {
+- connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
++ m_reloadAppsTimer->setSingleShot(true);
++ m_reloadAppsTimer->setInterval(100ms);
++ connect(m_reloadAppsTimer, &QTimer::timeout, this, &ApplicationListModel::sycocaDbChanged);
++
++ connect(KSycoca::self(), &KSycoca::databaseChanged, m_reloadAppsTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
+
+ // initialize wayland window checking
+ KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
+@@ -51,12 +60,6 @@ void ApplicationListModel::load()
+
+ const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
+
+- beginResetModel();
+-
+- m_delegates.clear();
+-
+- QList<FolioDelegate *> unorderedList;
+-
+ auto filter = [blacklist](const KService::Ptr &service) -> bool {
+ if (service->noDisplay()) {
+ return false;
+@@ -73,6 +76,12 @@ void ApplicationListModel::load()
+ return true;
+ };
+
++ beginResetModel();
++
++ m_delegates.clear();
++
++ QList<FolioDelegate *> unorderedList;
++
+ const KService::List apps = KApplicationTrader::query(filter);
+
+ for (const KService::Ptr &service : apps) {
+diff --git a/containments/homescreens/folio/applicationlistmodel.h b/containments/homescreens/folio/applicationlistmodel.h
+index 15a2ae9bf..5e3a0ed07 100644
+--- a/containments/homescreens/folio/applicationlistmodel.h
++++ b/containments/homescreens/folio/applicationlistmodel.h
+@@ -44,6 +44,8 @@ public Q_SLOTS:
+ protected:
+ HomeScreen *m_homeScreen{nullptr};
+ QList<FolioDelegate *> m_delegates;
++
++ QTimer *m_reloadAppsTimer{nullptr};
+ };
+
+ class ApplicationListSearchModel : public QSortFilterProxyModel
+diff --git a/containments/homescreens/halcyon/plugin/applicationlistmodel.cpp b/containments/homescreens/halcyon/plugin/applicationlistmodel.cpp
+index 1ede67127..ca6bf2998 100644
+--- a/containments/homescreens/halcyon/plugin/applicationlistmodel.cpp
++++ b/containments/homescreens/halcyon/plugin/applicationlistmodel.cpp
+@@ -18,10 +18,19 @@
+ #include <KSharedConfig>
+ #include <KSycoca>
+
++#include <chrono>
++
++using namespace std::chrono_literals;
++
+ ApplicationListModel::ApplicationListModel(QObject *parent)
+ : QAbstractListModel(parent)
++ , m_reloadAppsTimer{new QTimer{this}}
+ {
+- connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
++ m_reloadAppsTimer->setSingleShot(true);
++ m_reloadAppsTimer->setInterval(100ms);
++ connect(m_reloadAppsTimer, &QTimer::timeout, this, &ApplicationListModel::sycocaDbChanged);
++
++ connect(KSycoca::self(), &KSycoca::databaseChanged, m_reloadAppsTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
+ }
+
+ ApplicationListModel::~ApplicationListModel() = default;
+@@ -49,12 +58,6 @@ void ApplicationListModel::loadApplications()
+
+ const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
+
+- beginResetModel();
+-
+- m_applicationList.clear();
+-
+- QList<Application *> unorderedList;
+-
+ auto filter = [blacklist](const KService::Ptr &service) -> bool {
+ if (service->noDisplay()) {
+ return false;
+@@ -71,6 +74,12 @@ void ApplicationListModel::loadApplications()
+ return true;
+ };
+
++ beginResetModel();
++
++ m_applicationList.clear();
++
++ QList<Application *> unorderedList;
++
+ const KService::List apps = KApplicationTrader::query(filter);
+
+ for (const KService::Ptr &service : apps) {
+diff --git a/containments/homescreens/halcyon/plugin/applicationlistmodel.h b/containments/homescreens/halcyon/plugin/applicationlistmodel.h
+index 167ed6f65..0bebcfedd 100644
+--- a/containments/homescreens/halcyon/plugin/applicationlistmodel.h
++++ b/containments/homescreens/halcyon/plugin/applicationlistmodel.h
+@@ -11,6 +11,7 @@
+ #include <QObject>
+ #include <QQuickItem>
+ #include <QSet>
++#include <QTimer>
+
+ /**
+ * @short The base application list, used directly by the full app list page.
+@@ -37,4 +38,5 @@ public Q_SLOTS:
+
+ protected:
+ QList<Application *> m_applicationList;
++ QTimer *m_reloadAppsTimer{nullptr};
+ };
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_8b8c9585_bin-Don-t-force-QT-QPA-PLATFORM-wayland.patch plasma-mobile-6.3.6/debian/patches/upstream_8b8c9585_bin-Don-t-force-QT-QPA-PLATFORM-wayland.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_8b8c9585_bin-Don-t-force-QT-QPA-PLATFORM-wayland.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_8b8c9585_bin-Don-t-force-QT-QPA-PLATFORM-wayland.patch 2025-07-19 17:24:46.000000000 +0200
@@ -0,0 +1,27 @@
+From 8b8c9585587638dd3af90482255c6c6a8ac47c35 Mon Sep 17 00:00:00 2001
+From: Devin Lin <espidev@gmail.com>
+Date: Sun, 9 Mar 2025 11:44:23 -0400
+Subject: [PATCH] bin: Don't force QT_QPA_PLATFORM=wayland
+
+Qt apps for the most part should already default to wayland, since we are in a wayland
+session. Don't force apps that default to x11 to be wayland (same
+behaviour as desktop).
+---
+ bin/startplasmamobile.in | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/bin/startplasmamobile.in b/bin/startplasmamobile.in
+index 12af661af..3f9489bbf 100755
+--- a/bin/startplasmamobile.in
++++ b/bin/startplasmamobile.in
+@@ -7,7 +7,6 @@
+
+ [ -f /etc/profile ] && . /etc/profile
+
+-export QT_QPA_PLATFORM=wayland
+ export QT_QPA_PLATFORMTHEME=KDE
+ export EGL_PLATFORM=wayland
+
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_9707f503_GestureNavigation-Fix-Action-Drawer-State-being-out-of-Sync-on-First-Start.patch plasma-mobile-6.3.6/debian/patches/upstream_9707f503_GestureNavigation-Fix-Action-Drawer-State-being-out-of-Sync-on-First-Start.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_9707f503_GestureNavigation-Fix-Action-Drawer-State-being-out-of-Sync-on-First-Start.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_9707f503_GestureNavigation-Fix-Action-Drawer-State-being-out-of-Sync-on-First-Start.patch 2025-07-19 18:25:29.000000000 +0200
@@ -0,0 +1,54 @@
+From 9707f503a217cb0d2b289e13576f99038dac0a62 Mon Sep 17 00:00:00 2001
+From: Micah Stanley <stanleymicah@proton.me>
+Date: Thu, 10 Apr 2025 14:30:51 +0000
+Subject: [PATCH] GestureNavigation: Fix Action Drawer State being out of Sync
+ on First Start
+
+---
+ components/mobileshellstate/shelldbusclient.cpp | 1 -
+ containments/panel/package/contents/ui/main.qml | 10 +---------
+ 2 files changed, 1 insertion(+), 10 deletions(-)
+
+diff --git a/components/mobileshellstate/shelldbusclient.cpp b/components/mobileshellstate/shelldbusclient.cpp
+index 1281b3963..c933ae020 100644
+--- a/components/mobileshellstate/shelldbusclient.cpp
++++ b/components/mobileshellstate/shelldbusclient.cpp
+@@ -51,7 +51,6 @@ void ShellDBusClient::connectSignals()
+ connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested);
+ connect(m_interface, &OrgKdePlasmashellInterface::showVolumeOSDRequested, this, &ShellDBusClient::showVolumeOSDRequested);
+
+- updateIsActionDrawerOpen();
+ updateDoNotDisturb();
+ updateIsTaskSwitcherVisible();
+ }
+diff --git a/containments/panel/package/contents/ui/main.qml b/containments/panel/package/contents/ui/main.qml
+index 0be24b595..ae1d60af4 100644
+--- a/containments/panel/package/contents/ui/main.qml
++++ b/containments/panel/package/contents/ui/main.qml
+@@ -67,14 +67,6 @@ ContainmentItem {
+ }
+ }
+
+-
+- Binding {
+- target: MobileShellState.ShellDBusClient
+- property: "isActionDrawerOpen"
+- value: drawer.visible
+- }
+-
+-
+ // only opaque if there are no maximized windows on this screen
+ readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1
+ readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback
+@@ -119,7 +111,7 @@ ContainmentItem {
+ Binding {
+ target: MobileShellState.ShellDBusClient
+ property: "isActionDrawerOpen"
+- value: drawer.intendedToBeVisible
++ value: drawer.visible
+ }
+
+ //END API implementation
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_a452bc30_Folio-Settings-Component-Bugfix.patch plasma-mobile-6.3.6/debian/patches/upstream_a452bc30_Folio-Settings-Component-Bugfix.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_a452bc30_Folio-Settings-Component-Bugfix.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_a452bc30_Folio-Settings-Component-Bugfix.patch 2025-07-19 19:50:18.000000000 +0200
@@ -0,0 +1,63 @@
+From a452bc30306d30b4126ad13c63b74c452d9b2866 Mon Sep 17 00:00:00 2001
+From: Micah Stanley <stanleymicah@proton.me>
+Date: Thu, 24 Apr 2025 13:42:16 +0000
+Subject: [PATCH] Folio: Settings Component Bugfix
+
+Fixes a bug where if one presses and holds the home screen to bring up the settings component, then swipes up to access the application drawer while the settings component open animation is playing, both the drawer and settings component will be visible on screen at the same time. Fixing this was achieved by setting swipe state back to none whenever the settings component is opening and a onSwipeMove event happens.
+
+Video of the bug.
+
+
+---
+ containments/homescreens/folio/homescreenstate.cpp | 5 +++++
+ containments/homescreens/folio/homescreenstate.h | 1 +
+ .../homescreens/folio/package/contents/ui/HomeScreen.qml | 5 +++++
+ 3 files changed, 11 insertions(+)
+
+diff --git a/containments/homescreens/folio/homescreenstate.cpp b/containments/homescreens/folio/homescreenstate.cpp
+index ab84c058c..9c09daa9a 100644
+--- a/containments/homescreens/folio/homescreenstate.cpp
++++ b/containments/homescreens/folio/homescreenstate.cpp
+@@ -971,6 +971,11 @@ void HomeScreenState::swipeEnded()
+ setSwipeState(SwipeState::None);
+ }
+
++void HomeScreenState::swipeCancelled()
++{
++ setSwipeState(SwipeState::None);
++}
++
+ void HomeScreenState::swipeMoved(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY)
+ {
+ m_movingUp = deltaY > 0;
+diff --git a/containments/homescreens/folio/homescreenstate.h b/containments/homescreens/folio/homescreenstate.h
+index 9614ba8cc..40053f363 100644
+--- a/containments/homescreens/folio/homescreenstate.h
++++ b/containments/homescreens/folio/homescreenstate.h
+@@ -341,6 +341,7 @@ public Q_SLOTS:
+ // from SwipeArea
+ void swipeStarted(qreal deltaX, qreal deltaY);
+ void swipeEnded();
++ void swipeCancelled();
+ void swipeMoved(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY);
+
+ private:
+diff --git a/containments/homescreens/folio/package/contents/ui/HomeScreen.qml b/containments/homescreens/folio/package/contents/ui/HomeScreen.qml
+index 215307cfd..e03f5d5f2 100644
+--- a/containments/homescreens/folio/package/contents/ui/HomeScreen.qml
++++ b/containments/homescreens/folio/package/contents/ui/HomeScreen.qml
+@@ -121,6 +121,11 @@ Item {
+ homeScreenState.swipeEnded();
+ }
+ onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
++ // cancel swipe when settings component is opening to prevent conflicts
++ if (folio.HomeScreenState.settingsOpenProgress && folio.HomeScreenState.viewState !== Folio.HomeScreenState.SettingsView) {
++ homeScreenState.swipeCancelled();
++ return;
++ }
+ homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
+ }
+
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_c5f0d15b_folio-Fix-AppDelegate-null-errors.patch plasma-mobile-6.3.6/debian/patches/upstream_c5f0d15b_folio-Fix-AppDelegate-null-errors.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_c5f0d15b_folio-Fix-AppDelegate-null-errors.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_c5f0d15b_folio-Fix-AppDelegate-null-errors.patch 2025-07-19 18:27:09.000000000 +0200
@@ -0,0 +1,86 @@
+From c5f0d15b142c21cef0f73a8161a8bdccc5ef4b8d Mon Sep 17 00:00:00 2001
+From: Devin Lin <espidev@gmail.com>
+Date: Wed, 19 Mar 2025 11:09:09 -0400
+Subject: [PATCH] folio: Fix AppDelegate null errors
+
+Sometimes due to model changes the application will be null, which
+causes a lot of errors in the console. Add null checks to avoid this.
+---
+ .../contents/ui/delegate/AppDelegate.qml | 18 +++++++++++-------
+ 1 file changed, 11 insertions(+), 7 deletions(-)
+
+diff --git a/containments/homescreens/folio/package/contents/ui/delegate/AppDelegate.qml b/containments/homescreens/folio/package/contents/ui/delegate/AppDelegate.qml
+index bbbab535f..f8ee9a93a 100644
+--- a/containments/homescreens/folio/package/contents/ui/delegate/AppDelegate.qml
++++ b/containments/homescreens/folio/package/contents/ui/delegate/AppDelegate.qml
+@@ -17,8 +17,9 @@ AbstractDelegate {
+ id: root
+
+ shadow: true
+- name: application.name
++ name: application ? application.name : ""
+
++ // This may be null for short periods of time due to model changes
+ property Folio.FolioApplication application
+
+ property alias iconItem: icon
+@@ -27,6 +28,10 @@ AbstractDelegate {
+ property bool turnToFolderAnimEnabled: false
+
+ function launchApp() {
++ if (!application) {
++ return;
++ }
++
+ if (application.icon !== "" && !root.application.running) {
+ MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
+ Plasmoid.screen,
+@@ -50,7 +55,7 @@ AbstractDelegate {
+ height: folio.FolioSettings.delegateIconSize
+ width: folio.FolioSettings.delegateIconSize
+
+- // background for folder creation animation
++ // Background for folder creation animation
+ Rectangle {
+ id: rect
+ radius: Kirigami.Units.cornerRadius
+@@ -77,12 +82,12 @@ AbstractDelegate {
+ }
+ }
+
+- // app icon
++ // Application icon
+ DelegateAppIcon {
+ id: icon
+ folio: root.folio
+ anchors.fill: parent
+- source: root.application.icon
++ source: root.application ? root.application.icon : ""
+
+ property real scaleAmount: root.turnToFolder ? 0.3 : 1.0
+ Behavior on scaleAmount {
+@@ -97,13 +102,14 @@ AbstractDelegate {
+ yScale: icon.scaleAmount
+ }
+
++ // Running indicator
+ Rectangle {
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ bottom: parent.bottom
+ bottomMargin: -Kirigami.Units.smallSpacing
+ }
+- visible: root.application.running
++ visible: root.application && root.application.running
+ radius: width
+ width: Kirigami.Units.smallSpacing
+ height: width
+@@ -112,5 +118,3 @@ AbstractDelegate {
+ }
+ }
+ }
+-
+-
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/debian/patches/upstream_e4323f4e_notification-list-fix-notification-scrolling-within-action-drawer-and-lockscreen.patch plasma-mobile-6.3.6/debian/patches/upstream_e4323f4e_notification-list-fix-notification-scrolling-within-action-drawer-and-lockscreen.patch
--- plasma-mobile-6.3.5/debian/patches/upstream_e4323f4e_notification-list-fix-notification-scrolling-within-action-drawer-and-lockscreen.patch 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/debian/patches/upstream_e4323f4e_notification-list-fix-notification-scrolling-within-action-drawer-and-lockscreen.patch 2025-07-19 18:41:13.000000000 +0200
@@ -0,0 +1,1516 @@
+From e4323f4ef0278b6c18149bbce351e79f28048219 Mon Sep 17 00:00:00 2001
+From: Micah Stanley <stanleymicah@proton.me>
+Date: Thu, 20 Mar 2025 02:06:33 +0000
+Subject: [PATCH] notification list: fix notification scrolling within action
+ drawer and lockscreen
+
+This merge request fixes an issue with notification list scrolling and also adds a few general improvements.
+
+To accomplish this, the notification widget was moved outside of the action drawer swipe area and lock screen swipe area, separating them from the parent-child relationship. Instead, the notification widget is now layered separately on top. This change seems to fix the conflict when both areas are accepting swipes from the same direction.
+
+Additionally, changes were made to the notification list widget for the action drawer to make it behave similarly to the folio home screen app library. Specifically, when at the top of the list, one can swipe down over the notification area to expand the action drawer. In landscape mode, the media widget, clock, and date were also added to the notification list to provide more room for viewing notifications when scrolling.
+
+Closes https://invent.kde.org/plasma/plasma-mobile/-/issues/318
+---
+ .../qml/actiondrawer/ActionDrawer.qml | 74 +++----
+ .../qml/actiondrawer/ContentContainer.qml | 208 +++++++++++++++---
+ .../LandscapeContentContainer.qml | 86 +-------
+ .../qml/actiondrawer/NotificationDrawer.qml | 200 +++++++++++++++++
+ .../actiondrawer/PortraitContentContainer.qml | 41 +---
+ .../components}/FlickableOpacityGradient.qml | 0
+ .../notifications/NotificationsWidget.qml | 170 +++++++-------
+ .../folio/package/contents/ui/AppDrawer.qml | 4 +-
+ .../contents/ui/settings/AppletListViewer.qml | 3 +-
+ shell/contents/lockscreen/HeaderComponent.qml | 2 +
+ shell/contents/lockscreen/LockScreen.qml | 194 ++++++++--------
+ .../contents/lockscreen/LockScreenContent.qml | 20 +-
+ .../lockscreen/NotificationsComponent.qml | 28 ++-
+ 13 files changed, 653 insertions(+), 377 deletions(-)
+ create mode 100644 components/mobileshell/qml/actiondrawer/NotificationDrawer.qml
+ rename {containments/homescreens/folio/package/contents/ui/private => components/mobileshell/qml/components}/FlickableOpacityGradient.qml (100%)
+
+diff --git a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml
+index 3427d25fb..d099deeb7 100644
+--- a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml
++++ b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml
+@@ -59,6 +59,19 @@ Item {
+ */
+ property real offset: 0
+
++ /**
++ * Same as offset value except this adds resistance when passing the open position of the current drawer state.
++ */
++ readonly property real offsetResistance: {
++ if (!openToPinnedMode) {
++ return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset);
++ } else if (!opened) {
++ return root.calculateResistance(offset, contentContainer.minimizedQuickSettingsOffset);
++ } else {
++ return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset);
++ }
++ }
++
+ /**
+ * Whether the panel is being dragged.
+ */
+@@ -80,11 +93,6 @@ Item {
+ */
+ property int direction: MobileShell.Direction.None
+
+- /**
+- * The notifications widget being shown. May be null.
+- */
+- property var notificationsWidget: contentContainer.notificationsWidget
+-
+ /**
+ * The mode of the action drawer (portrait or landscape).
+ */
+@@ -135,8 +143,8 @@ Item {
+ }
+
+ root.direction = (oldOffset === offset)
+- ? MobileShell.Direction.None
+- : (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up);
++ ? MobileShell.Direction.None
++ : (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up);
+
+ oldOffset = offset;
+
+@@ -150,6 +158,15 @@ Item {
+ }
+ }
+
++ // calculates offset resistance for the action drawer overshoots it's open position
++ function calculateResistance(value : double, threshold : int) : double {
++ if (value > threshold) {
++ return threshold + Math.pow(value - threshold + 1, Math.max(0.8 - (value - threshold) / ((root.height - threshold) * 15), 0.35));
++ } else {
++ return value;
++ }
++ }
++
+ function cancelAnimations() {
+ root.state = "";
+ }
+@@ -267,45 +284,12 @@ Item {
+ }
+ }
+
+- MobileShell.SwipeArea {
+- id: swipeArea
+- mode: MobileShell.SwipeArea.VerticalOnly
++ // action drawer ui content
++ ContentContainer {
++ id: contentContainer
+ anchors.fill: parent
+
+- function startSwipe() {
+- root.cancelAnimations();
+- root.dragging = true;
+-
+- // Immediately open action drawer if we interact with it and it's already open
+- // This allows us to have 2 quick flicks from minimized -> expanded
+- if (root.visible && !root.opened) {
+- root.opened = true;
+- }
+- }
+-
+- function endSwipe() {
+- root.dragging = false;
+- root.updateState();
+- }
+-
+- function moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) {
+- root.offset += deltaY;
+- }
+-
+- onSwipeStarted: startSwipe()
+- onSwipeEnded: endSwipe()
+- onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
+-
+- onTouchpadScrollStarted: startSwipe()
+- onTouchpadScrollEnded: endSwipe()
+- onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
+-
+- ContentContainer {
+- id: contentContainer
+- anchors.fill: parent
+-
+- actionDrawer: root
+- quickSettingsModel: root.quickSettingsModel
+- }
++ actionDrawer: root
++ quickSettingsModel: root.quickSettingsModel
+ }
+ }
+diff --git a/components/mobileshell/qml/actiondrawer/ContentContainer.qml b/components/mobileshell/qml/actiondrawer/ContentContainer.qml
+index 206bb608d..fabb6d9ec 100644
+--- a/components/mobileshell/qml/actiondrawer/ContentContainer.qml
++++ b/components/mobileshell/qml/actiondrawer/ContentContainer.qml
+@@ -3,9 +3,10 @@
+
+ import QtQuick 2.15
+ import QtQuick.Controls 2.15
+-import QtQuick.Layouts 1.1
+ import QtQuick.Window 2.2
++import QtQuick.Layouts
+
++import org.kde.plasma.components 3.0 as PlasmaComponents
+ import org.kde.plasma.private.mobileshell as MobileShell
+ import org.kde.plasma.components 3.0 as PC3
+ import org.kde.kirigami as Kirigami
+@@ -23,30 +24,190 @@ Item {
+ readonly property real minimizedQuickSettingsOffset: contentContainerLoader.minimizedQuickSettingsOffset
+ readonly property real maximizedQuickSettingsOffset: contentContainerLoader.maximizedQuickSettingsOffset
+
++ Kirigami.Theme.colorSet: Kirigami.Theme.View
++ Kirigami.Theme.inherit: false
++
++ readonly property alias brightnessPressedValue: quickSettings.brightnessPressedValue
++
+ function applyMinMax(val) {
+ return Math.max(0, Math.min(1, val));
+ }
+
+- Kirigami.Theme.colorSet: Kirigami.Theme.View
+- Kirigami.Theme.inherit: false
++ function startSwipe() {
++ actionDrawer.cancelAnimations();
++ actionDrawer.dragging = true;
++ // Immediately open action drawer if we interact with it and it's already open
++ // This allows us to have 2 quick flicks from minimized -> expanded
++ if (actionDrawer.visible && !actionDrawer.opened) {
++ actionDrawer.opened = true;
++ }
++ }
+
+- readonly property alias brightnessPressedValue: quickSettings.brightnessPressedValue
++ function endSwipe() {
++ actionDrawer.dragging = false;
++ actionDrawer.updateState();
++ }
++
++ function moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) {
++ actionDrawer.offset += deltaY;
++ }
+
+ // Background color
+ Rectangle {
+ anchors.fill: parent
+ color: Qt.rgba(Kirigami.Theme.backgroundColor.r,
+- Kirigami.Theme.backgroundColor.g,
+- Kirigami.Theme.backgroundColor.b,
+- (root.actionDrawer.mode == ActionDrawer.Portrait || notificationWidget.hasNotifications) ? 0.95 : 0.9)
++ Kirigami.Theme.backgroundColor.g,
++ Kirigami.Theme.backgroundColor.b,
++ 0.95)
+ Behavior on color { ColorAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutQuad } }
+ opacity: Math.max(0, Math.min(brightnessPressedValue, actionDrawer.offset / root.minimizedQuickSettingsOffset))
+ }
+
++ // The base swipe area.
++ // Used to cover the full surface of the drawer to allow dismissing or expanding it.
++ MobileShell.SwipeArea {
++ id: swipeAreaBase
++ mode: MobileShell.SwipeArea.VerticalOnly
++ anchors.fill: parent
++
++ onSwipeStarted: root.startSwipe()
++ onSwipeEnded: root.endSwipe()
++ onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
++
++ onTouchpadScrollStarted: root.startSwipe()
++ onTouchpadScrollEnded: root.endSwipe()
++ onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
++
++ // Proxy in the layout that switches between landscape and portrait mode.
++ ColumnLayout {
++ anchors.fill: parent
++ visible: root.actionDrawer.mode != ActionDrawer.Portrait
++ LayoutItemProxy { target: contentContainerLoader }
++ }
++
++ // Mouse area for dismissing action drawer in portrait mode when background is clicked.
++ MouseArea {
++ anchors.fill: parent
++ visible: root.actionDrawer.mode == ActionDrawer.Portrait
++
++ // dismiss drawer when background is clicked
++ onClicked: root.actionDrawer.close();
++ }
++
++ // The clear all notification history button.
++ Item {
++ id: toolButtons
++ height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0
++
++ visible: actionDrawer.intendedToBeVisible
++ opacity: Math.max(0, Math.min(root.brightnessPressedValue, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset))
++
++ anchors {
++ topMargin: notificationDrawer.height
++ leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : 10
++ rightMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : notificationDrawer.notificationWidget.anchors.rightMargin + Kirigami.Units.gridUnit - notificationDrawer.anchors.leftMargin + 370
++ top: parent.top
++ left: parent.left
++ right: parent.right
++ }
++
++ Rectangle {
++ id: spacer
++ anchors.left: parent.left
++ anchors.right: parent.right
++
++ visible: notificationDrawer.listOverflowing
++ height: 1
++ opacity: 0.25
++ color: Kirigami.Theme.textColor
++ }
++
++ RowLayout {
++ id: toolLayout
++
++ anchors {
++ top: spacer.bottom
++ right: parent.right
++ left: parent.left
++ leftMargin: Kirigami.Units.largeSpacing
++ rightMargin: Kirigami.Units.largeSpacing
++ topMargin: Kirigami.Units.largeSpacing
++ bottomMargin: Kirigami.Units.largeSpacing
++ }
++
++ PlasmaComponents.ToolButton {
++ id: clearButton
++
++ Layout.alignment: Qt.AlignCenter
++
++ visible: notificationDrawer.hasNotifications
++
++ font.bold: true
++ font.pointSize: Kirigami.Theme.smallFont.pointSize
++
++ icon.name: "edit-clear-history"
++ text: i18n("Clear All Notifications")
++ onClicked: notificationDrawer.notificationWidget.clearHistory()
++ }
++ }
++ }
++ }
++
++ // notification drawer ui
++ // separated from the main drawer ui swipe area to prevent scrolling conflicts
++ NotificationDrawer {
++ id: notificationDrawer
++
++ swipeArea: swipeAreaPortrait
++ actionDrawer: root.actionDrawer
++ mediaControlsWidget: root.mediaControlsWidget
++ contentContainer: root
++ opacity: Math.max(0, Math.min(root.brightnessPressedValue, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset))
++
++ anchors {
++ top: parent.top
++ left: parent.left
++ right: parent.right
++ rightMargin: root.actionDrawer.mode == ActionDrawer.Portrait ? 0 : 360
++ leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : notificationDrawer.minWidthHeight * 0.06
++ }
++ }
++
++ // Secondary swipe area for uses in portrait.
++ // Covers the surface area of the quick settings panel to allow dismissing or expanding the drawer while also having it over top of the notification list.
++ MobileShell.SwipeArea {
++ id: swipeAreaPortrait
++ mode: MobileShell.SwipeArea.VerticalOnly
++ anchors {
++ top: parent.top
++ left: parent.left
++ right: parent.right
++ }
++ height: root.actionDrawer.mode === ActionDrawer.Portrait ? actionDrawer.offsetResistance : root.height
++ interactive: root.actionDrawer.mode === ActionDrawer.Portrait
++
++ onSwipeStarted: root.startSwipe()
++ onSwipeEnded: root.endSwipe()
++ onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
++
++ onTouchpadScrollStarted: root.startSwipe()
++ onTouchpadScrollEnded: root.endSwipe()
++ onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
++
++ // Proxy in the layout that switches between landscape and portrait mode.
++ ColumnLayout {
++ anchors.fill: parent
++ visible: root.actionDrawer.mode == ActionDrawer.Portrait
++ LayoutItemProxy { target: contentContainerLoader }
++ }
++ }
++
+ // Layout that switches between landscape and portrait mode
+ Loader {
+ id: contentContainerLoader
+- anchors.fill: parent
++
++ Layout.fillWidth: true
++ Layout.fillHeight: true
+
+ readonly property real minimizedQuickSettingsOffset: item ? item.minimizedQuickSettingsOffset : 0
+ readonly property real maximizedQuickSettingsOffset: item ? item.maximizedQuickSettingsOffset : 0
+@@ -59,6 +220,7 @@ Item {
+ sourceComponent: root.actionDrawer.mode == ActionDrawer.Portrait ? portraitContentContainer : landscapeContentContainer
+ }
+
++ // The portrait content container.
+ Component {
+ id: portraitContentContainer
+ PortraitContentContainer {
+@@ -69,10 +231,10 @@ Item {
+ quickSettings: root.quickSettings
+ statusBar: root.statusBar
+ mediaControlsWidget: root.mediaControlsWidget
+- notificationsWidget: root.notificationsWidget
+ }
+ }
+
++ // The landscape content container.
+ Component {
+ id: landscapeContentContainer
+ LandscapeContentContainer {
+@@ -82,12 +244,9 @@ Item {
+
+ quickSettings: root.quickSettings
+ statusBar: root.statusBar
+- mediaControlsWidget: root.mediaControlsWidget
+- notificationsWidget: root.notificationsWidget
+ }
+ }
+
+-
+ // Components shared between the two layouts.
+ // This allows us to avoid having to reload the components every time the screen size changes.
+
+@@ -116,29 +275,8 @@ Item {
+
+ property MobileShell.MediaControlsWidget mediaControlsWidget: MobileShell.MediaControlsWidget {
+ id: mediaWidget
+- inActionDrawer: true
+-
+- opacity: brightnessPressedValue
+- }
+-
+- property MobileShell.NotificationsWidget notificationsWidget: MobileShell.NotificationsWidget {
+- id: notificationWidget
+- historyModel: root.actionDrawer.notificationModel
+- historyModelType: root.actionDrawer.notificationModelType
+- notificationSettings: root.actionDrawer.notificationSettings
+- actionsRequireUnlock: root.actionDrawer.restrictedPermissions
+- onUnlockRequested: root.actionDrawer.permissionsRequested()
++ inActionDrawer: root.actionDrawer.mode == ActionDrawer.Portrait
+
+ opacity: brightnessPressedValue
+-
+- Connections {
+- target: root.actionDrawer
+-
+- function onRunPendingNotificationAction() {
+- notificationWidget.runPendingAction();
+- }
+- }
+-
+- onBackgroundClicked: root.actionDrawer.close();
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml b/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml
+index 7dbc79e52..cb22d5899 100644
+--- a/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml
++++ b/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml
+@@ -24,14 +24,12 @@ Item {
+
+ property alias quickSettings: quickSettingsPanel.quickSettings
+ property alias statusBar: quickSettingsPanel.statusBar
+- property alias mediaControlsWidget: mediaControlsWidgetProxy.contentItem
+- property alias notificationsWidget: notificationWidgetProxy.contentItem
+
+ readonly property real minimizedQuickSettingsOffset: height
+ readonly property real maximizedQuickSettingsOffset: height
+ readonly property bool isOnLargeScreen: width > quickSettingsPanel.width * 2.5
+ readonly property real minWidthHeight: Math.min(root.width, root.height)
+- readonly property real opacityValue: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset))
++ readonly property real opacityValue: Math.max(0, Math.min(1, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset))
+ readonly property double brightnessPressedValue: quickSettings.brightnessPressedValue
+
+ Kirigami.Theme.colorSet: Kirigami.Theme.View
+@@ -50,86 +48,6 @@ Item {
+ // dismiss drawer when background is clicked
+ onClicked: root.actionDrawer.close();
+
+- // left side
+- ColumnLayout {
+- id: columnLayout
+-
+- opacity: opacityValue
+- spacing: 0
+-
+- anchors {
+- top: mediaControlsWidgetProxy.bottom
+- topMargin: 0
+- bottom: parent.bottom
+- bottomMargin: 0
+- right: quickSettingsPanel.left
+- left: parent.left
+- }
+- anchors.margins: minWidthHeight * 0.06
+-
+- MobileShell.BaseItem {
+- id: notificationWidgetProxy
+-
+- // don't allow notifications widget to get too wide
+- Layout.maximumWidth: Kirigami.Units.gridUnit * 25
+- Layout.fillHeight: true
+- Layout.fillWidth: true
+- Layout.topMargin: minWidthHeight * 0.02
+- }
+- }
+-
+- PlasmaComponents.Label {
+- id: clock
+- text: Qt.formatTime(timeSource.data.Local.DateTime, MobileShell.ShellUtil.isSystem24HourFormat ? "h:mm" : "h:mm ap")
+- verticalAlignment: Qt.AlignVCenter
+- opacity: Math.min(brightnessPressedValue, columnLayout.opacity)
+-
+- anchors {
+- left: parent.left
+- top: parent.top
+- topMargin: columnLayout.anchors.margins / 2
+- leftMargin: columnLayout.anchors.margins
+- }
+-
+- font.pixelSize: Math.min(40, minWidthHeight * 0.1)
+- font.weight: Font.ExtraLight
+- elide: Text.ElideRight
+- }
+-
+- PlasmaComponents.Label {
+- id: date
+- text: Qt.formatDate(timeSource.data.Local.DateTime, "ddd MMMM d")
+- verticalAlignment: Qt.AlignTop
+- color: Kirigami.Theme.disabledTextColor
+- opacity: Math.min(brightnessPressedValue, columnLayout.opacity)
+-
+- anchors {
+- left: parent.left
+- top: clock.bottom
+- bottom: isOnLargeScreen ? columnLayout.top : mediaControlsWidgetProxy.top
+- topMargin: Kirigami.Units.smallSpacing
+- leftMargin: columnLayout.anchors.margins
+- }
+-
+- font.pixelSize: Math.min(20, minWidthHeight * 0.05)
+- font.weight: Font.Light
+- }
+-
+- MobileShell.BaseItem {
+- id: mediaControlsWidgetProxy
+- property real fullHeight: visible ? height + Kirigami.Units.smallSpacing * 6 : 0
+-
+- y: isOnLargeScreen ? date.y - height + date.implicitHeight : date.y + date.implicitHeight + columnLayout.anchors.margins / 2
+- opacity: columnLayout.opacity
+-
+- anchors {
+- right: quickSettingsPanel.left
+- left: isOnLargeScreen ? date.right : parent.left
+- leftMargin: columnLayout.anchors.margins
+- rightMargin: columnLayout.anchors.margins - quickSettingsPanel.leftPadding
+- }
+- }
+-
+ // right sidebar
+ MobileShell.QuickSettingsPanel {
+ id: quickSettingsPanel
+@@ -139,7 +57,7 @@ Item {
+ readonly property real intendedWidth: 360
+
+ property real offsetRatio: quickSettingsPanel.height / root.height
+- anchors.topMargin: Math.min(root.actionDrawer.offset * offsetRatio - quickSettingsPanel.height, 0)
++ anchors.topMargin: Math.min(root.actionDrawer.offsetResistance * offsetRatio - quickSettingsPanel.height, 0)
+ anchors.top: parent.top
+ anchors.right: parent.right
+
+diff --git a/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml b/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml
+new file mode 100644
+index 000000000..480ba7d31
+--- /dev/null
++++ b/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml
+@@ -0,0 +1,200 @@
++/*
++ * SPDX-FileCopyrightText: 2024 Micah Stanley <stanleymicah@proton.me>
++ *
++ * SPDX-License-Identifier: LGPL-2.0-or-later
++ */
++
++import QtQuick 2.15
++import QtQuick.Controls 2.15
++import QtQuick.Layouts 1.1
++
++import org.kde.plasma.plasma5support 2.0 as P5Support
++import org.kde.plasma.components 3.0 as PlasmaComponents
++import org.kde.plasma.private.mobileshell as MobileShell
++import org.kde.kirigami 2.20 as Kirigami
++
++Item {
++ id: root
++
++ required property var actionDrawer
++ required property var contentContainer
++ required property var swipeArea
++
++ property alias mediaControlsWidget: notificationWidget.header
++ property alias notificationWidget: notificationWidget
++ property real contentY: notificationWidget.listView.contentY
++
++ property real topPadding: actionDrawer.mode == ActionDrawer.Portrait ? Kirigami.Units.largeSpacing : date.y + date.height + Kirigami.Units.smallSpacing * 6
++ property real topMargin: actionDrawer.mode == ActionDrawer.Portrait ? actionDrawer.offsetResistance + 1 : 0
++
++ readonly property real minWidthHeight: Math.min(actionDrawer.width, actionDrawer.height)
++ readonly property bool hasNotifications: notificationWidget.hasNotifications
++ readonly property bool listOverflowing: notificationWidget.listView.listOverflowing
++
++ height: Math.min(actionDrawer.height - toolButtons.height, notificationWidget.listView.contentHeight + 10 + topMargin)
++
++ // time source for the time and date whenin landscape mode
++ P5Support.DataSource {
++ id: timeSource
++ engine: "time"
++ connectedSources: ["Local"]
++ interval: 60 * 1000
++ }
++
++ Kirigami.Theme.colorSet: Kirigami.Theme.View
++ Kirigami.Theme.inherit: false
++
++ MobileShell.VelocityCalculator {
++ id: velocityCalculator
++ }
++
++ // notification list widget
++ // margin adjusted to fit and postion into the action drawer
++ MobileShell.NotificationsWidget {
++ id: notificationWidget
++ anchors.fill: parent
++ anchors.topMargin: root.topMargin
++ anchors.rightMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0)
++ anchors.leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : -Kirigami.Units.gridUnit
++
++ historyModel: actionDrawer.notificationModel
++ historyModelType: actionDrawer.notificationModelType
++ notificationSettings: actionDrawer.notificationSettings
++ actionsRequireUnlock: actionDrawer.restrictedPermissions
++ onUnlockRequested: actionDrawer.permissionsRequested()
++ topPadding: root.topPadding
++ showHeader: actionDrawer.mode != ActionDrawer.Portrait
++ listView.interactive: !actionDrawer.dragging && root.listOverflowing
++
++ Connections {
++ target: actionDrawer
++
++ function onRunPendingNotificationAction() {
++ notificationWidget.runPendingAction();
++ }
++ }
++
++ // the first swipe when at the top of the notification list is handeled using a MouseArea, not the flickable
++ // this is so one can swipe down from the top of the notification drawer to expand the action drawer
++ DragHandler {
++ id: dragHandler
++ yAxis.enabled: true
++ xAxis.enabled: false
++
++ property bool startActive: false
++
++ property real startOffset: 0
++ property real startMouseY: 0
++ property real lastMouseY: 0
++
++ property bool startedAtYBeginning: false
++ property bool startedAtYEnd: false
++ property bool drawerDrag: true
++
++ property string currentState
++
++ onTranslationChanged: {
++ if (startActive) {
++ dragHandler.startedAtYBeginning = notificationWidget.listView.atYBeginning;
++ dragHandler.startedAtYEnd = notificationWidget.listView.atYEnd;
++ startActive = false;
++
++ if (notificationWidget.listView.atYBeginning) {
++ currentState = actionDrawer.state;
++ actionDrawer.cancelAnimations();
++ actionDrawer.dragging = true;
++ actionDrawer.opened = true;
++ dragHandler.startOffset = actionDrawer.offset;
++ dragHandler.startMouseY = translation.y;
++ dragHandler.lastMouseY = dragHandler.startMouseY;
++ dragHandler.drawerDrag = true;
++
++ velocityCalculator.startMeasure();
++ velocityCalculator.changePosition(notificationWidget.listView.contentY);
++ }
++ }
++
++ if (!actionDrawer.dragging) {
++ return;
++ }
++
++ if (!(dragHandler.startedAtYBeginning && dragHandler.startedAtYEnd) && ((dragHandler.startedAtYBeginning && (dragHandler.startMouseY - translation.y) > 0) || (dragHandler.startedAtYEnd && (translation.y - dragHandler.startMouseY) > 0))) {
++ actionDrawer.state = currentState;
++ dragHandler.drawerDrag = false;
++ }
++
++ if (dragHandler.drawerDrag) {
++ actionDrawer.offset = dragHandler.startOffset - (dragHandler.startMouseY - translation.y);
++ } else {
++ let contentY = notificationWidget.listView.contentY - (translation.y - dragHandler.lastMouseY);
++
++ notificationWidget.listView.contentY = contentY;
++ velocityCalculator.changePosition(notificationWidget.listView.contentY);
++ dragHandler.lastMouseY = translation.y;
++ }
++ }
++
++ onActiveChanged: {
++ startActive = active;
++
++ if (!active) { // release event
++ if (actionDrawer.dragging) {
++ if (dragHandler.drawerDrag) {
++ actionDrawer.updateState();
++ } else {
++ notificationWidget.listView.flick(0, -velocityCalculator.velocity);
++ }
++ }
++ actionDrawer.dragging = false;
++ dragHandler.drawerDrag = true;
++ }
++ }
++ }
++
++ }
++
++ // time and date displayed in landscape mode
++ Item {
++ id: landscapeModeHeader
++ anchors.fill: parent
++ visible: actionDrawer.mode != ActionDrawer.Portrait
++
++ transform: [
++ Translate {
++ y: -notificationWidget.listView.contentY + notificationWidget.listView.originY
++ }
++ ]
++
++ PlasmaComponents.Label {
++ id: clock
++ text: Qt.formatTime(timeSource.data.Local.DateTime, MobileShell.ShellUtil.isSystem24HourFormat ? "h:mm" : "h:mm ap")
++ verticalAlignment: Qt.AlignVCenter
++
++ anchors {
++ left: parent.left
++ top: parent.top
++ topMargin: minWidthHeight * 0.03
++ }
++
++ font.pixelSize: Math.min(40, minWidthHeight * 0.1)
++ font.weight: Font.ExtraLight
++ elide: Text.ElideRight
++ }
++
++ PlasmaComponents.Label {
++ id: date
++ text: Qt.formatDate(timeSource.data.Local.DateTime, "ddd MMMM d")
++ verticalAlignment: Qt.AlignTop
++ color: Kirigami.Theme.disabledTextColor
++
++ anchors {
++ left: parent.left
++ top: clock.bottom
++ topMargin: Kirigami.Units.smallSpacing
++ }
++
++ font.pixelSize: Math.min(20, minWidthHeight * 0.05)
++ font.weight: Font.Light
++ }
++ }
++}
+diff --git a/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml b/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml
+index c4f2e26c4..ad892d027 100644
+--- a/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml
++++ b/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml
+@@ -28,7 +28,6 @@ Item {
+ property alias quickSettings: quickSettingsDrawer.quickSettings
+ property alias statusBar: quickSettingsDrawer.statusBar
+ property alias mediaControlsWidget: quickSettingsDrawer.mediaControlsWidget
+- property alias notificationsWidget: notificationWidgetProxy.contentItem
+
+ Kirigami.Theme.colorSet: Kirigami.Theme.View
+ Kirigami.Theme.inherit: false
+@@ -39,19 +38,20 @@ Item {
+
+ MobileShell.QuickSettingsDrawer {
+ id: quickSettingsDrawer
+- z: 1 // ensure it's above notifications
+
+ // physically move the drawer when between closed <-> pinned mode
+ readonly property real offsetHeight: actionDrawer.openToPinnedMode ? minimizedQuickSettingsOffset : maximizedQuickSettingsOffset
+- anchors.topMargin: Math.min(root.actionDrawer.offset - offsetHeight, 0)
+- anchors.top: parent.top
+- anchors.left: parent.left
+- anchors.right: parent.right
++ anchors {
++ topMargin: Math.min(root.actionDrawer.offsetResistance - offsetHeight, 0)
++ top: parent.top
++ left: parent.left
++ right: parent.right
++ }
+
+ actionDrawer: root.actionDrawer
+
+ // opacity and move animation (disabled when openToPinnedMode is false)
+- property real offsetDist: actionDrawer.offset - minimizedQuickSettingsOffset
++ property real offsetDist: actionDrawer.offsetResistance - minimizedQuickSettingsOffset
+ property real totalOffsetDist: maximizedQuickSettingsOffset - minimizedQuickSettingsOffset
+ minimizedToFullProgress: actionDrawer.openToPinnedMode ? (actionDrawer.opened ? applyMinMax(offsetDist / totalOffsetDist) : 0) : 1
+
+@@ -65,33 +65,12 @@ Item {
+ addedHeight: {
+ if (!actionDrawer.openToPinnedMode) {
+ // if pinned mode disabled, just go to full height
+- let progress = (root.actionDrawer.offset - maximizedQuickSettingsOffset) / (quickSettingsDrawer.maxAddedHeight * 4);
+- let effectProgress = Math.atan(Math.max(0, progress));
+- return (quickSettingsDrawer.maxAddedHeight * effectProgress) + quickSettingsDrawer.maxAddedHeight;
++ return Math.max(maximizedQuickSettingsOffset - minimizedQuickSettingsOffset, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset)
+ } else if (!actionDrawer.opened) {
+- // over-scroll effect for initial opening
+- let progress = (root.actionDrawer.offset - minimizedQuickSettingsOffset) / quickSettingsDrawer.maxAddedHeight;
+- let effectProgress = Math.atan(Math.max(0, progress));
+- return quickSettingsDrawer.maxAddedHeight * 0.25 * effectProgress;
++ return Math.max(0, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset)
+ } else {
+- // over-scroll effect for full drawer
+- let progress = (root.actionDrawer.offset - maximizedQuickSettingsOffset) / (quickSettingsDrawer.maxAddedHeight * 4);
+- let effectProgress = Math.atan(Math.max(0, progress));
+- // as the drawer opens, add height to the rectangle, revealing content
+- return (quickSettingsDrawer.maxAddedHeight * effectProgress) + Math.max(0, Math.min(quickSettingsDrawer.maxAddedHeight, root.actionDrawer.offset - minimizedQuickSettingsOffset));
++ return Math.max(0, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset)
+ }
+ }
+ }
+-
+- MobileShell.BaseItem {
+- id: notificationWidgetProxy
+-
+- anchors {
+- top: quickSettingsDrawer.bottom
+- bottom: parent.bottom
+- left: parent.left
+- right: parent.right
+- }
+- opacity: applyMinMax(root.actionDrawer.offset / root.minimizedQuickSettingsOffset)
+- }
+ }
+diff --git a/containments/homescreens/folio/package/contents/ui/private/FlickableOpacityGradient.qml b/components/mobileshell/qml/components/FlickableOpacityGradient.qml
+similarity index 100%
+rename from containments/homescreens/folio/package/contents/ui/private/FlickableOpacityGradient.qml
+rename to components/mobileshell/qml/components/FlickableOpacityGradient.qml
+diff --git a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml
+index 0e32a7cfd..ce127eecf 100644
+--- a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml
++++ b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml
+@@ -53,6 +53,31 @@ Item {
+ */
+ property bool actionsRequireUnlock: false
+
++ /**
++ * Top padding of the notification list.
++ */
++ property int topPadding: 0
++
++ /**
++ * Bottom padding of the notification list.
++ */
++ property int bottomPadding: 0
++
++ /**
++ * Header component for notification list.
++ */
++ property var header
++
++ /**
++ * Whether to show the header component.
++ */
++ property bool showHeader: false
++
++ /**
++ * Gives access to the notification list view outside of the notification widget.
++ */
++ property alias listView: list
++
+ /**
+ * Whether the widget has notifications.
+ */
+@@ -73,11 +98,6 @@ Item {
+ */
+ signal unlockRequested()
+
+- /**
+- * Emitted when the background is clicked (not a notification or other element).
+- */
+- signal backgroundClicked()
+-
+ /**
+ * Run pending action that was pending for authentication when unlockRequested() was emitted.
+ */
+@@ -128,13 +148,6 @@ Item {
+ intervalAlignment: P5Support.Types.AlignToMinute
+ }
+
+- // implement background clicking signal
+- MouseArea {
+- anchors.fill: parent
+- onClicked: backgroundClicked()
+- z: -1 // ensure that this is below notification items so we don't steal button clicks
+- }
+-
+ ListView {
+ id: list
+ model: historyModel
+@@ -148,10 +161,11 @@ Item {
+ readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.longDuration : 0
+
+ // If a screen overflow occurs, fix height in order to maintain tool buttons in place.
+- readonly property bool listOverflowing: contentItem.childrenRect.height + toolButtons.height + spacing >= root.height
++ readonly property bool listOverflowing: listHeight + spacing >= root.height
++ readonly property int listHeight: contentItem.childrenRect.height
+
+ bottomMargin: spacing
+- height: count === 0 ? 0 : (listOverflowing ? root.height - toolButtons.height : contentItem.childrenRect.height + bottomMargin)
++ height: count === 0 ? (root.topPadding + (showHeader ? root.header.height + listHeight : 0)) : (listOverflowing ? root.height : listHeight + bottomMargin)
+
+ anchors {
+ top: parent.top
+@@ -159,7 +173,7 @@ Item {
+ right: parent.right
+ }
+
+- boundsBehavior: Flickable.StopAtBounds
++ boundsBehavior: Flickable.DragAndOvershootBounds
+ spacing: Kirigami.Units.largeSpacing
+
+ // TODO keyboard focus
+@@ -167,6 +181,39 @@ Item {
+ highlightResizeDuration: 0
+ highlight: Item {}
+
++ // media control widget
++ // added to the notification list when in landscape mode
++ Component {
++ id: headerComponent
++ Item {
++ width: parent.width
++
++ MobileShell.BaseItem {
++ id: headerComponentProxy
++
++ contentItem: showHeader ? root.header : null
++ y: root.topPadding - Kirigami.Units.largeSpacing
++
++ width: parent.width - Kirigami.Units.gridUnit * 2
++ anchors.left: parent.left
++ anchors.leftMargin: Kirigami.Units.gridUnit
++ }
++ }
++ }
++
++ // set bottom padding for the notification list
++ Component {
++ id: footerComponent
++ Item {
++ width: parent.width
++ height: root.bottomPadding
++ }
++ }
++
++ header: headerComponent
++
++ footer: footerComponent
++
+ section {
+ property: "isGroup"
+ criteria: ViewSection.FullString
+@@ -250,12 +297,28 @@ Item {
+ PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: false }
+ }
+
++ // adjust top paddding for media control widget
+ Component {
+ id: groupDelegate
+- NotificationGroupHeader {
+- applicationName: model.applicationName
+- applicationIconSource: model.applicationIconName
+- originName: model.originName || ""
++
++ Column {
++ spacing: Kirigami.Units.smallSpacing
++
++ height: headerSpace.height + groupHeader.height
++
++ Item {
++ id: headerSpace
++ width: parent.width
++ height: index == 0 ? root.topPadding + (showHeader ? root.header.height : 0) : 0
++ visible: index == 0
++ }
++
++ NotificationGroupHeader {
++ id: groupHeader
++ applicationName: model.applicationName
++ applicationIconSource: model.applicationIconName
++ originName: model.originName || ""
++ }
+ }
+ }
+
+@@ -265,7 +328,14 @@ Item {
+ Column {
+ spacing: Kirigami.Units.smallSpacing
+
+- height: notificationItem.height + showMoreLoader.height
++ height: headerSpace.height + notificationItem.height + showMoreLoader.height
++
++ Item {
++ id: headerSpace
++ width: parent.width
++ height: index == 0 ? root.topPadding + (showHeader ? root.header.height : 0) : 0
++ visible: index == 0
++ }
+
+ NotificationItem {
+ id: notificationItem
+@@ -303,7 +373,7 @@ Item {
+ return false;
+
+ return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
+- && delegateLoader.ListView.nextSection != delegateLoader.ListView.section;
++ && delegateLoader.ListView.nextSection != delegateLoader.ListView.section;
+ }
+
+ // state + transition: animates the item when it becomes visible. Fade off is handled by above ListView.onRemove.
+@@ -323,8 +393,8 @@ Item {
+ sourceComponent: PlasmaComponents3.ToolButton {
+ icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
+ text: model.isGroupExpanded ? i18n("Show Fewer")
+- : i18nc("Expand to show n more notifications",
+- "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
++ : i18nc("Expand to show n more notifications",
++ "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
+ onClicked: {
+ list.setGroupExpanded(model.index, !model.isGroupExpanded)
+ }
+@@ -334,58 +404,4 @@ Item {
+ }
+ }
+ }
+-
+- Item {
+- id: toolButtons
+- height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0
+-
+- // do not show on lockscreen
+- visible: !root.actionsRequireUnlock
+-
+- anchors {
+- top: list.bottom
+- left: parent.left
+- right: parent.right
+- }
+-
+- Rectangle {
+- id: spacer
+- anchors.left: parent.left
+- anchors.right: parent.right
+-
+- visible: list.listOverflowing
+- height: 1
+- opacity: 0.25
+- color: Kirigami.Theme.textColor
+- }
+-
+- RowLayout {
+- id: toolLayout
+-
+- anchors {
+- top: spacer.bottom
+- right: parent.right
+- left: parent.left
+- leftMargin: Kirigami.Units.largeSpacing
+- rightMargin: Kirigami.Units.largeSpacing
+- topMargin: list.spacing
+- bottomMargin: list.spacing
+- }
+-
+- PlasmaComponents3.ToolButton {
+- id: clearButton
+-
+- Layout.alignment: Qt.AlignCenter
+-
+- visible: hasNotifications
+-
+- font.bold: true
+- font.pointSize: Kirigami.Theme.smallFont.pointSize
+-
+- icon.name: "edit-clear-history"
+- text: i18n("Clear All Notifications")
+- onClicked: clearHistory()
+- }
+- }
+- }
+ }
+diff --git a/containments/homescreens/folio/package/contents/ui/AppDrawer.qml b/containments/homescreens/folio/package/contents/ui/AppDrawer.qml
+index 10167a616..d479b505f 100644
+--- a/containments/homescreens/folio/package/contents/ui/AppDrawer.qml
++++ b/containments/homescreens/folio/package/contents/ui/AppDrawer.qml
+@@ -62,12 +62,12 @@ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+- opacity: 0
++ opacity: 0 // we display with the opacity gradient below
+ headerHeight: root.headerHeight
+ }
+
+ // opacity gradient at grid edges
+- FlickableOpacityGradient {
++ MobileShell.FlickableOpacityGradient {
+ anchors.fill: appDrawerGrid
+ flickable: appDrawerGrid
+ }
+diff --git a/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml b/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml
+index b02bb3129..518d25bff 100644
+--- a/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml
++++ b/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml
+@@ -13,6 +13,7 @@ import org.kde.plasma.private.shell 2.0
+ import org.kde.private.mobile.homescreen.folio 1.0 as Folio
+ import org.kde.kirigamiaddons.formcard 1.0 as FormCard
+ import org.kde.plasma.components 3.0 as PC3
++import org.kde.plasma.private.mobileshell as MobileShell
+
+ import '../delegate'
+ import '../private'
+@@ -199,7 +200,7 @@ MouseArea {
+ }
+
+ // opacity gradient at grid edges
+- FlickableOpacityGradient {
++ MobileShell.FlickableOpacityGradient {
+ anchors.fill: gridView
+ flickable: gridView
+ }
+diff --git a/shell/contents/lockscreen/HeaderComponent.qml b/shell/contents/lockscreen/HeaderComponent.qml
+index 00ee89cb1..8844e8f32 100644
+--- a/shell/contents/lockscreen/HeaderComponent.qml
++++ b/shell/contents/lockscreen/HeaderComponent.qml
+@@ -20,6 +20,8 @@ Item {
+
+ property var notificationsModel: []
+
++ readonly property bool actionDrawerVisible: swipeArea.actionDrawer.intendedToBeVisible
++
+ signal passwordRequested()
+
+ // The status bar and quicksettings take a while to load, don't pause initial lockscreen loading for it
+diff --git a/shell/contents/lockscreen/LockScreen.qml b/shell/contents/lockscreen/LockScreen.qml
+index 94346d5e8..314e8c71e 100644
+--- a/shell/contents/lockscreen/LockScreen.qml
++++ b/shell/contents/lockscreen/LockScreen.qml
+@@ -30,7 +30,7 @@ Item {
+ readonly property bool isWidescreen: root.height < 720 && (root.height < root.width * 0.75)
+ property bool notificationsShown: false
+
+- property var passwordBar: flickableLoader.item ? flickableLoader.item.passwordBar : null
++ property var passwordBar: flickableLoader.item ? flickableLoader.item.flickable.passwordBar : null
+
+ Component.onCompleted: {
+ forceActiveFocus();
+@@ -40,7 +40,7 @@ Item {
+ Keys.onPressed: (event) => {
+ if (flickableLoader.item) {
+ root.lockScreenState.isKeyboardMode = true;
+- flickableLoader.item.goToOpenPosition();
++ flickableLoader.item.flickable.goToOpenPosition();
+ passwordBar.textField.forceActiveFocus();
+
+ passwordBar.keyPress(event.text);
+@@ -63,7 +63,7 @@ Item {
+
+ sourceComponent: WallpaperBlur {
+ source: wallpaper
+- opacity: flickableLoader.item ? flickableLoader.item.openFactor : 0
++ opacity: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0
+ }
+ }
+
+@@ -73,7 +73,7 @@ Item {
+ // Ensure keypad is opened when password is updated (ex. keyboard)
+ function onPasswordChanged() {
+ if (root.lockScreenState.password !== "" && flickableLoader.item) {
+- flickableLoader.item.goToOpenPosition();
++ flickableLoader.item.flickable.goToOpenPosition();
+ }
+ }
+ }
+@@ -85,7 +85,7 @@ Item {
+ onDpmsTurnedOff: (screen) => {
+ if (screen.name === Screen.name) {
+ if (flickableLoader.item) {
+- flickableLoader.item.goToClosePosition();
++ flickableLoader.item.flickable.goToClosePosition();
+ }
+ lockScreenState.resetPassword();
+ }
+@@ -102,7 +102,7 @@ Item {
+ z: 1
+ anchors.fill: parent
+ statusBarHeight: MobileShell.Constants.topPanelHeight
+- openFactor: flickableLoader.item ? flickableLoader.item.openFactor : 0
++ openFactor: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0
+ notificationsModel: root.notifModel
+ onPasswordRequested: root.askPassword()
+ }
+@@ -145,43 +145,103 @@ Item {
+ }
+
+ // Container for lockscreen contents
+- sourceComponent: FlickContainer {
+- id: flickable
+- property alias passwordBar: keypad.passwordBar
+-
+- // Speed up animation when passwordless
+- animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800
++ sourceComponent: Item {
++ id: item
++ property alias flickable: flickable
++ FlickContainer {
++ id: flickable
++ anchors.fill: parent
++ property alias passwordBar: keypad.passwordBar
+
+- // Distance to swipe to fully open keypad
+- keypadHeight: Kirigami.Units.gridUnit * 20
++ // Speed up animation when passwordless
++ animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800
+
+- Component.onCompleted: {
+- // Go to closed position when loaded
+- flickable.position = 0;
+- flickable.goToClosePosition();
+- }
++ // Distance to swipe to fully open keypad
++ keypadHeight: Kirigami.Units.gridUnit * 20
+
+- // Unlock lockscreen if it's already unlocked and keypad is opened
+- onOpened: {
+- if (root.lockScreenState.canBeUnlocked) {
+- Qt.quit();
++ Component.onCompleted: {
++ // Go to closed position when loaded
++ flickable.position = 0;
++ flickable.goToClosePosition();
+ }
+- }
+
+- // Unlock lockscreen if it's already unlocked and keypad is open
+- Connections {
+- target: root.lockScreenState
+- function onCanBeUnlockedChanged() {
+- if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) {
++ // Unlock lockscreen if it's already unlocked and keypad is opened
++ onOpened: {
++ if (root.lockScreenState.canBeUnlocked) {
+ Qt.quit();
+ }
+ }
+- }
+
+- // Clear entered password after closing keypad
+- onOpenFactorChanged: {
+- if (flickable.openFactor < 0.1 && !flickable.movingUp) {
+- root.passwordBar.clear();
++ // Unlock lockscreen if it's already unlocked and keypad is open
++ Connections {
++ target: root.lockScreenState
++ function onCanBeUnlockedChanged() {
++ if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) {
++ Qt.quit();
++ }
++ }
++ }
++
++ // Clear entered password after closing keypad
++ onOpenFactorChanged: {
++ if (flickable.openFactor < 0.1 && !flickable.movingUp) {
++ root.passwordBar.clear();
++ }
++ }
++
++ QuickActionButton {
++ id: leftButton
++ buttonAction: ShellSettings.Settings.lockscreenLeftButtonAction
++ opacity: Math.max(0, 1 - flickable.openFactor * 2)
++ anchors {
++ bottom: parent.bottom
++ left: parent.left
++ bottomMargin: Kirigami.Units.largeSpacing * 3
++ leftMargin: Kirigami.Units.largeSpacing * 3
++ }
++ }
++
++ // scroll up icon
++ BottomIconIndicator {
++ id: scrollUpIconLoader
++ lockScreenState: root.lockScreenState
++ opacity: Math.max(0, 1 - flickable.openFactor * 2)
++
++ anchors.bottom: parent.bottom
++ anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1
++ anchors.horizontalCenter: parent.horizontalCenter
++ }
++
++ QuickActionButton {
++ id: rightButton
++ buttonAction: ShellSettings.Settings.lockscreenRightButtonAction
++ opacity: Math.max(0, 1 - flickable.openFactor * 2)
++ anchors {
++ bottom: parent.bottom
++ right: parent.right
++ bottomMargin: Kirigami.Units.largeSpacing * 3
++ rightMargin: Kirigami.Units.largeSpacing * 3
++ }
++ }
++
++ Rectangle {
++ id: keypadScrim
++ anchors.fill: parent
++ visible: opacity > 0
++ opacity: flickable.openFactor
++ color: Qt.rgba(0, 0, 0, 0.5)
++ }
++
++ Keypad {
++ id: keypad
++ visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login
++ anchors.fill: parent
++ openProgress: flickable.openFactor
++ lockScreenState: root.lockScreenState
++
++ // only show in last 50% of anim
++ opacity: (flickable.openFactor - 0.5) * 2
++ transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 }
+ }
+ }
+
+@@ -199,50 +259,14 @@ Item {
+ }
+ ]
+
+- fullHeight: root.height
+-
+ lockScreenState: root.lockScreenState
+ notificationsModel: root.notifModel
+ onNotificationsShownChanged: root.notificationsShown = notificationsShown
+ onPasswordRequested: flickable.goToOpenPosition()
+
+- anchors.topMargin: headerBar.statusBarHeight
+- anchors.top: parent.top
+- anchors.bottom: parent.bottom
+- anchors.left: parent.left
+- anchors.right: parent.right
+- }
+-
+- // scroll up icon
+- BottomIconIndicator {
+- id: scrollUpIconLoader
+- lockScreenState: root.lockScreenState
+- opacity: Math.max(0, 1 - flickable.openFactor * 2)
+-
+- anchors.bottom: parent.bottom
+- anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1
+- anchors.horizontalCenter: parent.horizontalCenter
+- }
++ scrollLock: headerBar.actionDrawerVisible || (flickableLoader.item ? flickableLoader.item.flickable.openFactor > 0.2 : false)
++ z: scrollLock ? -1 : 0
+
+- Rectangle {
+- id: keypadScrim
+- anchors.fill: parent
+- visible: opacity > 0
+- opacity: flickable.openFactor
+- color: Qt.rgba(0, 0, 0, 0.5)
+- }
+-
+- Keypad {
+- id: keypad
+- visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login
+- anchors.fill: parent
+- openProgress: flickable.openFactor
+- lockScreenState: root.lockScreenState
+-
+- // only show in last 50% of anim
+- opacity: (flickable.openFactor - 0.5) * 2
+- transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 }
+- }
+ }
+ }
+ }
+diff --git a/shell/contents/lockscreen/LockScreenContent.qml b/shell/contents/lockscreen/LockScreenContent.qml
+index a76f724fe..d5acc97e0 100644
+--- a/shell/contents/lockscreen/LockScreenContent.qml
++++ b/shell/contents/lockscreen/LockScreenContent.qml
+@@ -13,13 +13,15 @@ import org.kde.plasma.private.mobileshell as MobileShell
+ Item {
+ id: root
+
+- required property var lockScreenState
+ required property bool isVertical
++ required property var lockScreenState
++
++ readonly property bool listOverflowing: notificationComponent.listOverflowing
+
+ property var notificationsModel: []
+ property bool notificationsShown: false
+
+- property real fullHeight
++ property bool scrollLock: false
+
+ signal passwordRequested()
+
+@@ -29,7 +31,6 @@ Item {
+ visible: root.isVertical
+ spacing: 0
+
+- // Center clock when no notifications are shown, otherwise move the clock upward
+ anchors.topMargin: Kirigami.Units.gridUnit * 3.5
+ anchors.bottomMargin: Kirigami.Units.gridUnit * 2
+ anchors.fill: parent
+@@ -85,6 +86,7 @@ Item {
+ }
+
+ MobileShell.MediaControlsWidget {
++ id: mediaControlsWidget
+ Layout.alignment: root.isVertical ? Qt.AlignHCenter : Qt.AlignLeft
+ Layout.fillWidth: true
+ Layout.maximumWidth: Kirigami.Units.gridUnit * 25
+@@ -93,20 +95,24 @@ Item {
+ }
+ }
+
++ // notification widget column
+ NotificationsComponent {
+ id: notificationComponent
+ lockScreenState: root.lockScreenState
+ notificationsModel: root.notificationsModel
+
+- Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+- Layout.fillHeight: true
++ Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ Layout.fillWidth: true
++ Layout.fillHeight: true
+ Layout.maximumWidth: Kirigami.Units.gridUnit * (25 + 2) // clip margins
+
++ topPadding: root.isVertical ? (mediaControlsWidget.visible ? Kirigami.Units.smallSpacing : Kirigami.Units.gridUnit) : Kirigami.Units.gridUnit
++
+ leftMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit
+ rightMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit
+- bottomMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit
+- topMargin: Kirigami.Units.gridUnit
++ topMargin: root.isVertical ? 0 : MobileShell.Constants.topPanelHeight
++ bottomMargin: Kirigami.Units.gridUnit * 2
++ scrollLock: root.scrollLock
+
+ onPasswordRequested: root.passwordRequested()
+ onNotificationsShownChanged: root.notificationsShown = notificationsShown
+diff --git a/shell/contents/lockscreen/NotificationsComponent.qml b/shell/contents/lockscreen/NotificationsComponent.qml
+index e5992220d..09fa51ea7 100644
+--- a/shell/contents/lockscreen/NotificationsComponent.qml
++++ b/shell/contents/lockscreen/NotificationsComponent.qml
+@@ -20,7 +20,13 @@ Loader {
+ property real rightMargin: 0
+ property real topMargin: 0
+ property real bottomMargin: 0
++
++ property real topPadding: 0
++
+ readonly property bool notificationsShown: item && item.notificationsList.hasNotifications
++ readonly property bool listOverflowing: item && item.notificationsList.listView.listOverflowing
++
++ property bool scrollLock: false
+
+ property var notificationsList: item ? item.notificationsList : null
+
+@@ -56,24 +62,31 @@ Loader {
+ property alias notificationsList: notificationsList
+
+ Item {
+- anchors.fill: parent
++ anchors.top: parent.top
++ anchors.left: parent.left
++ anchors.right: parent.right
+ anchors.topMargin: root.topMargin
+- anchors.bottomMargin: root.bottomMargin
+ anchors.leftMargin: root.leftMargin
+ anchors.rightMargin: root.rightMargin
+
+ Kirigami.Theme.colorSet: Kirigami.Theme.Window
+ Kirigami.Theme.inherit: false
+
++ height: Math.min(parent.height - root.topMargin - root.bottomMargin, notificationsList.listView.listHeight + Kirigami.Units.gridUnit)
++
+ MobileShell.NotificationsWidget {
+ id: notificationsList
+ anchors.fill: parent
++ opacity: 0 // we display with the opacity gradient below
+
+ historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel
+ actionsRequireUnlock: true
+ historyModel: root.notificationsModel
+ notificationSettings: root.notificationSettings
+ inLockscreen: true
++ topPadding: root.topPadding // Kirigami.Units.gridUnit
++ bottomPadding: Kirigami.Units.gridUnit
++ listView.interactive: !root.scrollLock && listView.listOverflowing
+
+ property bool requestNotificationAction: false
+
+@@ -82,6 +95,17 @@ Loader {
+ root.passwordRequested();
+ }
+ }
++
++ // opacity gradient at flickable edges
++ MobileShell.FlickableOpacityGradient {
++ anchors {
++ top: notificationsList.top
++ left: notificationsList.left
++ right: notificationsList.right
++ }
++ height: notificationsList.listView.height
++ flickable: notificationsList.listView
++ }
+ }
+ }
+ }
+--
+GitLab
+
diff -Nru plasma-mobile-6.3.5/envmanager/CMakeLists.txt plasma-mobile-6.3.6/envmanager/CMakeLists.txt
--- plasma-mobile-6.3.5/envmanager/CMakeLists.txt 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/envmanager/CMakeLists.txt 2025-07-08 13:47:14.000000000 +0200
@@ -4,7 +4,7 @@
set(plasma-mobile-envmanager_SRCS
main.cpp
settings.cpp
- utils.h
+ utils.cpp
config.h
)
diff -Nru plasma-mobile-6.3.5/envmanager/config.h plasma-mobile-6.3.6/envmanager/config.h
--- plasma-mobile-6.3.5/envmanager/config.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/envmanager/config.h 2025-07-08 13:47:14.000000000 +0200
@@ -32,7 +32,7 @@
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_SETTINGS = {{"KDE", {{"LookAndFeelPackage", "org.kde.breeze.mobile"}}}};
-// kwinrc
+// plasma-mobile/kwinrc
QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mobileConfig)
{
auto group = KConfigGroup{m_mobileConfig, QStringLiteral("General")};
@@ -40,6 +40,7 @@
return {{"Windows",
{
+ {"BorderlessMaximizedWindows", !convergenceModeEnabled}, // turn off window decorations when not in convergence mode
{"Placement", convergenceModeEnabled ? "Centered" : "Maximizing"}, // maximize all windows by default if we aren't in convergence mode
{"InteractiveWindowMoveEnabled", convergenceModeEnabled} // only allow window moving in convergence mode
}},
@@ -72,5 +73,5 @@
const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "screenedge"};
const QList<QString> KWIN_SCRIPTS = {"convergentwindows"};
-//ksmserver
+// plasma-mobile/ksmserver
const QMap<QString, QMap<QString, QVariant>> KSMSERVER_SETTINGS = {{"General", {{"loginMode", "emptySession"}}}};
diff -Nru plasma-mobile-6.3.5/envmanager/settings.cpp plasma-mobile-6.3.6/envmanager/settings.cpp
--- plasma-mobile-6.3.5/envmanager/settings.cpp 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/envmanager/settings.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -18,15 +18,16 @@
const QString CONFIG_FILE = u"plasmamobilerc"_s;
const QString SAVED_CONFIG_GROUP = u"SavedConfig"_s;
+// In bin/startplasmamobile, we add `~/.config/plasma-mobile` to XDG_CONFIG_DIRS to overlay our own configs
+const QString MOBILE_KWINRC_FILE = u"plasma-mobile/kwinrc"_s;
+const QString MOBILE_KSMSERVERRC_FILE = u"plasma-mobile/ksmserverrc"_s;
+const QString MOBILE_KDEGLOBALS_FILE = u"plasma-mobile/kdeglobals"_s;
+
Settings::Settings(QObject *parent)
: QObject{parent}
, m_isMobilePlatform{KRuntimePlatform::runtimePlatform().contains(u"phone"_s)}
, m_mobileConfig{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)}
- , m_kwinrcConfig{KSharedConfig::openConfig(u"kwinrc"_s, KConfig::SimpleConfig)}
, m_appBlacklistConfig{KSharedConfig::openConfig(u"applications-blacklistrc"_s, KConfig::SimpleConfig)}
- , m_kdeglobalsConfig{KSharedConfig::openConfig(u"kdeglobals"_s, KConfig::SimpleConfig)}
- , m_ksmServerConfig{KSharedConfig::openConfig(u"ksmserverrc"_s, KConfig::SimpleConfig)}
- , m_configWatcher{KConfigWatcher::create(m_mobileConfig)}
{
}
@@ -51,19 +52,20 @@
void Settings::loadSavedConfiguration()
{
- // kwinrc
- loadKeys(u"kwinrc"_s, m_kwinrcConfig, getKwinrcSettings(m_mobileConfig));
- m_kwinrcConfig->sync();
+ // kwinrc (legacy, we only write in the plasma-mobile/kwinrc file now)
+ auto originalKwinrcConfig = KSharedConfig::openConfig(u"kwinrc"_s, KConfig::SimpleConfig);
+ loadKeys(u"kwinrc"_s, originalKwinrcConfig, getKwinrcSettings(m_mobileConfig));
+ originalKwinrcConfig->sync();
reloadKWinConfig();
// applications-blacklistrc
loadKeys(u"applications-blacklistrc"_s, m_appBlacklistConfig, APPLICATIONS_BLACKLIST_DEFAULT_SETTINGS);
m_appBlacklistConfig->sync();
- // kdeglobals
- loadKeys(u"kdeglobals"_s, m_kdeglobalsConfig, KDEGLOBALS_DEFAULT_SETTINGS);
- loadKeys(u"kdeglobals"_s, m_kdeglobalsConfig, KDEGLOBALS_SETTINGS);
- m_kdeglobalsConfig->sync();
+ // kdeglobals (legacy, we only write in the plasma-mobile/kdeglobals file now)
+ auto originalKdeglobalsConfig = KSharedConfig::openConfig(u"kdeglobals"_s, KConfig::SimpleConfig);
+ loadKeys(u"kdeglobals"_s, originalKdeglobalsConfig, KDEGLOBALS_SETTINGS);
+ originalKdeglobalsConfig->sync();
// save our changes
m_mobileConfig->sync();
@@ -72,32 +74,71 @@
void Settings::applyMobileConfiguration()
{
// kwinrc
- writeKeys(u"kwinrc"_s, m_kwinrcConfig, getKwinrcSettings(m_mobileConfig), false);
- m_kwinrcConfig->sync();
- reloadKWinConfig();
+ {
+ auto kwinSettings = getKwinrcSettings(m_mobileConfig);
+ setOptionsImmutable(false, MOBILE_KWINRC_FILE, kwinSettings);
+
+ auto kwinrc = kwinrcConfig();
+ writeKeys(MOBILE_KWINRC_FILE, kwinrc, kwinSettings);
+ kwinrc->sync();
+ reloadKWinConfig();
+
+ setOptionsImmutable(true, MOBILE_KWINRC_FILE, kwinSettings);
+ }
// applications-blacklistrc
- writeKeys(u"applications-blacklistrc"_s,
- m_appBlacklistConfig,
- APPLICATIONS_BLACKLIST_DEFAULT_SETTINGS,
- true); // only write entries if they are not already defined in the config
- m_appBlacklistConfig->sync();
+ {
+ writeKeysAndSave(u"applications-blacklistrc"_s,
+ m_appBlacklistConfig,
+ APPLICATIONS_BLACKLIST_DEFAULT_SETTINGS,
+ true); // only write entries if they are not already defined in the config
+ m_appBlacklistConfig->sync();
+ }
// kdeglobals
- writeKeys(u"kdeglobals"_s, m_kdeglobalsConfig, KDEGLOBALS_DEFAULT_SETTINGS,
- true); // only write entries if they are not already defined in the config
- writeKeys(u"kdeglobals"_s, m_kdeglobalsConfig, KDEGLOBALS_SETTINGS, false);
- m_kdeglobalsConfig->sync();
+ {
+ setOptionsImmutable(false, MOBILE_KDEGLOBALS_FILE, KDEGLOBALS_SETTINGS);
+
+ auto kdeglobals = KSharedConfig::openConfig(MOBILE_KDEGLOBALS_FILE, KConfig::SimpleConfig);
+ writeKeys(MOBILE_KDEGLOBALS_FILE, kdeglobals, KDEGLOBALS_DEFAULT_SETTINGS); // only write, don't make immutable
+ writeKeys(MOBILE_KDEGLOBALS_FILE, kdeglobals, KDEGLOBALS_SETTINGS);
+ kdeglobals->sync();
+
+ setOptionsImmutable(true, MOBILE_KDEGLOBALS_FILE, KDEGLOBALS_SETTINGS);
+ }
// ksmserver
- writeKeys(u"ksmserverrc"_s, m_ksmServerConfig, KSMSERVER_SETTINGS, false);
- m_ksmServerConfig->sync();
+ {
+ setOptionsImmutable(false, MOBILE_KSMSERVERRC_FILE, KSMSERVER_SETTINGS);
+
+ auto ksmserver = KSharedConfig::openConfig(MOBILE_KSMSERVERRC_FILE, KConfig::SimpleConfig);
+ writeKeys(MOBILE_KSMSERVERRC_FILE, ksmserver, KSMSERVER_SETTINGS);
+ ksmserver->sync();
+
+ setOptionsImmutable(true, MOBILE_KSMSERVERRC_FILE, KSMSERVER_SETTINGS);
+ }
// save our changes
m_mobileConfig->sync();
}
-void Settings::writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings, bool overwriteOnlyIfEmpty)
+void Settings::writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings)
+{
+ const auto groupNames = settings.keys();
+ for (const auto &groupName : groupNames) {
+ auto group = KConfigGroup{config, groupName};
+
+ const auto keys = settings[groupName].keys();
+ for (const auto &key : keys) {
+ group.writeEntry(key, settings[groupName][key], KConfigGroup::Notify);
+ }
+ }
+}
+
+void Settings::writeKeysAndSave(const QString &fileName,
+ KSharedConfig::Ptr &config,
+ const QMap<QString, QMap<QString, QVariant>> &settings,
+ bool overwriteOnlyIfEmpty)
{
const auto groupNames = settings.keys();
for (const auto &groupName : groupNames) {
@@ -174,6 +215,11 @@
return value;
}
+KSharedConfig::Ptr Settings::kwinrcConfig() const
+{
+ return KSharedConfig::openConfig(MOBILE_KWINRC_FILE, KConfig::SimpleConfig);
+}
+
void Settings::reloadKWinConfig()
{
// Reload config
@@ -182,7 +228,7 @@
// Effects need to manually be loaded/unloaded in a live KWin session.
- KConfigGroup pluginsGroup{m_kwinrcConfig, QStringLiteral("Plugins")};
+ KConfigGroup pluginsGroup{kwinrcConfig(), QStringLiteral("Plugins")};
for (const auto &effect : KWIN_EFFECTS) {
// Read from the config whether the effect is enabled (settings are suffixed with "Enabled", ex. blurEnabled)
@@ -209,4 +255,8 @@
// Call "start" to load enabled KWin scripts.
QDBusMessage message = QDBusMessage::createMethodCall(u"org.kde.KWin"_s, u"/Scripting"_s, u"org.kde.kwin.Scripting"_s, u"start"_s);
QDBusConnection::sessionBus().send(message);
+
+ // Call reconfigure
+ QDBusMessage reconfigureMessage = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reconfigure");
+ QDBusConnection::sessionBus().send(reconfigureMessage);
}
diff -Nru plasma-mobile-6.3.5/envmanager/settings.h plasma-mobile-6.3.6/envmanager/settings.h
--- plasma-mobile-6.3.5/envmanager/settings.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/envmanager/settings.h 2025-07-08 13:47:14.000000000 +0200
@@ -28,21 +28,26 @@
// applies our mobile configuration
void applyMobileConfiguration();
- void writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings, bool overwriteOnlyIfEmpty);
+ void writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings);
+
+ void
+ writeKeysAndSave(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings, bool overwriteOnlyIfEmpty);
void loadKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings);
void saveConfigSetting(const QString &fileName, const QString &group, const QString &key, const QVariant value);
const QString loadSavedConfigSetting(KSharedConfig::Ptr &config, const QString &fileName, const QString &group, const QString &key, bool write = true);
+ KSharedConfig::Ptr kwinrcConfig() const;
void reloadKWinConfig();
// whether this is Plasma Mobile
bool m_isMobilePlatform;
KSharedConfig::Ptr m_mobileConfig;
- KSharedConfig::Ptr m_kwinrcConfig;
+ KSharedConfig::Ptr m_kwinrcConfig; // (~/.config/plasma-mobile/kwinrc)
KSharedConfig::Ptr m_appBlacklistConfig;
- KSharedConfig::Ptr m_kdeglobalsConfig;
- KSharedConfig::Ptr m_ksmServerConfig;
+ KSharedConfig::Ptr m_kdeglobalsConfig; // (~/.config/plasma-mobile/kdeglobals)
+ KSharedConfig::Ptr m_ksmServerConfig; // (~/.config/plamsma-mobile/ksmserverrc)
- KConfigWatcher::Ptr m_configWatcher;
+ // For legacy upgrade purposes
+ KSharedConfig::Ptr m_originalKdeglobalsConfig; // (~/.config/kdeglobals)
};
diff -Nru plasma-mobile-6.3.5/envmanager/utils.cpp plasma-mobile-6.3.6/envmanager/utils.cpp
--- plasma-mobile-6.3.5/envmanager/utils.cpp 1970-01-01 01:00:00.000000000 +0100
+++ plasma-mobile-6.3.6/envmanager/utils.cpp 2025-07-08 13:47:14.000000000 +0200
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "utils.h"
+
+#include <QStandardPaths>
+
+void setOptionsImmutable(bool immutable, const QString &configFilePath, const QMap<QString, QMap<QString, QVariant>> &options)
+{
+ // Find ~/.config/{configFilePath}
+ QDir basePath{QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)};
+ QString fullPath = basePath.filePath(configFilePath);
+
+ QFile file{fullPath};
+ if (!file.exists()) {
+ return;
+ }
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qCCritical(LOGGING_CATEGORY) << "Unable to read from" << configFilePath << "to change immutability!";
+ return;
+ }
+
+ QTextStream in(&file);
+ QStringList lines;
+
+ QString configGroup;
+
+ // Read file line by line, and add/remove [$i] suffixes from each option
+ while (!in.atEnd()) {
+ QString line = in.readLine();
+
+ if (line.trimmed().startsWith("//")) {
+ lines << line;
+ continue;
+ }
+
+ // Split by first '=' sign
+ int equalsIndex = line.indexOf('=');
+ if (equalsIndex == -1) {
+ lines << line;
+
+ // Is it a group?
+ if (line.startsWith("[") && line.endsWith("]")) {
+ configGroup = line.mid(1, line.length() - 2);
+ }
+
+ continue;
+ }
+
+ QString key = line.mid(0, equalsIndex);
+ QString value = line.mid(equalsIndex + 1);
+ const QString immutableSuffix = "[$i]";
+
+ // Remove [$i] key suffix
+ if (key.endsWith(immutableSuffix)) {
+ key.chop(immutableSuffix.length());
+ }
+
+ // Add [$i] key suffix, only edit line if it's found in provided options
+ if (immutable && (options.contains(configGroup) && options[configGroup].contains(key))) {
+ key += immutableSuffix;
+ }
+
+ lines << (key + "=" + value);
+ }
+ file.close();
+
+ // Overwrite file with edited lines
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qCCritical(LOGGING_CATEGORY) << "Unable to write to" << configFilePath << "to change immutability!";
+ return;
+ }
+
+ QTextStream out(&file);
+ for (const QString &line : std::as_const(lines)) {
+ out << line << "\n";
+ }
+ file.close();
+}
diff -Nru plasma-mobile-6.3.5/envmanager/utils.h plasma-mobile-6.3.6/envmanager/utils.h
--- plasma-mobile-6.3.5/envmanager/utils.h 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/envmanager/utils.h 2025-07-08 13:47:14.000000000 +0200
@@ -1,12 +1,25 @@
-// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
+// SPDX-FileCopyrightText: 2023-2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <QDir>
+#include <QFile>
#include <QLoggingCategory>
+#include <QTextStream>
static const QLoggingCategory &LOGGING_CATEGORY()
{
static const QLoggingCategory category("plasma-mobile-envmanager");
return category;
}
+
+/**
+ * Sets each config option in the config file to be immutable or not (appended with [$i])
+ * See https://api.kde.org/frameworks/kconfig/html/options.html for more details.
+ *
+ * @param immutable whether to set options to be immutable, or to remove immutability
+ * @param configFilePath path to the config file
+ * @param options the options in the config file to affect (format: <config group, <key, value>>)
+ */
+void setOptionsImmutable(bool immutable, const QString &configFilePath, const QMap<QString, QMap<QString, QVariant>> &options);
diff -Nru plasma-mobile-6.3.5/kwin/mobiletaskswitcher/package/contents/ui/Task.qml plasma-mobile-6.3.6/kwin/mobiletaskswitcher/package/contents/ui/Task.qml
--- plasma-mobile-6.3.5/kwin/mobiletaskswitcher/package/contents/ui/Task.qml 2025-05-06 19:58:57.000000000 +0200
+++ plasma-mobile-6.3.6/kwin/mobiletaskswitcher/package/contents/ui/Task.qml 2025-07-08 13:47:14.000000000 +0200
@@ -159,6 +159,10 @@
// header
RowLayout {
id: appHeader
+
+ Kirigami.Theme.inherit: false
+ Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
+
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: column.height - appView.height
diff -Nru plasma-mobile-6.3.5/po/ia/kcm_mobile_power.po plasma-mobile-6.3.6/po/ia/kcm_mobile_power.po
--- plasma-mobile-6.3.5/po/ia/kcm_mobile_power.po 2025-05-06 19:58:58.000000000 +0200
+++ plasma-mobile-6.3.6/po/ia/kcm_mobile_power.po 2025-07-08 13:47:14.000000000 +0200
@@ -1,13 +1,13 @@
-# Copyright (C) YEAR This file is copyright:
+# Copyright (C) 2025 This file is copyright:
# This file is distributed under the same license as the plasma-settings package.
#
-# Giovanni Sora <g.sora@tiscali.it>, 2020, 2021, 2022, 2023, 2024.
+# SPDX-FileCopyrightText: 2020, 2021, 2022, 2023, 2024, 2025 Giovanni Sora <g.sora@tiscali.it>
msgid ""
msgstr ""
"Project-Id-Version: plasma-settings\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2025-01-10 02:00+0000\n"
-"PO-Revision-Date: 2024-01-04 10:43+0100\n"
+"PO-Revision-Date: 2025-05-09 18:55+0200\n"
"Last-Translator: giovanni <g.sora@tiscali.it>\n"
"Language-Team: Interlingua <kde-i18n-doc@kde.org>\n"
"Language: ia\n"
@@ -15,7 +15,7 @@
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Lokalize 22.12.3\n"
+"X-Generator: Lokalize 23.08.5\n"
#: mobilepower.cpp:30
#, kde-format
@@ -75,7 +75,7 @@
#: ui/BatteryPage.qml:83
#, kde-format
msgid "Is Rechargeable"
-msgstr "Es Recarcabile"
+msgstr "Es Recargabile"
#: ui/BatteryPage.qml:84
#, kde-format
diff -Nru plasma-mobile-6.3.5/shell/contents/applet/CompactApplet.qml plasma-mobile-6.3.6/shell/contents/applet/CompactApplet.qml
--- plasma-mobile-6.3.5/shell/contents/applet/CompactApplet.qml 2025-05-06 19:58:58.000000000 +0200
+++ plasma-mobile-6.3.6/shell/contents/applet/CompactApplet.qml 2025-07-08 13:47:14.000000000 +0200
@@ -41,7 +41,6 @@
}
onFullRepresentationChanged: {
-
if (!fullRepresentation) {
return;
}
@@ -49,6 +48,18 @@
fullRepresentation.parent = appletParent;
fullRepresentation.anchors.fill = fullRepresentation.parent;
fullRepresentation.anchors.margins = appletParent.margins.top;
+ fullRepresentation.visible = true;
+
+ // If plasmoidItem is expanded, we need to update expanded overlay to display it
+ updateExpandedOverlay();
+ }
+
+ function updateExpandedOverlay(): void {
+ if (plasmoidItem?.expanded && fullRepresentation) {
+ expandedOverlay.showFullScreen()
+ } else {
+ expandedOverlay.visible = false;
+ }
}
FocusScope {
@@ -95,18 +106,16 @@
Connections {
target: plasmoidItem
function onExpandedChanged() {
- if (plasmoidItem.expanded) {
- expandedOverlay.showFullScreen()
- } else {
- expandedOverlay.visible = false;
- }
+ // When plasmoidItem is expanded, it can be possible fullRepresentation is null
+ // so we not need to display expandedOverlay now to avoid display empty expandedOverlay
+ updateExpandedOverlay();
}
}
NanoShell.FullScreenOverlay {
id: expandedOverlay
color: Qt.rgba(0, 0, 0, 0.6)
- visible: plasmoidItem && plasmoidItem.expanded
+ visible: plasmoidItem?.expanded && fullRepresentation
width: Screen.width
height: Screen.height
MouseArea {
Reply to: