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
|
||||
Constants.startupState.keycardEmpty
|
||||
]
|
||||
|
||||
readonly property string mnemonic: "dog dog dog dog dog dog dog dog dog dog dog dog"
|
||||
}
|
||||
|
||||
OnboardingLayout {
|
||||
|
@ -59,14 +61,30 @@ SplitView {
|
|||
}
|
||||
|
||||
function getPasswordStrengthScore(password) {
|
||||
logs.logEvent("StartupStore.getPasswordStrengthScore", ["password"], arguments)
|
||||
return Math.min(password.length-1, 4)
|
||||
}
|
||||
function validMnemonic(mnemonic) {
|
||||
return true
|
||||
logs.logEvent("StartupStore.validMnemonic", ["mnemonic"], arguments)
|
||||
return mnemonic === keycardMock.mnemonic
|
||||
}
|
||||
function getPin() {
|
||||
logs.logEvent("StartupStore.getPin()")
|
||||
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 {
|
||||
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"]
|
||||
|
||||
function getMnemonic() {
|
||||
logs.logEvent("PrivacyStore.getMnemonic()")
|
||||
return words.join(" ")
|
||||
}
|
||||
|
||||
function mnemonicWasShown() {
|
||||
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
|
||||
}
|
||||
|
||||
onFinished: (success, primaryPath, secondaryPath) => {
|
||||
console.warn("!!! ONBOARDING FINISHED; success:", success, "; primary path:", primaryPath, "; secondary:", secondaryPath)
|
||||
logs.logEvent("onFinished", ["success", "primaryPath", "secondaryPath"], arguments)
|
||||
onFinished: (primaryPath, secondaryPath, data) => {
|
||||
console.warn("!!! ONBOARDING FINISHED; primary path:", primaryPath, "; secondary:", secondaryPath, "; data:", JSON.stringify(data))
|
||||
logs.logEvent("onFinished", ["primaryPath", "secondaryPath", "data"], arguments)
|
||||
|
||||
console.warn("!!! RESTARTING FLOW")
|
||||
restartFlow()
|
||||
|
@ -151,7 +175,7 @@ SplitView {
|
|||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
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 {
|
||||
text: `Current path: ${onboarding.primaryPath} -> ${onboarding.secondaryPath}`
|
||||
|
@ -177,7 +201,7 @@ SplitView {
|
|||
Button {
|
||||
text: "Copy seedphrase"
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: ClipboardUtils.setText("dog dog dog dog dog dog dog dog dog dog dog dog")
|
||||
onClicked: ClipboardUtils.setText(keycardMock.mnemonic)
|
||||
}
|
||||
Button {
|
||||
text: "Copy PIN (\"%1\")".arg(ctrlPin.text)
|
||||
|
|
|
@ -7,6 +7,7 @@ import Storybook 1.0
|
|||
import mainui 1.0
|
||||
import shared.views 1.0
|
||||
import shared.stores 1.0 as SharedStores
|
||||
import shared.popups 1.0
|
||||
|
||||
import AppLayouts.stores 1.0 as AppLayoutStores
|
||||
|
||||
|
@ -33,9 +34,19 @@ SplitView {
|
|||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
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)
|
||||
}
|
||||
|
||||
Component {
|
||||
id: instructionsPopup
|
||||
GetSyncCodeInstructionsPopup {
|
||||
destroyOnClose: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
|
|
|
@ -8,12 +8,10 @@ StatusAnimatedImage 0.1 StatusAnimatedImage.qml
|
|||
StatusBadge 0.1 StatusBadge.qml
|
||||
StatusBetaTag 0.1 StatusBetaTag.qml
|
||||
StatusCard 0.1 StatusCard.qml
|
||||
StatusChart 0.1 StatusChart.qml
|
||||
StatusChartPanel 0.1 StatusChartPanel.qml
|
||||
StatusChatInfoToolBar 0.1 StatusChatInfoToolBar.qml
|
||||
StatusChatList 0.1 StatusChatList.qml
|
||||
StatusChatListAndCategories 0.1 StatusChatListAndCategories.qml
|
||||
StatusChatListCategory 0.1 StatusChatListCategory.qml
|
||||
StatusChatListCategoryItem 0.1 StatusChatListCategoryItem.qml
|
||||
StatusChatListItem 0.1 StatusChatListItem.qml
|
||||
StatusColorSpace 0.0 StatusColorSpace.qml
|
||||
|
@ -23,7 +21,6 @@ StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListIte
|
|||
StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
|
||||
StatusCursorDelegate 0.1 StatusCursorDelegate.qml
|
||||
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
|
||||
StatusDateInput 0.1 StatusDateInput.qml
|
||||
StatusDatePicker 0.1 StatusDatePicker.qml
|
||||
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
|
||||
StatusDotsLoadingIndicator 0.1 StatusDotsLoadingIndicator.qml
|
||||
|
|
|
@ -8349,6 +8349,7 @@
|
|||
<file>assets/png/onboarding/status_keycard.png</file>
|
||||
<file>assets/png/onboarding/status_keycard_multiple.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/keycard/empty.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.views 1.0
|
||||
|
|
|
@ -31,7 +31,7 @@ Page {
|
|||
readonly property alias primaryPath: d.primaryPath
|
||||
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 keycardReloaded()
|
||||
|
||||
|
@ -57,8 +57,9 @@ Page {
|
|||
|
||||
// state collected
|
||||
property string password
|
||||
property bool enableBiometrics
|
||||
property string keycardPin
|
||||
property bool enableBiometrics
|
||||
property string syncConnectionString
|
||||
|
||||
function resetState() {
|
||||
d.primaryPath = OnboardingLayout.PrimaryPath.Unknown
|
||||
|
@ -66,16 +67,14 @@ Page {
|
|||
d.password = ""
|
||||
d.keycardPin = ""
|
||||
d.enableBiometrics = false
|
||||
d.settings.seedphraseRevealed = false
|
||||
d.syncConnectionString = ""
|
||||
}
|
||||
|
||||
readonly property Settings settings: Settings {
|
||||
property bool keycardPromoShown // whether we've seen the keycard promo banner on KeycardIntroPage
|
||||
property bool seedphraseRevealed
|
||||
|
||||
function reset() {
|
||||
keycardPromoShown = false
|
||||
seedphraseRevealed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,12 +87,16 @@ Page {
|
|||
|
||||
enum SecondaryPath {
|
||||
Unknown,
|
||||
|
||||
CreateProfileWithPassword,
|
||||
CreateProfileWithSeedphrase,
|
||||
CreateProfileWithKeycard,
|
||||
CreateProfileWithKeycardNewSeedphrase,
|
||||
CreateProfileWithKeycardExistingSeedphrase
|
||||
// TODO secondary Login paths
|
||||
CreateProfileWithKeycardExistingSeedphrase,
|
||||
|
||||
LoginWithSeedphrase,
|
||||
LoginWithSyncing,
|
||||
LoginWithKeycard
|
||||
}
|
||||
|
||||
// page stack
|
||||
|
@ -104,17 +107,17 @@ Page {
|
|||
|
||||
pushEnter: Transition {
|
||||
ParallelAnimation {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 50; 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: "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: d.swipeDuration; easing.type: Easing.OutCubic }
|
||||
}
|
||||
}
|
||||
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 {
|
||||
ParallelAnimation {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 50; 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: "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: d.swipeDuration; easing.type: Easing.OutCubic }
|
||||
}
|
||||
}
|
||||
popExit: pushExit
|
||||
|
@ -130,17 +133,13 @@ Page {
|
|||
onClicked: stack.pop()
|
||||
}
|
||||
|
||||
// back button
|
||||
StatusButton {
|
||||
objectName: "onboardingBackButton"
|
||||
isRoundIcon: true
|
||||
StatusBackButton {
|
||||
width: 44
|
||||
height: 44
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.padding
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.padding
|
||||
icon.name: "arrow-left"
|
||||
visible: stack.depth > 1 && !stack.busy
|
||||
onClicked: stack.pop()
|
||||
}
|
||||
|
@ -175,6 +174,7 @@ Page {
|
|||
function onLoginRequested() {
|
||||
console.warn("!!! PRIMARY: LOG IN")
|
||||
d.primaryPath = OnboardingLayout.PrimaryPath.Login
|
||||
stack.push(helpUsImproveStatusPage)
|
||||
}
|
||||
|
||||
// help us improve page
|
||||
|
@ -187,7 +187,7 @@ Page {
|
|||
if (d.primaryPath === OnboardingLayout.PrimaryPath.CreateProfile)
|
||||
stack.push(createProfilePage)
|
||||
else if (d.primaryPath === OnboardingLayout.PrimaryPath.Login)
|
||||
; // TODO Login path
|
||||
stack.push(loginPage)
|
||||
}
|
||||
|
||||
// create profile page
|
||||
|
@ -199,7 +199,7 @@ Page {
|
|||
function onCreateProfileWithSeedphraseRequested() {
|
||||
console.warn("!!! SECONDARY: CREATE PROFILE WITH SEEDPHRASE")
|
||||
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() {
|
||||
console.warn("!!! SECONDARY: CREATE PROFILE WITH KEYCARD")
|
||||
|
@ -207,10 +207,28 @@ Page {
|
|||
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
|
||||
function onSetPasswordRequested(password: string) {
|
||||
console.warn("!!! SET PASSWORD REQUESTED")
|
||||
d.password = password
|
||||
// TODO set the password immediately?
|
||||
stack.clear()
|
||||
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
|
||||
function onSeedphraseValidated() {
|
||||
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")
|
||||
stack.push(createPasswordPage)
|
||||
} else if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithKeycardExistingSeedphrase) {
|
||||
|
@ -238,15 +256,31 @@ Page {
|
|||
}
|
||||
function onKeycardFactoryResetRequested() {
|
||||
console.warn("!!! KEYCARD FACTORY RESET REQUESTED")
|
||||
// TODO start keycard factory reset in a popup here
|
||||
root.keycardFactoryResetRequested()
|
||||
}
|
||||
function onLoginWithKeycardRequested() {
|
||||
console.warn("!!! LOGIN WITH KEYCARD REQUESTED")
|
||||
stack.push(keycardEnterPinPage)
|
||||
function onLoginWithThisKeycardRequested() {
|
||||
console.warn("!!! LOGIN WITH THIS KEYCARD REQUESTED")
|
||||
d.primaryPath = OnboardingLayout.PrimaryPath.Login
|
||||
d.secondaryPath = OnboardingLayout.SecondaryPath.LoginWithKeycard
|
||||
if (root.startupStore.getPin() !== "")
|
||||
stack.push(keycardEnterPinPage)
|
||||
else
|
||||
stack.push(keycardCreatePinPage)
|
||||
}
|
||||
function onEmptyKeycardDetected() {
|
||||
console.warn("!!! EMPTY KEYCARD DETECTED")
|
||||
stack.replace(createKeycardProfilePage) // NB: replacing the keycardIntroPage
|
||||
if (d.secondaryPath === OnboardingLayout.SecondaryPath.LoginWithKeycard)
|
||||
stack.replace(keycardEmptyPage) // NB: replacing the loginPage
|
||||
else
|
||||
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() {
|
||||
|
@ -267,6 +301,7 @@ Page {
|
|||
function onKeycardPinCreated(pin) {
|
||||
console.warn("!!! KEYCARD PIN CREATED:", pin)
|
||||
d.keycardPin = pin
|
||||
// TODO set the PIN immediately?
|
||||
Backpressure.debounce(root, 2000, function() {
|
||||
stack.clear()
|
||||
stack.push(enableBiometricsPage, // FIXME make optional on unsupported platforms
|
||||
|
@ -277,6 +312,7 @@ Page {
|
|||
function onKeycardPinEntered(pin) {
|
||||
console.warn("!!! KEYCARD PIN ENTERED:", pin)
|
||||
d.keycardPin = pin
|
||||
// TODO set the PIN immediately?
|
||||
stack.clear()
|
||||
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.")})
|
||||
|
@ -295,7 +331,6 @@ Page {
|
|||
|
||||
function onBackupSeedphraseConfirmed() {
|
||||
console.warn("!!! BACKUP SEED CONFIRMED")
|
||||
d.settings.seedphraseRevealed = true
|
||||
root.privacyStore.mnemonicWasShown()
|
||||
stack.push(backupSeedVerifyPage)
|
||||
}
|
||||
|
@ -311,6 +346,19 @@ Page {
|
|||
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
|
||||
function onEnableBiometricsRequested(enabled: bool) {
|
||||
console.warn("!!! ENABLE BIOMETRICS:", enabled)
|
||||
|
@ -346,30 +394,26 @@ Page {
|
|||
id: createPasswordPage
|
||||
CreatePasswordPage {
|
||||
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
|
||||
StackView.onRemoved: {
|
||||
d.password = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: enableBiometricsPage
|
||||
EnableBiometricsPage {
|
||||
StackView.onRemoved: d.enableBiometrics = false
|
||||
}
|
||||
EnableBiometricsPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: splashScreen
|
||||
DidYouKnowSplashScreen {
|
||||
readonly property string title: "Splash"
|
||||
readonly property string pageClassName: "Splash"
|
||||
property bool runningProgressAnimation
|
||||
NumberAnimation on progress {
|
||||
from: 0.0
|
||||
to: 1
|
||||
duration: root.splashScreenDurationMs
|
||||
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 {
|
||||
id: 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
|
||||
if (keycardState === Constants.startupState.keycardEmpty)
|
||||
emptyKeycardDetected()
|
||||
else if (keycardState === Constants.startupState.keycardNotEmpty)
|
||||
notEmptyKeycardDetected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: keycardEmptyPage
|
||||
KeycardEmptyPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: keycardNotEmptyPage
|
||||
KeycardNotEmptyPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: keycardCreatePinPage
|
||||
KeycardCreatePinPage {}
|
||||
|
@ -427,7 +486,6 @@ Page {
|
|||
Component {
|
||||
id: backupSeedRevealPage
|
||||
BackupSeedphraseReveal {
|
||||
seedphraseRevealed: d.settings.seedphraseRevealed
|
||||
seedWords: d.seedWords
|
||||
}
|
||||
}
|
||||
|
@ -451,6 +509,20 @@ Page {
|
|||
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
|
||||
Component {
|
||||
id: privacyPolicyPopup
|
||||
|
|
|
@ -46,25 +46,18 @@ StatusTextField {
|
|||
switch (event.key) {
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter: {
|
||||
if (!!text && filteredModel.count > 0) {
|
||||
if (filteredModel.count > 0) {
|
||||
root.text = filteredModel.get(suggestionsList.currentIndex).seedWord
|
||||
}
|
||||
break
|
||||
}
|
||||
case Qt.Key_Down: {
|
||||
suggestionsList.incrementCurrentIndex()
|
||||
break
|
||||
}
|
||||
case Qt.Key_Up: {
|
||||
suggestionsList.decrementCurrentIndex()
|
||||
break
|
||||
}
|
||||
case Qt.Key_Space: {
|
||||
event.accepted = !event.text.match(/^[a-zA-Z]$/)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Keys.forwardTo: [suggestionsList]
|
||||
|
||||
StatusDropdown {
|
||||
x: 0
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 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
|
||||
ListItemButton 1.0 ListItemButton.qml
|
||||
MaybeOutlineButton 1.0 MaybeOutlineButton.qml
|
||||
|
|
|
@ -12,6 +12,8 @@ OnboardingPage {
|
|||
|
||||
signal backupSeedphraseContinue()
|
||||
|
||||
pageClassName: "BackupSeedphraseAcks"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -12,6 +12,8 @@ OnboardingPage {
|
|||
|
||||
signal backupSeedphraseRequested()
|
||||
|
||||
pageClassName: "BackupSeedphraseIntro"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -14,6 +14,8 @@ OnboardingPage {
|
|||
|
||||
signal backupSeedphraseRemovalConfirmed()
|
||||
|
||||
pageClassName: "BackupSeedphraseOutro"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -14,10 +14,16 @@ OnboardingPage {
|
|||
id: root
|
||||
|
||||
required property var seedWords
|
||||
property bool seedphraseRevealed
|
||||
|
||||
signal backupSeedphraseConfirmed()
|
||||
|
||||
pageClassName: "BackupSeedphraseReveal"
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property bool seedphraseRevealed
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
@ -78,7 +84,7 @@ OnboardingPage {
|
|||
}
|
||||
}
|
||||
}
|
||||
layer.enabled: !root.seedphraseRevealed
|
||||
layer.enabled: !d.seedphraseRevealed
|
||||
layer.effect: GaussianBlur {
|
||||
radius: 16
|
||||
samples: 33
|
||||
|
@ -91,8 +97,8 @@ OnboardingPage {
|
|||
text: qsTr("Reveal recovery phrase")
|
||||
icon.name: "show"
|
||||
type: StatusBaseButton.Type.Primary
|
||||
visible: !root.seedphraseRevealed
|
||||
onClicked: root.seedphraseRevealed = true
|
||||
visible: !d.seedphraseRevealed
|
||||
onClicked: d.seedphraseRevealed = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +113,7 @@ OnboardingPage {
|
|||
StatusButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Confirm recovery phrase")
|
||||
enabled: root.seedphraseRevealed
|
||||
enabled: d.seedphraseRevealed
|
||||
onClicked: root.backupSeedphraseConfirmed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ OnboardingPage {
|
|||
|
||||
signal backupSeedphraseVerified()
|
||||
|
||||
pageClassName: "BackupSeedphraseVerify"
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property var seedSuggestions: BIP39_en {} // [{seedWord:string}, ...]
|
||||
|
@ -87,9 +89,13 @@ OnboardingPage {
|
|||
seedSuggestions: d.seedSuggestions
|
||||
Component.onCompleted: if (index === 0) forceActiveFocus()
|
||||
onAccepted: {
|
||||
const nextItem = seedRepeater.itemAt(index + 1) ?? seedRepeater.itemAt(0)
|
||||
if (!!nextItem) {
|
||||
nextItem.input.forceActiveFocus()
|
||||
if (seedRepeater.allValid) { /// move to next page
|
||||
root.backupSeedphraseVerified()
|
||||
} else { // move to next field
|
||||
const nextItem = seedRepeater.itemAt(index + 1) ?? seedRepeater.itemAt(0)
|
||||
if (!!nextItem) {
|
||||
nextItem.input.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ OnboardingPage {
|
|||
signal createKeycardProfileWithNewSeedphrase()
|
||||
signal createKeycardProfileWithExistingSeedphrase()
|
||||
|
||||
pageClassName: "CreateKeycardProfilePage"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
|
|
@ -19,6 +19,8 @@ OnboardingPage {
|
|||
|
||||
title: qsTr("Create profile password")
|
||||
|
||||
pageClassName: "CreatePasswordPage"
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
@ -22,6 +21,8 @@ OnboardingPage {
|
|||
signal createProfileWithSeedphraseRequested()
|
||||
signal createProfileWithEmptyKeycardRequested()
|
||||
|
||||
pageClassName: "CreateProfilePage"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -16,6 +16,8 @@ OnboardingPage {
|
|||
|
||||
signal enableBiometricsRequested(bool enable)
|
||||
|
||||
pageClassName: "EnableBiometricsPage"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -20,6 +20,8 @@ OnboardingPage {
|
|||
signal shareUsageDataRequested(bool enabled)
|
||||
signal privacyPolicyRequested()
|
||||
|
||||
pageClassName: "HelpUsImproveStatusPage"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -8,8 +8,6 @@ import StatusQ.Controls 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
import utils 1.0
|
||||
|
||||
OnboardingPage {
|
||||
id: root
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ KeycardBasePage {
|
|||
|
||||
signal keycardPinCreated(string pin)
|
||||
|
||||
pageClassName: "KeycardCreatePinPage"
|
||||
image.source: Theme.png("onboarding/keycard/reading")
|
||||
|
||||
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.Validators 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Backpressure 0.1
|
||||
|
||||
import AppLayouts.Onboarding2.controls 1.0
|
||||
|
||||
|
@ -23,6 +24,7 @@ KeycardBasePage {
|
|||
signal keycardFactoryResetRequested()
|
||||
signal keycardLocked()
|
||||
|
||||
pageClassName: "KeycardEnterPinPage"
|
||||
image.source: Theme.png("onboarding/keycard/reading")
|
||||
|
||||
QtObject {
|
||||
|
@ -134,7 +136,9 @@ KeycardBasePage {
|
|||
}
|
||||
StateChangeScript {
|
||||
script: {
|
||||
root.keycardPinEntered(pinInput.pinInput)
|
||||
Backpressure.debounce(root, 2000, function() {
|
||||
root.keycardPinEntered(pinInput.pinInput)
|
||||
})()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -17,11 +17,12 @@ KeycardBasePage {
|
|||
required property string keycardState // Constants.startupState.keycardXXX
|
||||
property bool displayPromoBanner
|
||||
|
||||
signal reloadKeycardRequested()
|
||||
signal keycardFactoryResetRequested()
|
||||
signal loginWithKeycardRequested()
|
||||
|
||||
signal reloadKeycardRequested()
|
||||
signal emptyKeycardDetected()
|
||||
signal notEmptyKeycardDetected()
|
||||
|
||||
pageClassName: "KeycardIntroPage"
|
||||
|
||||
OnboardingFrame {
|
||||
id: promoBanner
|
||||
|
@ -77,46 +78,22 @@ KeycardBasePage {
|
|||
}
|
||||
|
||||
buttons: [
|
||||
MaybeOutlineButton {
|
||||
id: btnLogin
|
||||
text: qsTr("Log in with this Keycard")
|
||||
onClicked: root.loginWithKeycardRequested()
|
||||
},
|
||||
MaybeOutlineButton {
|
||||
id: btnFactoryReset
|
||||
visible: false
|
||||
text: qsTr("Factory reset Keycard")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onClicked: root.keycardFactoryResetRequested()
|
||||
},
|
||||
MaybeOutlineButton {
|
||||
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()
|
||||
}
|
||||
]
|
||||
|
||||
// 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: [
|
||||
// normal/intro states
|
||||
State {
|
||||
|
@ -139,15 +116,18 @@ KeycardBasePage {
|
|||
PropertyChanges {
|
||||
target: root
|
||||
title: qsTr("Insert your Keycard")
|
||||
infoText.text: qsTr("Need a little %1?").arg(Utils.getStyledLink(qsTr("help"), "https://keycard.tech/docs/", infoText.hoveredLink,
|
||||
Theme.palette.baseColor1, Theme.palette.primaryColor1))
|
||||
infoText.text: qsTr("Need a little %1?").arg(Utils.getStyledLink(qsTr("help"), "https://keycard.tech/docs/",
|
||||
infoText.hoveredLink,
|
||||
Theme.palette.baseColor1,
|
||||
Theme.palette.primaryColor1))
|
||||
image.source: Theme.png("onboarding/keycard/insert")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "reading"
|
||||
when: root.keycardState === Constants.startupState.keycardReadingKeycard ||
|
||||
root.keycardState === Constants.startupState.keycardInsertedKeycard
|
||||
root.keycardState === Constants.startupState.keycardInsertedKeycard ||
|
||||
root.keycardState === Constants.startupState.keycardRecognizedKeycard
|
||||
PropertyChanges {
|
||||
target: root
|
||||
title: qsTr("Reading Keycard...")
|
||||
|
@ -156,9 +136,42 @@ KeycardBasePage {
|
|||
},
|
||||
// error states
|
||||
State {
|
||||
name: "error"
|
||||
name: "notKeycard"
|
||||
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: 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")
|
||||
}
|
||||
PropertyChanges {
|
||||
|
@ -170,63 +183,38 @@ KeycardBasePage {
|
|||
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 {
|
||||
name: "locked"
|
||||
extend: "error"
|
||||
when: root.keycardState === Constants.startupState.keycardLocked
|
||||
PropertyChanges {
|
||||
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")
|
||||
}
|
||||
},
|
||||
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")
|
||||
image.source: Theme.png("onboarding/keycard/error")
|
||||
}
|
||||
PropertyChanges {
|
||||
target: btnLogin
|
||||
target: btnFactoryReset
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: btnReload
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
// success/exit state
|
||||
// exit states
|
||||
State {
|
||||
name: "emptyDetected"
|
||||
name: "empty"
|
||||
when: root.keycardState === Constants.startupState.keycardEmpty
|
||||
StateChangeScript {
|
||||
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
|
||||
|
||||
Page {
|
||||
required property string pageClassName
|
||||
|
||||
signal openLink(string link)
|
||||
signal openLinkWithConfirmation(string link, string domain)
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ OnboardingPage {
|
|||
|
||||
signal seedphraseValidated()
|
||||
|
||||
pageClassName: "SeedphrasePage"
|
||||
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
@ -15,6 +15,7 @@ import utils 1.0
|
|||
OnboardingPage {
|
||||
id: root
|
||||
|
||||
pageClassName: "WelcomePage"
|
||||
title: qsTr("Welcome to Status")
|
||||
|
||||
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
|
||||
BackupSeedphraseIntro 1.0 BackupSeedphraseIntro.qml
|
||||
BackupSeedphraseOutro 1.0 BackupSeedphraseOutro.qml
|
||||
BackupSeedphraseReveal 1.0 BackupSeedphraseReveal.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) {
|
||||
return qsTr("Copy the")
|
||||
}
|
||||
return qsTr("Enable camera")
|
||||
return qsTr("Enable camera access")
|
||||
}
|
||||
return qsTr("Click")
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ Column {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
return qsTr("Enable camera")
|
||||
return qsTr("Enable camera access")
|
||||
}
|
||||
text2Color: Theme.palette.directColor1
|
||||
text3: {
|
||||
|
|
|
@ -166,7 +166,7 @@ Column {
|
|||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
|
@ -7,7 +7,7 @@ import shared.views 1.0
|
|||
StatusDialog {
|
||||
id: root
|
||||
|
||||
title: qsTr("How to get a sync code on...")
|
||||
title: qsTr("How to get a pairing code on...")
|
||||
horizontalPadding: 24
|
||||
verticalPadding: 32
|
||||
footer: null
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
@ -13,48 +13,54 @@ ColumnLayout {
|
|||
id: root
|
||||
|
||||
property string firstTabName: qsTr("Scan QR code")
|
||||
property string secondTabName: qsTr("Enter sync code")
|
||||
property string firstInstructionButtonName: qsTr("How to get a sync code")
|
||||
property string secondInstructionButtonName: qsTr("How to get a sync code")
|
||||
property string syncQrErrorMessage: qsTr("This does not look like a sync QR code")
|
||||
property string syncCodeErrorMessage: qsTr("This does not look like a sync code")
|
||||
property string syncCodeLabel: qsTr("Paste sync code")
|
||||
property string secondTabName: qsTr("Enter code")
|
||||
property string firstInstructionButtonName: qsTr("How to get a pairing code")
|
||||
property string secondInstructionButtonName: qsTr("How to get a pairing code")
|
||||
property string syncQrErrorMessage: qsTr("This does not look like a pairing QR code")
|
||||
property string syncCodeErrorMessage: qsTr("This does not look like a pairing code")
|
||||
property string syncCodeLabel: qsTr("Type or paste pairing code")
|
||||
property alias showBetaTag: betaTag.visible
|
||||
|
||||
property var validateConnectionString: function(stringValue) { return true }
|
||||
|
||||
readonly property bool syncViaQr: !switchTabBar.currentIndex
|
||||
readonly property bool syncViaQr: !switchTabBar.currentIndex
|
||||
|
||||
signal displayInstructions()
|
||||
signal proceed(string connectionString)
|
||||
|
||||
spacing: 8
|
||||
spacing: Theme.halfPadding
|
||||
|
||||
StatusSwitchTabBar {
|
||||
id: switchTabBar
|
||||
RowLayout {
|
||||
spacing: root.spacing
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
currentIndex: 0
|
||||
Layout.leftMargin: Theme.bigPadding
|
||||
Layout.rightMargin: Theme.bigPadding
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: root.firstTabName
|
||||
StatusSwitchTabBar {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: betaTag.visible ? betaTag.width : 0
|
||||
id: switchTabBar
|
||||
|
||||
currentIndex: 0
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: root.firstTabName
|
||||
}
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: root.secondTabName
|
||||
}
|
||||
}
|
||||
|
||||
StatusSwitchTabButton {
|
||||
text: root.secondTabName
|
||||
StatusBetaTag {
|
||||
id: betaTag
|
||||
}
|
||||
}
|
||||
|
||||
StatusBetaTag {
|
||||
anchors.left: switchTabBar.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.verticalCenter: switchTabBar.verticalCenter
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.max(syncQr.implicitHeight, syncCode.implicitHeight)
|
||||
Layout.topMargin: 24
|
||||
Layout.topMargin: Theme.bigPadding
|
||||
currentIndex: switchTabBar.currentIndex
|
||||
|
||||
// StackLayout doesn't support alignment, so we create an `Item` wrappers
|
||||
|
@ -80,11 +86,12 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 20
|
||||
Layout.topMargin: Theme.padding
|
||||
spacing: Theme.padding
|
||||
StatusSyncCodeInput {
|
||||
id: syncCode
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: 424
|
||||
Layout.preferredWidth: 440
|
||||
|
||||
mode: StatusSyncCodeInput.Mode.WriteMode
|
||||
label: root.syncCodeLabel
|
||||
|
@ -97,30 +104,36 @@ ColumnLayout {
|
|||
validate: root.validateConnectionString
|
||||
}
|
||||
]
|
||||
input.onValidChanged: {
|
||||
if (!input.valid)
|
||||
return
|
||||
root.proceed(syncCode.text)
|
||||
}
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Theme.palette.baseColor1
|
||||
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 {
|
||||
Layout.topMargin: Theme.xlPadding
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: switchTabBar.currentIndex == 0 && !!root.firstInstructionButtonName ||
|
||||
switchTabBar.currentIndex == 1 && !!root.secondInstructionButtonName
|
||||
text: switchTabBar.currentIndex == 0?
|
||||
root.firstInstructionButtonName :
|
||||
root.secondInstructionButtonName
|
||||
font.pixelSize: Theme.additionalTextSize
|
||||
normalColor: "transparent"
|
||||
borderWidth: 1
|
||||
borderColor: Theme.palette.baseColor2
|
||||
onClicked: {
|
||||
root.displayInstructions()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue