diff --git a/storybook/pages/OnboardingLayoutPage.qml b/storybook/pages/OnboardingLayoutPage.qml
index 93fc82aee9..369117c67a 100644
--- a/storybook/pages/OnboardingLayoutPage.qml
+++ b/storybook/pages/OnboardingLayoutPage.qml
@@ -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)
diff --git a/storybook/pages/SyncingEnterCodePage.qml b/storybook/pages/SyncingEnterCodePage.qml
index d423f036d7..0b762994b7 100644
--- a/storybook/pages/SyncingEnterCodePage.qml
+++ b/storybook/pages/SyncingEnterCodePage.qml
@@ -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 {
diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir
index 28cd64f3bb..1aac49fb44 100644
--- a/ui/StatusQ/src/StatusQ/Components/qmldir
+++ b/ui/StatusQ/src/StatusQ/Components/qmldir
@@ -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
diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc
index d6610e3a52..00b9aa9541 100644
--- a/ui/StatusQ/src/assets.qrc
+++ b/ui/StatusQ/src/assets.qrc
@@ -8349,6 +8349,7 @@
assets/png/onboarding/status_keycard.png
assets/png/onboarding/status_keycard_multiple.png
assets/png/onboarding/status_seedphrase.png
+ assets/png/onboarding/status_sync.png
assets/png/onboarding/enable_biometrics.png
assets/png/onboarding/keycard/empty.png
assets/png/onboarding/keycard/insert.png
diff --git a/ui/StatusQ/src/assets/png/onboarding/status_sync.png b/ui/StatusQ/src/assets/png/onboarding/status_sync.png
new file mode 100644
index 0000000000..d1034b3082
Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_sync.png differ
diff --git a/ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml b/ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml
index ebe2c3f45d..c052fa4e39 100644
--- a/ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml
+++ b/ui/app/AppLayouts/Onboarding/views/SyncCodeView.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.13
+import QtQuick 2.15
import shared.popups 1.0
import shared.views 1.0
diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml
index 5fecbb8a8f..bb1517c954 100644
--- a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml
+++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml
@@ -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
diff --git a/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml b/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml
index 0e08511d77..0b042fc9c9 100644
--- a/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml
+++ b/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml
@@ -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
diff --git a/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml b/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml
index c28b16b703..5cfae0d7a3 100644
--- a/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml
+++ b/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml
@@ -1,5 +1,4 @@
import QtQuick 2.15
-import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
diff --git a/ui/app/AppLayouts/Onboarding2/controls/MaybeOutlineButton.qml b/ui/app/AppLayouts/Onboarding2/controls/MaybeOutlineButton.qml
new file mode 100644
index 0000000000..4527718177
--- /dev/null
+++ b/ui/app/AppLayouts/Onboarding2/controls/MaybeOutlineButton.qml
@@ -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
+ }
+}
diff --git a/ui/app/AppLayouts/Onboarding2/controls/qmldir b/ui/app/AppLayouts/Onboarding2/controls/qmldir
index b513ee4081..9607ac395e 100644
--- a/ui/app/AppLayouts/Onboarding2/controls/qmldir
+++ b/ui/app/AppLayouts/Onboarding2/controls/qmldir
@@ -1,2 +1,3 @@
OnboardingFrame 1.0 OnboardingFrame.qml
ListItemButton 1.0 ListItemButton.qml
+MaybeOutlineButton 1.0 MaybeOutlineButton.qml
diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml
index 9597d83dc1..6f475458e0 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml
@@ -12,6 +12,8 @@ OnboardingPage {
signal backupSeedphraseContinue()
+ pageClassName: "BackupSeedphraseAcks"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml
index 94fdc77c4d..f14cbeaa99 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml
@@ -12,6 +12,8 @@ OnboardingPage {
signal backupSeedphraseRequested()
+ pageClassName: "BackupSeedphraseIntro"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml
index 11c6c359d3..307de382a3 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml
@@ -14,6 +14,8 @@ OnboardingPage {
signal backupSeedphraseRemovalConfirmed()
+ pageClassName: "BackupSeedphraseOutro"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml
index 414506344a..f174aa44c8 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml
@@ -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()
}
}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml
index 9a7f53ab78..e19302200b 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml
@@ -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()
+ }
}
}
}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml
index 94750192bc..92ee60c573 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml
@@ -19,6 +19,8 @@ OnboardingPage {
signal createKeycardProfileWithNewSeedphrase()
signal createKeycardProfileWithExistingSeedphrase()
+ pageClassName: "CreateKeycardProfilePage"
+
contentItem: Item {
ColumnLayout {
width: parent.width
diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml
index 1041eea266..3eaec9eabd 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml
@@ -19,6 +19,8 @@ OnboardingPage {
title: qsTr("Create profile password")
+ pageClassName: "CreatePasswordPage"
+
QtObject {
id: d
diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml
index 879f2023a0..70fd3ecd30 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml
@@ -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
diff --git a/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml b/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml
index 5a21d96a0c..e0cba2efd7 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml
@@ -16,6 +16,8 @@ OnboardingPage {
signal enableBiometricsRequested(bool enable)
+ pageClassName: "EnableBiometricsPage"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml b/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml
index 4adb4d4a18..cb356ffdf9 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml
@@ -20,6 +20,8 @@ OnboardingPage {
signal shareUsageDataRequested(bool enabled)
signal privacyPolicyRequested()
+ pageClassName: "HelpUsImproveStatusPage"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml
index 866f1367ba..2ffd2b72b0 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml
@@ -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
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml
index ad13826e07..ce3481e00d 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml
@@ -17,6 +17,7 @@ KeycardBasePage {
signal keycardPinCreated(string pin)
+ pageClassName: "KeycardCreatePinPage"
image.source: Theme.png("onboarding/keycard/reading")
QtObject {
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardEmptyPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardEmptyPage.qml
new file mode 100644
index 0000000000..f4233370c5
--- /dev/null
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardEmptyPage.qml
@@ -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()
+ }
+ ]
+}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml
index 1d1c417ac2..260e82f326 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml
@@ -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)
+ })()
}
}
},
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml
index a2a74b2084..d9922241e3 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml
@@ -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: "".arg(Theme.palette.dangerColor1) + qsTr("Keycard locked") + ""
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()
+ }
}
]
}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardNotEmptyPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardNotEmptyPage.qml
new file mode 100644
index 0000000000..8e0f800c89
--- /dev/null
+++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardNotEmptyPage.qml
@@ -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()
+ }
+ ]
+}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/LoginBySyncingPage.qml b/ui/app/AppLayouts/Onboarding2/pages/LoginBySyncingPage.qml
new file mode 100644
index 0000000000..166f98f8aa
--- /dev/null
+++ b/ui/app/AppLayouts/Onboarding2/pages/LoginBySyncingPage.qml
@@ -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
+ }
+ }
+}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/LoginPage.qml b/ui/app/AppLayouts/Onboarding2/pages/LoginPage.qml
new file mode 100644
index 0000000000..916a7469b8
--- /dev/null
+++ b/ui/app/AppLayouts/Onboarding2/pages/LoginPage.qml
@@ -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()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml b/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml
index 5a6a81f04f..6a9efcbc0f 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml
@@ -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)
diff --git a/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml
index 13b7923b5a..ba9d9eeb24 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml
@@ -18,6 +18,8 @@ OnboardingPage {
signal seedphraseValidated()
+ pageClassName: "SeedphrasePage"
+
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
diff --git a/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml b/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml
index 740394e393..7c58b71c5f 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml
+++ b/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml
@@ -15,6 +15,7 @@ import utils 1.0
OnboardingPage {
id: root
+ pageClassName: "WelcomePage"
title: qsTr("Welcome to Status")
signal createProfileRequested()
diff --git a/ui/app/AppLayouts/Onboarding2/pages/qmldir b/ui/app/AppLayouts/Onboarding2/pages/qmldir
index 5cc71f3440..58f98e41dc 100644
--- a/ui/app/AppLayouts/Onboarding2/pages/qmldir
+++ b/ui/app/AppLayouts/Onboarding2/pages/qmldir
@@ -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
diff --git a/ui/imports/shared/controls/GetSyncCodeDesktopInstructions.qml b/ui/imports/shared/controls/GetSyncCodeDesktopInstructions.qml
index 3284528764..60ab2dfb77 100644
--- a/ui/imports/shared/controls/GetSyncCodeDesktopInstructions.qml
+++ b/ui/imports/shared/controls/GetSyncCodeDesktopInstructions.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: {
diff --git a/ui/imports/shared/controls/StatusSyncCodeScan.qml b/ui/imports/shared/controls/StatusSyncCodeScan.qml
index 2266a29d7e..4bdfdc4c96 100644
--- a/ui/imports/shared/controls/StatusSyncCodeScan.qml
+++ b/ui/imports/shared/controls/StatusSyncCodeScan.qml
@@ -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 {
diff --git a/ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml b/ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml
index 80e5e95c8e..1807f3f6bc 100644
--- a/ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml
+++ b/ui/imports/shared/popups/GetSyncCodeInstructionsPopup.qml
@@ -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
diff --git a/ui/imports/shared/views/SyncingEnterCode.qml b/ui/imports/shared/views/SyncingEnterCode.qml
index aae94bf460..71e15e11b9 100644
--- a/ui/imports/shared/views/SyncingEnterCode.qml
+++ b/ui/imports/shared/views/SyncingEnterCode.qml
@@ -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()
}