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
parent
ea19844300
commit
75a167665b
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
24
src/main.cpp
24
src/main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue