Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/settings-phone.qml
Fimeg 4d5690b547 quickshell: Idle & sleep settings page
Live idle stage readout (polled over session IPC), the native-coordinator
toggle, and dim/lock timers. Timers grey out while the coordinator is off.
2026-07-14 20:17:43 -04:00

221 lines
7.6 KiB
QML

//@ pragma UseQApplication
//@ pragma Env QS_NO_RELOAD_POPUP=1
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000
// Souveraine mobile settings — purpose-built for portrait/touch.
// Modeled on TouchLockSurface.qml: a full-screen surface with phone-native
// stack navigation (list -> push page -> back), not the desktop settings.qml's
// ApplicationWindow with titlebar + side rail. Reuses the existing
// ConfigSwitch/ConfigSpinBox pages (PhoneConfig.qml etc.) as the pushed
// content; only the chrome is mobile.
//
// Sized for 540x1080 logical (1080x2160 @ scale 2).
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import Quickshell.Io
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions as CF
ApplicationWindow {
id: root
property int currentPage: -1 // -1 = list view; >=0 = drilled into that page
readonly property real rowHeight: 68 // finger-sized tap target
readonly property real edgeMargin: 16
// The page set. Same {name, icon, component} shape as the desktop
// settings.qml registry so a page is interchangeable between the two.
// One page per domain we own; About reuses ii's stock page verbatim.
property var pages: [
{
name: Translation.tr("Device"),
icon: "smartphone",
component: "modules/settings/DeviceConfig.qml"
},
{
name: Translation.tr("Lock screen"),
icon: "lock",
component: "modules/settings/LockConfig.qml"
},
{
name: Translation.tr("Dock"),
icon: "dock_to_bottom",
component: "modules/settings/DockConfig.qml"
},
{
name: Translation.tr("Navigation"),
icon: "gesture",
component: "modules/settings/NavigationConfig.qml"
},
{
name: Translation.tr("Idle & sleep"),
icon: "bedtime",
component: "modules/settings/IdleConfig.qml"
},
{
name: Translation.tr("Keyboard"),
icon: "keyboard",
component: "modules/settings/KeyboardConfig.qml"
},
{
name: Translation.tr("About"),
icon: "info",
component: "modules/settings/About.qml"
}
// Next: Display (brightness/idle), Cellular (mmcli service),
// Bluetooth (after org.bluez activation is fixed), app health.
]
visible: true
onClosing: Qt.quit()
title: "Settings"
// Full-screen on the phone: no titlebar, no window-frame padding, edge to edge.
visibility: Window.FullScreen
color: Appearance.m3colors.m3background
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme()
Config.readWriteDelay = 0
}
// --- List view (idle state) ------------------------------------------
ColumnLayout {
id: listView
anchors.fill: parent
spacing: 0
visible: root.currentPage < 0
// Header
Item {
Layout.fillWidth: true
Layout.leftMargin: root.edgeMargin
Layout.rightMargin: root.edgeMargin
Layout.topMargin: root.edgeMargin + 16
Layout.bottomMargin: 8
implicitHeight: titleText.implicitHeight
StyledText {
id: titleText
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: Appearance.colors.colOnLayer0
text: Translation.tr("Settings")
font {
family: Appearance.font.family.title
pixelSize: Appearance.font.pixelSize.title
variableAxes: Appearance.font.variableAxes.title
}
}
}
// Full-width finger-sized rows
StyledListView {
id: pageList
Layout.fillWidth: true
Layout.fillHeight: true
model: root.pages
delegate: RippleButton {
required property var index
required property var modelData
width: pageList.width
height: root.rowHeight
buttonRadius: 0
onClicked: root.currentPage = index
contentItem: RowLayout {
anchors.fill: parent
anchors.leftMargin: root.edgeMargin
anchors.rightMargin: root.edgeMargin
spacing: 16
MaterialSymbol {
text: modelData.icon
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer0
}
StyledText {
Layout.fillWidth: true
text: modelData.name
color: Appearance.colors.colOnLayer0
font.pixelSize: Appearance.font.pixelSize.larger
}
MaterialSymbol {
text: "chevron_right"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colSubtext
}
}
}
}
}
// --- Page view (drilled-in state) ------------------------------------
Item {
id: pageView
anchors.fill: parent
visible: root.currentPage >= 0
// Back header
Item {
id: pageHeader
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: 56
RippleButton {
id: backButton
anchors {
left: parent.left
leftMargin: 4
verticalCenter: parent.verticalCenter
}
buttonRadius: Appearance.rounding.full
implicitWidth: 48
implicitHeight: 48
onClicked: root.currentPage = -1
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "arrow_back"
iconSize: 24
}
}
StyledText {
anchors {
left: backButton.right
leftMargin: 8
right: parent.right
rightMargin: root.edgeMargin
verticalCenter: parent.verticalCenter
}
color: Appearance.colors.colOnLayer0
text: root.currentPage >= 0 ? root.pages[root.currentPage].name : ""
elide: Text.ElideRight
font {
family: Appearance.font.family.title
pixelSize: Appearance.font.pixelSize.larger
}
}
}
// Pushed page. ContentPage is a StyledFlickable that clamps to
// baseWidth 600 when forceWidth; we let it scroll and give it the
// full window width so it reads edge-to-edge.
Loader {
id: pageLoader
anchors {
top: pageHeader.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
active: root.currentPage >= 0 && Config.ready
source: root.currentPage >= 0 ? root.pages[root.currentPage].component : ""
}
}
}