From 88c35d7d0712f3d7c42469bb9745964cea338cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Thu, 30 Jan 2025 15:12:53 +0100 Subject: [PATCH] feat(Onboarding): implement the KeycardFactoryReset flow - integrate it into the UI and StoryBook - a new keycardState is introduced: `FactoryResetting` (matching the backend) - a new store method introduced: `startKeycardFactoryReset()` --- storybook/pages/OnboardingLayoutPage.qml | 10 ++ ui/StatusQ/src/onboarding/enums.h | 1 + .../Onboarding2/KeycardFactoryResetFlow.qml | 119 ++++++++++++++++++ .../AppLayouts/Onboarding2/OnboardingFlow.qml | 27 +++- .../Onboarding2/OnboardingLayout.qml | 11 +- .../components/LoginKeycardBox.qml | 9 +- .../Onboarding2/pages/KeycardEnterPinPage.qml | 11 +- .../Onboarding2/pages/LoginScreen.qml | 2 - ui/app/AppLayouts/Onboarding2/qmldir | 1 + .../Onboarding2/stores/OnboardingStore.qml | 4 + 10 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 ui/app/AppLayouts/Onboarding2/KeycardFactoryResetFlow.qml diff --git a/storybook/pages/OnboardingLayoutPage.qml b/storybook/pages/OnboardingLayoutPage.qml index f4273fd39fe..f0a319cd999 100644 --- a/storybook/pages/OnboardingLayoutPage.qml +++ b/storybook/pages/OnboardingLayoutPage.qml @@ -8,6 +8,7 @@ import StatusQ.Core.Theme 0.1 import StatusQ.Core 0.1 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 +import StatusQ.Core.Backpressure 0.1 import AppLayouts.Onboarding.enums 1.0 import AppLayouts.Onboarding2 1.0 @@ -96,6 +97,15 @@ SplitView { addKeyPairState = Onboarding.AddKeyPairState.InProgress } + function startKeycardFactoryReset() { + logs.logEvent("OnboardingStore.startKeycardFactoryReset") + console.warn("!!! SIMULATION: KEYCARD FACTORY RESET") + keycardState = Onboarding.KeycardState.FactoryResetting // SIMULATION: factory reset + Backpressure.debounce(root, 3000, () => { + keycardState = Onboarding.KeycardState.Empty + })() + } + // password function getPasswordStrengthScore(password: string) { // -> int logs.logEvent("OnboardingStore.getPasswordStrengthScore", ["password"], arguments) diff --git a/ui/StatusQ/src/onboarding/enums.h b/ui/StatusQ/src/onboarding/enums.h index 0a594e95ad4..b1e7d615542 100644 --- a/ui/StatusQ/src/onboarding/enums.h +++ b/ui/StatusQ/src/onboarding/enums.h @@ -66,6 +66,7 @@ class OnboardingEnums: public QObject MaxPairingSlotsReached, BlockedPIN, // PIN remaining attempts == 0 BlockedPUK, // PUK remaining attempts == 0 + FactoryResetting, // exit states NotEmpty, Empty diff --git a/ui/app/AppLayouts/Onboarding2/KeycardFactoryResetFlow.qml b/ui/app/AppLayouts/Onboarding2/KeycardFactoryResetFlow.qml new file mode 100644 index 00000000000..5be57c2592a --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/KeycardFactoryResetFlow.qml @@ -0,0 +1,119 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils +import StatusQ.Core.Backpressure 0.1 + +import AppLayouts.Onboarding2.pages 1.0 +import AppLayouts.Onboarding.enums 1.0 + +SQUtils.QObject { + id: root + + required property StackView stackView + + required property int keycardState + required property var tryToSetPinFunction + required property int remainingPinAttempts + required property int remainingPukAttempts + + required property int keycardPinInfoPageDelay + + signal keycardPinEntered(string pin) + signal unblockWithPukRequested + signal unblockWithSeedphraseRequested + + signal performKeycardFactoryResetRequested + + signal finished(bool fromLoginScreen) + + function init(fromLoginScreen = false) { + d.fromLoginScreen = fromLoginScreen + root.stackView.push(d.initialComponent()) + } + + QtObject { + id: d + + property bool fromLoginScreen + + function initialComponent() { + if (root.keycardState === Onboarding.KeycardState.FactoryResetting) + return keycardResetPageComponent + return keycardResetAcks + } + } + + Component { + id: keycardResetAcks + + KeycardBasePage { + image.source: Theme.png("onboarding/keycard/reading") + title: qsTr("Factory reset Keycard") + subtitle: "".arg(Theme.palette.dangerColor1) + + qsTr("All data including the stored key pair and derived accounts will be removed from the Keycard") + + "" + buttons: [ + StatusCheckBox { + id: ack + width: Math.min(implicitWidth, parent.width) + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("I understand the key pair will be deleted") + }, + Item { + width: parent.width + height: parent.spacing + }, + StatusButton { + anchors.horizontalCenter: parent.horizontalCenter + type: StatusBaseButton.Type.Danger + text: qsTr("Factory reset this Keycard") + enabled: ack.checked + onClicked: { + root.performKeycardFactoryResetRequested() + root.stackView.replace(null, keycardResetPageComponent) + } + } + ] + } + } + + Component { + id: keycardResetPageComponent + + KeycardBasePage { + id: keycardResetPage + readonly property bool resetting: root.keycardState === Onboarding.KeycardState.FactoryResetting + + image.source: resetting ? Theme.png("onboarding/keycard/empty") // FIXME correct image + : Theme.png("onboarding/keycard/success") + title: resetting ? qsTr("Reseting Keycard") : qsTr("Keycard successfully factory reset") + subtitle: resetting ? "" : qsTr("You can now use this Keycard like it's a brand-new, empty Keycard") + infoText.text: resetting ? qsTr("Do not remove your Keycard or reader") : "" + buttons: [ + Row { + spacing: 4 + visible: keycardResetPage.resetting + anchors.horizontalCenter: parent.horizontalCenter + StatusLoadingIndicator { + color: Theme.palette.directColor1 + } + StatusBaseText { + text: qsTr("Please wait while the Keycard is being reset") + } + }, + StatusButton { + visible: !keycardResetPage.resetting + anchors.horizontalCenter: parent.horizontalCenter + width: 320 + text: d.fromLoginScreen ? qsTr("Back to Login screen") : qsTr("Log in or Create profile") + onClicked: root.finished(d.fromLoginScreen) + } + ] + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml index 80f4d732503..340aca375b4 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml @@ -40,8 +40,9 @@ SQUtils.QObject { signal seedphraseSubmitted(string seedphrase) signal setPasswordRequested(string password) signal reloadKeycardRequested - signal keycardFactoryResetRequested signal keyPairTransferRequested + signal performKeycardFactoryResetRequested + signal factoryResetDone(bool fromLoginScreen) signal mnemonicWasShown() signal mnemonicRemovalRequested() @@ -201,7 +202,7 @@ SQUtils.QObject { keycardPinInfoPageDelay: root.keycardPinInfoPageDelay onReloadKeycardRequested: root.reloadKeycardRequested() - onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() + onKeycardFactoryResetRequested: keycardFactoryResetFlow.init() onKeyPairTransferRequested: root.keyPairTransferRequested() onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) onLoginWithKeycardRequested: loginWithKeycardFlow.init() @@ -267,7 +268,7 @@ SQUtils.QObject { onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase) onReloadKeycardRequested: root.reloadKeycardRequested() onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init() - onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() + onKeycardFactoryResetRequested: keycardFactoryResetFlow.init() onUnblockWithPukRequested: unblockWithPukFlow.init() onFinished: { @@ -288,7 +289,7 @@ SQUtils.QObject { onReloadKeycardRequested: root.reloadKeycardRequested() onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) - onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() + onKeycardFactoryResetRequested: keycardFactoryResetFlow.init() onFinished: (success) => { if (success) { @@ -312,7 +313,7 @@ SQUtils.QObject { keycardPinInfoPageDelay: root.keycardPinInfoPageDelay onReloadKeycardRequested: root.reloadKeycardRequested() - onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() + onKeycardFactoryResetRequested: keycardFactoryResetFlow.init(true) onKeyPairTransferRequested: root.keyPairTransferRequested() onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) onLoginWithKeycardRequested: loginWithKeycardFlow.init() @@ -330,6 +331,22 @@ SQUtils.QObject { onFinished: d.pushOrSkipBiometricsPage() } + KeycardFactoryResetFlow { + id: keycardFactoryResetFlow + stackView: root.stackView + keycardState: root.keycardState + tryToSetPinFunction: root.tryToSetPinFunction + remainingPinAttempts: root.remainingPinAttempts + remainingPukAttempts: root.remainingPukAttempts + keycardPinInfoPageDelay: root.keycardPinInfoPageDelay + + onKeycardPinEntered: (pin) => root.keycardPinEntered(pin) + onUnblockWithPukRequested: unblockWithPukFlow.init() + onUnblockWithSeedphraseRequested: console.warn("!!! FIXME OnboardingFlow::onUnblockWithSeedphraseRequested") + onPerformKeycardFactoryResetRequested: root.performKeycardFactoryResetRequested() + onFinished: (fromLoginScreen) => root.factoryResetDone(fromLoginScreen) + } + Component { id: enableBiometricsPage diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml index 42eedf5cc0f..50c46306ee7 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml @@ -176,6 +176,14 @@ Page { onReloadKeycardRequested: root.reloadKeycardRequested() onMnemonicWasShown: root.onboardingStore.mnemonicWasShown() onMnemonicRemovalRequested: root.onboardingStore.removeMnemonic() + onPerformKeycardFactoryResetRequested: root.onboardingStore.startKeycardFactoryReset() + onFactoryResetDone: (fromLoginScreen) => { + stack.clear() + if (fromLoginScreen) + stack.push(loginScreenComponent) + else + onboardingFlow.init() + } onSyncProceedWithConnectionString: (connectionString) => root.onboardingStore.inputConnectionStringForBootstrapping(connectionString) @@ -184,7 +192,6 @@ Page { onEnableBiometricsRequested: (enabled) => d.enableBiometrics = enabled onLinkActivated: (link) => Qt.openUrlExternally(link) onFinished: (flow) => d.finishFlow(flow) - onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested") } Component { @@ -206,7 +213,6 @@ Page { d.selectedProfileKeyId = selectedProfileKeyId unblockWithPukFlow.init() } - onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested") } } @@ -225,7 +231,6 @@ Page { d.keycardPin = pin root.onboardingStore.setPin(pin) } - onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested") onFinished: (success) => { if (success) diff --git a/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml b/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml index 44b40202f22..4c2f337d22c 100644 --- a/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml +++ b/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml @@ -27,7 +27,6 @@ Control { signal unblockWithSeedphraseRequested() signal unblockWithPukRequested() - signal keycardFactoryResetRequested() signal loginRequested(string pin) @@ -86,16 +85,10 @@ Control { } MaybeOutlineButton { width: parent.width - visible: root.keycardState === Onboarding.KeycardState.BlockedPIN + visible: root.keycardState === Onboarding.KeycardState.BlockedPIN || root.keycardState === Onboarding.KeycardState.BlockedPUK text: qsTr("Unblock with recovery phrase") onClicked: root.unblockWithSeedphraseRequested() } - MaybeOutlineButton { - width: parent.width - visible: root.keycardState === Onboarding.KeycardState.BlockedPUK - text: qsTr("Factory reset Keycard") - onClicked: root.keycardFactoryResetRequested() - } } StatusPinInput { Layout.alignment: Qt.AlignHCenter diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml index c84b4adedb3..1be11e904d7 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml @@ -18,6 +18,7 @@ KeycardBasePage { property var tryToSetPinFunction: (pin) => { console.error("tryToSetPinFunction: IMPLEMENT ME"); return false } required property int remainingAttempts property bool unblockWithPukAvailable + property string pinCorrectText: qsTr("PIN correct") signal keycardPinEntered(string pin) signal reloadKeycardRequested @@ -134,18 +135,26 @@ KeycardBasePage { target: errorText visible: true } + PropertyChanges { + target: image + source: Theme.png("onboarding/keycard/error") + } }, State { name: "success" when: d.pinValid PropertyChanges { target: root - title: qsTr("PIN correct") + title: root.pinCorrectText } PropertyChanges { target: pinInput enabled: false } + PropertyChanges { + target: image + source: Theme.png("onboarding/keycard/success") + } StateChangeScript { script: root.keycardPinEntered(pinInput.pinInput) } diff --git a/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml b/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml index 881a71f10c4..0c89cfd4c8d 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml @@ -41,7 +41,6 @@ OnboardingPage { signal onboardingLoginFlowRequested() signal unblockWithSeedphraseRequested() signal unblockWithPukRequested() - signal keycardFactoryResetRequested() signal lostKeycard() QtObject { @@ -227,7 +226,6 @@ OnboardingPage { keycardRemainingPukAttempts: root.onboardingStore.keycardRemainingPukAttempts onUnblockWithSeedphraseRequested: root.unblockWithSeedphraseRequested() onUnblockWithPukRequested: root.unblockWithPukRequested() - onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() onPinEditedManually: { // reset state when typing the PIN manually; not to break the bindings inside the component d.resetBiometricsResult() diff --git a/ui/app/AppLayouts/Onboarding2/qmldir b/ui/app/AppLayouts/Onboarding2/qmldir index d6a7d21e283..58146ddbff5 100644 --- a/ui/app/AppLayouts/Onboarding2/qmldir +++ b/ui/app/AppLayouts/Onboarding2/qmldir @@ -1,6 +1,7 @@ CreateNewProfileFlow 1.0 CreateNewProfileFlow.qml KeycardCreateProfileFlow 1.0 KeycardCreateProfileFlow.qml KeycardCreateReplacementFlow 1.0 KeycardCreateReplacementFlow.qml +KeycardFactoryResetFlow 1.0 KeycardFactoryResetFlow.qml LoginByKeycardFlow 1.0 LoginByKeycardFlow.qml LoginBySyncingFlow 1.0 LoginBySyncingFlow.qml OnboardingFlow 1.0 OnboardingFlow.qml diff --git a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml index 5af022324c5..a99ed0deb0b 100644 --- a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml +++ b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml @@ -43,6 +43,10 @@ QtObject { d.onboardingModuleInst.startKeypairTransfer() } + function startKeycardFactoryReset() { + d.onboardingModuleInst.startKeycardFactoryReset() + } + // password signal accountLoginError(string error, bool wrongPassword)