shift-shell/initialstart/wizard.cpp
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

134 lines
4.3 KiB
C++

// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "wizard.h"
#include "settings.h"
#include "setupstate.h"
#include "utils.h"
#include <KPackage/PackageLoader>
#include <QFileInfo>
#include <QQmlComponent>
// TODO read distro provided config file
const QList<QString> WIZARD_MODULE_ORDER = {QStringLiteral("org.kde.plasma.mobileinitialstart.deviceprofile"),
QStringLiteral("org.kde.plasma.mobileinitialstart.experienceprofile"),
QStringLiteral("org.kde.plasma.mobileinitialstart.prepare"),
QStringLiteral("org.kde.plasma.mobileinitialstart.time"),
QStringLiteral("org.kde.plasma.mobileinitialstart.wifi"),
QStringLiteral("org.kde.plasma.mobileinitialstart.cellular"),
QStringLiteral("org.kde.plasma.mobileinitialstart.systemnavigation"),
QStringLiteral("org.kde.plasma.mobileinitialstart.finished")};
int moduleOrderIndex(const QString &pluginId)
{
const int index = WIZARD_MODULE_ORDER.indexOf(pluginId);
return index == -1 ? WIZARD_MODULE_ORDER.size() : index;
}
Wizard::Wizard(QObject *parent, QQmlEngine *engine)
: QObject{parent}
, m_testingMode{false}
, m_engine{engine}
{
}
void Wizard::load()
{
if (!m_engine) {
return;
}
qCDebug(LOGGING_CATEGORY) << "Loading initialstart packages...";
// load initialstart packages
const auto packages = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KPackage/GenericQML"), QStringLiteral("plasma/mobileinitialstart"));
for (auto &metaData : packages) {
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML"), QFileInfo(metaData.fileName()).path());
if (!package.isValid()) {
qCWarning(LOGGING_CATEGORY) << "initialstart package invalid:" << metaData.fileName();
continue;
}
m_modulePackages.push_back({new KPluginMetaData{metaData}, package});
}
// sort modules by order
std::sort(m_modulePackages.begin(), m_modulePackages.end(), [](const auto &lhs, const auto &rhs) {
return moduleOrderIndex(lhs.first->pluginId()) < moduleOrderIndex(rhs.first->pluginId());
});
QQmlComponent *c = new QQmlComponent(m_engine, this);
// load initialstart QML items
for (const auto &[pluginMetadata, package] : m_modulePackages) {
// load QML from kpackage
c->loadUrl(package.fileUrl("mainscript"), QQmlComponent::PreferSynchronous);
auto created = c->create(m_engine->rootContext());
InitialStartModule *createdItem = qobject_cast<InitialStartModule *>(created);
// print errors if there were issues loading
if (!createdItem) {
qCWarning(LOGGING_CATEGORY) << "Unable to load initialstart module:" << created;
for (auto error : c->errors()) {
qCWarning(LOGGING_CATEGORY) << error;
}
delete created;
continue;
}
connect(createdItem, &InitialStartModule::availableChanged, this, &Wizard::determineAvailableModuleItems);
m_moduleItems.push_back(createdItem);
qCDebug(LOGGING_CATEGORY) << "Loaded initialstart module" << pluginMetadata->pluginId();
}
delete c;
// Populate model
determineAvailableModuleItems();
}
void Wizard::setTestingMode(bool testingMode)
{
if (testingMode != m_testingMode) {
m_testingMode = testingMode;
Q_EMIT testingModeChanged();
}
}
bool Wizard::testingMode()
{
return m_testingMode;
}
QList<InitialStartModule *> Wizard::steps()
{
return m_availableModuleItems;
}
int Wizard::stepsCount()
{
return m_availableModuleItems.size();
}
void Wizard::wizardFinished()
{
if (!m_testingMode) {
SetupState::self()->apply();
}
QCoreApplication::quit();
}
void Wizard::determineAvailableModuleItems()
{
m_availableModuleItems.clear();
for (auto *moduleItem : m_moduleItems) {
if (moduleItem->available()) {
m_availableModuleItems.push_back(moduleItem);
}
}
Q_EMIT stepsChanged();
}