shift-shell/initialstart/qml/Wizard.qml
Marco Allegretti 42d41351e2 Add profile-aware initial setup
Detect the device class, stage the selected experience, and write the resulting setup choices through SetupState.

Load the new device and experience modules before the existing setup pages, and use the Shift icon on the finished page.
2026-05-11 10:03:07 +02:00

253 lines
7.9 KiB
QML

// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import initialstart as InitialStart
Kirigami.Page {
id: root
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
property int currentIndex: 0
readonly property int stepCount: InitialStart.Wizard.stepsCount
property bool showingLanding: true
// filled by items
property var currentStepItem
property var nextStepItem
property var previousStepItem
readonly property bool onFinalPage: currentIndex === (stepCount - 1)
function updateStepItems() {
if (stepRepeater.count === 0) {
return;
}
root.previousStepItem = currentIndex > 0 ? stepRepeater.itemAt(currentIndex - 1) : null;
root.currentStepItem = stepRepeater.itemAt(currentIndex);
root.nextStepItem = currentIndex < stepRepeater.count - 1 ? stepRepeater.itemAt(currentIndex + 1) : null;
}
// step animation
// manually doing the animation is more performant and less glitchy with window resize than a SwipeView
property real previousStepItemX: -root.width
property real currentStepItemX: 0
property real nextStepItemX: root.width
onStepCountChanged: {
// reset position
updateStepItems();
requestPreviousPage();
}
function finishFinalPage() {
// the app exits
InitialStart.Wizard.wizardFinished();
}
function requestNextPage() {
if (currentIndex >= stepCount - 1) {
return;
}
currentIndex++;
updateStepItems();
stepHeading.changeText(currentStepItem.name);
previousStepItemX = -root.width;
currentStepItemX = 0;
nextStepItemX = root.width;
}
function requestPreviousPage() {
if (currentIndex === 0) {
root.showingLanding = true;
landingComponent.returnToLanding();
} else {
currentIndex--;
updateStepItems();
stepHeading.changeText(currentStepItem.name);
previousStepItemX = -root.width;
currentStepItemX = 0;
nextStepItemX = root.width;
}
}
LandingComponent {
id: landingComponent
anchors.fill: parent
enabled: root.showingLanding
visible: root.showingLanding
onRequestNextPage: {
root.showingLanding = false;
root.updateStepItems();
stepHeading.changeText(root.currentStepItem.name);
}
}
Item {
id: stepsComponent
width: parent.width
height: parent.height
// animation when we switch to step stage
opacity: root.showingLanding ? 0 : 1
x: 0
y: root.showingLanding ? overlaySteps.height : 0
// heading for all the wizard steps
Label {
id: stepHeading
opacity: 0
color: "white"
horizontalAlignment: Text.AlignHCenter
font.pointSize: 18
anchors.left: parent.left
anchors.leftMargin: Kirigami.Units.gridUnit
anchors.right: parent.right
anchors.rightMargin: Kirigami.Units.gridUnit
anchors.bottom: parent.bottom
anchors.bottomMargin: root.height * 0.7 + Kirigami.Units.gridUnit
property string toText
function changeText(text) {
stepHeading.text = text;
stepHeading.opacity = 1;
}
}
Rectangle {
id: overlaySteps
clip: true
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: Kirigami.Theme.backgroundColor
topLeftRadius: Kirigami.Units.gridUnit
topRightRadius: Kirigami.Units.gridUnit
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: root.height * 0.7
width: Math.min(parent.width, Kirigami.Units.gridUnit * 30)
// all steps are in this container
Item {
anchors.fill: parent
anchors.bottomMargin: stepFooter.implicitHeight
// setup steps
Repeater {
id: stepRepeater
model: InitialStart.Wizard.steps
delegate: MobileShell.BaseItem {
id: item
visible: !root.showingLanding && Math.abs(item.currentIndex - root.currentIndex) <= 1
contentItem: modelData.contentItem
transform: Translate {
x: {
if (item.currentIndex === root.currentIndex - 1) {
return previousStepItemX;
} else if (item.currentIndex === root.currentIndex + 1) {
return nextStepItemX;
} else if (item.currentIndex === root.currentIndex) {
return currentStepItemX;
}
return 0;
}
}
anchors.fill: parent
// pass up the property
property string name: modelData.name
property int currentIndex: model.index
Component.onCompleted: {
root.updateStepItems();
}
}
}
}
// bottom footer
RowLayout {
id: stepFooter
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
Button {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.bottomMargin: Kirigami.Units.gridUnit
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.gridUnit
rightPadding: Kirigami.Units.gridUnit
text: i18n("Back")
icon.name: "arrow-left"
onClicked: root.requestPreviousPage()
}
Item {}
Button {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: Kirigami.Units.gridUnit
Layout.bottomMargin: Kirigami.Units.gridUnit
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.gridUnit
rightPadding: Kirigami.Units.gridUnit
visible: !root.onFinalPage
text: i18n("Next")
icon.name: "arrow-right"
onClicked: root.requestNextPage();
}
Button {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: Kirigami.Units.gridUnit
Layout.bottomMargin: Kirigami.Units.gridUnit
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.gridUnit
rightPadding: Kirigami.Units.gridUnit
visible: root.onFinalPage
text: i18n("Finish")
icon.name: "dialog-ok"
onClicked: root.finishFinalPage();
}
}
}
}
}