refactor: UI (page) navigation from C++ code

An app::Navigation class is introduced with an API very similar to the
Kirigami.PageRouter in QML. This new class is responsible for pusing
populated view model classes from C++ into QML ownership and triggering
appropriate navigation events. On the QML side a signal handler
forwards these calls to the Kirigami.PageRouter to perform the actual
navigation in the UI.

This construct paves the way for moving state transition and related
logic out of QML and towards C++, since this logic is currently mostly
concerned with page navigation in Keysmith. Ultimately that transition
should make the QML (page) views more easily re-usable.
master
Johan Ouwerkerk 2021-02-11 20:52:30 +01:00 committed by Bhushan Shah
parent 571afeacdf
commit 12e060923e
5 changed files with 98 additions and 10 deletions

View File

@ -1,6 +1,6 @@
#
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
# SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(keysmith_SRCS
@ -9,4 +9,4 @@ set(keysmith_SRCS
)
add_library(keysmith_lib STATIC ${keysmith_SRCS})
target_link_libraries(keysmith_lib Qt5::Core Qt5::Gui Qt5::Concurrent KF5::I18n model_lib account_lib)
target_link_libraries(keysmith_lib Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Qml KF5::I18n model_lib account_lib)

View File

@ -1,6 +1,6 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "keysmith.h"
#include "../logging_p.h"
@ -12,7 +12,47 @@ KEYSMITH_LOGGER(logger, ".app.keysmith")
namespace app
{
Keysmith::Keysmith(QObject *parent): QObject(parent), m_storage(nullptr)
static QMetaEnum pagesEnum = QMetaEnum::fromType<Navigation::Page>();
Navigation::Navigation(QQmlEngine *engine) :
QObject(engine), m_engine(engine)
{
Q_ASSERT_X(m_engine, Q_FUNC_INFO, "must have an engine to work with");
}
QString Navigation::name(Navigation::Page page) const
{
const char *cname = pagesEnum.valueToKey(page);
Q_ASSERT_X(cname, Q_FUNC_INFO, "must be able to lookup pages enum constant's name");
QString result;
result.append(cname);
return result;
}
void Navigation::navigate(Navigation::Page page, QObject *modelToTransfer)
{
const QString route = name(page);
if (modelToTransfer) {
m_engine->setObjectOwnership(modelToTransfer, QQmlEngine::JavaScriptOwnership);
}
qCDebug(logger) << "Requesting switch to route:" << route << "using (view) model:" << modelToTransfer;
Q_EMIT routed(route, modelToTransfer);
}
void Navigation::push(Navigation::Page page, QObject *modelToTransfer)
{
const QString route = name(page);
if (modelToTransfer) {
m_engine->setObjectOwnership(modelToTransfer, QQmlEngine::JavaScriptOwnership);
}
qCDebug(logger) << "Requesting to push route:" << route << "using (view) model:" << modelToTransfer;
Q_EMIT pushed(route, modelToTransfer);
}
Keysmith::Keysmith(Navigation *navigation, QObject *parent): QObject(parent), m_navigation(navigation), m_storage(nullptr)
{
}
@ -24,6 +64,11 @@ namespace app
}
}
Navigation * Keysmith::navigation(void) const
{
return m_navigation;
}
void Keysmith::copyToClipboard(const QString &text)
{
QClipboard * clipboard = QGuiApplication::clipboard();

View File

@ -1,6 +1,6 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#ifndef APP_KEYSMITH_H
#define APP_KEYSMITH_H
@ -9,24 +9,58 @@
#include "../model/accounts.h"
#include "../model/password.h"
#include <QMetaEnum>
#include <QObject>
#include <QQmlEngine>
namespace app
{
class Keysmith: public QObject
class Navigation: public QObject
{
Q_OBJECT
public:
explicit Keysmith(QObject *parent = nullptr);
enum Page
{
Error,
AddAccount,
RenameAccount,
AccountsOverview,
SetupPassword,
UnlockAccounts
};
Q_ENUM(Page)
public:
explicit Navigation(QQmlEngine * const engine);
Q_INVOKABLE QString name(app::Navigation::Page page) const;
public Q_SLOTS:
void push(app::Navigation::Page page, QObject *modelToTransfer);
void navigate(app::Navigation::Page page, QObject *modelToTransfer);
Q_SIGNALS:
void routed(const QString &route, QObject *transferred);
void pushed(const QString &route, QObject *transferred);
private:
QQmlEngine * const m_engine;
};
class Keysmith: public QObject
{
Q_OBJECT
Q_PROPERTY(app::Navigation * navigation READ navigation CONSTANT)
public:
explicit Keysmith(Navigation * const navigation, QObject *parent = nullptr);
virtual ~Keysmith();
Navigation * navigation(void) const;
Q_INVOKABLE void copyToClipboard(const QString &text);
Q_INVOKABLE model::SimpleAccountListModel * accountListModel(void);
Q_INVOKABLE model::PasswordRequest * passwordRequest(void);
private:
accounts::AccountStorage * storage(void);
private:
Navigation * const m_navigation;
accounts::AccountStorage *m_storage;
};
}
Q_DECLARE_METATYPE(app::Navigation *);
#endif

View File

@ -148,6 +148,16 @@ Kirigami.ApplicationWindow {
}
}
Connections {
target: Keysmith.navigation
function onRouted (route, data) {
root.router.navigateToRoute({route, data});
}
function onPushed(route, data) {
root.router.pushRoute({route, data});
}
}
function autoAddNewAccountFromCommandLine() {
// accounts not yet loaded, keep the user waiting...
if (!accounts.loaded) {

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2019 Bhushan Shah <bshah@kde.org>
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include <QApplication>
@ -92,10 +92,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<validators::UnsignedLongValidator>("Keysmith.Validators", 1, 0, "HOTPCounterValidator");
qmlRegisterSingletonType<app::Keysmith>("Keysmith.Application", 1, 0, "Keysmith", [](QQmlEngine *qml, QJSEngine *js) -> QObject *
{
Q_UNUSED(qml);
Q_UNUSED(js);
return new app::Keysmith();
return new app::Keysmith(new app::Navigation(qml));
});
qmlRegisterSingletonType<app::CommandLineOptions>("Keysmith.Application", 1, 0, "CommandLine", [parseOk, &cliParser](QQmlEngine *qml, QJSEngine *js) -> QObject *
{