refactor: switch to page navigation from C++

Rework QML to use the ViewModel classes and use the C++ flow classes to
drive QML page navigation.
master
Johan Ouwerkerk 2021-02-22 21:22:35 +01:00 committed by Bhushan Shah
parent ea19844300
commit 75a167665b
8 changed files with 126 additions and 323 deletions

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>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*/
@ -8,7 +8,7 @@ import QtQuick 2.1
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.15 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Application 1.0 as Application
import Keysmith.Models 1.0 as Models
Kirigami.ScrollablePage {
@ -20,9 +20,7 @@ Kirigami.ScrollablePage {
supportsRefreshing: false
title: i18nc("@title:window", "Accounts")
signal accountWanted
property bool addActionEnabled
property Models.AccountListModel accounts
property Application.AccountsOverviewViewModel vm
property string accountErrorMessage: i18nc("generic error shown when adding or updating an account failed", "Failed to update accounts")
property string loadingErrorMessage: i18nc("error message shown when loading accounts from storage failed", "Some accounts failed to load.")
@ -34,9 +32,9 @@ Kirigami.ScrollablePage {
spacing: 0
Kirigami.InlineMessage {
id: message
visible: root.accounts.error
visible: vm.accounts.error // FIXME : should be managed via vm
type: Kirigami.MessageType.Error
text: root.errorMessage
text: root.errorMessage // FIXME : should be managed via vm
Layout.fillWidth: true
Layout.margins: Kirigami.Units.smallSpacing
/*
@ -55,13 +53,14 @@ Kirigami.ScrollablePage {
MouseArea {
anchors.fill: parent
onClicked: {
root.accounts.error = false;
// FIXME : should be managed via vm
vm.accounts.error = false;
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
visible: root.accounts.error
visible: vm.accounts.error // FIXME : should be managed via vm
}
}
@ -70,7 +69,8 @@ Kirigami.ScrollablePage {
HOTPAccountEntryView {
account: value
onActionTriggered: {
root.accounts.error = false;
// FIXME : should be managed via vm
vm.accounts.error = false;
root.errorMessage = root.accountErrorMessage;
}
}
@ -81,7 +81,8 @@ Kirigami.ScrollablePage {
TOTPAccountEntryView {
account: value
onActionTriggered: {
root.accounts.error = false;
// FIXME : should be managed via vm
vm.accounts.error = false;
root.errorMessage = root.accountErrorMessage;
}
}
@ -90,13 +91,13 @@ Kirigami.ScrollablePage {
ListView {
id: mainList
model: Models.SortedAccountListModel {
sourceModel: accounts
sourceModel: vm.accounts
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4)
visible: accounts.loaded && mainList.count == 0
visible: vm.accounts.loaded && mainList.count == 0
text: i18n("No accounts added")
icon.name: "unlock"
@ -104,9 +105,10 @@ Kirigami.ScrollablePage {
iconName: "list-add"
text: "Add account"
onTriggered: {
root.accounts.error = false;
// FIXME : should be managed via vm
vm.accounts.error = false;
root.errorMessage = root.accountErrorMessage;
root.accountWanted();
vm.addNewAccount();
}
}
}
@ -168,12 +170,13 @@ Kirigami.ScrollablePage {
id: addAction
text: i18n("Add")
iconName: "list-add"
enabled: addActionEnabled
visible: addActionEnabled
enabled: vm.actionsEnabled
visible: vm.actionsEnabled
onTriggered: {
root.accounts.error = false;
// FIXME : should be managed via vm
vm.accounts.error = false;
root.errorMessage = root.accountErrorMessage;
root.accountWanted();
vm.addNewAccount();
}
}
}

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>
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
*/
@ -9,32 +9,27 @@ import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Application 1.0 as Application
import Keysmith.Models 1.0 as Models
import Keysmith.Validators 1.0 as Validators
Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Add new account")
signal quit
signal cancelled
signal newAccount(var input)
property Models.AccountListModel accounts
property bool quitEnabled: false
property Application.AddAccountViewModel vm
property bool detailsEnabled: false
property bool validateAccountAvailability: true
property bool secretAcceptable: accountSecret.acceptableInput
property bool tokenTypeAcceptable: hotpRadio.checked || totpRadio.checked
property bool hotpDetailsAcceptable: hotpDetails.acceptable || validatedInput.type === Models.ValidatedAccountInput.Totp
property bool totpDetailsAcceptable: totpDetails.acceptable || validatedInput.type === Models.ValidatedAccountInput.Hotp
property bool hotpDetailsAcceptable: hotpDetails.acceptable || vm.input.type === Models.ValidatedAccountInput.Totp
property bool totpDetailsAcceptable: totpDetails.acceptable || vm.input.type === Models.ValidatedAccountInput.Hotp
property bool tokenDetailsAcceptable: hotpDetailsAcceptable && totpDetailsAcceptable
property bool acceptable: accountName.acceptable && secretAcceptable && tokenTypeAcceptable && tokenDetailsAcceptable
property Models.ValidatedAccountInput validatedInput
Connections {
target: validatedInput
target: vm.input
onTypeChanged: {
root.detailsEnabled = false;
}
@ -45,9 +40,9 @@ Kirigami.ScrollablePage {
AccountNameForm {
id: accountName
Layout.fillWidth: true
accounts: root.accounts
validateAccountAvailability: root.validateAccountAvailability
validatedInput: root.validatedInput
accounts: vm.accounts
validateAccountAvailability: vm.validateAvailability
validatedInput: vm.input
twinFormLayouts: [requiredDetails, hotpDetails, totpDetails]
}
@ -61,21 +56,21 @@ Kirigami.ScrollablePage {
Kirigami.FormData.buddyFor: totpRadio
Controls.RadioButton {
id: totpRadio
checked: validatedInput.type === Models.ValidatedAccountInput.Totp
checked: vm.input.type === Models.ValidatedAccountInput.Totp
text: i18nc("@option:radio", "Time-based OTP")
onCheckedChanged: {
if (checked) {
validatedInput.type = Models.ValidatedAccountInput.Totp;
vm.input.type = Models.ValidatedAccountInput.Totp;
}
}
}
Controls.RadioButton {
id: hotpRadio
checked: validatedInput.type === Models.ValidatedAccountInput.Hotp
checked: vm.input.type === Models.ValidatedAccountInput.Hotp
text: i18nc("@option:radio", "Hash-based OTP")
onCheckedChanged: {
if (checked) {
validatedInput.type = Models.ValidatedAccountInput.Hotp;
vm.input.type = Models.ValidatedAccountInput.Hotp;
}
}
}
@ -83,7 +78,7 @@ Kirigami.ScrollablePage {
Kirigami.PasswordField {
id: accountSecret
placeholderText: i18n("Token secret")
text: validatedInput.secret
text: vm.input.secret
Kirigami.FormData.label: i18nc("@label:textbox", "Secret key:")
validator: Validators.Base32SecretValidator {
id: secretValidator
@ -91,7 +86,7 @@ Kirigami.ScrollablePage {
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhSensitiveData | Qt.ImhHiddenText
onTextChanged: {
if (acceptableInput) {
validatedInput.secret = text;
vm.input.secret = text;
}
}
}
@ -110,17 +105,17 @@ Kirigami.ScrollablePage {
visible: enabled
id: hotpDetails
Layout.fillWidth: true
validatedInput: root.validatedInput
validatedInput: root.vm.input
twinFormLayouts: [accountName, requiredDetails, totpDetails]
enabled: root.detailsEnabled && validatedInput.type === Models.ValidatedAccountInput.Hotp
enabled: root.detailsEnabled && vm.input.type === Models.ValidatedAccountInput.Hotp
}
TOTPDetailsForm {
visible: enabled
id: totpDetails
Layout.fillWidth: true
validatedInput: root.validatedInput
validatedInput: root.vm.input
twinFormLayouts: [accountName, requiredDetails, hotpDetails]
enabled: root.detailsEnabled && validatedInput.type === Models.ValidatedAccountInput.Totp
enabled: root.detailsEnabled && vm.input.type === Models.ValidatedAccountInput.Totp
}
}
@ -128,16 +123,16 @@ Kirigami.ScrollablePage {
text: i18nc("@action:button cancel and dismiss the add account form", "Cancel")
iconName: "edit-undo"
onTriggered: {
root.cancelled();
vm.cancelled();
}
}
actions.right: Kirigami.Action {
text: i18nc("@action:button Dismiss the error page and quit Keysmtih", "Quit")
iconName: "application-exit"
enabled: root.quitEnabled
visible: root.quitEnabled
enabled: vm.quitEnabled
visible: vm.quitEnabled
onTriggered: {
root.quit();
Qt.quit();
}
}
actions.main: Kirigami.Action {
@ -145,7 +140,7 @@ Kirigami.ScrollablePage {
iconName: "answer-correct"
enabled: acceptable
onTriggered: {
root.newAccount(root.validatedInput);
vm.accepted();
}
}
}

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>
*/
import QtQuick 2.1
@ -8,19 +8,21 @@ import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0 as Application
Kirigami.Page {
id: root
signal quit
signal dismissed
property bool quitEnabled: false
property string error
property Application.ErrorViewModel vm
title: vm.errorTitle
ColumnLayout {
anchors {
horizontalCenter: parent.horizontalCenter
}
Controls.Label {
text: root.error
text: vm.errorText
color: Kirigami.Theme.negativeTextColor
Layout.maximumWidth: root.width - 2 * Kirigami.Units.largeSpacing
wrapMode: Text.WordWrap
@ -31,16 +33,16 @@ Kirigami.Page {
text: i18nc("@action:button Button to dismiss the error page", "Continue")
iconName: "answer-correct"
onTriggered: {
root.dismissed();
vm.dismissed();
}
}
actions.right: Kirigami.Action {
text: i18nc("@action:button Dismiss the error page and quit Keysmtih", "Quit")
iconName: "application-exit"
enabled: root.quitEnabled
visible: root.quitEnabled
enabled: vm.quitEnabled
visible: vm.quitEnabled
onTriggered: {
root.quit();
Qt.quit();
}
}
}

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>
*/
import QtQuick 2.1
@ -8,22 +8,20 @@ import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Application 1.0 as Application
import Keysmith.Models 1.0 as Models
import Keysmith.Validators 1.0 as Validators
Kirigami.Page {
id: root
title: i18nc("@title:window", "Rename account to add")
signal cancelled
signal newAccount(var input)
property Models.AccountListModel accounts
property Application.RenameAccountViewModel vm
property bool acceptable: accountName.acceptable
property Models.ValidatedAccountInput validatedInput
Connections {
target: validatedInput
target: vm.input
onTypeChanged: {
root.detailsEnabled = false;
}
@ -41,9 +39,9 @@ Kirigami.Page {
}
AccountNameForm {
id: accountName
accounts: root.accounts
accounts: vm.accounts
validateAccountAvailability: true
validatedInput: root.validatedInput
validatedInput: root.vm.input
}
}
@ -51,7 +49,7 @@ Kirigami.Page {
text: i18nc("@action:button cancel and dismiss the rename account form", "Cancel")
iconName: "edit-undo"
onTriggered: {
root.cancelled();
vm.cancelled();
}
}
actions.main: Kirigami.Action {
@ -59,7 +57,7 @@ Kirigami.Page {
iconName: "answer-correct"
enabled: acceptable
onTriggered: {
root.newAccount(root.validatedInput);
vm.accepted();
}
}
}

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>
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*/
@ -10,15 +10,13 @@ import QtQuick.Layouts 1.2
import QtQuick.Controls 2.5 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
import Keysmith.Application 1.0 as Application
Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Password")
property bool bannerTextError
property Models.PasswordRequestModel passwordRequest
property Application.SetupPasswordViewModel vm
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
@ -48,39 +46,27 @@ Kirigami.ScrollablePage {
Kirigami.PasswordField {
id: newPassword
text: ""
enabled: !passwordRequest.passwordProvided
enabled: !vm.busy
Kirigami.FormData.label: i18nc("@label:textbox", "New password:")
onAccepted: newPasswordCopy.forceActiveFocus()
}
Kirigami.PasswordField {
id: newPasswordCopy
text: ""
enabled: !passwordRequest.passwordProvided
enabled: !vm.busy
Kirigami.FormData.label: i18nc("@label:textbox", "Verify password:")
onAccepted: applyAction.trigger()
}
}
Connections {
target: passwordRequest
onPasswordRejected: {
bannerTextError = true
}
}
}
actions.main : Kirigami.Action {
id: applyAction
text: i18n("Apply")
iconName: "answer-correct"
enabled: !passwordRequest.passwordProvided && newPassword.text === newPasswordCopy.text && newPassword.text && newPassword.text.length > 0
enabled: !vm.busy && newPassword.text === newPasswordCopy.text && newPassword.text && newPassword.text.length > 0
onTriggered: {
// TODO convert to C++ helper, have proper logging?
if (passwordRequest) {
bannerTextError = !passwordRequest.provideBothPasswords(newPassword.text, newPasswordCopy.text);
}
// TODO warn if not
vm.setup(newPassword.text, newPasswordCopy.text);
}
}
@ -90,7 +76,7 @@ Kirigami.ScrollablePage {
id: errorMessage
Layout.fillWidth: true
text: i18n("Failed to set up your password")
visible: bannerTextError
visible: vm.failed
showCloseButton: true
}
}

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>
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*/
@ -10,22 +10,20 @@ import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
import Keysmith.Application 1.0 as Application
Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Password")
property bool bannerTextError
property Models.PasswordRequestModel passwordRequest
property Application.UnlockAccountsViewModel vm
header: Controls.Control {
padding: Kirigami.Units.smallSpacing
contentItem: Kirigami.InlineMessage {
id: errorMessage
text: i18n("Failed to unlock your accounts")
visible: bannerTextError
visible: vm.failed
showCloseButton: true
type: Kirigami.MessageType.Error
}
@ -60,7 +58,7 @@ Kirigami.ScrollablePage {
id: existingPassword
text: ""
Kirigami.FormData.label: i18nc("@label:textbox", "Password:")
enabled: !passwordRequest.passwordProvided
enabled: !vm.busy
onAccepted: {
if (unlockAction.enabled) {
unlockAction.trigger()
@ -68,26 +66,15 @@ Kirigami.ScrollablePage {
}
}
}
Connections {
target: passwordRequest
onPasswordRejected: {
bannerTextError = true
}
}
}
actions.main : Kirigami.Action {
id: unlockAction
text: i18n("Unlock")
iconName: "unlock"
enabled: !passwordRequest.passwordProvided && existingPassword.text && existingPassword.text.length > 0
enabled: !vm.busy && existingPassword.text && existingPassword.text.length > 0
onTriggered: {
// TODO convert to C++ helper, have proper logging?
if (passwordRequest) {
bannerTextError = !passwordRequest.providePassword(existingPassword.text);
}
// TODO warn if not
vm.unlock(existingPassword.text);
}
}
}

View File

@ -4,25 +4,14 @@
* SPDX-FileCopyrightText: 2019-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
import QtQuick 2.1
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.12 as Kirigami
import QtQml 2.15
import Keysmith.Application 1.0 as Application
Kirigami.ApplicationWindow {
id: root
property bool addActionEnabled: !addAccountRequested
property bool addAccountAvailable: false
property bool addAccountConfirmed: false
property bool addAccountRequested: CommandLine.newAccountRequested
property Models.AccountListModel accounts: Keysmith.accountListModel()
property Models.ValidatedAccountInput validatedInput: Models.ValidatedAccountInput {}
property Models.PasswordRequestModel passwordRequest: Keysmith.passwordRequest()
Kirigami.PageRouter {
id: router
initialRoute: "__init__"
@ -38,248 +27,67 @@ Kirigami.ApplicationWindow {
}
Kirigami.PageRoute {
name: "setup"
name: Application.Keysmith.navigation.name(Application.Navigation.SetupPassword)
Component {
SetupPassword {
bannerTextError : false
passwordRequest: root.passwordRequest
vm: Kirigami.PageRouter.data
}
}
}
Kirigami.PageRoute {
name: "unlock"
name: Application.Keysmith.navigation.name(Application.Navigation.UnlockAccounts)
Component {
UnlockAccounts {
bannerTextError : false
passwordRequest: root.passwordRequest
vm: Kirigami.PageRouter.data
}
}
}
Kirigami.PageRoute {
name: "accounts"
name: Application.Keysmith.navigation.name(Application.Navigation.AccountsOverview)
Component {
AccountsOverview {
accounts: root.accounts
addActionEnabled: root.addActionEnabled
onAccountWanted: {
Kirigami.PageRouter.pushRoute("add-new");
root.addActionEnabled = false;
}
vm: Kirigami.PageRouter.data
}
}
}
Kirigami.PageRoute {
name: "add-new"
name: Application.Keysmith.navigation.name(Application.Navigation.AddAccount)
Component {
AddAccount {
quitEnabled: false
validateAccountAvailability: true
accounts: root.accounts
validatedInput: Models.ValidatedAccountInput {}
onCancelled: {
root.addActionEnabled = true;
Kirigami.PageRouter.navigateToRoute("accounts");
}
onNewAccount: {
root.addActionEnabled = true;
root.accounts.addAccount(input);
Kirigami.PageRouter.navigateToRoute("accounts");
}
vm: Kirigami.PageRouter.data
}
}
}
Kirigami.PageRoute {
name: "accept-pushed"
Component {
AddAccount {
quitEnabled: true
validateAccountAvailability: false
validatedInput: root.validatedInput
accounts: root.accounts
onQuit: {
Qt.quit();
}
onCancelled: {
root.addAccountConfirmed = false;
root.addActionEnabled = true;
pushPasswordPage();
}
onNewAccount: {
root.addAccountConfirmed = true;
root.addActionEnabled = true;
pushPasswordPage();
}
}
}
}
Kirigami.PageRoute {
name: "rename-pushed"
name: Application.Keysmith.navigation.name(Application.Navigation.RenameAccount)
Component {
RenameAccount {
accounts: root.accounts
validatedInput: root.validatedInput
onCancelled: {
root.addActionEnabled = true;
Kirigami.PageRouter.navigateToRoute("accounts");
}
onNewAccount: {
root.addActionEnabled = true;
root.accounts.addAccount(root.validatedInput);
Kirigami.PageRouter.navigateToRoute("accounts");
}
vm: Kirigami.PageRouter.data
}
}
}
Kirigami.PageRoute {
name: "invalid-uri"
name: Application.Keysmith.navigation.name(Application.Navigation.ErrorPage)
Component {
ErrorPage {
quitEnabled: true
title: i18nc("@title:window", "Invalid account")
error: i18nc("@info:label", "The account you are trying to add is invalid. You can either quit the app, or continue without adding the account.")
onDismissed: {
pushPasswordPage();
}
onQuit: {
Qt.quit();
}
vm: Kirigami.PageRouter.data
}
}
}
}
Connections {
target: Keysmith.navigation
target: Application.Keysmith.navigation
function onRouted (route, data) {
root.router.navigateToRoute({route, data});
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) {
return;
}
if (!passwordRequest.keyAvailable) {
return; // TODO warn if not
}
// nothing to do (anymore)
if (!root.addAccountConfirmed) {
return;
}
// claim the new account, try to see if it can be added directly or needs renaming/recovery
addAccountConfirmed = false;
if(accounts.isAccountStillAvailable(validatedInput.name, validatedInput.issuer)) {
accounts.addAccount(validatedInput);
addActionEnabled = true;
router.navigateToRoute("accounts");
} else {
router.navigateToRoute("rename-pushed");
}
}
function pushPasswordPage() {
/*
* Sanity check that the password request has been resolved already.
* If it is not yet clear which type of password request is needed, then signals should still
* fire once this becomes clear and the request is fully resolved.
*/
if (passwordRequest.previouslyDefined || passwordRequest.firstRun) {
router.navigateToRoute(passwordRequest.previouslyDefined ? "unlock" : "setup");
}
}
/*
* TODO maybe have a onPasswordProvided handler to push a "progress" page to provide visual feedback for devices
* where key derivation is slow?
*/
Connections {
target: passwordRequest
onPasswordAccepted : {
// TODO convert to C++ helper, have proper logging?
if (!passwordRequest.keyAvailable) {
return; // TODO warn if not
}
if (root.addAccountConfirmed) {
autoAddNewAccountFromCommandLine();
} else {
root.addActionEnabled = true;
router.navigateToRoute("accounts");
}
}
onPasswordExists: {
/*
* Ignore if there is an account from the commandline to process: in that case password unlocking is
* deferred until after account confirmation/rejection by the user
*/
if (!addAccountRequested) {
pushPasswordPage();
}
}
onNewPasswordNeeded: {
/*
* Ignore if there is an account from the commandline to process: in that case password setup is deferred
* until after account confirmation/rejection by the user
*/
if (!addAccountRequested) {
pushPasswordPage();
}
}
}
Connections {
target: accounts
onLoadedChanged: {
if (accounts.loaded) {
autoAddNewAccountFromCommandLine();
}
}
}
Connections {
target: CommandLine
onNewAccountInvalid: {
// TODO properly report error in UX
if (addAccountRequested) {
addAccountAvailable = false;
addAccountConfirmed = false;
addActionEnabled = true;
router.navigateToRoute("invalid-uri");
}
// TODO warn if not
}
onNewAccountProcessed: {
addAccountAvailable = true;
router.navigateToRoute("accept-pushed");
}
}
Component.onCompleted: {
if (addAccountRequested) {
root.validatedInput.reset();
CommandLine.handleNewAccount(root.validatedInput);
} else {
/*
* In case password request resolves more quickly than the UI, make sure to check if it has been resolved
* yet and prompt for passwords.
*/
pushPasswordPage();
router.pushRoute({route, data});
}
}
}

View File

@ -15,6 +15,7 @@
#include "app/cli.h"
#include "app/keysmith.h"
#include "app/vms.h"
#include "model/accounts.h"
#include "model/input.h"
#include "validators/countervalidator.h"
@ -74,6 +75,28 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QQmlApplicationEngine engine;
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
qmlRegisterUncreatableType<app::AddAccountViewModel>("Keysmith.Application", 1, 0, "AddAccountViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Application.Navigation signals")
);
qmlRegisterUncreatableType<app::RenameAccountViewModel>("Keysmith.Application", 1, 0, "RenameAccountViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Navigation signals")
);
qmlRegisterUncreatableType<app::ErrorViewModel>("Keysmith.Application", 1, 0, "ErrorViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Navigation signals")
);
qmlRegisterUncreatableType<app::SetupPasswordViewModel>("Keysmith.Application", 1, 0, "SetupPasswordViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Navigation signals")
);
qmlRegisterUncreatableType<app::UnlockAccountsViewModel>("Keysmith.Application", 1, 0, "UnlockAccountsViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Navigation signals")
);
qmlRegisterUncreatableType<app::AccountsOverviewViewModel>("Keysmith.Application", 1, 0, "AccountsOverviewViewModel",
QStringLiteral("Should be automatically provided through Keysmith.Navigation signals")
);
qmlRegisterUncreatableType<app::Navigation>("Keysmith.Application", 1, 0, "Navigation",
QStringLiteral("Use the Keysmith singleton to obtain a Navigation")
);
qmlRegisterUncreatableType<model::SimpleAccountListModel>("Keysmith.Models", 1, 0, "AccountListModel",
QStringLiteral("Use the Keysmith singleton to obtain an AccountListModel")
);
@ -110,6 +133,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
if (engine.rootObjects().isEmpty()) {
return -1;
}
proxy.proxy(cliParser, parseOk);
int ret = app.exec();
return ret;
}