shift-shell/initialstart/qml/Wizard.qml
Devin Lin 5c0fd57600 initialstart: Add first login experience
Adds an experience for users on first login, allowing some basic configuration.

This is separate from a first start wizard, which would run as a separate user with elevated permissions, and include options that an installer would have.
2023-04-01 07:09:57 +00:00

340 lines
10 KiB
QML

// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import initialstart 1.0 as InitialStart
Kirigami.Page {
id: root
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
property int currentIndex: 0
property int stepCount: 0
property bool showingLanding: true
// filled by items
property var currentStepItem
property var nextStepItem
property var previousStepItem
readonly property bool onFinalPage: currentIndex === (stepCount - 1)
// step animation
// manually doing the animation is more performant and less glitchy with window resize than a SwipeView
property real previousStepItemX: 0
property real currentStepItemX: 0
property real nextStepItemX: 0
NumberAnimation on previousStepItemX {
id: previousStepAnim
duration: 400
easing.type: Easing.OutExpo
onFinished: {
if (root.previousStepItemX != 0) {
root.previousStepItem.visible = false;
}
}
}
NumberAnimation on currentStepItemX {
id: currentStepAnim
duration: 400
easing.type: Easing.OutExpo
}
NumberAnimation on nextStepItemX {
id: nextStepAnim
duration: 400
easing.type: Easing.OutExpo
onFinished: {
if (root.nextStepItemX != 0) {
root.nextStepItem.visible = false;
}
}
}
function finishFinalPage() {
InitialStart.Wizard.wizardFinished();
applicationWindow().close();
}
function requestNextPage() {
if (previousStepAnim.running || currentStepAnim.running || nextStepAnim.running) {
return;
}
previousStepItemX = 0;
currentIndex++;
stepHeading.changeText(currentStepItem.name);
currentStepItemX = root.width;
currentStepItem.visible = true;
previousStepAnim.to = -root.width;
previousStepAnim.restart();
currentStepAnim.to = 0;
currentStepAnim.restart();
}
function requestPreviousPage() {
if (previousStepAnim.running || currentStepAnim.running || nextStepAnim.running) {
return;
}
if (currentIndex === 0) {
root.showingLanding = true;
landingComponent.returnToLanding();
} else {
nextStepItemX = 0;
currentIndex--;
stepHeading.changeText(currentStepItem.name);
currentStepItemX = -root.width;
currentStepItem.visible = true;
nextStepAnim.to = root.width;
nextStepAnim.restart();
currentStepAnim.to = 0;
currentStepAnim.restart();
}
}
// top status bar
MobileShell.StatusBar {
id: statusBar
z: 1
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: Kirigami.Units.gridUnit * 1.25
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
backgroundColor: "transparent"
showSecondRow: false
showDropShadow: true
showTime: true
disableSystemTray: true // prevent SIGABRT, since loading the system tray leads to bad... things
}
LandingComponent {
id: landingComponent
anchors.fill: parent
onRequestNextPage: {
root.showingLanding = false;
stepHeading.changeText(root.currentStepItem.name);
}
}
Item {
id: stepsComponent
anchors.fill: parent
// animation when we switch to step stage
opacity: root.showingLanding ? 0 : 1
property real translateY: root.showingLanding ? overlaySteps.height : 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutExpo
}
}
Behavior on translateY {
NumberAnimation {
duration: 1000
easing.type: Easing.OutExpo
}
}
transform: Translate { y: stepsComponent.translateY }
// 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) {
toText = text;
toHidden.restart();
}
NumberAnimation on opacity {
id: toHidden
duration: 200
to: 0
onFinished: {
stepHeading.text = stepHeading.toText;
toShown.restart();
}
}
NumberAnimation on opacity {
id: toShown
duration: 200
to: 1
}
}
Rectangle {
id: overlaySteps
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: Kirigami.Theme.backgroundColor
anchors.fill: parent
anchors.topMargin: root.height * 0.3
// all steps are in this container
Item {
anchors.fill: parent
anchors.bottomMargin: stepFooter.implicitHeight
// setup steps
Repeater {
model: InitialStart.Wizard.steps
delegate: MobileShell.BaseItem {
id: item
visible: model.index === 0 // the binding is broken later
contentItem: modelData
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: contentItem.name
property int currentIndex: model.index
function updateRootItems() {
if (model.index === root.currentIndex) {
root.currentStepItem = item;
} else if (model.index === root.currentIndex - 1) {
root.previousStepItem = item;
} else if (model.index === root.currentIndex + 1) {
root.nextStepItem = item;
}
}
Component.onCompleted: {
root.stepCount++
updateRootItems();
}
// keep root properties updated
Connections {
target: root
function onCurrentIndexChanged() {
item.updateRootItems();
}
}
}
}
}
// 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();
}
}
}
}
}