Promote management tiles to status rows in convergence

Wi-Fi, Bluetooth, Sound and Battery behave differently from
toggle tiles: tapping them should open a detail panel, not
flip a switch. On a phone the long-press convention handles
this, but with a mouse long-press is unnatural.

Pull these four tiles out of the grid when convergence mode
is active and show them as full-width rows above the remaining
tiles. Each row has two click zones — a toggle pill on the
left that still switches the service on/off, and a detail
area on the right (name, status, chevron) that opens the
Plasma applet popup. The grid hides the duplicates so they
only appear once.
This commit is contained in:
Marco Allegretti 2026-04-16 13:41:24 +02:00
parent 8017e4eaa0
commit 2a137d1ac9
3 changed files with 260 additions and 2 deletions

View file

@ -137,6 +137,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES
qml/actiondrawer/private/QuickSettingsDrawer.qml
qml/actiondrawer/private/QuickSettingsFullDelegate.qml
qml/actiondrawer/private/QuickSettingsMinimizedDelegate.qml
qml/actiondrawer/private/QuickSettingsStatusRow.qml
qml/actiondrawer/private/QuickSettingsPanel.qml
qml/actiondrawer/private/ContentContainer.qml
qml/actiondrawer/private/DetailPopup.qml

View file

@ -54,6 +54,16 @@ Item {
readonly property int pageSize: rowCount * columnCount
readonly property int quickSettingsCount: quickSettingsModel.count
// Management tiles promoted to full-width status rows in convergence.
readonly property var __managementCommands: ({
"plasma-open-settings kcm_mobile_wifi": "org.kde.plasma.networkmanagement",
"plasma-open-settings kcm_bluetooth": "org.kde.plasma.bluetooth",
"plasma-open-settings kcm_pulseaudio": "org.kde.plasma.volume",
"plasma-open-settings kcm_mobile_power": "org.kde.plasma.battery",
})
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
function isManagementTile(cmd) { return cmd in __managementCommands; }
readonly property alias brightnessPressedValue: brightnessItem.brightnessPressedValue
function resetSwipeView() {
@ -126,6 +136,34 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
// Management status rows (convergence mode only)
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
visible: root.isConvergence
Repeater {
model: root.quickSettingsModel
delegate: QuickSettingsStatusRow {
required property var modelData
Layout.fillWidth: true
visible: root.isManagementTile(modelData.settingsCommand)
text: modelData.text
status: modelData.status
icon: modelData.icon
enabled: modelData.enabled
toggleFunction: modelData.toggle
onDetailClicked: {
let pluginId = root.__managementCommands[modelData.settingsCommand];
if (pluginId) detailPopup.show(pluginId);
}
}
}
}
// Quick settings view
ColumnLayout {
Layout.fillWidth: true
@ -159,8 +197,10 @@ Item {
delegate: MobileShell.BaseItem {
required property var modelData
height: root.rowHeight
width: root.columnWidth
readonly property bool __hidden: root.isConvergence && root.isManagementTile(modelData.settingsCommand)
height: __hidden ? 0 : root.rowHeight
width: __hidden ? 0 : root.columnWidth
visible: !__hidden
padding: Kirigami.Units.smallSpacing
contentItem: QuickSettingsFullDelegate {

View file

@ -0,0 +1,217 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
import QtQuick 2.15
import QtQuick.Layouts 1.1
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
/**
* Full-width management row (Wi-Fi, Bluetooth, Audio, Battery) shown in
* convergence mode. Two interaction zones:
* - Left toggle pill: icon + indicator dot, tap toggles the service.
* - Right detail area: name + status + chevron, tap opens detail popup.
*/
Item {
id: root
required property string text
required property string status
required property string icon
required property bool enabled
required property var toggleFunction
signal detailClicked()
implicitHeight: Kirigami.Units.gridUnit * 3.6
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Button
// Palette (shared with tile delegates)
readonly property color enabledBg: Kirigami.ColorUtils.tintWithAlpha(
Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.6)
readonly property color enabledBgPressed: Kirigami.ColorUtils.tintWithAlpha(
Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.4)
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
readonly property color disabledBg: Kirigami.Theme.backgroundColor
readonly property color disabledBgPressed: Qt.darker(disabledBg, 1.1)
readonly property color disabledBorder: {
let bg = Kirigami.Theme.backgroundColor;
let fg = Kirigami.Theme.textColor;
if (Kirigami.ColorUtils.brightnessForColor(bg) === Kirigami.ColorUtils.Light) {
return Kirigami.ColorUtils.linearInterpolation(bg, fg, 0.2);
} else {
return Kirigami.ColorUtils.linearInterpolation(bg, fg, 0.1);
}
}
MobileShell.HapticsEffect { id: haptics }
// Outer card
// Shadow
Rectangle {
anchors.top: parent.top
anchors.topMargin: 1
anchors.left: parent.left
anchors.right: parent.right
height: parent.height
radius: Kirigami.Units.cornerRadius
color: Qt.rgba(0, 0, 0, 0.075)
}
// Card background always neutral base (the toggle pill carries the
// enabled highlight, not the whole row).
Rectangle {
id: cardBg
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
border.pixelAligned: false
border.width: 1
border.color: root.disabledBorder
color: root.disabledBg
}
RowLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
// Toggle pill (left zone)
Item {
id: togglePill
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: root.height - Kirigami.Units.smallSpacing * 2
Layout.fillHeight: true
Rectangle {
id: pillBg
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
border.pixelAligned: false
border.width: 1
border.color: root.enabled ? root.enabledBorder : root.disabledBorder
color: {
if (root.enabled) {
return toggleMouse.pressed ? root.enabledBgPressed : root.enabledBg;
}
return toggleMouse.pressed ? root.disabledBgPressed : root.disabledBg;
}
Behavior on color {
ColorAnimation { duration: Kirigami.Units.shortDuration }
}
}
// Scale on press
property real zoomScale: (ShellSettings.Settings.animationsEnabled && toggleMouse.pressed) ? 0.9 : 1
Behavior on zoomScale {
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutExpo }
}
transform: Scale {
origin.x: togglePill.width / 2
origin.y: togglePill.height / 2
xScale: togglePill.zoomScale
yScale: togglePill.zoomScale
}
ColumnLayout {
anchors.centerIn: parent
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: root.icon
}
// Indicator dot
Rectangle {
Layout.alignment: Qt.AlignHCenter
width: Kirigami.Units.smallSpacing * 1.5
height: width
radius: width / 2
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
opacity: root.enabled ? 1.0 : 0.4
}
}
MouseArea {
id: toggleMouse
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: haptics.buttonVibrate()
onClicked: {
if (root.toggleFunction) root.toggleFunction();
}
}
}
// Detail area (right zone)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
// Hover/press highlight
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: detailMouse.pressed ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.06)
: detailMouse.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.03)
: "transparent"
}
MouseArea {
id: detailMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: haptics.buttonVibrate()
onClicked: root.detailClicked()
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.rightMargin: Kirigami.Units.smallSpacing * 2
spacing: Kirigami.Units.smallSpacing
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: root.text
font.weight: Font.Bold
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
}
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: root.status ? root.status : (root.enabled ? i18n("On") : i18n("Off"))
opacity: 0.6
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
}
}
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: implicitWidth
source: "go-next-symbolic"
opacity: 0.5
}
}
}
}
}