feat(Onboarding): Login flows
- implement the Login flows (seed, sync, keycard) - amend the keycard flow sequences with separate (non) empty page
This commit is contained in:
parent
69cc5d52f5
commit
71ca20a9f3
|
@ -47,6 +47,8 @@ SplitView {
|
||||||
// create keycard profile
|
// create keycard profile
|
||||||
Constants.startupState.keycardEmpty
|
Constants.startupState.keycardEmpty
|
||||||
]
|
]
|
||||||
|
|
||||||
|
readonly property string mnemonic: "dog dog dog dog dog dog dog dog dog dog dog dog"
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingLayout {
|
OnboardingLayout {
|
||||||
|
@ -59,14 +61,30 @@ SplitView {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPasswordStrengthScore(password) {
|
function getPasswordStrengthScore(password) {
|
||||||
|
logs.logEvent("StartupStore.getPasswordStrengthScore", ["password"], arguments)
|
||||||
return Math.min(password.length-1, 4)
|
return Math.min(password.length-1, 4)
|
||||||
}
|
}
|
||||||
function validMnemonic(mnemonic) {
|
function validMnemonic(mnemonic) {
|
||||||
return true
|
logs.logEvent("StartupStore.validMnemonic", ["mnemonic"], arguments)
|
||||||
|
return mnemonic === keycardMock.mnemonic
|
||||||
}
|
}
|
||||||
function getPin() {
|
function getPin() {
|
||||||
|
logs.logEvent("StartupStore.getPin()")
|
||||||
return ctrlPin.text
|
return ctrlPin.text
|
||||||
}
|
}
|
||||||
|
function getSeedPhrase() {
|
||||||
|
logs.logEvent("StartupStore.getSeedPhrase()")
|
||||||
|
// FIXME needed? cf getMnemonic()
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateLocalPairingConnectionString(connectionString) {
|
||||||
|
logs.logEvent("StartupStore.validateLocalPairingConnectionString", ["connectionString"], arguments)
|
||||||
|
return !Number.isNaN(parseInt(connectionString))
|
||||||
|
}
|
||||||
|
function setConnectionString(connectionString) {
|
||||||
|
logs.logEvent("StartupStore.setConnectionString", ["connectionString"], arguments)
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var startupModuleInst: QtObject {
|
readonly property var startupModuleInst: QtObject {
|
||||||
property int remainingAttempts: 5
|
property int remainingAttempts: 5
|
||||||
}
|
}
|
||||||
|
@ -89,12 +107,18 @@ SplitView {
|
||||||
readonly property var words: ["apple", "banana", "cat", "cow", "catalog", "catch", "category", "cattle", "dog", "elephant", "fish", "grape"]
|
readonly property var words: ["apple", "banana", "cat", "cow", "catalog", "catch", "category", "cattle", "dog", "elephant", "fish", "grape"]
|
||||||
|
|
||||||
function getMnemonic() {
|
function getMnemonic() {
|
||||||
|
logs.logEvent("PrivacyStore.getMnemonic()")
|
||||||
return words.join(" ")
|
return words.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
function mnemonicWasShown() {
|
function mnemonicWasShown() {
|
||||||
console.warn("!!! MNEMONIC SHOWN")
|
console.warn("!!! MNEMONIC SHOWN")
|
||||||
logs.logEvent("mnemonicWasShown")
|
logs.logEvent("PrivacyStore.mnemonicWasShown()")
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMnemonic() {
|
||||||
|
console.warn("!!! REMOVE MNEMONIC")
|
||||||
|
logs.logEvent("PrivacyStore.removeMnemonic()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,9 +129,9 @@ SplitView {
|
||||||
property bool metricsPopupSeen
|
property bool metricsPopupSeen
|
||||||
}
|
}
|
||||||
|
|
||||||
onFinished: (success, primaryPath, secondaryPath) => {
|
onFinished: (primaryPath, secondaryPath, data) => {
|
||||||
console.warn("!!! ONBOARDING FINISHED; success:", success, "; primary path:", primaryPath, "; secondary:", secondaryPath)
|
console.warn("!!! ONBOARDING FINISHED; primary path:", primaryPath, "; secondary:", secondaryPath, "; data:", JSON.stringify(data))
|
||||||
logs.logEvent("onFinished", ["success", "primaryPath", "secondaryPath"], arguments)
|
logs.logEvent("onFinished", ["primaryPath", "secondaryPath", "data"], arguments)
|
||||||
|
|
||||||
console.warn("!!! RESTARTING FLOW")
|
console.warn("!!! RESTARTING FLOW")
|
||||||
restartFlow()
|
restartFlow()
|
||||||
|
@ -151,7 +175,7 @@ SplitView {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
text: "Current page: %1".arg(onboarding.stack.currentItem ? onboarding.stack.currentItem.title : "")
|
text: "Current page: %1".arg(onboarding.stack.currentItem ? onboarding.stack.currentItem.pageClassName : "")
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: `Current path: ${onboarding.primaryPath} -> ${onboarding.secondaryPath}`
|
text: `Current path: ${onboarding.primaryPath} -> ${onboarding.secondaryPath}`
|
||||||
|
@ -177,7 +201,7 @@ SplitView {
|
||||||
Button {
|
Button {
|
||||||
text: "Copy seedphrase"
|
text: "Copy seedphrase"
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.NoFocus
|
||||||
onClicked: ClipboardUtils.setText("dog dog dog dog dog dog dog dog dog dog dog dog")
|
onClicked: ClipboardUtils.setText(keycardMock.mnemonic)
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
text: "Copy PIN (\"%1\")".arg(ctrlPin.text)
|
text: "Copy PIN (\"%1\")".arg(ctrlPin.text)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Storybook 1.0
|
||||||
import mainui 1.0
|
import mainui 1.0
|
||||||
import shared.views 1.0
|
import shared.views 1.0
|
||||||
import shared.stores 1.0 as SharedStores
|
import shared.stores 1.0 as SharedStores
|
||||||
|
import shared.popups 1.0
|
||||||
|
|
||||||
import AppLayouts.stores 1.0 as AppLayoutStores
|
import AppLayouts.stores 1.0 as AppLayoutStores
|
||||||
|
|
||||||
|
@ -33,9 +34,19 @@ SplitView {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
validateConnectionString: (stringValue) => !Number.isNaN(parseInt(stringValue))
|
validateConnectionString: (stringValue) => !Number.isNaN(parseInt(stringValue))
|
||||||
|
|
||||||
onDisplayInstructions: logs.logEvent("SyncingEnterCode::displayInstructions")
|
onDisplayInstructions: {
|
||||||
|
logs.logEvent("SyncingEnterCode::displayInstructions")
|
||||||
|
instructionsPopup.createObject(root).open()
|
||||||
|
}
|
||||||
onProceed: (connectionString) => logs.logEvent("SyncingEnterCode::proceed", ["connectionString"], arguments)
|
onProceed: (connectionString) => logs.logEvent("SyncingEnterCode::proceed", ["connectionString"], arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: instructionsPopup
|
||||||
|
GetSyncCodeInstructionsPopup {
|
||||||
|
destroyOnClose: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogsAndControlsPanel {
|
LogsAndControlsPanel {
|
||||||
|
|
|
@ -8,12 +8,10 @@ StatusAnimatedImage 0.1 StatusAnimatedImage.qml
|
||||||
StatusBadge 0.1 StatusBadge.qml
|
StatusBadge 0.1 StatusBadge.qml
|
||||||
StatusBetaTag 0.1 StatusBetaTag.qml
|
StatusBetaTag 0.1 StatusBetaTag.qml
|
||||||
StatusCard 0.1 StatusCard.qml
|
StatusCard 0.1 StatusCard.qml
|
||||||
StatusChart 0.1 StatusChart.qml
|
|
||||||
StatusChartPanel 0.1 StatusChartPanel.qml
|
StatusChartPanel 0.1 StatusChartPanel.qml
|
||||||
StatusChatInfoToolBar 0.1 StatusChatInfoToolBar.qml
|
StatusChatInfoToolBar 0.1 StatusChatInfoToolBar.qml
|
||||||
StatusChatList 0.1 StatusChatList.qml
|
StatusChatList 0.1 StatusChatList.qml
|
||||||
StatusChatListAndCategories 0.1 StatusChatListAndCategories.qml
|
StatusChatListAndCategories 0.1 StatusChatListAndCategories.qml
|
||||||
StatusChatListCategory 0.1 StatusChatListCategory.qml
|
|
||||||
StatusChatListCategoryItem 0.1 StatusChatListCategoryItem.qml
|
StatusChatListCategoryItem 0.1 StatusChatListCategoryItem.qml
|
||||||
StatusChatListItem 0.1 StatusChatListItem.qml
|
StatusChatListItem 0.1 StatusChatListItem.qml
|
||||||
StatusColorSpace 0.0 StatusColorSpace.qml
|
StatusColorSpace 0.0 StatusColorSpace.qml
|
||||||
|
@ -23,7 +21,6 @@ StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListIte
|
||||||
StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
|
StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
|
||||||
StatusCursorDelegate 0.1 StatusCursorDelegate.qml
|
StatusCursorDelegate 0.1 StatusCursorDelegate.qml
|
||||||
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
|
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
|
||||||
StatusDateInput 0.1 StatusDateInput.qml
|
|
||||||
StatusDatePicker 0.1 StatusDatePicker.qml
|
StatusDatePicker 0.1 StatusDatePicker.qml
|
||||||
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
|
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
|
||||||
StatusDotsLoadingIndicator 0.1 StatusDotsLoadingIndicator.qml
|
StatusDotsLoadingIndicator 0.1 StatusDotsLoadingIndicator.qml
|
||||||
|
|
|
@ -8349,6 +8349,7 @@
|
||||||
<file>assets/png/onboarding/status_keycard.png</file>
|
<file>assets/png/onboarding/status_keycard.png</file>
|
||||||
<file>assets/png/onboarding/status_keycard_multiple.png</file>
|
<file>assets/png/onboarding/status_keycard_multiple.png</file>
|
||||||
<file>assets/png/onboarding/status_seedphrase.png</file>
|
<file>assets/png/onboarding/status_seedphrase.png</file>
|
||||||
|
<file>assets/png/onboarding/status_sync.png</file>
|
||||||
<file>assets/png/onboarding/enable_biometrics.png</file>
|
<file>assets/png/onboarding/enable_biometrics.png</file>
|
||||||
<file>assets/png/onboarding/keycard/empty.png</file>
|
<file>assets/png/onboarding/keycard/empty.png</file>
|
||||||
<file>assets/png/onboarding/keycard/insert.png</file>
|
<file>assets/png/onboarding/keycard/insert.png</file>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.13
|
import QtQuick 2.15
|
||||||
|
|
||||||
import shared.popups 1.0
|
import shared.popups 1.0
|
||||||
import shared.views 1.0
|
import shared.views 1.0
|
||||||
|
|
|
@ -31,7 +31,7 @@ Page {
|
||||||
readonly property alias primaryPath: d.primaryPath
|
readonly property alias primaryPath: d.primaryPath
|
||||||
readonly property alias secondaryPath: d.secondaryPath
|
readonly property alias secondaryPath: d.secondaryPath
|
||||||
|
|
||||||
signal finished(bool success, int primaryPath, int secondaryPath)
|
signal finished(int primaryPath, int secondaryPath, var data)
|
||||||
signal keycardFactoryResetRequested() // TODO integrate/switch to an external flow
|
signal keycardFactoryResetRequested() // TODO integrate/switch to an external flow
|
||||||
signal keycardReloaded()
|
signal keycardReloaded()
|
||||||
|
|
||||||
|
@ -57,8 +57,9 @@ Page {
|
||||||
|
|
||||||
// state collected
|
// state collected
|
||||||
property string password
|
property string password
|
||||||
property bool enableBiometrics
|
|
||||||
property string keycardPin
|
property string keycardPin
|
||||||
|
property bool enableBiometrics
|
||||||
|
property string syncConnectionString
|
||||||
|
|
||||||
function resetState() {
|
function resetState() {
|
||||||
d.primaryPath = OnboardingLayout.PrimaryPath.Unknown
|
d.primaryPath = OnboardingLayout.PrimaryPath.Unknown
|
||||||
|
@ -66,16 +67,14 @@ Page {
|
||||||
d.password = ""
|
d.password = ""
|
||||||
d.keycardPin = ""
|
d.keycardPin = ""
|
||||||
d.enableBiometrics = false
|
d.enableBiometrics = false
|
||||||
d.settings.seedphraseRevealed = false
|
d.syncConnectionString = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property Settings settings: Settings {
|
readonly property Settings settings: Settings {
|
||||||
property bool keycardPromoShown // whether we've seen the keycard promo banner on KeycardIntroPage
|
property bool keycardPromoShown // whether we've seen the keycard promo banner on KeycardIntroPage
|
||||||
property bool seedphraseRevealed
|
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
keycardPromoShown = false
|
keycardPromoShown = false
|
||||||
seedphraseRevealed = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,12 +87,16 @@ Page {
|
||||||
|
|
||||||
enum SecondaryPath {
|
enum SecondaryPath {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
||||||
CreateProfileWithPassword,
|
CreateProfileWithPassword,
|
||||||
CreateProfileWithSeedphrase,
|
CreateProfileWithSeedphrase,
|
||||||
CreateProfileWithKeycard,
|
CreateProfileWithKeycard,
|
||||||
CreateProfileWithKeycardNewSeedphrase,
|
CreateProfileWithKeycardNewSeedphrase,
|
||||||
CreateProfileWithKeycardExistingSeedphrase
|
CreateProfileWithKeycardExistingSeedphrase,
|
||||||
// TODO secondary Login paths
|
|
||||||
|
LoginWithSeedphrase,
|
||||||
|
LoginWithSyncing,
|
||||||
|
LoginWithKeycard
|
||||||
}
|
}
|
||||||
|
|
||||||
// page stack
|
// page stack
|
||||||
|
@ -104,17 +107,17 @@ Page {
|
||||||
|
|
||||||
pushEnter: Transition {
|
pushEnter: Transition {
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 50; easing.type: Easing.InQuint }
|
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: d.opacityDuration; easing.type: Easing.InQuint }
|
||||||
NumberAnimation { property: "x"; from: (stack.mirrored ? -0.3 : 0.3) * stack.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
|
NumberAnimation { property: "x"; from: (stack.mirrored ? -0.3 : 0.3) * stack.width; to: 0; duration: d.swipeDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pushExit: Transition {
|
pushExit: Transition {
|
||||||
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 50; easing.type: Easing.OutQuint }
|
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: d.opacityDuration; easing.type: Easing.OutQuint }
|
||||||
}
|
}
|
||||||
popEnter: Transition {
|
popEnter: Transition {
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 50; easing.type: Easing.InQuint }
|
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: d.opacityDuration; easing.type: Easing.InQuint }
|
||||||
NumberAnimation { property: "x"; from: (stack.mirrored ? -0.3 : 0.3) * -stack.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
|
NumberAnimation { property: "x"; from: (stack.mirrored ? -0.3 : 0.3) * -stack.width; to: 0; duration: d.swipeDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
popExit: pushExit
|
popExit: pushExit
|
||||||
|
@ -130,17 +133,13 @@ Page {
|
||||||
onClicked: stack.pop()
|
onClicked: stack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// back button
|
StatusBackButton {
|
||||||
StatusButton {
|
|
||||||
objectName: "onboardingBackButton"
|
|
||||||
isRoundIcon: true
|
|
||||||
width: 44
|
width: 44
|
||||||
height: 44
|
height: 44
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.padding
|
anchors.leftMargin: Theme.padding
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: Theme.padding
|
anchors.bottomMargin: Theme.padding
|
||||||
icon.name: "arrow-left"
|
|
||||||
visible: stack.depth > 1 && !stack.busy
|
visible: stack.depth > 1 && !stack.busy
|
||||||
onClicked: stack.pop()
|
onClicked: stack.pop()
|
||||||
}
|
}
|
||||||
|
@ -175,6 +174,7 @@ Page {
|
||||||
function onLoginRequested() {
|
function onLoginRequested() {
|
||||||
console.warn("!!! PRIMARY: LOG IN")
|
console.warn("!!! PRIMARY: LOG IN")
|
||||||
d.primaryPath = OnboardingLayout.PrimaryPath.Login
|
d.primaryPath = OnboardingLayout.PrimaryPath.Login
|
||||||
|
stack.push(helpUsImproveStatusPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// help us improve page
|
// help us improve page
|
||||||
|
@ -187,7 +187,7 @@ Page {
|
||||||
if (d.primaryPath === OnboardingLayout.PrimaryPath.CreateProfile)
|
if (d.primaryPath === OnboardingLayout.PrimaryPath.CreateProfile)
|
||||||
stack.push(createProfilePage)
|
stack.push(createProfilePage)
|
||||||
else if (d.primaryPath === OnboardingLayout.PrimaryPath.Login)
|
else if (d.primaryPath === OnboardingLayout.PrimaryPath.Login)
|
||||||
; // TODO Login path
|
stack.push(loginPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create profile page
|
// create profile page
|
||||||
|
@ -199,7 +199,7 @@ Page {
|
||||||
function onCreateProfileWithSeedphraseRequested() {
|
function onCreateProfileWithSeedphraseRequested() {
|
||||||
console.warn("!!! SECONDARY: CREATE PROFILE WITH SEEDPHRASE")
|
console.warn("!!! SECONDARY: CREATE PROFILE WITH SEEDPHRASE")
|
||||||
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithSeedphrase
|
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithSeedphrase
|
||||||
stack.push(seedphrasePage, { title: qsTr("Create profile with a recovery phrase"), subtitle: qsTr("Enter your 12, 18 or 24 word recovery phrase")})
|
stack.push(seedphrasePage, { title: qsTr("Create profile using a recovery phrase"), subtitle: qsTr("Enter your 12, 18 or 24 word recovery phrase")})
|
||||||
}
|
}
|
||||||
function onCreateProfileWithEmptyKeycardRequested() {
|
function onCreateProfileWithEmptyKeycardRequested() {
|
||||||
console.warn("!!! SECONDARY: CREATE PROFILE WITH KEYCARD")
|
console.warn("!!! SECONDARY: CREATE PROFILE WITH KEYCARD")
|
||||||
|
@ -207,10 +207,28 @@ Page {
|
||||||
stack.push(keycardIntroPage)
|
stack.push(keycardIntroPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// login page
|
||||||
|
function onLoginWithSeedphraseRequested() {
|
||||||
|
console.warn("!!! SECONDARY: LOGIN WITH SEEDPHRASE")
|
||||||
|
d.secondaryPath = OnboardingLayout.SecondaryPath.LoginWithSeedphrase
|
||||||
|
stack.push(seedphrasePage, { title: qsTr("Sign in with your Status recovery phrase"), subtitle: qsTr("Enter your 12, 18 or 24 word recovery phrase")})
|
||||||
|
}
|
||||||
|
function onLoginWithSyncingRequested() {
|
||||||
|
console.warn("!!! SECONDARY: LOGIN WITH SYNCING")
|
||||||
|
d.secondaryPath = OnboardingLayout.SecondaryPath.LoginWithSyncing
|
||||||
|
stack.push(loginBySyncPage)
|
||||||
|
}
|
||||||
|
function onLoginWithKeycardRequested() {
|
||||||
|
console.warn("!!! SECONDARY: LOGIN WITH KEYCARD")
|
||||||
|
d.secondaryPath = OnboardingLayout.SecondaryPath.LoginWithKeycard
|
||||||
|
stack.push(keycardIntroPage)
|
||||||
|
}
|
||||||
|
|
||||||
// create password page
|
// create password page
|
||||||
function onSetPasswordRequested(password: string) {
|
function onSetPasswordRequested(password: string) {
|
||||||
console.warn("!!! SET PASSWORD REQUESTED")
|
console.warn("!!! SET PASSWORD REQUESTED")
|
||||||
d.password = password
|
d.password = password
|
||||||
|
// TODO set the password immediately?
|
||||||
stack.clear()
|
stack.clear()
|
||||||
stack.push(enableBiometricsPage, {subtitle: qsTr("Use biometrics to fill in your password?")}) // FIXME make optional on unsupported platforms
|
stack.push(enableBiometricsPage, {subtitle: qsTr("Use biometrics to fill in your password?")}) // FIXME make optional on unsupported platforms
|
||||||
}
|
}
|
||||||
|
@ -218,7 +236,7 @@ Page {
|
||||||
// seedphrase page
|
// seedphrase page
|
||||||
function onSeedphraseValidated() {
|
function onSeedphraseValidated() {
|
||||||
console.warn("!!! SEEDPHRASE VALIDATED")
|
console.warn("!!! SEEDPHRASE VALIDATED")
|
||||||
if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithSeedphrase) {
|
if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithSeedphrase || d.secondaryPath === OnboardingLayout.SecondaryPath.LoginWithSeedphrase) {
|
||||||
console.warn("!!! AFTER SEEDPHRASE -> PASSWORD PAGE")
|
console.warn("!!! AFTER SEEDPHRASE -> PASSWORD PAGE")
|
||||||
stack.push(createPasswordPage)
|
stack.push(createPasswordPage)
|
||||||
} else if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithKeycardExistingSeedphrase) {
|
} else if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithKeycardExistingSeedphrase) {
|
||||||
|
@ -238,16 +256,32 @@ Page {
|
||||||
}
|
}
|
||||||
function onKeycardFactoryResetRequested() {
|
function onKeycardFactoryResetRequested() {
|
||||||
console.warn("!!! KEYCARD FACTORY RESET REQUESTED")
|
console.warn("!!! KEYCARD FACTORY RESET REQUESTED")
|
||||||
|
// TODO start keycard factory reset in a popup here
|
||||||
root.keycardFactoryResetRequested()
|
root.keycardFactoryResetRequested()
|
||||||
}
|
}
|
||||||
function onLoginWithKeycardRequested() {
|
function onLoginWithThisKeycardRequested() {
|
||||||
console.warn("!!! LOGIN WITH KEYCARD REQUESTED")
|
console.warn("!!! LOGIN WITH THIS KEYCARD REQUESTED")
|
||||||
|
d.primaryPath = OnboardingLayout.PrimaryPath.Login
|
||||||
|
d.secondaryPath = OnboardingLayout.SecondaryPath.LoginWithKeycard
|
||||||
|
if (root.startupStore.getPin() !== "")
|
||||||
stack.push(keycardEnterPinPage)
|
stack.push(keycardEnterPinPage)
|
||||||
|
else
|
||||||
|
stack.push(keycardCreatePinPage)
|
||||||
}
|
}
|
||||||
function onEmptyKeycardDetected() {
|
function onEmptyKeycardDetected() {
|
||||||
console.warn("!!! EMPTY KEYCARD DETECTED")
|
console.warn("!!! EMPTY KEYCARD DETECTED")
|
||||||
|
if (d.secondaryPath === OnboardingLayout.SecondaryPath.LoginWithKeycard)
|
||||||
|
stack.replace(keycardEmptyPage) // NB: replacing the loginPage
|
||||||
|
else
|
||||||
stack.replace(createKeycardProfilePage) // NB: replacing the keycardIntroPage
|
stack.replace(createKeycardProfilePage) // NB: replacing the keycardIntroPage
|
||||||
}
|
}
|
||||||
|
function onNotEmptyKeycardDetected() {
|
||||||
|
console.warn("!!! NOT EMPTY KEYCARD DETECTED")
|
||||||
|
if (d.secondaryPath === OnboardingLayout.SecondaryPath.LoginWithKeycard)
|
||||||
|
stack.push(keycardEnterPinPage)
|
||||||
|
else
|
||||||
|
stack.push(keycardNotEmptyPage)
|
||||||
|
}
|
||||||
|
|
||||||
function onCreateKeycardProfileWithNewSeedphrase() {
|
function onCreateKeycardProfileWithNewSeedphrase() {
|
||||||
console.warn("!!! CREATE KEYCARD PROFILE WITH NEW SEEDPHRASE")
|
console.warn("!!! CREATE KEYCARD PROFILE WITH NEW SEEDPHRASE")
|
||||||
|
@ -267,6 +301,7 @@ Page {
|
||||||
function onKeycardPinCreated(pin) {
|
function onKeycardPinCreated(pin) {
|
||||||
console.warn("!!! KEYCARD PIN CREATED:", pin)
|
console.warn("!!! KEYCARD PIN CREATED:", pin)
|
||||||
d.keycardPin = pin
|
d.keycardPin = pin
|
||||||
|
// TODO set the PIN immediately?
|
||||||
Backpressure.debounce(root, 2000, function() {
|
Backpressure.debounce(root, 2000, function() {
|
||||||
stack.clear()
|
stack.clear()
|
||||||
stack.push(enableBiometricsPage, // FIXME make optional on unsupported platforms
|
stack.push(enableBiometricsPage, // FIXME make optional on unsupported platforms
|
||||||
|
@ -277,6 +312,7 @@ Page {
|
||||||
function onKeycardPinEntered(pin) {
|
function onKeycardPinEntered(pin) {
|
||||||
console.warn("!!! KEYCARD PIN ENTERED:", pin)
|
console.warn("!!! KEYCARD PIN ENTERED:", pin)
|
||||||
d.keycardPin = pin
|
d.keycardPin = pin
|
||||||
|
// TODO set the PIN immediately?
|
||||||
stack.clear()
|
stack.clear()
|
||||||
stack.push(enableBiometricsPage, // FIXME make optional on unsupported platforms
|
stack.push(enableBiometricsPage, // FIXME make optional on unsupported platforms
|
||||||
{subtitle: qsTr("Would you like to enable biometrics to fill in your password? You will use biometrics for signing in to Status and for signing transactions.")})
|
{subtitle: qsTr("Would you like to enable biometrics to fill in your password? You will use biometrics for signing in to Status and for signing transactions.")})
|
||||||
|
@ -295,7 +331,6 @@ Page {
|
||||||
|
|
||||||
function onBackupSeedphraseConfirmed() {
|
function onBackupSeedphraseConfirmed() {
|
||||||
console.warn("!!! BACKUP SEED CONFIRMED")
|
console.warn("!!! BACKUP SEED CONFIRMED")
|
||||||
d.settings.seedphraseRevealed = true
|
|
||||||
root.privacyStore.mnemonicWasShown()
|
root.privacyStore.mnemonicWasShown()
|
||||||
stack.push(backupSeedVerifyPage)
|
stack.push(backupSeedVerifyPage)
|
||||||
}
|
}
|
||||||
|
@ -311,6 +346,19 @@ Page {
|
||||||
stack.replace(splashScreen, { runningProgressAnimation: true })
|
stack.replace(splashScreen, { runningProgressAnimation: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// login with sync pages
|
||||||
|
function onSyncProceedWithConnectionString(connectionString) {
|
||||||
|
console.warn("!!! SYNC PROCEED WITH CONNECTION STRING:", connectionString)
|
||||||
|
d.syncConnectionString = connectionString
|
||||||
|
root.startupStore.setConnectionString(connectionString)
|
||||||
|
// TODO backend: start the sync
|
||||||
|
Backpressure.debounce(root, 1000, function() {
|
||||||
|
stack.clear()
|
||||||
|
// TODO show the sync in progress screen instead of the final splash page?
|
||||||
|
stack.replace(splashScreen, { runningProgressAnimation: true })
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
// enable biometrics page
|
// enable biometrics page
|
||||||
function onEnableBiometricsRequested(enabled: bool) {
|
function onEnableBiometricsRequested(enabled: bool) {
|
||||||
console.warn("!!! ENABLE BIOMETRICS:", enabled)
|
console.warn("!!! ENABLE BIOMETRICS:", enabled)
|
||||||
|
@ -346,30 +394,26 @@ Page {
|
||||||
id: createPasswordPage
|
id: createPasswordPage
|
||||||
CreatePasswordPage {
|
CreatePasswordPage {
|
||||||
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
|
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
|
||||||
StackView.onRemoved: {
|
|
||||||
d.password = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: enableBiometricsPage
|
id: enableBiometricsPage
|
||||||
EnableBiometricsPage {
|
EnableBiometricsPage {}
|
||||||
StackView.onRemoved: d.enableBiometrics = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: splashScreen
|
id: splashScreen
|
||||||
DidYouKnowSplashScreen {
|
DidYouKnowSplashScreen {
|
||||||
readonly property string title: "Splash"
|
readonly property string pageClassName: "Splash"
|
||||||
property bool runningProgressAnimation
|
property bool runningProgressAnimation
|
||||||
NumberAnimation on progress {
|
NumberAnimation on progress {
|
||||||
from: 0.0
|
from: 0.0
|
||||||
to: 1
|
to: 1
|
||||||
duration: root.splashScreenDurationMs
|
duration: root.splashScreenDurationMs
|
||||||
running: runningProgressAnimation
|
running: runningProgressAnimation
|
||||||
onStopped: root.finished(true, d.primaryPath, d.secondaryPath)
|
onStopped: root.finished(d.primaryPath, d.secondaryPath,
|
||||||
|
{"password": d.password, "keycardPin": d.keycardPin, "enableBiometrics": d.enableBiometrics, "syncConnectionString": d.syncConnectionString})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,7 +428,10 @@ Page {
|
||||||
Component {
|
Component {
|
||||||
id: createKeycardProfilePage
|
id: createKeycardProfilePage
|
||||||
CreateKeycardProfilePage {
|
CreateKeycardProfilePage {
|
||||||
StackView.onActivated: d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycard
|
StackView.onActivated: {
|
||||||
|
d.primaryPath = OnboardingLayout.PrimaryPath.CreateProfile
|
||||||
|
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,10 +444,22 @@ Page {
|
||||||
// NB just to make sure we don't miss the signal when we (re)load the page in the final state already
|
// NB just to make sure we don't miss the signal when we (re)load the page in the final state already
|
||||||
if (keycardState === Constants.startupState.keycardEmpty)
|
if (keycardState === Constants.startupState.keycardEmpty)
|
||||||
emptyKeycardDetected()
|
emptyKeycardDetected()
|
||||||
|
else if (keycardState === Constants.startupState.keycardNotEmpty)
|
||||||
|
notEmptyKeycardDetected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: keycardEmptyPage
|
||||||
|
KeycardEmptyPage {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: keycardNotEmptyPage
|
||||||
|
KeycardNotEmptyPage {}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: keycardCreatePinPage
|
id: keycardCreatePinPage
|
||||||
KeycardCreatePinPage {}
|
KeycardCreatePinPage {}
|
||||||
|
@ -427,7 +486,6 @@ Page {
|
||||||
Component {
|
Component {
|
||||||
id: backupSeedRevealPage
|
id: backupSeedRevealPage
|
||||||
BackupSeedphraseReveal {
|
BackupSeedphraseReveal {
|
||||||
seedphraseRevealed: d.settings.seedphraseRevealed
|
|
||||||
seedWords: d.seedWords
|
seedWords: d.seedWords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,6 +509,20 @@ Page {
|
||||||
BackupSeedphraseOutro {}
|
BackupSeedphraseOutro {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loginPage
|
||||||
|
LoginPage {
|
||||||
|
StackView.onActivated: d.secondaryPath = OnboardingLayout.SecondaryPath.Unknown // reset when we get back here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loginBySyncPage
|
||||||
|
LoginBySyncingPage {
|
||||||
|
validateConnectionString: root.startupStore.validateLocalPairingConnectionString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// common popups
|
// common popups
|
||||||
Component {
|
Component {
|
||||||
id: privacyPolicyPopup
|
id: privacyPolicyPopup
|
||||||
|
|
|
@ -46,25 +46,18 @@ StatusTextField {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
case Qt.Key_Enter: {
|
case Qt.Key_Enter: {
|
||||||
if (!!text && filteredModel.count > 0) {
|
if (filteredModel.count > 0) {
|
||||||
root.text = filteredModel.get(suggestionsList.currentIndex).seedWord
|
root.text = filteredModel.get(suggestionsList.currentIndex).seedWord
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case Qt.Key_Down: {
|
|
||||||
suggestionsList.incrementCurrentIndex()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case Qt.Key_Up: {
|
|
||||||
suggestionsList.decrementCurrentIndex()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case Qt.Key_Space: {
|
case Qt.Key_Space: {
|
||||||
event.accepted = !event.text.match(/^[a-zA-Z]$/)
|
event.accepted = !event.text.match(/^[a-zA-Z]$/)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Keys.forwardTo: [suggestionsList]
|
||||||
|
|
||||||
StatusDropdown {
|
StatusDropdown {
|
||||||
x: 0
|
x: 0
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
|
|
||||||
import StatusQ.Core 0.1
|
import StatusQ.Core 0.1
|
||||||
import StatusQ.Components 0.1
|
import StatusQ.Components 0.1
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQml 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
StatusButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 320
|
||||||
|
|
||||||
|
// inside a Column (or another Positioner), make all but the first button outline
|
||||||
|
Binding on normalColor {
|
||||||
|
value: "transparent"
|
||||||
|
when: !root.Positioner.isFirstItem
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
}
|
||||||
|
Binding on borderWidth {
|
||||||
|
value: 1
|
||||||
|
when: !root.Positioner.isFirstItem
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
}
|
||||||
|
Binding on borderColor {
|
||||||
|
value: Theme.palette.baseColor2
|
||||||
|
when: !root.Positioner.isFirstItem
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
OnboardingFrame 1.0 OnboardingFrame.qml
|
OnboardingFrame 1.0 OnboardingFrame.qml
|
||||||
ListItemButton 1.0 ListItemButton.qml
|
ListItemButton 1.0 ListItemButton.qml
|
||||||
|
MaybeOutlineButton 1.0 MaybeOutlineButton.qml
|
||||||
|
|
|
@ -12,6 +12,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal backupSeedphraseContinue()
|
signal backupSeedphraseContinue()
|
||||||
|
|
||||||
|
pageClassName: "BackupSeedphraseAcks"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -12,6 +12,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal backupSeedphraseRequested()
|
signal backupSeedphraseRequested()
|
||||||
|
|
||||||
|
pageClassName: "BackupSeedphraseIntro"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -14,6 +14,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal backupSeedphraseRemovalConfirmed()
|
signal backupSeedphraseRemovalConfirmed()
|
||||||
|
|
||||||
|
pageClassName: "BackupSeedphraseOutro"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -14,10 +14,16 @@ OnboardingPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var seedWords
|
required property var seedWords
|
||||||
property bool seedphraseRevealed
|
|
||||||
|
|
||||||
signal backupSeedphraseConfirmed()
|
signal backupSeedphraseConfirmed()
|
||||||
|
|
||||||
|
pageClassName: "BackupSeedphraseReveal"
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
property bool seedphraseRevealed
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
@ -78,7 +84,7 @@ OnboardingPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layer.enabled: !root.seedphraseRevealed
|
layer.enabled: !d.seedphraseRevealed
|
||||||
layer.effect: GaussianBlur {
|
layer.effect: GaussianBlur {
|
||||||
radius: 16
|
radius: 16
|
||||||
samples: 33
|
samples: 33
|
||||||
|
@ -91,8 +97,8 @@ OnboardingPage {
|
||||||
text: qsTr("Reveal recovery phrase")
|
text: qsTr("Reveal recovery phrase")
|
||||||
icon.name: "show"
|
icon.name: "show"
|
||||||
type: StatusBaseButton.Type.Primary
|
type: StatusBaseButton.Type.Primary
|
||||||
visible: !root.seedphraseRevealed
|
visible: !d.seedphraseRevealed
|
||||||
onClicked: root.seedphraseRevealed = true
|
onClicked: d.seedphraseRevealed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +113,7 @@ OnboardingPage {
|
||||||
StatusButton {
|
StatusButton {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("Confirm recovery phrase")
|
text: qsTr("Confirm recovery phrase")
|
||||||
enabled: root.seedphraseRevealed
|
enabled: d.seedphraseRevealed
|
||||||
onClicked: root.backupSeedphraseConfirmed()
|
onClicked: root.backupSeedphraseConfirmed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal backupSeedphraseVerified()
|
signal backupSeedphraseVerified()
|
||||||
|
|
||||||
|
pageClassName: "BackupSeedphraseVerify"
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
readonly property var seedSuggestions: BIP39_en {} // [{seedWord:string}, ...]
|
readonly property var seedSuggestions: BIP39_en {} // [{seedWord:string}, ...]
|
||||||
|
@ -87,6 +89,9 @@ OnboardingPage {
|
||||||
seedSuggestions: d.seedSuggestions
|
seedSuggestions: d.seedSuggestions
|
||||||
Component.onCompleted: if (index === 0) forceActiveFocus()
|
Component.onCompleted: if (index === 0) forceActiveFocus()
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
|
if (seedRepeater.allValid) { /// move to next page
|
||||||
|
root.backupSeedphraseVerified()
|
||||||
|
} else { // move to next field
|
||||||
const nextItem = seedRepeater.itemAt(index + 1) ?? seedRepeater.itemAt(0)
|
const nextItem = seedRepeater.itemAt(index + 1) ?? seedRepeater.itemAt(0)
|
||||||
if (!!nextItem) {
|
if (!!nextItem) {
|
||||||
nextItem.input.forceActiveFocus()
|
nextItem.input.forceActiveFocus()
|
||||||
|
@ -96,6 +101,7 @@ OnboardingPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StatusButton {
|
StatusButton {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
|
@ -19,6 +19,8 @@ OnboardingPage {
|
||||||
signal createKeycardProfileWithNewSeedphrase()
|
signal createKeycardProfileWithNewSeedphrase()
|
||||||
signal createKeycardProfileWithExistingSeedphrase()
|
signal createKeycardProfileWithExistingSeedphrase()
|
||||||
|
|
||||||
|
pageClassName: "CreateKeycardProfilePage"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
|
@ -19,6 +19,8 @@ OnboardingPage {
|
||||||
|
|
||||||
title: qsTr("Create profile password")
|
title: qsTr("Create profile password")
|
||||||
|
|
||||||
|
pageClassName: "CreatePasswordPage"
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import QtGraphicalEffects 1.15
|
|
||||||
|
|
||||||
import StatusQ.Core 0.1
|
import StatusQ.Core 0.1
|
||||||
import StatusQ.Components 0.1
|
import StatusQ.Components 0.1
|
||||||
|
@ -22,6 +21,8 @@ OnboardingPage {
|
||||||
signal createProfileWithSeedphraseRequested()
|
signal createProfileWithSeedphraseRequested()
|
||||||
signal createProfileWithEmptyKeycardRequested()
|
signal createProfileWithEmptyKeycardRequested()
|
||||||
|
|
||||||
|
pageClassName: "CreateProfilePage"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -16,6 +16,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal enableBiometricsRequested(bool enable)
|
signal enableBiometricsRequested(bool enable)
|
||||||
|
|
||||||
|
pageClassName: "EnableBiometricsPage"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -20,6 +20,8 @@ OnboardingPage {
|
||||||
signal shareUsageDataRequested(bool enabled)
|
signal shareUsageDataRequested(bool enabled)
|
||||||
signal privacyPolicyRequested()
|
signal privacyPolicyRequested()
|
||||||
|
|
||||||
|
pageClassName: "HelpUsImproveStatusPage"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -8,8 +8,6 @@ import StatusQ.Controls 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
|
|
||||||
import utils 1.0
|
|
||||||
|
|
||||||
OnboardingPage {
|
OnboardingPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ KeycardBasePage {
|
||||||
|
|
||||||
signal keycardPinCreated(string pin)
|
signal keycardPinCreated(string pin)
|
||||||
|
|
||||||
|
pageClassName: "KeycardCreatePinPage"
|
||||||
image.source: Theme.png("onboarding/keycard/reading")
|
image.source: Theme.png("onboarding/keycard/reading")
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
import AppLayouts.Onboarding2.controls 1.0
|
||||||
|
|
||||||
|
KeycardBasePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal createProfileWithEmptyKeycardRequested()
|
||||||
|
|
||||||
|
title: qsTr("Keycard is empty")
|
||||||
|
subtitle: qsTr("There is no profile key pair on this Keycard")
|
||||||
|
image.source: Theme.png("onboarding/keycard/error")
|
||||||
|
|
||||||
|
pageClassName: "KeycardEmptyPage"
|
||||||
|
|
||||||
|
buttons: [
|
||||||
|
MaybeOutlineButton {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: qsTr("Create new profile on this Keycard")
|
||||||
|
onClicked: root.createProfileWithEmptyKeycardRequested()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import StatusQ.Components 0.1
|
||||||
import StatusQ.Controls 0.1
|
import StatusQ.Controls 0.1
|
||||||
import StatusQ.Controls.Validators 0.1
|
import StatusQ.Controls.Validators 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Core.Backpressure 0.1
|
||||||
|
|
||||||
import AppLayouts.Onboarding2.controls 1.0
|
import AppLayouts.Onboarding2.controls 1.0
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ KeycardBasePage {
|
||||||
signal keycardFactoryResetRequested()
|
signal keycardFactoryResetRequested()
|
||||||
signal keycardLocked()
|
signal keycardLocked()
|
||||||
|
|
||||||
|
pageClassName: "KeycardEnterPinPage"
|
||||||
image.source: Theme.png("onboarding/keycard/reading")
|
image.source: Theme.png("onboarding/keycard/reading")
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
@ -134,7 +136,9 @@ KeycardBasePage {
|
||||||
}
|
}
|
||||||
StateChangeScript {
|
StateChangeScript {
|
||||||
script: {
|
script: {
|
||||||
|
Backpressure.debounce(root, 2000, function() {
|
||||||
root.keycardPinEntered(pinInput.pinInput)
|
root.keycardPinEntered(pinInput.pinInput)
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,11 +17,12 @@ KeycardBasePage {
|
||||||
required property string keycardState // Constants.startupState.keycardXXX
|
required property string keycardState // Constants.startupState.keycardXXX
|
||||||
property bool displayPromoBanner
|
property bool displayPromoBanner
|
||||||
|
|
||||||
signal reloadKeycardRequested()
|
|
||||||
signal keycardFactoryResetRequested()
|
signal keycardFactoryResetRequested()
|
||||||
signal loginWithKeycardRequested()
|
signal reloadKeycardRequested()
|
||||||
|
|
||||||
signal emptyKeycardDetected()
|
signal emptyKeycardDetected()
|
||||||
|
signal notEmptyKeycardDetected()
|
||||||
|
|
||||||
|
pageClassName: "KeycardIntroPage"
|
||||||
|
|
||||||
OnboardingFrame {
|
OnboardingFrame {
|
||||||
id: promoBanner
|
id: promoBanner
|
||||||
|
@ -77,46 +78,22 @@ KeycardBasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons: [
|
buttons: [
|
||||||
MaybeOutlineButton {
|
|
||||||
id: btnLogin
|
|
||||||
text: qsTr("Log in with this Keycard")
|
|
||||||
onClicked: root.loginWithKeycardRequested()
|
|
||||||
},
|
|
||||||
MaybeOutlineButton {
|
MaybeOutlineButton {
|
||||||
id: btnFactoryReset
|
id: btnFactoryReset
|
||||||
|
visible: false
|
||||||
text: qsTr("Factory reset Keycard")
|
text: qsTr("Factory reset Keycard")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onClicked: root.keycardFactoryResetRequested()
|
onClicked: root.keycardFactoryResetRequested()
|
||||||
},
|
},
|
||||||
MaybeOutlineButton {
|
MaybeOutlineButton {
|
||||||
id: btnReload
|
id: btnReload
|
||||||
text: qsTr("I’ve inserted a Keycard")
|
visible: false
|
||||||
|
text: qsTr("I’ve inserted a different Keycard")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onClicked: root.reloadKeycardRequested()
|
onClicked: root.reloadKeycardRequested()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// inside a Column (or another Positioner), make all but the first button outline
|
|
||||||
component MaybeOutlineButton: StatusButton {
|
|
||||||
id: maybeOutlineButton
|
|
||||||
width: 320
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: false
|
|
||||||
Binding on normalColor {
|
|
||||||
value: "transparent"
|
|
||||||
when: !maybeOutlineButton.Positioner.isFirstItem
|
|
||||||
restoreMode: Binding.RestoreBindingOrValue
|
|
||||||
}
|
|
||||||
Binding on borderWidth {
|
|
||||||
value: 1
|
|
||||||
when: !maybeOutlineButton.Positioner.isFirstItem
|
|
||||||
restoreMode: Binding.RestoreBindingOrValue
|
|
||||||
}
|
|
||||||
Binding on borderColor {
|
|
||||||
value: Theme.palette.baseColor2
|
|
||||||
when: !maybeOutlineButton.Positioner.isFirstItem
|
|
||||||
restoreMode: Binding.RestoreBindingOrValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
// normal/intro states
|
// normal/intro states
|
||||||
State {
|
State {
|
||||||
|
@ -139,15 +116,18 @@ KeycardBasePage {
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: root
|
target: root
|
||||||
title: qsTr("Insert your Keycard")
|
title: qsTr("Insert your Keycard")
|
||||||
infoText.text: qsTr("Need a little %1?").arg(Utils.getStyledLink(qsTr("help"), "https://keycard.tech/docs/", infoText.hoveredLink,
|
infoText.text: qsTr("Need a little %1?").arg(Utils.getStyledLink(qsTr("help"), "https://keycard.tech/docs/",
|
||||||
Theme.palette.baseColor1, Theme.palette.primaryColor1))
|
infoText.hoveredLink,
|
||||||
|
Theme.palette.baseColor1,
|
||||||
|
Theme.palette.primaryColor1))
|
||||||
image.source: Theme.png("onboarding/keycard/insert")
|
image.source: Theme.png("onboarding/keycard/insert")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "reading"
|
name: "reading"
|
||||||
when: root.keycardState === Constants.startupState.keycardReadingKeycard ||
|
when: root.keycardState === Constants.startupState.keycardReadingKeycard ||
|
||||||
root.keycardState === Constants.startupState.keycardInsertedKeycard
|
root.keycardState === Constants.startupState.keycardInsertedKeycard ||
|
||||||
|
root.keycardState === Constants.startupState.keycardRecognizedKeycard
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: root
|
target: root
|
||||||
title: qsTr("Reading Keycard...")
|
title: qsTr("Reading Keycard...")
|
||||||
|
@ -156,9 +136,42 @@ KeycardBasePage {
|
||||||
},
|
},
|
||||||
// error states
|
// error states
|
||||||
State {
|
State {
|
||||||
name: "error"
|
name: "notKeycard"
|
||||||
|
when: root.keycardState === Constants.startupState.keycardWrongKeycard ||
|
||||||
|
root.keycardState === Constants.startupState.keycardNotKeycard
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: root
|
target: root
|
||||||
|
title: qsTr("Oops this isn’t a Keycard")
|
||||||
|
subtitle: qsTr("Remove card and insert a Keycard")
|
||||||
|
image.source: Theme.png("onboarding/keycard/invalid")
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: btnReload
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "noService"
|
||||||
|
when: root.keycardState === Constants.startupState.keycardNoPCSCService
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
title: qsTr("Smartcard reader service unavailable")
|
||||||
|
subtitle: qsTr("The Smartcard reader service (PCSC service), required for using Keycard, is not currently working. Ensure PCSC is installed and running and try again.")
|
||||||
|
image.source: Theme.png("onboarding/keycard/error")
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: btnReload
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Retry")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "occupied"
|
||||||
|
when: root.keycardState === Constants.startupState.keycardMaxPairingSlotsReached
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
title: qsTr("All pairing slots occupied")
|
||||||
|
subtitle: qsTr("Factory reset this Keycard or insert a different one")
|
||||||
image.source: Theme.png("onboarding/keycard/error")
|
image.source: Theme.png("onboarding/keycard/error")
|
||||||
}
|
}
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
|
@ -170,63 +183,38 @@ KeycardBasePage {
|
||||||
visible: true
|
visible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
|
||||||
name: "notKeycard"
|
|
||||||
extend: "error"
|
|
||||||
when: root.keycardState === Constants.startupState.keycardWrongKeycard ||
|
|
||||||
root.keycardState === Constants.startupState.keycardNotKeycard
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
title: qsTr("Oops this isn’t a Keycard")
|
|
||||||
subtitle: qsTr("Remove card and insert a Keycard")
|
|
||||||
image.source: Theme.png("onboarding/keycard/invalid")
|
|
||||||
}
|
|
||||||
PropertyChanges {
|
|
||||||
target: btnFactoryReset
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "occupied"
|
|
||||||
extend: "error"
|
|
||||||
when: root.keycardState === Constants.startupState.keycardMaxPairingSlotsReached
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
title: qsTr("All pairing slots occupied")
|
|
||||||
subtitle: qsTr("Factory reset this Keycard or insert a different one")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
State {
|
||||||
name: "locked"
|
name: "locked"
|
||||||
extend: "error"
|
|
||||||
when: root.keycardState === Constants.startupState.keycardLocked
|
when: root.keycardState === Constants.startupState.keycardLocked
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: root
|
target: root
|
||||||
title: qsTr("Keycard locked")
|
title: "<font color='%1'>".arg(Theme.palette.dangerColor1) + qsTr("Keycard locked") + "</font>"
|
||||||
subtitle: qsTr("The Keycard you have inserted is locked, you will need to factory reset it or insert a different one")
|
subtitle: qsTr("The Keycard you have inserted is locked, you will need to factory reset it or insert a different one")
|
||||||
}
|
image.source: Theme.png("onboarding/keycard/error")
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "notEmpty"
|
|
||||||
extend: "error"
|
|
||||||
when: root.keycardState === Constants.startupState.keycardNotEmpty
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
title: qsTr("Keycard is not empty")
|
|
||||||
subtitle: qsTr("You can’t use it to store new keys right now")
|
|
||||||
}
|
}
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: btnLogin
|
target: btnFactoryReset
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: btnReload
|
||||||
visible: true
|
visible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// success/exit state
|
// exit states
|
||||||
State {
|
State {
|
||||||
name: "emptyDetected"
|
name: "empty"
|
||||||
when: root.keycardState === Constants.startupState.keycardEmpty
|
when: root.keycardState === Constants.startupState.keycardEmpty
|
||||||
StateChangeScript {
|
StateChangeScript {
|
||||||
script: root.emptyKeycardDetected()
|
script: root.emptyKeycardDetected()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "notEmpty"
|
||||||
|
when: root.keycardState === Constants.startupState.keycardNotEmpty
|
||||||
|
StateChangeScript {
|
||||||
|
script: root.notEmptyKeycardDetected()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
import AppLayouts.Onboarding2.controls 1.0
|
||||||
|
|
||||||
|
KeycardBasePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal reloadKeycardRequested()
|
||||||
|
signal loginWithThisKeycardRequested()
|
||||||
|
signal keycardFactoryResetRequested()
|
||||||
|
|
||||||
|
title: qsTr("Keycard is not empty")
|
||||||
|
subtitle: qsTr("You can’t use it to store new keys right now")
|
||||||
|
image.source: Theme.png("onboarding/keycard/error")
|
||||||
|
|
||||||
|
pageClassName: "KeycardNotEmptyPage"
|
||||||
|
|
||||||
|
buttons: [
|
||||||
|
MaybeOutlineButton {
|
||||||
|
id: btnReload
|
||||||
|
text: qsTr("I’ve inserted a Keycard")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onClicked: root.reloadKeycardRequested()
|
||||||
|
},
|
||||||
|
MaybeOutlineButton {
|
||||||
|
id: btnLogin
|
||||||
|
text: qsTr("Log in with this Keycard")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onClicked: root.loginWithThisKeycardRequested()
|
||||||
|
},
|
||||||
|
MaybeOutlineButton {
|
||||||
|
id: btnFactoryReset
|
||||||
|
text: qsTr("Factory reset Keycard")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onClicked: root.keycardFactoryResetRequested()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
import shared.views 1.0
|
||||||
|
import shared.popups 1.0
|
||||||
|
|
||||||
|
OnboardingPage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var validateConnectionString: (stringValue) => { console.error("validateConnectionString IMPLEMENT ME"); return false }
|
||||||
|
|
||||||
|
signal syncProceedWithConnectionString(string connectionString)
|
||||||
|
|
||||||
|
title: qsTr("Log in by syncing")
|
||||||
|
|
||||||
|
pageClassName: "LoginBySyncingPage"
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(440, root.availableWidth)
|
||||||
|
spacing: Theme.xlPadding
|
||||||
|
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.title
|
||||||
|
font.pixelSize: 22
|
||||||
|
font.bold: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -12
|
||||||
|
text: qsTr("If you have Status on another device")
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncingEnterCode {
|
||||||
|
id: syncView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
validateConnectionString: root.validateConnectionString
|
||||||
|
|
||||||
|
secondTabName: qsTr("Enter code")
|
||||||
|
showBetaTag: false
|
||||||
|
|
||||||
|
onDisplayInstructions: instructionsPopup.createObject(root).open()
|
||||||
|
onProceed: (connectionString) => root.syncProceedWithConnectionString(connectionString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: instructionsPopup
|
||||||
|
GetSyncCodeInstructionsPopup {
|
||||||
|
destroyOnClose: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Popups 0.1
|
||||||
|
import StatusQ.Popups.Dialog 0.1
|
||||||
|
|
||||||
|
import AppLayouts.Onboarding2.controls 1.0
|
||||||
|
|
||||||
|
import utils 1.0
|
||||||
|
|
||||||
|
OnboardingPage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: qsTr("Log in")
|
||||||
|
|
||||||
|
signal loginWithSeedphraseRequested()
|
||||||
|
signal loginWithSyncingRequested()
|
||||||
|
signal loginWithKeycardRequested()
|
||||||
|
|
||||||
|
pageClassName: "LoginPage"
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(380, root.availableWidth)
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.title
|
||||||
|
font.pixelSize: 22
|
||||||
|
font.bold: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -12
|
||||||
|
text: qsTr("How would you like to log in to Status?")
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
OnboardingFrame {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 20
|
||||||
|
StatusImage {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.preferredWidth: Math.min(268, parent.width)
|
||||||
|
Layout.preferredHeight: Math.min(164, height)
|
||||||
|
source: Theme.png("onboarding/status_seedphrase")
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Log in with recovery phrase")
|
||||||
|
font.pixelSize: Theme.secondaryAdditionalTextSize
|
||||||
|
font.bold: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -Theme.padding
|
||||||
|
text: qsTr("If you have your Status recovery phrase")
|
||||||
|
font.pixelSize: Theme.additionalTextSize
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
}
|
||||||
|
StatusButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Enter recovery phrase")
|
||||||
|
font.pixelSize: Theme.additionalTextSize
|
||||||
|
onClicked: root.loginWithSeedphraseRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnboardingFrame {
|
||||||
|
id: buttonFrame
|
||||||
|
Layout.fillWidth: true
|
||||||
|
padding: 1
|
||||||
|
dropShadow: false
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
ListItemButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
title: qsTr("Log in by syncing")
|
||||||
|
subTitle: qsTr("If you have Status on another device")
|
||||||
|
asset.name: Theme.svg("mobile-sync") // FIXME correct icon
|
||||||
|
onClicked: loginWithSyncAck.createObject(root).open()
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: -buttonFrame.padding
|
||||||
|
Layout.rightMargin: -buttonFrame.padding
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: Theme.palette.statusMenu.separatorColor
|
||||||
|
}
|
||||||
|
ListItemButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
title: qsTr("Log in with Keycard")
|
||||||
|
subTitle: qsTr("If your profile keys are stored on a Keycard")
|
||||||
|
asset.name: Theme.png("onboarding/create_profile_keycard")
|
||||||
|
onClicked: root.loginWithKeycardRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loginWithSyncAck
|
||||||
|
StatusDialog {
|
||||||
|
title: qsTr("Log in by syncing")
|
||||||
|
width: 480
|
||||||
|
padding: 20
|
||||||
|
destroyOnClose: true
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 20
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
text: qsTr("To pair your devices and sync your profile, make sure to check and complete the following steps:")
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.padding
|
||||||
|
StatusCheckBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
id: ack1
|
||||||
|
text: qsTr("Connect both devices to the same network")
|
||||||
|
}
|
||||||
|
StatusCheckBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
id: ack2
|
||||||
|
text: qsTr("Make sure you are logged in on the other device")
|
||||||
|
}
|
||||||
|
StatusCheckBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
id: ack3
|
||||||
|
text: qsTr("Disable the firewall and VPN on both devices")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
footer: StatusDialogFooter {
|
||||||
|
spacing: Theme.padding
|
||||||
|
rightButtons: ObjectModel {
|
||||||
|
StatusFlatButton {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: close()
|
||||||
|
}
|
||||||
|
StatusButton {
|
||||||
|
text: qsTr("Continue")
|
||||||
|
enabled: ack1.checked && ack2.checked && ack3.checked
|
||||||
|
onClicked: {
|
||||||
|
root.loginWithSyncingRequested()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import QtQuick.Controls 2.15
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
|
required property string pageClassName
|
||||||
|
|
||||||
signal openLink(string link)
|
signal openLink(string link)
|
||||||
signal openLinkWithConfirmation(string link, string domain)
|
signal openLinkWithConfirmation(string link, string domain)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ OnboardingPage {
|
||||||
|
|
||||||
signal seedphraseValidated()
|
signal seedphraseValidated()
|
||||||
|
|
||||||
|
pageClassName: "SeedphrasePage"
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -15,6 +15,7 @@ import utils 1.0
|
||||||
OnboardingPage {
|
OnboardingPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
pageClassName: "WelcomePage"
|
||||||
title: qsTr("Welcome to Status")
|
title: qsTr("Welcome to Status")
|
||||||
|
|
||||||
signal createProfileRequested()
|
signal createProfileRequested()
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
WelcomePage 1.0 WelcomePage.qml
|
|
||||||
HelpUsImproveStatusPage 1.0 HelpUsImproveStatusPage.qml
|
|
||||||
CreateProfilePage 1.0 CreateProfilePage.qml
|
|
||||||
CreatePasswordPage 1.0 CreatePasswordPage.qml
|
|
||||||
EnableBiometricsPage 1.0 EnableBiometricsPage.qml
|
|
||||||
SeedphrasePage 1.0 SeedphrasePage.qml
|
|
||||||
KeycardIntroPage 1.0 KeycardIntroPage.qml
|
|
||||||
CreateKeycardProfilePage 1.0 CreateKeycardProfilePage.qml
|
|
||||||
KeycardCreatePinPage 1.0 KeycardCreatePinPage.qml
|
|
||||||
KeycardEnterPinPage 1.0 KeycardEnterPinPage.qml
|
|
||||||
BackupSeedphraseIntro 1.0 BackupSeedphraseIntro.qml
|
|
||||||
BackupSeedphraseAcks 1.0 BackupSeedphraseAcks.qml
|
BackupSeedphraseAcks 1.0 BackupSeedphraseAcks.qml
|
||||||
|
BackupSeedphraseIntro 1.0 BackupSeedphraseIntro.qml
|
||||||
|
BackupSeedphraseOutro 1.0 BackupSeedphraseOutro.qml
|
||||||
BackupSeedphraseReveal 1.0 BackupSeedphraseReveal.qml
|
BackupSeedphraseReveal 1.0 BackupSeedphraseReveal.qml
|
||||||
BackupSeedphraseVerify 1.0 BackupSeedphraseVerify.qml
|
BackupSeedphraseVerify 1.0 BackupSeedphraseVerify.qml
|
||||||
BackupSeedphraseOutro 1.0 BackupSeedphraseOutro.qml
|
CreateKeycardProfilePage 1.0 CreateKeycardProfilePage.qml
|
||||||
|
CreatePasswordPage 1.0 CreatePasswordPage.qml
|
||||||
|
CreateProfilePage 1.0 CreateProfilePage.qml
|
||||||
|
EnableBiometricsPage 1.0 EnableBiometricsPage.qml
|
||||||
|
HelpUsImproveStatusPage 1.0 HelpUsImproveStatusPage.qml
|
||||||
|
KeycardCreatePinPage 1.0 KeycardCreatePinPage.qml
|
||||||
|
KeycardEmptyPage 1.0 KeycardEmptyPage.qml
|
||||||
|
KeycardEnterPinPage 1.0 KeycardEnterPinPage.qml
|
||||||
|
KeycardIntroPage 1.0 KeycardIntroPage.qml
|
||||||
|
KeycardNotEmptyPage 1.0 KeycardNotEmptyPage.qml
|
||||||
|
LoginPage 1.0 LoginPage.qml
|
||||||
|
LoginBySyncingPage 1.0 LoginBySyncingPage.qml
|
||||||
|
SeedphrasePage 1.0 SeedphrasePage.qml
|
||||||
|
WelcomePage 1.0 WelcomePage.qml
|
||||||
|
|
|
@ -71,7 +71,7 @@ Column {
|
||||||
if (root.type === SyncingCodeInstructions.Type.EncryptedKey) {
|
if (root.type === SyncingCodeInstructions.Type.EncryptedKey) {
|
||||||
return qsTr("Copy the")
|
return qsTr("Copy the")
|
||||||
}
|
}
|
||||||
return qsTr("Enable camera")
|
return qsTr("Enable camera access")
|
||||||
}
|
}
|
||||||
return qsTr("Click")
|
return qsTr("Click")
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ Column {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return qsTr("Enable camera")
|
return qsTr("Enable camera access")
|
||||||
}
|
}
|
||||||
text2Color: Theme.palette.directColor1
|
text2Color: Theme.palette.directColor1
|
||||||
text3: {
|
text3: {
|
||||||
|
|
|
@ -166,7 +166,7 @@ Column {
|
||||||
color: Theme.palette.baseColor1
|
color: Theme.palette.baseColor1
|
||||||
font.pixelSize: Theme.tertiaryTextFontSize
|
font.pixelSize: Theme.tertiaryTextFontSize
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("Ensure both devices are on the same network")
|
text: qsTr("Ensure both devices are on the same local network")
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBaseText {
|
StatusBaseText {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.14
|
import QtQuick 2.15
|
||||||
|
|
||||||
import StatusQ.Popups.Dialog 0.1
|
import StatusQ.Popups.Dialog 0.1
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import shared.views 1.0
|
||||||
StatusDialog {
|
StatusDialog {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
title: qsTr("How to get a sync code on...")
|
title: qsTr("How to get a pairing code on...")
|
||||||
horizontalPadding: 24
|
horizontalPadding: 24
|
||||||
verticalPadding: 32
|
verticalPadding: 32
|
||||||
footer: null
|
footer: null
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import QtQuick 2.14
|
import QtQuick 2.15
|
||||||
import QtQuick.Layouts 1.14
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import StatusQ.Core 0.1
|
import StatusQ.Core 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
@ -13,12 +13,13 @@ ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string firstTabName: qsTr("Scan QR code")
|
property string firstTabName: qsTr("Scan QR code")
|
||||||
property string secondTabName: qsTr("Enter sync code")
|
property string secondTabName: qsTr("Enter code")
|
||||||
property string firstInstructionButtonName: qsTr("How to get a sync code")
|
property string firstInstructionButtonName: qsTr("How to get a pairing code")
|
||||||
property string secondInstructionButtonName: qsTr("How to get a sync code")
|
property string secondInstructionButtonName: qsTr("How to get a pairing code")
|
||||||
property string syncQrErrorMessage: qsTr("This does not look like a sync QR code")
|
property string syncQrErrorMessage: qsTr("This does not look like a pairing QR code")
|
||||||
property string syncCodeErrorMessage: qsTr("This does not look like a sync code")
|
property string syncCodeErrorMessage: qsTr("This does not look like a pairing code")
|
||||||
property string syncCodeLabel: qsTr("Paste sync code")
|
property string syncCodeLabel: qsTr("Type or paste pairing code")
|
||||||
|
property alias showBetaTag: betaTag.visible
|
||||||
|
|
||||||
property var validateConnectionString: function(stringValue) { return true }
|
property var validateConnectionString: function(stringValue) { return true }
|
||||||
|
|
||||||
|
@ -27,13 +28,19 @@ ColumnLayout {
|
||||||
signal displayInstructions()
|
signal displayInstructions()
|
||||||
signal proceed(string connectionString)
|
signal proceed(string connectionString)
|
||||||
|
|
||||||
spacing: 8
|
spacing: Theme.halfPadding
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: root.spacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Theme.bigPadding
|
||||||
|
Layout.rightMargin: Theme.bigPadding
|
||||||
|
|
||||||
StatusSwitchTabBar {
|
StatusSwitchTabBar {
|
||||||
id: switchTabBar
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: betaTag.visible ? betaTag.width : 0
|
||||||
Layout.rightMargin: 16
|
id: switchTabBar
|
||||||
|
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
|
|
||||||
StatusSwitchTabButton {
|
StatusSwitchTabButton {
|
||||||
|
@ -46,15 +53,14 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBetaTag {
|
StatusBetaTag {
|
||||||
anchors.left: switchTabBar.right
|
id: betaTag
|
||||||
anchors.leftMargin: 8
|
}
|
||||||
anchors.verticalCenter: switchTabBar.verticalCenter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StackLayout {
|
StackLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Math.max(syncQr.implicitHeight, syncCode.implicitHeight)
|
Layout.preferredHeight: Math.max(syncQr.implicitHeight, syncCode.implicitHeight)
|
||||||
Layout.topMargin: 24
|
Layout.topMargin: Theme.bigPadding
|
||||||
currentIndex: switchTabBar.currentIndex
|
currentIndex: switchTabBar.currentIndex
|
||||||
|
|
||||||
// StackLayout doesn't support alignment, so we create an `Item` wrappers
|
// StackLayout doesn't support alignment, so we create an `Item` wrappers
|
||||||
|
@ -80,11 +86,12 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 20
|
Layout.topMargin: Theme.padding
|
||||||
|
spacing: Theme.padding
|
||||||
StatusSyncCodeInput {
|
StatusSyncCodeInput {
|
||||||
id: syncCode
|
id: syncCode
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: 424
|
Layout.preferredWidth: 440
|
||||||
|
|
||||||
mode: StatusSyncCodeInput.Mode.WriteMode
|
mode: StatusSyncCodeInput.Mode.WriteMode
|
||||||
label: root.syncCodeLabel
|
label: root.syncCodeLabel
|
||||||
|
@ -97,30 +104,36 @@ ColumnLayout {
|
||||||
validate: root.validateConnectionString
|
validate: root.validateConnectionString
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
input.onValidChanged: {
|
|
||||||
if (!input.valid)
|
|
||||||
return
|
|
||||||
root.proceed(syncCode.text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
StatusBaseText {
|
StatusBaseText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
color: Theme.palette.baseColor1
|
color: Theme.palette.baseColor1
|
||||||
font.pixelSize: Theme.tertiaryTextFontSize
|
font.pixelSize: Theme.tertiaryTextFontSize
|
||||||
text: qsTr("Ensure both devices are on the same network")
|
text: qsTr("Ensure both devices are on the same local network")
|
||||||
|
}
|
||||||
|
StatusButton {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: Theme.padding
|
||||||
|
text: qsTr("Continue")
|
||||||
|
enabled: syncCode.input.valid
|
||||||
|
onClicked: root.proceed(syncCode.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusFlatButton {
|
StatusFlatButton {
|
||||||
|
Layout.topMargin: Theme.xlPadding
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
visible: switchTabBar.currentIndex == 0 && !!root.firstInstructionButtonName ||
|
visible: switchTabBar.currentIndex == 0 && !!root.firstInstructionButtonName ||
|
||||||
switchTabBar.currentIndex == 1 && !!root.secondInstructionButtonName
|
switchTabBar.currentIndex == 1 && !!root.secondInstructionButtonName
|
||||||
text: switchTabBar.currentIndex == 0?
|
text: switchTabBar.currentIndex == 0?
|
||||||
root.firstInstructionButtonName :
|
root.firstInstructionButtonName :
|
||||||
root.secondInstructionButtonName
|
root.secondInstructionButtonName
|
||||||
|
font.pixelSize: Theme.additionalTextSize
|
||||||
|
normalColor: "transparent"
|
||||||
|
borderWidth: 1
|
||||||
|
borderColor: Theme.palette.baseColor2
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.displayInstructions()
|
root.displayInstructions()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue