Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/settings-phone.qml
Fimeg 47858d5b06 phone: wallpaper picker settings page, drag-follow PIN reveal
Wallpaper grid in the settings app applying through the shell's
wallpapers IPC. Lock pad now tracks the finger during the swipe and
settles on release instead of snapping at a threshold.
2026-07-19 22:46:01 -04:00

226 lines
7.7 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("Wallpaper"),
icon: "wallpaper",
component: "modules/settings/WallpaperConfig.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 : ""
}
}
}