feat(Onboarding): Create Profile

- implement the basic Onboarding UI skeleton and the Create Profile
flows
- adjust the PasswordView and EnterSeedPhrase views to the latest design
- add the main OnboardingLayout and StatusPinInput pages to Storybook
- change terminology app-wide: "Seed phrase" -> "Recovery phrase"

Fixes #16719
Fixes #16742
Fixes #16743
This commit is contained in:
Lukáš Tinkl 2024-11-06 00:39:08 +01:00
parent 331f23421d
commit 69cc5d52f5
No known key found for this signature in database
GPG Key ID: 4ABB993B9382F296
100 changed files with 3134 additions and 215 deletions

View File

@ -17,7 +17,7 @@ import Status.Core.Theme
width: 240 width: 240
text: qsTr("Hello World!") text: qsTr("Hello World!")
font.pixelSize: 24 font.pixelSize: 24
color: Theme.pallete.directColor1 color: Theme.palette.directColor1
} }
\endqml \endqml

View File

@ -146,6 +146,10 @@ SplitView {
enabled: searchField.searchText !== "" enabled: searchField.searchText !== ""
onClicked: searchField.clear() onClicked: searchField.clear()
} }
Label {
text: "INFO: Reload the page after selecting 'Dark mode'"
font.weight: Font.Medium
}
} }
ColorFlow { ColorFlow {

View File

@ -0,0 +1,215 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import Models 1.0
import Storybook 1.0
import utils 1.0
import AppLayouts.Onboarding2 1.0
import AppLayouts.Profile.stores 1.0 as ProfileStores
import shared.stores 1.0 as SharedStores
// compat
import AppLayouts.Onboarding.stores 1.0 as OOBS
SplitView {
id: root
orientation: Qt.Vertical
Logs { id: logs }
QtObject {
id: keycardMock
property string stateType: ctrlKeycardState.currentValue
readonly property var keycardStates: [
// initial
//Constants.startupState.keycardNoPCSCService,
Constants.startupState.keycardPluginReader,
Constants.startupState.keycardInsertKeycard,
Constants.startupState.keycardInsertedKeycard, Constants.startupState.keycardReadingKeycard,
// initial errors
Constants.startupState.keycardWrongKeycard, Constants.startupState.keycardNotKeycard,
Constants.startupState.keycardMaxPairingSlotsReached,
Constants.startupState.keycardLocked,
Constants.startupState.keycardNotEmpty,
// create keycard profile
Constants.startupState.keycardEmpty
]
}
OnboardingLayout {
id: onboarding
SplitView.fillWidth: true
SplitView.fillHeight: true
startupStore: OOBS.StartupStore {
readonly property var currentStartupState: QtObject {
property string stateType: keycardMock.stateType
}
function getPasswordStrengthScore(password) {
return Math.min(password.length-1, 4)
}
function validMnemonic(mnemonic) {
return true
}
function getPin() {
return ctrlPin.text
}
readonly property var startupModuleInst: QtObject {
property int remainingAttempts: 5
}
}
metricsStore: SharedStores.MetricsStore {
readonly property var d: QtObject {
id: d
property bool isCentralizedMetricsEnabled
}
function toggleCentralizedMetrics(enabled) {
d.isCentralizedMetricsEnabled = enabled
}
function addCentralizedMetricIfEnabled(eventName, eventValue = null) {}
readonly property bool isCentralizedMetricsEnabled : d.isCentralizedMetricsEnabled
}
privacyStore: ProfileStores.PrivacyStore {
readonly property var words: ["apple", "banana", "cat", "cow", "catalog", "catch", "category", "cattle", "dog", "elephant", "fish", "grape"]
function getMnemonic() {
return words.join(" ")
}
function mnemonicWasShown() {
console.warn("!!! MNEMONIC SHOWN")
logs.logEvent("mnemonicWasShown")
}
}
splashScreenDurationMs: 3000
QtObject {
id: localAppSettings
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)
console.warn("!!! RESTARTING FLOW")
restartFlow()
ctrlKeycardState.currentIndex = 0
}
onKeycardFactoryResetRequested: {
logs.logEvent("onKeycardFactoryResetRequested")
console.warn("!!! FACTORY RESET; RESTARTING FLOW")
restartFlow()
ctrlKeycardState.currentIndex = 0
}
onKeycardReloaded: {
logs.logEvent("onKeycardReloaded")
console.warn("!!! RELOAD KEYCARD")
ctrlKeycardState.currentIndex = 0
}
}
Connections {
target: Global
function onOpenLink(link: string) {
console.debug("Opening link in an external web browser:", link)
Qt.openUrlExternally(link)
}
function onOpenLinkWithConfirmation(link: string, domain: string) {
console.debug("Opening link in an external web browser:", link, domain)
Qt.openUrlExternally(link)
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 150
SplitView.preferredHeight: 150
logsView.logText: logs.logText
RowLayout {
anchors.fill: parent
ColumnLayout {
Layout.fillWidth: true
Label {
text: "Current page: %1".arg(onboarding.stack.currentItem ? onboarding.stack.currentItem.title : "")
}
Label {
text: `Current path: ${onboarding.primaryPath} -> ${onboarding.secondaryPath}`
}
Label {
text: "Stack depth: %1".arg(onboarding.stack.depth)
}
}
Item { Layout.fillWidth: true }
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Button {
text: "Restart"
focusPolicy: Qt.NoFocus
onClicked: onboarding.restartFlow()
}
Button {
text: "Copy password"
focusPolicy: Qt.NoFocus
onClicked: ClipboardUtils.setText("0123456789")
}
Button {
text: "Copy seedphrase"
focusPolicy: Qt.NoFocus
onClicked: ClipboardUtils.setText("dog dog dog dog dog dog dog dog dog dog dog dog")
}
Button {
text: "Copy PIN (\"%1\")".arg(ctrlPin.text)
focusPolicy: Qt.NoFocus
enabled: ctrlPin.acceptableInput
onClicked: ClipboardUtils.setText(ctrlPin.text)
}
}
RowLayout {
Label {
text: "Keycard PIN:"
}
TextField {
id: ctrlPin
text: "111111"
inputMask: "999999"
}
Label {
text: "State:"
}
ComboBox {
Layout.preferredWidth: 250
id: ctrlKeycardState
focusPolicy: Qt.NoFocus
model: keycardMock.keycardStates
}
}
}
}
}
}
// category: Onboarding
// status: good
// https://www.figma.com/design/Lw4nPYQcZOPOwTgETiiIYo/Desktop-Onboarding-Redesign?node-id=1-25&node-type=canvas&m=dev

View File

@ -0,0 +1,41 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Theme 0.1
Item {
id: root
ColumnLayout {
anchors.centerIn: parent
spacing: 16
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
text: "ENTER NUMERIC PIN, EXPECTED LENGTH: %1".arg(pinInput.pinLen)
}
StatusPinInput {
Layout.alignment: Qt.AlignHCenter
id: pinInput
validator: StatusIntValidator { bottom: 0; top: 999999 }
Component.onCompleted: {
statesInitialization()
forceFocus()
}
}
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
text: "ENTERED PIN: %1".arg(pinInput.pinInput || "[empty]")
}
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
text: "VALID: %1".arg(pinInput.valid ? "true" : "false")
}
}
}
// category: Controls
// status: good

View File

@ -1,28 +1,3 @@
import QtQuick 2.15 import QtQml 2.15
QtObject { QtObject {}
property QtObject privacyModule: QtObject {
signal passwordChanged(success: bool, errorMsg: string)
signal storeToKeychainError(errorDescription: string)
signal storeToKeychainSuccess()
}
function tryStoreToKeyChain(errorDescription) {
if (generateMacKeyChainStoreError.checked) {
privacyModule.storeToKeychainError(errorDescription)
} else {
passwordView.localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.store
privacyModule.storeToKeychainSuccess()
privacyModule.passwordChanged(true, "")
}
}
function tryRemoveFromKeyChain() {
if (generateMacKeyChainStoreError.checked) {
privacyModule.storeToKeychainError("Error removing from keychain")
} else {
passwordView.localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.notNow
privacyModule.storeToKeychainSuccess()
}
}
}

View File

@ -5,7 +5,7 @@ ListModel {
Component.onCompleted: { Component.onCompleted: {
var englishWords = [ var englishWords = [
"apple", "banana", "cat", "cow", "catalog", "catch", "category", "cattle", "dog", "elephant", "fish", "grape", "horse", "ice cream", "jellyfish", "age", "agent", "apple", "banana", "cat", "cow", "catalog", "catch", "category", "cattle", "dog", "elephant", "fish", "grape", "horse", "icecream", "jellyfish",
"kiwi", "lemon", "mango", "nut", "orange", "pear", "quail", "rabbit", "strawberry", "turtle", "kiwi", "lemon", "mango", "nut", "orange", "pear", "quail", "rabbit", "strawberry", "turtle",
"umbrella", "violet", "watermelon", "xylophone", "yogurt", "zebra" "umbrella", "violet", "watermelon", "xylophone", "yogurt", "zebra"
// Add more English words here... // Add more English words here...

View File

@ -0,0 +1,3 @@
import QtQml 2.15
QtObject {}

View File

@ -8,3 +8,4 @@ PermissionsStore 1.0 PermissionsStore.qml
ProfileStore 1.0 ProfileStore.qml ProfileStore 1.0 ProfileStore.qml
RootStore 1.0 RootStore.qml RootStore 1.0 RootStore.qml
UtilsStore 1.0 UtilsStore.qml UtilsStore 1.0 UtilsStore.qml
MetricsStore 1.0 MetricsStore.qml

View File

@ -1,5 +1,4 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Window 2.15
/*! /*!
\qmltype StatusImage \qmltype StatusImage

View File

@ -53,6 +53,7 @@ Loader {
objectName: "statusRoundImage" objectName: "statusRoundImage"
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: asset.bgRadius
image.source: root.asset.isImage ? root.asset.name : "" image.source: root.asset.isImage ? root.asset.name : ""
showLoadingIndicator: true showLoadingIndicator: true
border.width: root.asset.imgIsIdenticon ? 1 : 0 border.width: root.asset.imgIsIdenticon ? 1 : 0

View File

@ -11,6 +11,7 @@ ItemDelegate {
property bool centerTextHorizontally: false property bool centerTextHorizontally: false
property int radius: 0 property int radius: 0
property int cursorShape: Qt.PointingHandCursor property int cursorShape: Qt.PointingHandCursor
property color highlightColor: Theme.palette.statusMenu.hoverBackgroundColor
padding: 8 padding: 8
spacing: 8 spacing: 8
@ -19,7 +20,7 @@ ItemDelegate {
icon.height: 16 icon.height: 16
font.family: Theme.baseFont.name font.family: Theme.baseFont.name
font.pixelSize: 15 font.pixelSize: Theme.primaryTextFontSize
contentItem: RowLayout { contentItem: RowLayout {
spacing: root.spacing spacing: root.spacing
@ -40,7 +41,7 @@ ItemDelegate {
text: root.text text: root.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1 color: root.highlighted ? Theme.palette.white : root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
Binding on horizontalAlignment { Binding on horizontalAlignment {
when: root.centerTextHorizontally when: root.centerTextHorizontally
@ -50,16 +51,11 @@ ItemDelegate {
} }
background: Rectangle { background: Rectangle {
color: root.highlighted color: root.highlighted ? root.highlightColor : "transparent"
? Theme.palette.statusMenu.hoverBackgroundColor
: "transparent"
radius: root.radius radius: root.radius
}
MouseArea { HoverHandler {
anchors.fill: parent cursorShape: root.cursorShape
cursorShape: root.cursorShape
acceptedButtons: Qt.NoButton
}
} }
} }

View File

@ -74,7 +74,7 @@ StatusProgressBar {
Default value: "So-so" Default value: "So-so"
*/ */
property string labelSoso: qsTr("So-so") property string labelSoso: qsTr("Okay")
/*! /*!
\qmlproperty string StatusPasswordStrengthIndicator::labelGood \qmlproperty string StatusPasswordStrengthIndicator::labelGood
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Good. This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Good.
@ -88,7 +88,7 @@ StatusProgressBar {
Default value: "Great" Default value: "Great"
*/ */
property string labelGreat: qsTr("Great") property string labelGreat: qsTr("Very strong")
enum Strength { enum Strength {
None, // 0 None, // 0

View File

@ -42,7 +42,7 @@ Item {
property alias pinInput: inputText.text property alias pinInput: inputText.text
/*! /*!
\qmlproperty Validator StatusPinInput::validator \qmlproperty StatusValidator StatusPinInput::validator
This property allows you to set a validator on the StatusPinInput. When a validator is set the StatusPinInput will only accept This property allows you to set a validator on the StatusPinInput. When a validator is set the StatusPinInput will only accept
input which leaves the pinInput property in an acceptable state. input which leaves the pinInput property in an acceptable state.
@ -59,6 +59,13 @@ Item {
*/ */
property alias validator: d.statusValidator property alias validator: d.statusValidator
/*!
\qmlproperty bool StatusPinInput::pinInput
This property holds whether the entered PIN is valid; PIN is considered valid when it passes the internal validator
and its length matches that of @p pinLen
*/
readonly property bool valid: inputText.acceptableInput && inputText.length === pinLen
/*! /*!
\qmlproperty int StatusPinInput::pinLen \qmlproperty int StatusPinInput::pinLen
This property allows you to set a specific pin input length. The default value is 6. This property allows you to set a specific pin input length. The default value is 6.
@ -169,6 +176,23 @@ Item {
} }
} }
/*
\qmlmethod StatusPinInput::clearPin()
Sets the pin input to an empty string, setting state of each digit to "EMPTY", and stops the blinking animation
Doesn't change the current `pinLen`.
*/
function clearPin() {
inputText.text = ""
d.currentPinIndex = 0
d.deactivateBlink()
for (var i = 0; i < root.pinLen; i++) {
const currItem = repeater.itemAt(i)
currItem.innerState = "EMPTY"
}
}
implicitWidth: childrenRect.width implicitWidth: childrenRect.width
implicitHeight: childrenRect.height implicitHeight: childrenRect.height

View File

@ -75,6 +75,9 @@ Item {
input text. input text.
*/ */
property ListModel filteredList: ListModel { } property ListModel filteredList: ListModel { }
readonly property bool suggestionsOpened: suggListContainer.opened
/*! /*!
\qmlsignal doneInsertingWord \qmlsignal doneInsertingWord
This signal is emitted when the user selects a word from the suggestions list This signal is emitted when the user selects a word from the suggestions list
@ -117,9 +120,10 @@ Item {
Component { Component {
id: seedInputLeftComponent id: seedInputLeftComponent
StatusBaseText { StatusBaseText {
leftPadding: 4 leftPadding: text.length == 1 ? 10 : 6
rightPadding: 6 rightPadding: 4
text: root.leftComponentText text: root.leftComponentText
font.family: Theme.monoFont.name
color: seedWordInput.input.edit.activeFocus ? color: seedWordInput.input.edit.activeFocus ?
Theme.palette.primaryColor1 : Theme.palette.baseColor1 Theme.palette.primaryColor1 : Theme.palette.baseColor1
} }
@ -197,7 +201,7 @@ Item {
id: suggListContainer id: suggListContainer
contentWidth: seedSuggestionsList.width contentWidth: seedSuggestionsList.width
contentHeight: ((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34 contentHeight: ((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34
x: 16 x: 0
y: seedWordInput.height + 4 y: seedWordInput.height + 4
topPadding: 8 topPadding: 8
bottomPadding: 8 bottomPadding: 8

View File

@ -1,4 +1,4 @@
import QtQuick 2.14 import QtQuick 2.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1

View File

@ -86,5 +86,8 @@ QtObject {
'lightDesktopBlue10': '#ECEFFB', 'lightDesktopBlue10': '#ECEFFB',
'darkDesktopBlue10': '#273251', 'darkDesktopBlue10': '#273251',
// new/mobile colors
'neutral-95': '#0D1625'
} }
} }

View File

@ -279,4 +279,20 @@ QtObject {
function stripHttpsAndwwwFromUrl(text) { function stripHttpsAndwwwFromUrl(text) {
return text.replace(/http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?(\/)/gim, '') return text.replace(/http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?(\/)/gim, '')
} }
/**
- given a contiguous array of non repeating numbers from [0..totalCount-1]
- @return an array of @p n random numbers, sorted in ascending order
Example:
const arr = [0, 1, 2, 3, 4, 5]
const indexes = nSamples(3, 6) // pick 3 random numbers from an array of 6 elements [0..5]
console.log(indexes) -> Array[0, 4, 5] // example output
*/
function nSamples(n, totalCount) {
let set = new Set()
while (set.size < n) {
set.add(~~(Math.random() * totalCount))
}
return [...set].sort((a, b) => a - b)
}
} }

View File

@ -0,0 +1,24 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import StatusQ.Popups.Dialog 0.1
StatusDialog {
width: 600
padding: 0
standardButtons: Dialog.Ok
property alias content: contentText
StatusScrollView {
id: scrollView
anchors.fill: parent
contentWidth: availableWidth
StatusBaseText {
id: contentText
width: scrollView.availableWidth
wrapMode: Text.Wrap
}
}
}

View File

@ -14,5 +14,6 @@ StatusModalDivider 0.1 StatusModalDivider.qml
StatusSearchLocationMenu 0.1 StatusSearchLocationMenu.qml StatusSearchLocationMenu 0.1 StatusSearchLocationMenu.qml
StatusSearchPopup 0.1 StatusSearchPopup.qml StatusSearchPopup 0.1 StatusSearchPopup.qml
StatusSearchPopupMenuItem 0.1 StatusSearchPopupMenuItem.qml StatusSearchPopupMenuItem 0.1 StatusSearchPopupMenuItem.qml
StatusSimpleTextPopup 0.1 StatusSimpleTextPopup.qml
StatusStackModal 0.1 StatusStackModal.qml StatusStackModal 0.1 StatusStackModal.qml
StatusSuccessAction 0.1 StatusSuccessAction.qml StatusSuccessAction 0.1 StatusSuccessAction.qml

View File

@ -8339,6 +8339,23 @@
<file>assets/png/onboarding/profile_fetching_in_progress.png</file> <file>assets/png/onboarding/profile_fetching_in_progress.png</file>
<file>assets/png/onboarding/seed-phrase.png</file> <file>assets/png/onboarding/seed-phrase.png</file>
<file>assets/png/onboarding/welcome.png</file> <file>assets/png/onboarding/welcome.png</file>
<file>assets/png/onboarding/status_totebag_artwork_1.png</file>
<file>assets/png/onboarding/status_generate_keys.png</file>
<file>assets/png/onboarding/status_generate_keycard.png</file>
<file>assets/png/onboarding/create_profile_seed.png</file>
<file>assets/png/onboarding/create_profile_keycard.png</file>
<file>assets/png/onboarding/status_chat.png</file>
<file>assets/png/onboarding/status_key.png</file>
<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/enable_biometrics.png</file>
<file>assets/png/onboarding/keycard/empty.png</file>
<file>assets/png/onboarding/keycard/insert.png</file>
<file>assets/png/onboarding/keycard/invalid.png</file>
<file>assets/png/onboarding/keycard/reading.png</file>
<file>assets/png/onboarding/keycard/error.png</file>
<file>assets/png/onboarding/keycard/success.png</file>
<file>assets/png/onRampProviders/latamex.png</file> <file>assets/png/onRampProviders/latamex.png</file>
<file>assets/png/onRampProviders/mercuryo.png</file> <file>assets/png/onRampProviders/mercuryo.png</file>
<file>assets/png/onRampProviders/moonPay.png</file> <file>assets/png/onRampProviders/moonPay.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

After

Width:  |  Height:  |  Size: 776 KiB

View File

@ -245,6 +245,7 @@
<file>StatusQ/Popups/StatusSearchLocationMenu.qml</file> <file>StatusQ/Popups/StatusSearchLocationMenu.qml</file>
<file>StatusQ/Popups/StatusSearchPopup.qml</file> <file>StatusQ/Popups/StatusSearchPopup.qml</file>
<file>StatusQ/Popups/StatusSearchPopupMenuItem.qml</file> <file>StatusQ/Popups/StatusSearchPopupMenuItem.qml</file>
<file>StatusQ/Popups/StatusSimpleTextPopup.qml</file>
<file>StatusQ/Popups/StatusStackModal.qml</file> <file>StatusQ/Popups/StatusStackModal.qml</file>
<file>StatusQ/Popups/StatusSuccessAction.qml</file> <file>StatusQ/Popups/StatusSuccessAction.qml</file>
<file>StatusQ/Popups/qmldir</file> <file>StatusQ/Popups/qmldir</file>

View File

@ -29,10 +29,10 @@ Item {
onWrongSeedPhraseChanged: { onWrongSeedPhraseChanged: {
if (wrongSeedPhrase) { if (wrongSeedPhrase) {
if (root.startupStore.startupModuleInst.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) { if (root.startupStore.startupModuleInst.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) {
seedPhraseView.setWrongSeedPhraseMessage(qsTr("Profile key pair for the inserted seed phrase is already set up")) seedPhraseView.setWrongSeedPhraseMessage(qsTr("Profile key pair for the inserted recovery phrase is already set up"))
return return
} }
seedPhraseView.setWrongSeedPhraseMessage(qsTr("Seed phrase doesnt match the profile of an existing Keycard user on this device")) seedPhraseView.setWrongSeedPhraseMessage(qsTr("Recovery phrase doesnt match the profile of an existing Keycard user on this device"))
} }
else { else {
seedPhraseView.setWrongSeedPhraseMessage("") seedPhraseView.setWrongSeedPhraseMessage("")
@ -52,7 +52,7 @@ Item {
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Enter seed phrase") text: qsTr("Enter recovery phrase")
} }
EnterSeedPhrase { EnterSeedPhrase {

View File

@ -0,0 +1,478 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import Qt.labs.settings 1.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Backpressure 0.1
import StatusQ.Popups 0.1
import AppLayouts.Onboarding2.pages 1.0
import AppLayouts.Profile.stores 1.0 as ProfileStores
import shared.panels 1.0
import shared.stores 1.0 as SharedStores
import utils 1.0
// compat
import AppLayouts.Onboarding.stores 1.0 as OOBS
Page {
id: root
property OOBS.StartupStore startupStore: OOBS.StartupStore {} // TODO replace with a new OnboardingStore, with just the needed props/functions?
required property SharedStores.MetricsStore metricsStore // TODO externalize the centralized metrics handling too?
required property ProfileStores.PrivacyStore privacyStore
property int splashScreenDurationMs: 30000
readonly property alias stack: stack
readonly property alias primaryPath: d.primaryPath
readonly property alias secondaryPath: d.secondaryPath
signal finished(bool success, int primaryPath, int secondaryPath)
signal keycardFactoryResetRequested() // TODO integrate/switch to an external flow
signal keycardReloaded()
function restartFlow() {
stack.clear()
stack.push(welcomePage)
d.resetState()
d.settings.reset()
}
QtObject {
id: d
// logic
property int primaryPath: OnboardingLayout.PrimaryPath.Unknown
property int secondaryPath: OnboardingLayout.SecondaryPath.Unknown
readonly property string currentKeycardState: root.startupStore.currentStartupState.stateType
readonly property var seedWords: root.privacyStore.getMnemonic().split(" ")
readonly property int numWordsToVerify: 4
// UI
readonly property int opacityDuration: 50
readonly property int swipeDuration: 400
// state collected
property string password
property bool enableBiometrics
property string keycardPin
function resetState() {
d.primaryPath = OnboardingLayout.PrimaryPath.Unknown
d.secondaryPath = OnboardingLayout.SecondaryPath.Unknown
d.password = ""
d.keycardPin = ""
d.enableBiometrics = false
d.settings.seedphraseRevealed = false
}
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
}
}
}
enum PrimaryPath {
Unknown,
CreateProfile,
Login
}
enum SecondaryPath {
Unknown,
CreateProfileWithPassword,
CreateProfileWithSeedphrase,
CreateProfileWithKeycard,
CreateProfileWithKeycardNewSeedphrase,
CreateProfileWithKeycardExistingSeedphrase
// TODO secondary Login paths
}
// page stack
StackView {
id: stack
anchors.fill: parent
initialItem: welcomePage
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 }
}
}
pushExit: Transition {
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 50; 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 }
}
}
popExit: pushExit
replaceEnter: pushEnter
replaceExit: pushExit
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton
enabled: stack.depth > 1 && !stack.busy
cursorShape: undefined // fall thru
onClicked: stack.pop()
}
// back button
StatusButton {
objectName: "onboardingBackButton"
isRoundIcon: true
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()
}
// main signal handler
Connections {
target: stack.currentItem
ignoreUnknownSignals: true
// common popups
function onPrivacyPolicyRequested() {
console.warn("!!! AUX: PRIVACY POLICY")
privacyPolicyPopup.createObject(root).open()
}
function onTermsOfUseRequested() {
console.warn("!!! AUX: TERMS OF USE")
termsOfUsePopup.createObject(root).open()
}
function onOpenLink(link: string) {
Global.openLink(link)
}
function onOpenLinkWithConfirmation(link: string, domain: string) {
Global.openLinkWithConfirmation(link, domain)
}
// welcome page
function onCreateProfileRequested() {
console.warn("!!! PRIMARY: CREATE PROFILE")
d.primaryPath = OnboardingLayout.PrimaryPath.CreateProfile
stack.push(helpUsImproveStatusPage)
}
function onLoginRequested() {
console.warn("!!! PRIMARY: LOG IN")
d.primaryPath = OnboardingLayout.PrimaryPath.Login
}
// help us improve page
function onShareUsageDataRequested(enabled: bool) {
console.warn("!!! SHARE USAGE DATA:", enabled)
metricsStore.toggleCentralizedMetrics(enabled)
Global.addCentralizedMetricIfEnabled("usage_data_shared", {placement: Constants.metricsEnablePlacement.onboarding})
localAppSettings.metricsPopupSeen = true
if (d.primaryPath === OnboardingLayout.PrimaryPath.CreateProfile)
stack.push(createProfilePage)
else if (d.primaryPath === OnboardingLayout.PrimaryPath.Login)
; // TODO Login path
}
// create profile page
function onCreateProfileWithPasswordRequested() {
console.warn("!!! SECONDARY: CREATE PROFILE WITH PASSWORD")
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithPassword
stack.push(createPasswordPage)
}
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")})
}
function onCreateProfileWithEmptyKeycardRequested() {
console.warn("!!! SECONDARY: CREATE PROFILE WITH KEYCARD")
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycard
stack.push(keycardIntroPage)
}
// create password page
function onSetPasswordRequested(password: string) {
console.warn("!!! SET PASSWORD REQUESTED")
d.password = password
stack.clear()
stack.push(enableBiometricsPage, {subtitle: qsTr("Use biometrics to fill in your password?")}) // FIXME make optional on unsupported platforms
}
// seedphrase page
function onSeedphraseValidated() {
console.warn("!!! SEEDPHRASE VALIDATED")
if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithSeedphrase) {
console.warn("!!! AFTER SEEDPHRASE -> PASSWORD PAGE")
stack.push(createPasswordPage)
} else if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithKeycardExistingSeedphrase) {
console.warn("!!! AFTER SEEDPHRASE -> KEYCARD PIN PAGE")
if (root.startupStore.getPin() !== "")
stack.push(keycardEnterPinPage)
else
stack.push(keycardCreatePinPage)
}
}
// keycard pages
function onReloadKeycardRequested() {
console.warn("!!! RELOAD KEYCARD REQUESTED")
root.keycardReloaded()
stack.replace(keycardIntroPage)
}
function onKeycardFactoryResetRequested() {
console.warn("!!! KEYCARD FACTORY RESET REQUESTED")
root.keycardFactoryResetRequested()
}
function onLoginWithKeycardRequested() {
console.warn("!!! LOGIN WITH KEYCARD REQUESTED")
stack.push(keycardEnterPinPage)
}
function onEmptyKeycardDetected() {
console.warn("!!! EMPTY KEYCARD DETECTED")
stack.replace(createKeycardProfilePage) // NB: replacing the keycardIntroPage
}
function onCreateKeycardProfileWithNewSeedphrase() {
console.warn("!!! CREATE KEYCARD PROFILE WITH NEW SEEDPHRASE")
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycardNewSeedphrase
if (root.startupStore.getPin())
stack.push(keycardEnterPinPage)
else
stack.push(keycardCreatePinPage)
}
function onCreateKeycardProfileWithExistingSeedphrase() {
console.warn("!!! CREATE KEYCARD PROFILE WITH EXISTING SEEDPHRASE")
d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycardExistingSeedphrase
stack.push(seedphrasePage, { title: qsTr("Create profile on empty Keycard using a recovery phrase"), subtitle: qsTr("Enter your 12, 18 or 24 word recovery phrase")})
}
function onKeycardPinCreated(pin) {
console.warn("!!! KEYCARD PIN CREATED:", pin)
d.keycardPin = pin
Backpressure.debounce(root, 2000, function() {
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.")})
})()
}
function onKeycardPinEntered(pin) {
console.warn("!!! KEYCARD PIN ENTERED:", pin)
d.keycardPin = pin
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.")})
}
// backup seedphrase pages
function onBackupSeedphraseRequested() {
console.warn("!!! BACKUP SEED REQUESTED")
stack.push(backupSeedAcksPage)
}
function onBackupSeedphraseContinue() {
console.warn("!!! BACKUP SEED CONTINUE")
stack.push(backupSeedRevealPage)
}
function onBackupSeedphraseConfirmed() {
console.warn("!!! BACKUP SEED CONFIRMED")
d.settings.seedphraseRevealed = true
root.privacyStore.mnemonicWasShown()
stack.push(backupSeedVerifyPage)
}
function onBackupSeedphraseVerified() {
console.warn("!!! BACKUP SEED VERIFIED")
stack.push(backupSeedOutroPage)
}
function onBackupSeedphraseRemovalConfirmed() {
console.warn("!!! BACKUP SEED REMOVAL CONFIRMED")
root.privacyStore.removeMnemonic()
stack.replace(splashScreen, { runningProgressAnimation: true })
}
// enable biometrics page
function onEnableBiometricsRequested(enabled: bool) {
console.warn("!!! ENABLE BIOMETRICS:", enabled)
d.enableBiometrics = enabled
if (d.secondaryPath === OnboardingLayout.SecondaryPath.CreateProfileWithKeycardNewSeedphrase)
stack.push(backupSeedIntroPage)
else
stack.replace(splashScreen, { runningProgressAnimation: true })
}
}
// pages
Component {
id: welcomePage
WelcomePage {
StackView.onActivated: d.resetState()
}
}
Component {
id: helpUsImproveStatusPage
HelpUsImproveStatusPage {}
}
Component {
id: createProfilePage
CreateProfilePage {
StackView.onActivated: d.secondaryPath = OnboardingLayout.SecondaryPath.Unknown // reset when we get back here
}
}
Component {
id: createPasswordPage
CreatePasswordPage {
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
StackView.onRemoved: {
d.password = ""
}
}
}
Component {
id: enableBiometricsPage
EnableBiometricsPage {
StackView.onRemoved: d.enableBiometrics = false
}
}
Component {
id: splashScreen
DidYouKnowSplashScreen {
readonly property string title: "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)
}
}
}
Component {
id: seedphrasePage
SeedphrasePage {
isSeedPhraseValid: root.startupStore.validMnemonic
}
}
Component {
id: createKeycardProfilePage
CreateKeycardProfilePage {
StackView.onActivated: d.secondaryPath = OnboardingLayout.SecondaryPath.CreateProfileWithKeycard
}
}
Component {
id: keycardIntroPage
KeycardIntroPage {
keycardState: d.currentKeycardState
displayPromoBanner: !d.settings.keycardPromoShown
StackView.onActivated: {
// 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()
}
}
}
Component {
id: keycardCreatePinPage
KeycardCreatePinPage {}
}
Component {
id: keycardEnterPinPage
KeycardEnterPinPage {
existingPin: root.startupStore.getPin()
remainingAttempts: root.startupStore.startupModuleInst.remainingAttempts
}
}
Component {
id: backupSeedIntroPage
BackupSeedphraseIntro {}
}
Component {
id: backupSeedAcksPage
BackupSeedphraseAcks {}
}
Component {
id: backupSeedRevealPage
BackupSeedphraseReveal {
seedphraseRevealed: d.settings.seedphraseRevealed
seedWords: d.seedWords
}
}
Component {
id: backupSeedVerifyPage
BackupSeedphraseVerify {
seedWordsToVerify: {
let result = []
const randomIndexes = SQUtils.Utils.nSamples(d.numWordsToVerify, d.seedWords.length)
for (const i of randomIndexes) {
result.push({seedWordNumber: i+1, seedWord: d.seedWords[i]})
}
return result
}
}
}
Component {
id: backupSeedOutroPage
BackupSeedphraseOutro {}
}
// common popups
Component {
id: privacyPolicyPopup
StatusSimpleTextPopup {
title: qsTr("Status Software Privacy Policy")
content {
textFormat: Text.MarkdownText
text: SQUtils.StringUtils.readTextFile(Qt.resolvedUrl("../../../imports/assets/docs/privacy.mdwn"))
}
destroyOnClose: true
}
}
Component {
id: termsOfUsePopup
StatusSimpleTextPopup {
title: qsTr("Status Software Terms of Use")
content {
textFormat: Text.MarkdownText
text: SQUtils.StringUtils.readTextFile(Qt.resolvedUrl("../../../imports/assets/docs/terms-of-use.mdwn"))
}
destroyOnClose: true
}
}
}

View File

@ -0,0 +1,110 @@
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
Control {
id: root
// [{primary:string, secondary:string, image:string}]
required property var newsModel
background: Rectangle {
color: StatusColors.colors["neutral-95"]
radius: 20
}
contentItem: Item {
id: newsPage
readonly property string primaryText: root.newsModel.get(pageIndicator.currentIndex).primary
readonly property string secondaryText: root.newsModel.get(pageIndicator.currentIndex).secondary
Image {
readonly property int size: Math.min(parent.width / 3 * 2, parent.height / 2, 370)
anchors.centerIn: parent
width: size
height: size
source: Theme.png(root.newsModel.get(pageIndicator.currentIndex).image)
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 48 - root.padding
width: Math.min(300, parent.width)
spacing: 4
StatusBaseText {
Layout.fillWidth: true
text: newsPage.primaryText
horizontalAlignment: Text.AlignHCenter
font.weight: Font.DemiBold
color: Theme.palette.white
}
StatusBaseText {
Layout.fillWidth: true
text: newsPage.secondaryText
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.additionalTextSize
color: Theme.palette.white
wrapMode: Text.WordWrap
}
PageIndicator {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Theme.halfPadding
id: pageIndicator
interactive: true
count: root.newsModel.count
currentIndex: -1
Component.onCompleted: currentIndex = 0 // start switching pages
function switchToNextOrFirstPage() {
if (currentIndex < count - 1)
currentIndex++
else
currentIndex = 0
}
delegate: Control {
id: pageIndicatorDelegate
implicitWidth: 44
implicitHeight: 8
readonly property bool isCurrentPage: index === pageIndicator.currentIndex
background: Rectangle {
color: Qt.rgba(1, 1, 1, 0.1)
radius: 4
HoverHandler {
cursorShape: hovered ? Qt.PointingHandCursor : undefined
}
}
contentItem: Item {
Rectangle {
NumberAnimation on width {
from: 0
to: pageIndicatorDelegate.availableWidth
duration: 2000
running: pageIndicatorDelegate.isCurrentPage
onStopped: {
if (pageIndicatorDelegate.isCurrentPage)
pageIndicator.switchToNextOrFirstPage()
}
}
height: parent.height
color: pageIndicatorDelegate.isCurrentPage ? Theme.palette.white : "transparent"
radius: 4
}
}
}
}
}
}
}

View File

@ -0,0 +1,131 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import SortFilterProxyModel 0.2
StatusTextField {
id: root
required property bool valid
required property var seedSuggestions // [{seedWord:string}, ...]
placeholderText: qsTr("Enter word")
leftPadding: Theme.padding
rightPadding: Theme.padding + rightIcon.width + spacing
topPadding: Theme.smallPadding
bottomPadding: Theme.smallPadding
background: Rectangle {
radius: Theme.radius
color: d.isEmpty ? Theme.palette.baseColor2 : root.valid ? Theme.palette.successColor2 : Theme.palette.dangerColor3
border.width: 1
border.color: {
if (d.isEmpty)
return Theme.palette.primaryColor1
if (root.valid)
return Theme.palette.successColor3
return Theme.palette.dangerColor2
}
}
QtObject {
id: d
readonly property int delegateHeight: 33
readonly property bool isEmpty: root.text === ""
}
Keys.onPressed: (event) => {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter: {
if (!!text && 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
}
}
}
StatusDropdown {
x: 0
y: parent.height + 4
width: parent.width
contentHeight: ((suggestionsList.count <= 5) ? suggestionsList.count : 5) * d.delegateHeight
visible: filteredModel.count > 0 && root.cursorVisible && !root.valid
verticalPadding: Theme.halfPadding
horizontalPadding: 0
contentItem: StatusListView {
id: suggestionsList
currentIndex: 0
model: SortFilterProxyModel {
id: filteredModel
sourceModel: root.seedSuggestions
filters: SQUtils.SearchFilter {
searchPhrase: root.text
}
sorters: StringSorter {
roleName: "seedWord"
}
}
delegate: StatusItemDelegate {
width: ListView.view.width
height: d.delegateHeight
text: model.seedWord
font.pixelSize: Theme.additionalTextSize
highlightColor: Theme.palette.primaryColor1
highlighted: hovered || index === suggestionsList.currentIndex
onClicked: {
root.text = text
root.accepted()
}
}
onCountChanged: currentIndex = 0
}
}
StatusIcon {
id: rightIcon
width: 20
height: 20
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Theme.padding
visible: !d.isEmpty
icon: root.valid ? "checkmark-circle" : root.activeFocus ? "clear" : "warning"
color: root.valid ? Theme.palette.successColor1 :
root.activeFocus ? Theme.palette.directColor9 : Theme.palette.dangerColor1
HoverHandler {
id: hhandler
cursorShape: hovered ? Qt.PointingHandCursor : undefined
}
TapHandler {
enabled: rightIcon.icon === "clear"
onSingleTapped: root.clear()
}
StatusToolTip {
text: root.valid ? qsTr("Correct word") : root.activeFocus ? qsTr("Clear") : qsTr("Wrong word")
visible: hhandler.hovered && rightIcon.visible
}
}
}

View File

@ -0,0 +1,42 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
ColumnLayout {
id: root
required property int currentStep
required property int totalSteps
required property string caption
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Step %1 of %2").arg(root.currentStep).arg(root.totalSteps)
font.pixelSize: Theme.additionalTextSize
color: Theme.palette.baseColor1
horizontalAlignment: Text.AlignHCenter
}
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: 2
Repeater {
model: root.totalSteps
Rectangle {
width: 80
height: 4
radius: 2
color: index <= root.currentStep - 1 ? Theme.palette.primaryColor1 : Theme.palette.baseColor2
}
}
}
StatusBaseText {
Layout.fillWidth: true
text: root.caption
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}

View File

@ -0,0 +1,3 @@
NewsCarousel 1.0 NewsCarousel.qml
SeedphraseVerifyInput 1.0 SeedphraseVerifyInput.qml
StepIndicator 1.0 StepIndicator.qml

View File

@ -0,0 +1,26 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
StatusListItem {
radius: 20
asset.width: 32
asset.height: 32
asset.bgRadius: 0
asset.bgColor: "transparent"
asset.isImage: true
statusListItemTitle.font.pixelSize: Theme.additionalTextSize
statusListItemTitle.font.weight: Font.Medium
statusListItemSubTitle.font.pixelSize: Theme.additionalTextSize
components: [
StatusIcon {
icon: "next"
width: 16
height: 16
color: Theme.palette.baseColor1
}
]
}

View File

@ -0,0 +1,36 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Frame {
id: root
property bool dropShadow: true
property alias cornerRadius: background.radius
padding: Theme.bigPadding
background: Rectangle {
id: background
border.width: 1
border.color: Theme.palette.baseColor2
radius: 20
color: Theme.palette.background
}
layer.enabled: root.dropShadow
layer.effect: DropShadow {
verticalOffset: 4
radius: 7
samples: 15
cached: true
color: Theme.palette.name === Constants.darkThemeName ? Theme.palette.dropShadow
: Qt.rgba(0, 34/255, 51/255, 0.03)
}
}

View File

@ -0,0 +1,2 @@
OnboardingFrame 1.0 OnboardingFrame.qml
ListItemButton 1.0 ListItemButton.qml

View File

@ -0,0 +1,92 @@
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
OnboardingPage {
id: root
signal backupSeedphraseContinue()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(440, root.availableWidth)
spacing: Theme.xlPadding
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Backup your recovery phrase")
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Frame {
Layout.fillWidth: true
padding: 12
background: Rectangle {
color: Theme.palette.dangerColor3
radius: Theme.radius
}
contentItem: StatusBaseText {
text: qsTr("Store your recovery phrase in a secure location so you never lose access to your funds.")
color: Theme.palette.dangerColor1
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
lineHeightMode: Text.FixedHeight
lineHeight: 22
}
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
font.weight: Font.DemiBold
text: qsTr("Backup checklist:")
}
Frame {
Layout.fillWidth: true
Layout.topMargin: -20
padding: 20
background: Rectangle {
color: "transparent"
radius: 12
border.width: 1
border.color: Theme.palette.baseColor2
}
contentItem: ColumnLayout {
StatusCheckBox {
Layout.fillWidth: true
id: ack1
text: qsTr("I have a pen and paper")
}
StatusCheckBox {
Layout.fillWidth: true
id: ack2
text: qsTr("I am ready to write down my recovery phrase")
}
StatusCheckBox {
Layout.fillWidth: true
id: ack3
text: qsTr("I know where Ill store it")
}
StatusCheckBox {
Layout.fillWidth: true
id: ack4
text: qsTr("I know I can only reveal it once")
}
}
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Continue")
enabled: ack1.checked && ack2.checked && ack3.checked && ack4.checked
onClicked: root.backupSeedphraseContinue()
}
}
}
}

View File

@ -0,0 +1,52 @@
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
OnboardingPage {
id: root
signal backupSeedphraseRequested()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(400, root.availableWidth)
spacing: 20
StatusImage {
id: image
Layout.preferredWidth: 296
Layout.preferredHeight: 260
Layout.alignment: Qt.AlignHCenter
mipmap: true
source: Theme.png("onboarding/status_seedphrase")
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Backup your recovery phrase")
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
text: qsTr("Your recovery phrase is a 12 word passcode to your funds that cannot be recovered if lost. Write it down offline and store it somewhere secure.")
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Backup recovery phrase")
onClicked: root.backupSeedphraseRequested()
}
}
}
}

View File

@ -0,0 +1,63 @@
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 AppLayouts.Onboarding2.components 1.0
OnboardingPage {
id: root
signal backupSeedphraseRemovalConfirmed()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(440, root.availableWidth)
spacing: Theme.xlPadding
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Backup your recovery phrase")
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StepIndicator {
Layout.fillWidth: true
spacing: Theme.halfPadding
currentStep: 3
totalSteps: 3
caption: qsTr("Store your phrase offline")
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
text: qsTr("Ensure you have written down your recovery phrase and have a safe place to keep it. Remember, anyone who has your recovery phrase has access to your funds.")
}
Item { Layout.preferredHeight: 120 }
StatusCheckBox {
Layout.fillWidth: true
id: cbAck
text: qsTr("I understand my recovery phrase will now be removed and I will no longer be able to access it via Status")
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Continue")
enabled: cbAck.checked
onClicked: root.backupSeedphraseRemovalConfirmed()
}
}
}
}

View File

@ -0,0 +1,115 @@
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
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding2.components 1.0
OnboardingPage {
id: root
required property var seedWords
property bool seedphraseRevealed
signal backupSeedphraseConfirmed()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(440, root.availableWidth)
spacing: Theme.xlPadding
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Backup your recovery phrase")
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StepIndicator {
Layout.fillWidth: true
spacing: Theme.halfPadding
currentStep: 1
totalSteps: 3
caption: qsTr("Write down your 12-word recovery phrase to keep offline")
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: seedGrid.height
GridLayout {
id: seedGrid
width: parent.width
columns: 2
columnSpacing: Theme.halfPadding
rowSpacing: columnSpacing
Repeater {
model: root.seedWords
delegate: Frame {
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPadding: Theme.padding
verticalPadding: Theme.smallPadding
background: Rectangle {
radius: Theme.radius
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
}
contentItem: RowLayout {
spacing: Theme.smallPadding
StatusBaseText {
text: index + 1
color: Theme.palette.baseColor1
}
StatusBaseText {
Layout.fillWidth: true
text: modelData
}
}
}
}
layer.enabled: !root.seedphraseRevealed
layer.effect: GaussianBlur {
radius: 16
samples: 33
transparentBorder: true
}
}
StatusButton {
anchors.centerIn: parent
text: qsTr("Reveal recovery phrase")
icon.name: "show"
type: StatusBaseButton.Type.Primary
visible: !root.seedphraseRevealed
onClicked: root.seedphraseRevealed = true
}
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Anyone who sees this will have access to your funds.")
color: Theme.palette.dangerColor1
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Confirm recovery phrase")
enabled: root.seedphraseRevealed
onClicked: root.backupSeedphraseConfirmed()
}
}
}
}

View File

@ -0,0 +1,108 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding2.components 1.0
import shared.stores 1.0
import SortFilterProxyModel 0.2
OnboardingPage {
id: root
required property var seedWordsToVerify // [{seedWordNumber:int, seedWord:string}, ...]
signal backupSeedphraseVerified()
QtObject {
id: d
readonly property var seedSuggestions: BIP39_en {} // [{seedWord:string}, ...]
}
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(440, root.availableWidth)
spacing: Theme.xlPadding
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Backup your recovery phrase")
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StepIndicator {
Layout.fillWidth: true
spacing: Theme.halfPadding
currentStep: 2
totalSteps: 3
caption: qsTr("Confirm the following words from your recovery phrase...")
}
ColumnLayout {
Layout.fillWidth: true
spacing: Theme.halfPadding
Repeater {
readonly property bool allValid: {
for (let i = 0; i < count; i++) {
if (!!itemAt(i) && !itemAt(i).valid)
return false
}
return true
}
id: seedRepeater
model: root.seedWordsToVerify
delegate: RowLayout {
id: seedWordDelegate
required property var modelData
required property int index
readonly property bool valid: seedInput.valid
readonly property alias input: seedInput
Layout.fillWidth: true
Layout.topMargin: Theme.halfPadding
Layout.bottomMargin: Theme.halfPadding
spacing: 12
StatusBaseText {
Layout.preferredWidth: 20
text: modelData.seedWordNumber
}
SeedphraseVerifyInput {
Layout.fillWidth: true
id: seedInput
valid: text === modelData.seedWord
seedSuggestions: d.seedSuggestions
Component.onCompleted: if (index === 0) forceActiveFocus()
onAccepted: {
const nextItem = seedRepeater.itemAt(index + 1) ?? seedRepeater.itemAt(0)
if (!!nextItem) {
nextItem.input.forceActiveFocus()
}
}
}
}
}
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Continue")
enabled: seedRepeater.allValid
onClicked: root.backupSeedphraseVerified()
}
}
}
}

View File

@ -0,0 +1,104 @@
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
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import AppLayouts.Onboarding2.controls 1.0
OnboardingPage {
id: root
title: qsTr("Create profile on empty Keycard")
signal createKeycardProfileWithNewSeedphrase()
signal createKeycardProfileWithExistingSeedphrase()
contentItem: Item {
ColumnLayout {
width: parent.width
anchors.centerIn: parent
StatusBaseText {
Layout.fillWidth: true
text: root.title
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("You will require your Keycard to log in to Status and sign transactions")
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
ColumnLayout {
Layout.maximumWidth: Math.min(380, root.availableWidth)
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 56
spacing: 20
OnboardingFrame {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 24
StatusImage {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Math.min(268, parent.width)
Layout.preferredHeight: Math.min(164, height)
source: Theme.png("onboarding/status_generate_keycard")
mipmap: true
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Use a new 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("To create your Keycard-stored profile ")
font.pixelSize: Theme.additionalTextSize
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.baseColor1
}
StatusButton {
Layout.fillWidth: true
text: qsTr("Lets go!")
font.pixelSize: Theme.additionalTextSize
onClicked: root.createKeycardProfileWithNewSeedphrase()
}
}
}
OnboardingFrame {
Layout.fillWidth: true
padding: 1
dropShadow: false
contentItem: ColumnLayout {
spacing: 0
ListItemButton {
Layout.fillWidth: true
title: qsTr("Use an existing recovery phrase")
subTitle: qsTr("To create your Keycard-stored profile ")
asset.name: Theme.png("onboarding/create_profile_seed")
onClicked: root.createKeycardProfileWithExistingSeedphrase()
}
}
}
}
}
}
}

View File

@ -0,0 +1,87 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import utils 1.0
import shared.views 1.0
OnboardingPage {
id: root
property var passwordStrengthScoreFunction: (password) => { console.error("passwordStrengthScoreFunction: IMPLEMENT ME") }
signal setPasswordRequested(string password)
title: qsTr("Create profile password")
QtObject {
id: d
function submit() {
if (!passView.ready)
return
root.setPasswordRequested(passView.newPswText)
}
}
Component.onCompleted: passView.forceNewPswInputFocus()
contentItem: Item {
ColumnLayout {
spacing: Theme.padding
anchors.centerIn: parent
width: Math.min(400, root.availableWidth)
PasswordView {
id: passView
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
highSizeIntro: true
title: root.title
introText: qsTr("This password cant be recovered")
recoverText: ""
passwordStrengthScoreFunction: root.passwordStrengthScoreFunction
onReturnPressed: d.submit()
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Confirm password")
enabled: passView.ready
onClicked: d.submit()
}
}
}
StatusButton {
width: 32
height: 32
icon.width: 20
icon.height: 20
icon.color: Theme.palette.directColor1
normalColor: Theme.palette.baseColor2
padding: 0
anchors.right: parent.right
anchors.top: parent.top
icon.name: "info"
onClicked: passwordDetailsPopup.createObject(root).open()
}
Component {
id: passwordDetailsPopup
StatusSimpleTextPopup {
title: qsTr("Create profile password")
width: 480
destroyOnClose: true
content.text: qsTr("Your Status keys are the foundation of your self-sovereign identity in Web3. You have complete control over these keys, which you can use to sign transactions, access your data, and interact with Web3 services.
Your keys are always securely stored on your device and protected by your Status profile password. Status doesn't know your password and can't reset it for you. If you forget your password, you may lose access to your Status profile and wallet funds.
Remember your password and don't share it with anyone.")
}
}
}

View File

@ -0,0 +1,117 @@
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
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import AppLayouts.Onboarding2.controls 1.0
import utils 1.0
OnboardingPage {
id: root
title: qsTr("Create your profile")
signal createProfileWithPasswordRequested()
signal createProfileWithSeedphraseRequested()
signal createProfileWithEmptyKeycardRequested()
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 start using 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_generate_keys")
mipmap: true
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Start fresh")
font.pixelSize: Theme.secondaryAdditionalTextSize
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: -Theme.padding
text: qsTr("Create a new profile from scratch")
font.pixelSize: Theme.additionalTextSize
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.baseColor1
}
StatusButton {
Layout.fillWidth: true
text: qsTr("Lets go!")
font.pixelSize: Theme.additionalTextSize
onClicked: root.createProfileWithPasswordRequested()
}
}
}
OnboardingFrame {
id: buttonFrame
Layout.fillWidth: true
padding: 1
dropShadow: false
contentItem: ColumnLayout {
spacing: 0
ListItemButton {
Layout.fillWidth: true
title: qsTr("Use a recovery phrase")
subTitle: qsTr("If you already have an Ethereum wallet")
asset.name: Theme.png("onboarding/create_profile_seed")
onClicked: root.createProfileWithSeedphraseRequested()
}
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("Use an empty Keycard")
subTitle: qsTr("Store your new profile keys on Keycard")
asset.name: Theme.png("onboarding/create_profile_keycard")
onClicked: root.createProfileWithEmptyKeycardRequested()
}
}
}
}
}
}

View File

@ -0,0 +1,65 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
OnboardingPage {
id: root
title: qsTr("Enable biometrics")
property string subtitle
signal enableBiometricsRequested(bool enable)
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
spacing: 20
width: Math.min(400, root.availableWidth)
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: root.subtitle
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusImage {
Layout.preferredWidth: 260
Layout.preferredHeight: 260
Layout.topMargin: 20
Layout.bottomMargin: 20
Layout.alignment: Qt.AlignHCenter
mipmap: true
source: Theme.png("onboarding/enable_biometrics")
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Yes, use biometrics")
onClicked: root.enableBiometricsRequested(true)
}
StatusFlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Maybe later")
onClicked: root.enableBiometricsRequested(false)
}
}
}
}

View File

@ -0,0 +1,163 @@
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 StatusQ.Popups.Dialog 0.1
import AppLayouts.Onboarding2.controls 1.0
import utils 1.0
OnboardingPage {
id: root
title: qsTr("Help us improve Status")
signal shareUsageDataRequested(bool enabled)
signal privacyPolicyRequested()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(320, root.availableWidth)
spacing: root.padding
StatusBaseText {
Layout.fillWidth: true
text: root.title
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Your usage data helps us make Status better")
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusImage {
Layout.preferredWidth: 300
Layout.preferredHeight: 300
Layout.topMargin: 36
Layout.bottomMargin: 36
Layout.alignment: Qt.AlignHCenter
mipmap: true
source: Theme.png("onboarding/status_totebag_artwork_1")
}
StatusButton {
Layout.fillWidth: true
text: qsTr("Share usage data")
onClicked: root.shareUsageDataRequested(true)
}
StatusButton {
Layout.fillWidth: true
text: qsTr("Not now")
normalColor: "transparent"
borderWidth: 1
borderColor: Theme.palette.baseColor2
onClicked: root.shareUsageDataRequested(false)
}
}
}
StatusButton {
width: 32
height: 32
icon.width: 20
icon.height: 20
icon.color: Theme.palette.directColor1
normalColor: Theme.palette.baseColor2
padding: 0
anchors.right: parent.right
anchors.top: parent.top
icon.name: "info"
onClicked: helpUsImproveDetails.createObject(root).open()
}
Component {
id: helpUsImproveDetails
StatusDialog {
title: qsTr("Help us improve Status")
width: 480
standardButtons: Dialog.Ok
padding: 20
destroyOnClose: true
contentItem: ColumnLayout {
spacing: 20
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Well collect anonymous analytics and diagnostics from your app to enhance Statuss quality and performance.")
wrapMode: Text.WordWrap
}
OnboardingFrame {
Layout.fillWidth: true
dropShadow: false
contentItem: ColumnLayout {
spacing: 12
BulletPoint {
text: qsTr("Gather basic usage data, like clicks and page views")
check: true
}
BulletPoint {
text: qsTr("Gather core diagnostics, like bandwidth usage")
check: true
}
BulletPoint {
text: qsTr("Never collect your profile information or wallet address")
}
BulletPoint {
text: qsTr("Never collect information you input or send")
}
BulletPoint {
text: qsTr("Never sell your usage analytics data")
}
}
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("For more details and other cases where we handle your data, refer to our %1.")
.arg(Utils.getStyledLink(qsTr("Privacy Policy"), "#privacy", hoveredLink, Theme.palette.primaryColor1, Theme.palette.primaryColor1, false))
color: Theme.palette.baseColor1
font.pixelSize: Theme.additionalTextSize
wrapMode: Text.WordWrap
textFormat: Text.RichText
onLinkActivated: {
if (link == "#privacy") {
close()
root.privacyPolicyRequested()
}
}
HoverHandler {
// Qt CSS doesn't support custom cursor shape
cursorShape: !!parent.hoveredLink ? Qt.PointingHandCursor : undefined
}
}
}
}
}
component BulletPoint: RowLayout {
property string text
property bool check
spacing: 6
StatusIcon {
Layout.preferredWidth: 20
Layout.preferredHeight: 20
icon: parent.check ? "check-circle" : "close-circle"
color: parent.check ? Theme.palette.successColor1 : Theme.palette.dangerColor1
}
StatusBaseText {
Layout.fillWidth: true
text: parent.text
font.pixelSize: Theme.additionalTextSize
}
}
}

View File

@ -0,0 +1,76 @@
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 StatusQ.Core.Utils 0.1 as SQUtils
import utils 1.0
OnboardingPage {
id: root
property string subtitle
property alias image: image
property alias infoText: infoText
property alias buttons: buttonsWrapper.children
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(400, root.availableWidth)
spacing: 20
StatusImage {
id: image
Layout.preferredWidth: 280
Layout.preferredHeight: 280
Layout.alignment: Qt.AlignHCenter
mipmap: true
}
StatusBaseText {
Layout.fillWidth: true
text: root.title
font.pixelSize: 22
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
text: root.subtitle
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
visible: !!text
}
StatusBaseText {
Layout.fillWidth: true
id: infoText
textFormat: Text.RichText
font.pixelSize: Theme.tertiaryTextFontSize
wrapMode: Text.WordWrap
color: Theme.palette.baseColor1
horizontalAlignment: Text.AlignHCenter
visible: !!text
onLinkActivated: openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link))
HoverHandler {
// Qt CSS doesn't support custom cursor shape
cursorShape: !!parent.hoveredLink ? Qt.PointingHandCursor : undefined
}
}
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 4
id: buttonsWrapper
spacing: 12
}
}
}
}

View File

@ -0,0 +1,123 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding2.controls 1.0
import utils 1.0
KeycardBasePage {
id: root
signal keycardPinCreated(string pin)
image.source: Theme.png("onboarding/keycard/reading")
QtObject {
id: d
property string pin
property string pin2
function setPins() {
if (pinInput.valid) {
if (root.state === "creating")
d.pin = pinInput.pinInput
else if (root.state === "repeating" || root.state === "mismatch")
d.pin2 = pinInput.pinInput
if (root.state === "mismatch")
pinInput.statesInitialization()
}
}
}
buttons: [
StatusPinInput {
id: pinInput
anchors.horizontalCenter: parent.horizontalCenter
validator: StatusIntValidator { bottom: 0; top: 999999 }
Component.onCompleted: {
statesInitialization()
forceFocus()
}
onPinInputChanged: {
Qt.callLater(d.setPins)
}
},
StatusBaseText {
id: errorText
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("PINs dont match")
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
visible: false
}
]
state: "creating"
states: [
State {
name: "creating"
PropertyChanges {
target: root
title: qsTr("Create new Keycard PIN")
}
},
State {
name: "mismatch"
extend: "repeating"
when: !!d.pin && !!d.pin2 && d.pin !== d.pin2
PropertyChanges {
target: errorText
visible: true
}
PropertyChanges {
target: root
image.source: Theme.png("onboarding/keycard/error")
}
},
State {
name: "success"
extend: "repeating"
when: !!d.pin && !!d.pin2 && d.pin === d.pin2
PropertyChanges {
target: root
title: qsTr("Keycard PIN set")
}
PropertyChanges {
target: pinInput
enabled: false
}
PropertyChanges {
target: root
image.source: Theme.png("onboarding/keycard/success")
}
StateChangeScript {
script: {
pinInput.setPin("123456") // set a fake PIN, doesn't matter at this point
root.keycardPinCreated(d.pin)
}
}
},
State {
name: "repeating"
when: d.pin !== ""
PropertyChanges {
target: root
title: qsTr("Repeat Keycard PIN")
}
StateChangeScript {
script: {
pinInput.statesInitialization()
}
}
}
]
}

View File

@ -0,0 +1,157 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding2.controls 1.0
import utils 1.0
KeycardBasePage {
id: root
required property string existingPin
required property int remainingAttempts
signal keycardPinEntered(string pin)
signal reloadKeycardRequested()
signal keycardFactoryResetRequested()
signal keycardLocked()
image.source: Theme.png("onboarding/keycard/reading")
QtObject {
id: d
property string tempPin
property int remainingAttempts: root.remainingAttempts
}
buttons: [
StatusPinInput {
id: pinInput
anchors.horizontalCenter: parent.horizontalCenter
validator: StatusIntValidator { bottom: 0; top: 999999 }
onPinInputChanged: {
if (pinInput.pinInput.length === pinInput.pinLen) {
d.tempPin = pinInput.pinInput
if (d.tempPin !== root.existingPin) {
pinInput.statesInitialization()
d.remainingAttempts--
}
}
}
},
StatusBaseText {
id: errorText
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("%n attempt(s) remaining", "", d.remainingAttempts)
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
visible: false
},
StatusButton {
id: btnFactoryReset
width: 320
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Theme.halfPadding
visible: false
text: qsTr("Factory reset Keycard")
onClicked: root.keycardFactoryResetRequested()
},
StatusButton {
id: btnReload
width: 320
anchors.horizontalCenter: parent.horizontalCenter
visible: false
text: qsTr("Ive inserted a Keycard")
normalColor: "transparent"
borderWidth: 1
borderColor: Theme.palette.baseColor2
onClicked: root.reloadKeycardRequested()
}
]
state: "entering"
states: [
State {
name: "locked"
when: d.remainingAttempts <= 0
PropertyChanges {
target: root
title: "<font color='%1'>".arg(Theme.palette.dangerColor1) + qsTr("Keycard locked") + "</font>"
}
PropertyChanges {
target: pinInput
enabled: false
}
PropertyChanges {
target: root
image.source: Theme.png("onboarding/keycard/error")
}
PropertyChanges {
target: btnFactoryReset
visible: true
}
PropertyChanges {
target: btnReload
visible: true
}
StateChangeScript {
script: {
pinInput.clearPin()
root.keycardLocked()
}
}
},
State {
name: "incorrect"
when: !!d.tempPin && d.tempPin !== root.existingPin
PropertyChanges {
target: root
title: qsTr("PIN incorrect")
}
PropertyChanges {
target: errorText
visible: true
}
},
State {
name: "success"
when: pinInput.pinInput === root.existingPin
PropertyChanges {
target: root
title: qsTr("PIN correct")
}
PropertyChanges {
target: pinInput
enabled: false
}
StateChangeScript {
script: {
root.keycardPinEntered(pinInput.pinInput)
}
}
},
State {
name: "entering"
PropertyChanges {
target: root
title: qsTr("Enter Keycard PIN")
}
StateChangeScript {
script: {
pinInput.statesInitialization()
pinInput.forceFocus()
d.tempPin = ""
d.remainingAttempts = root.remainingAttempts
}
}
}
]
}

View File

@ -0,0 +1,232 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding2.controls 1.0
import utils 1.0
KeycardBasePage {
id: root
required property string keycardState // Constants.startupState.keycardXXX
property bool displayPromoBanner
signal reloadKeycardRequested()
signal keycardFactoryResetRequested()
signal loginWithKeycardRequested()
signal emptyKeycardDetected()
OnboardingFrame {
id: promoBanner
visible: false
dropShadow: false
cornerRadius: 12
width: 600
leftPadding: 0
rightPadding: 20
topPadding: Theme.halfPadding
bottomPadding: 0
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.bigPadding
contentItem: RowLayout {
spacing: 0
StatusImage {
Layout.preferredWidth: 154
Layout.preferredHeight: 82
source: Theme.png("onboarding/status_keycard_multiple")
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: -promoBanner.topPadding
spacing: 2
StatusBaseText {
Layout.fillWidth: true
text: qsTr("New to Keycard?")
font.pixelSize: Theme.additionalTextSize
font.weight: Font.DemiBold
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Store and trade your crypto with a simple, secure and slim hardware wallet.")
wrapMode: Text.Wrap
font.pixelSize: Theme.additionalTextSize
color: Theme.palette.baseColor1
}
}
StatusButton {
Layout.leftMargin: 20
Layout.topMargin: -promoBanner.topPadding
size: StatusBaseButton.Size.Small
text: qsTr("keycard.tech")
icon.name: "external-link"
icon.width: 24
icon.height: 24
onClicked: openLink("https://keycard.tech/")
}
}
}
buttons: [
MaybeOutlineButton {
id: btnLogin
text: qsTr("Log in with this Keycard")
onClicked: root.loginWithKeycardRequested()
},
MaybeOutlineButton {
id: btnFactoryReset
text: qsTr("Factory reset Keycard")
onClicked: root.keycardFactoryResetRequested()
},
MaybeOutlineButton {
id: btnReload
text: qsTr("Ive inserted a Keycard")
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 {
name: "plugin"
when: root.keycardState === Constants.startupState.keycardPluginReader ||
root.keycardState === ""
PropertyChanges {
target: root
title: qsTr("Plug in your Keycard reader")
image.source: Theme.png("onboarding/keycard/empty")
}
PropertyChanges {
target: promoBanner
visible: root.displayPromoBanner
}
},
State {
name: "insert"
when: root.keycardState === Constants.startupState.keycardInsertKeycard
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))
image.source: Theme.png("onboarding/keycard/insert")
}
},
State {
name: "reading"
when: root.keycardState === Constants.startupState.keycardReadingKeycard ||
root.keycardState === Constants.startupState.keycardInsertedKeycard
PropertyChanges {
target: root
title: qsTr("Reading Keycard...")
image.source: Theme.png("onboarding/keycard/reading")
}
},
// error states
State {
name: "error"
PropertyChanges {
target: root
image.source: Theme.png("onboarding/keycard/error")
}
PropertyChanges {
target: btnFactoryReset
visible: true
}
PropertyChanges {
target: btnReload
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 isnt 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")
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 cant use it to store new keys right now")
}
PropertyChanges {
target: btnLogin
visible: true
}
},
// success/exit state
State {
name: "emptyDetected"
when: root.keycardState === Constants.startupState.keycardEmpty
StateChangeScript {
script: root.emptyKeycardDetected()
}
}
]
}

View File

@ -0,0 +1,18 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core.Theme 0.1
Page {
signal openLink(string link)
signal openLinkWithConfirmation(string link, string domain)
implicitWidth: 1200
implicitHeight: 700
padding: 12
background: Rectangle {
color: Theme.palette.background
}
}

View File

@ -0,0 +1,59 @@
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.panels 1.0
OnboardingPage {
id: root
property string subtitle
property var isSeedPhraseValid: (mnemonic) => { console.error("isSeedPhraseValid IMPLEMENT ME"); return false }
signal seedphraseValidated()
contentItem: Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.min(580, 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: root.subtitle
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
EnterSeedPhrase {
id: seedPanel
Layout.fillWidth: true
isSeedPhraseValid: root.isSeedPhraseValid
onSubmitSeedPhrase: root.seedphraseValidated()
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
enabled: seedPanel.seedPhraseIsValid
text: qsTr("Continue")
onClicked: root.seedphraseValidated()
}
}
}
}

View File

@ -0,0 +1,157 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.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 AppLayouts.Onboarding2.components 1.0
import utils 1.0
OnboardingPage {
id: root
title: qsTr("Welcome to Status")
signal createProfileRequested()
signal loginRequested()
signal privacyPolicyRequested()
signal termsOfUseRequested()
QtObject {
id: d
readonly property ListModel newsModel: ListModel {
ListElement {
primary: qsTr("Own, buy and swap your crypto")
secondary: qsTr("Use the leading multi-chain self-custodial wallet")
image: "onboarding/status_key"
}
ListElement {
primary: qsTr("Chat privately with friends")
secondary: qsTr("With full metadata privacy and e2e encryption")
image: "onboarding/status_chat"
}
ListElement {
primary: qsTr("Discover web3")
secondary: qsTr("Explore and interact with the decentralised web")
image: "onboarding/status_totebag_artwork_1"
}
ListElement {
primary: qsTr("Store your assets on Keycard")
secondary: qsTr("Be safe with secure cold wallet")
image: "onboarding/status_keycard"
}
}
}
contentItem: RowLayout {
spacing: root.padding
// left part (welcome + buttons)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: -headerText.height
ColumnLayout {
width: Math.min(400, parent.width)
spacing: 18
anchors.centerIn: parent
StatusImage {
Layout.preferredWidth: 90
Layout.preferredHeight: 90
Layout.alignment: Qt.AlignHCenter
source: Theme.png("status-logo-icon")
mipmap: true
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 4
radius: 12
samples: 25
spread: 0.2
color: Theme.palette.dropShadow
}
}
StatusBaseText {
id: headerText
Layout.fillWidth: true
text: root.title
font.pixelSize: 40
font.bold: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StatusBaseText {
Layout.fillWidth: true
text: qsTr("The open-source, decentralised wallet and messenger")
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 48 - root.padding
width: Math.min(320, parent.width)
spacing: 12
StatusButton {
Layout.fillWidth: true
text: qsTr("Create profile")
onClicked: root.createProfileRequested()
}
StatusButton {
Layout.fillWidth: true
text: qsTr("Log in")
onClicked: root.loginRequested()
normalColor: "transparent"
borderWidth: 1
borderColor: Theme.palette.baseColor2
}
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: Theme.halfPadding
text: qsTr("By proceeding you accept Status<br>%1 and %2")
.arg(Utils.getStyledLink(qsTr("Terms of Use"), "#terms", hoveredLink, Theme.palette.primaryColor1, Theme.palette.primaryColor1, false))
.arg(Utils.getStyledLink(qsTr("Privacy Policy"), "#privacy", hoveredLink, Theme.palette.primaryColor1, Theme.palette.primaryColor1, false))
textFormat: Text.RichText
font.pixelSize: Theme.tertiaryTextFontSize
lineHeightMode: Text.FixedHeight
lineHeight: 16
wrapMode: Text.WordWrap
color: Theme.palette.baseColor1
horizontalAlignment: Text.AlignHCenter
onLinkActivated: {
if (link == "#terms")
root.termsOfUseRequested()
else if (link == "#privacy")
root.privacyPolicyRequested()
}
HoverHandler {
// Qt CSS doesn't support custom cursor shape
cursorShape: !!parent.hoveredLink ? Qt.PointingHandCursor : undefined
}
}
}
}
// right part (news carousel)
NewsCarousel {
Layout.fillHeight: true
Layout.fillWidth: true
newsModel: d.newsModel
}
}
}

View File

@ -0,0 +1,15 @@
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
BackupSeedphraseReveal 1.0 BackupSeedphraseReveal.qml
BackupSeedphraseVerify 1.0 BackupSeedphraseVerify.qml
BackupSeedphraseOutro 1.0 BackupSeedphraseOutro.qml

View File

@ -0,0 +1 @@
OnboardingLayout 1.0 OnboardingLayout.qml

View File

@ -43,7 +43,7 @@ SortFilterProxyModel {
readonly property var entries: [ readonly property var entries: [
{ {
subsection: Constants.settingsSubsection.backUpSeed, subsection: Constants.settingsSubsection.backUpSeed,
text: qsTr("Back up seed phrase"), text: qsTr("Back up recovery phrase"),
icon: "seed-phrase" icon: "seed-phrase"
}, },
{ {
@ -73,7 +73,7 @@ SortFilterProxyModel {
text: qsTr("Syncing"), text: qsTr("Syncing"),
icon: "rotate", icon: "rotate",
isExperimental: true, isExperimental: true,
experimentalTooltip: qsTr("Connection problems can happen.<br>If they do, please use the Enter a Seed Phrase feature instead.") experimentalTooltip: qsTr("Connection problems can happen.<br>If they do, please use the Enter a Recovery Phrase feature instead.")
}, },
{ {
subsection: Constants.settingsSubsection.messaging, subsection: Constants.settingsSubsection.messaging,

View File

@ -31,7 +31,7 @@ StatusDialog {
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Are you sure you want to remove %1 key pair? The key pair will be removed from all of your synced devices. Make sure you have a backup of your keys or seed phrase before proceeding.").arg(name) text: qsTr("Are you sure you want to remove %1 key pair? The key pair will be removed from all of your synced devices. Make sure you have a backup of your keys or recovery phrase before proceeding.").arg(name)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 15 font.pixelSize: 15
} }

View File

@ -71,7 +71,7 @@ ColumnLayout {
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Your seed phrase is a 12-word passcode to your funds.") text: qsTr("Your recovery phrase is a 12-word passcode to your funds.")
Layout.fillWidth: true Layout.fillWidth: true
} }
@ -82,7 +82,7 @@ ColumnLayout {
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
lineHeight: 1.2 lineHeight: 1.2
text: qsTr("Your seed phrase cannot be recovered if lost. Therefore, you <b>must</b> back it up. The simplest way is to <b>write it down offline and store it somewhere secure.</b>") text: qsTr("Your recovery phrase cannot be recovered if lost. Therefore, you <b>must</b> back it up. The simplest way is to <b>write it down offline and store it somewhere secure.</b>")
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
@ -107,7 +107,7 @@ ColumnLayout {
id: writeDown id: writeDown
objectName: "Acknowledgements_writeDown" objectName: "Acknowledgements_writeDown"
spacing: Theme.padding spacing: Theme.padding
text: qsTr("I am ready to write down my seed phrase") text: qsTr("I am ready to write down my recovery phrase")
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
Layout.fillWidth: true Layout.fillWidth: true
} }
@ -140,7 +140,7 @@ ColumnLayout {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
lineHeight: 1.2 lineHeight: 1.2
text: qsTr("You can only complete this process once. Status will not store your seed phrase and can never help you recover it.") text: qsTr("You can only complete this process once. Status will not store your recovery phrase and can never help you recover it.")
} }
Rectangle { Rectangle {

View File

@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1

View File

@ -18,7 +18,7 @@ BackupSeedStepBase {
property bool hideSeed: true property bool hideSeed: true
property ProfileStores.PrivacyStore privacyStore property ProfileStores.PrivacyStore privacyStore
titleText: qsTr("Write down your 12-word seed phrase to keep offline") titleText: qsTr("Write down your 12-word recovery phrase to keep offline")
Item { Item {
implicitHeight: 304 implicitHeight: 304
@ -69,7 +69,7 @@ BackupSeedStepBase {
anchors.centerIn: parent anchors.centerIn: parent
visible: hideSeed visible: hideSeed
icon.name: "view" icon.name: "view"
text: qsTr("Reveal seed phrase") text: qsTr("Reveal recovery phrase")
onClicked: { onClicked: {
privacyStore.mnemonicWasShown(); privacyStore.mnemonicWasShown();
hideSeed = false; hideSeed = false;
@ -86,7 +86,7 @@ BackupSeedStepBase {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
textFormat: Text.RichText textFormat: Text.RichText
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
text: qsTr("The next screen contains your seed phrase.\n<b>Anyone</b> who sees it can use it to access to your funds.") text: qsTr("The next screen contains your recovery phrase.\n<b>Anyone</b> who sees it can use it to access to your funds.")
Layout.fillWidth: true Layout.fillWidth: true
} }
} }

View File

@ -38,7 +38,7 @@ BackupSeedStepBase {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
lineHeight: 1.2 lineHeight: 1.2
text: qsTr("By completing this process, you will remove your seed phrase from this applications storage. This makes your funds more secure.") text: qsTr("By completing this process, you will remove your recovery phrase from this applications storage. This makes your funds more secure.")
Layout.fillWidth: true Layout.fillWidth: true
} }
@ -48,7 +48,7 @@ BackupSeedStepBase {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
lineHeight: 1.2 lineHeight: 1.2
text: qsTr("You will remain logged in, and your seed phrase will be entirely in your hands.") text: qsTr("You will remain logged in, and your recovery phrase will be entirely in your hands.")
Layout.fillWidth: true Layout.fillWidth: true
} }
@ -57,7 +57,7 @@ BackupSeedStepBase {
objectName: "ConfirmStoringSeedPhrasePanel_storeCheck" objectName: "ConfirmStoringSeedPhrasePanel_storeCheck"
spacing: Theme.padding spacing: Theme.padding
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
text: qsTr("I acknowledge that Status will not be able to show me my seed phrase again.") text: qsTr("I acknowledge that Status will not be able to show me my recovery phrase again.")
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Theme.bigPadding Layout.topMargin: Theme.bigPadding
} }

View File

@ -187,7 +187,7 @@ SettingsContentBase {
anchors.left: parent.right anchors.left: parent.right
anchors.leftMargin: 8 anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
tooltipText: qsTr("Connection problems can happen.<br>If they do, please use the Enter a Seed Phrase feature instead.") tooltipText: qsTr("Connection problems can happen.<br>If they do, please use the Enter a Recovery Phrase feature instead.")
} }
} }
} }

View File

@ -120,7 +120,7 @@ ColumnLayout {
StatusListItem { StatusListItem {
Layout.fillWidth: true Layout.fillWidth: true
title: qsTr("Create a new Keycard account with a new seed phrase") title: qsTr("Create a new Keycard account with a new recovery phrase")
objectName: "createNewKeycardAccount" objectName: "createNewKeycardAccount"
components: [ components: [
StatusIcon { StatusIcon {
@ -143,7 +143,7 @@ ColumnLayout {
StatusListItem { StatusListItem {
Layout.fillWidth: true Layout.fillWidth: true
title: qsTr("Import or restore via a seed phrase") title: qsTr("Import or restore via a recovery phrase")
objectName: "importRestoreKeycard" objectName: "importRestoreKeycard"
components: [ components: [
StatusIcon { StatusIcon {

View File

@ -22,7 +22,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: qsTr("Back up your seed phrase") text: qsTr("Back up your recovery phrase")
font.pixelSize: 13 font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Theme.palette.white color: Theme.palette.white

View File

@ -984,7 +984,7 @@ Item {
active: !appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner active: !appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner
&& !appMain.rootStore.profileSectionStore.profileStore.privacyStore.mnemonicBackedUp && !appMain.rootStore.profileSectionStore.profileStore.privacyStore.mnemonicBackedUp
type: ModuleWarning.Danger type: ModuleWarning.Danger
text: qsTr("Secure your seed phrase") text: qsTr("Secure your recovery phrase")
buttonText: qsTr("Back up now") buttonText: qsTr("Back up now")
delay: false delay: false
onClicked: popups.openBackUpSeedPopup() onClicked: popups.openBackUpSeedPopup()

View File

@ -103,6 +103,7 @@ QtObject {
Global.openSwapModalRequested.connect(openSwapModal) Global.openSwapModalRequested.connect(openSwapModal)
Global.openBuyCryptoModalRequested.connect(openBuyCryptoModal) Global.openBuyCryptoModalRequested.connect(openBuyCryptoModal)
Global.privacyPolicyRequested.connect(() => openPopup(privacyPolicyPopupComponent)) Global.privacyPolicyRequested.connect(() => openPopup(privacyPolicyPopupComponent))
Global.termsOfUseRequested.connect(() => openPopup(termsOfUsePopupComponent))
} }
property var currentPopup property var currentPopup
@ -1253,23 +1254,25 @@ QtObject {
}, },
Component { Component {
id: privacyPolicyPopupComponent id: privacyPolicyPopupComponent
StatusDialog { StatusSimpleTextPopup {
width: 600
padding: 0
title: qsTr("Status Software Privacy Policy") title: qsTr("Status Software Privacy Policy")
StatusScrollView { content {
id: privacyDialogScrollView textFormat: Text.MarkdownText
anchors.fill: parent text: SQUtils.StringUtils.readTextFile(":/imports/assets/docs/privacy.mdwn")
contentWidth: availableWidth onLinkActivated: Global.openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link))
StatusBaseText { }
width: privacyDialogScrollView.availableWidth destroyOnClose: true
wrapMode: Text.Wrap }
textFormat: Text.MarkdownText },
text: SQUtils.StringUtils.readTextFile(":/imports/assets/docs/privacy.mdwn") Component {
onLinkActivated: Global.openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link)) id: termsOfUsePopupComponent
} StatusSimpleTextPopup {
title: qsTr("Status Software Terms of Use")
content {
textFormat: Text.MarkdownText
text: SQUtils.StringUtils.readTextFile(":/imports/assets/docs/terms-of-use.mdwn")
onLinkActivated: Global.openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link))
} }
standardButtons: Dialog.Ok
destroyOnClose: true destroyOnClose: true
} }
} }

View File

@ -57,7 +57,7 @@ ColumnLayout {
if (d.allEntriesValid) { if (d.allEntriesValid) {
mnemonicString = buildMnemonicString() mnemonicString = buildMnemonicString()
if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) { if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) {
root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase")) root.setWrongSeedPhraseMessage(qsTr("Invalid recovery phrase"))
d.allEntriesValid = false d.allEntriesValid = false
} }
} }

View File

@ -325,9 +325,9 @@ StatusModal {
case Constants.addAccountPopup.state.enterKeypairName: case Constants.addAccountPopup.state.enterKeypairName:
return qsTr("Continue") return qsTr("Continue")
case Constants.addAccountPopup.state.confirmAddingNewMasterKey: case Constants.addAccountPopup.state.confirmAddingNewMasterKey:
return qsTr("Reveal seed phrase") return qsTr("Reveal recovery phrase")
case Constants.addAccountPopup.state.displaySeedPhrase: case Constants.addAccountPopup.state.displaySeedPhrase:
return qsTr("Confirm seed phrase") return qsTr("Confirm recovery phrase")
} }
return "" return ""

View File

@ -86,7 +86,7 @@ StatusSelect {
subTitle: { subTitle: {
if (menu.isOption) { if (menu.isOption) {
if (model.keyPair.keyUid === Constants.appTranslatableConstants.addAccountLabelOptionAddNewMasterKey) if (model.keyPair.keyUid === Constants.appTranslatableConstants.addAccountLabelOptionAddNewMasterKey)
return qsTr("From Keycard, private key or seed phrase") return qsTr("From Keycard, private key or recovery phrase")
if (model.keyPair.keyUid === Constants.appTranslatableConstants.addAccountLabelOptionAddWatchOnlyAcc) if (model.keyPair.keyUid === Constants.appTranslatableConstants.addAccountLabelOptionAddWatchOnlyAcc)
return qsTr("Any ETH address") return qsTr("Any ETH address")
} }

View File

@ -119,7 +119,7 @@ Column {
AddressDetails { AddressDetails {
width: parent.width width: parent.width
addressDetailsItem: root.store.watchOnlyAccAddress addressDetailsItem: root.store.watchOnlyAccAddress
defaultMessage: qsTr("You will need to import your seed phrase or use your Keycard to transact with this account") defaultMessage: qsTr("You will need to import your recovery phrase or use your Keycard to transact with this account")
defaultMessageCondition: addressInput.text === "" || !addressInput.valid defaultMessageCondition: addressInput.text === "" || !addressInput.valid
} }
} }

View File

@ -73,7 +73,7 @@ Item {
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
lineHeight: d.lineHeight lineHeight: d.lineHeight
text: qsTr("Your seed phrase is a 12-word passcode to your funds.<br/><br/>Your seed phrase cannot be recovered if lost. Therefore, you <b>must</b> back it up. The simplest way is to <b>write it down offline and store it somewhere secure.</b>") text: qsTr("Your recovery phrase is a 12-word passcode to your funds.<br/><br/>Your recovery phrase cannot be recovered if lost. Therefore, you <b>must</b> back it up. The simplest way is to <b>write it down offline and store it somewhere secure.</b>")
} }
StatusCheckBox { StatusCheckBox {
@ -96,7 +96,7 @@ Item {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Theme.padding spacing: Theme.padding
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
text: qsTr("I am ready to write down my seed phrase") text: qsTr("I am ready to write down my recovery phrase")
} }
StatusCheckBox { StatusCheckBox {
@ -126,7 +126,7 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
lineHeight: d.lineHeight lineHeight: d.lineHeight
text: qsTr("You can only complete this process once. Status will not store your seed phrase and can never help you recover it.") text: qsTr("You can only complete this process once. Status will not store your recovery phrase and can never help you recover it.")
} }
} }
} }

View File

@ -73,7 +73,7 @@ Item {
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
lineHeight: 1.2 lineHeight: 1.2
color: Theme.palette.directColor1 color: Theme.palette.directColor1
text: qsTr("By completing this process, you will remove your seed phrase from this applications storage. This makes your funds more secure.\n\nYou will remain logged in, and your seed phrase will be entirely in your hands.") text: qsTr("By completing this process, you will remove your recovery phrase from this applications storage. This makes your funds more secure.\n\nYou will remain logged in, and your recovery phrase will be entirely in your hands.")
} }
StatusCheckBox { StatusCheckBox {
@ -84,7 +84,7 @@ Item {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Theme.padding spacing: Theme.padding
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
text: qsTr("I aknowledge that Status will not be able to show me my seed phrase again.") text: qsTr("I acknowledge that Status will not be able to show me my recovery phrase again.")
onToggled: { onToggled: {
root.store.seedPhraseBackupConfirmed = checked root.store.seedPhraseBackupConfirmed = checked
} }

View File

@ -43,7 +43,7 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: Constants.addAccountPopup.labelFontSize1 font.pixelSize: Constants.addAccountPopup.labelFontSize1
color: Theme.palette.directColor1 color: Theme.palette.directColor1
text: qsTr("Write down your 12-word seed phrase to keep offline") text: qsTr("Write down your 12-word recovery phrase to keep offline")
} }
SeedPhrase { SeedPhrase {
@ -69,7 +69,7 @@ Item {
textFormat: Text.RichText textFormat: Text.RichText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
text: qsTr("The next screen contains your seed phrase.<br/><b>Anyone</b> who sees it can use it to access to your funds.") text: qsTr("The next screen contains your recovery phrase.<br/><b>Anyone</b> who sees it can use it to access to your funds.")
} }
} }
} }

View File

@ -111,7 +111,7 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: Constants.addAccountPopup.labelFontSize1 font.pixelSize: Constants.addAccountPopup.labelFontSize1
color: Theme.palette.directColor1 color: Theme.palette.directColor1
text: qsTr("Confirm word #%1 of your seed phrase").arg(root.store.currentState.stateType === Constants.addAccountPopup.state.enterSeedPhraseWord1? text: qsTr("Confirm word #%1 of your recovery phrase").arg(root.store.currentState.stateType === Constants.addAccountPopup.state.enterSeedPhraseWord1?
root.store.seedPhraseWord1WordNumber + 1 : root.store.seedPhraseWord1WordNumber + 1 :
root.store.seedPhraseWord2WordNumber + 1) root.store.seedPhraseWord2WordNumber + 1)
} }

View File

@ -29,7 +29,7 @@ Item {
StatusListItem { StatusListItem {
objectName: "AddAccountPopup-ImportUsingSeedPhrase" objectName: "AddAccountPopup-ImportUsingSeedPhrase"
title: qsTr("Import using seed phrase") title: qsTr("Import using recovery phrase")
asset { asset {
name: "key_pair_seed_phrase" name: "key_pair_seed_phrase"
color: Theme.palette.primaryColor1 color: Theme.palette.primaryColor1

View File

@ -37,7 +37,7 @@ Item {
StatusBaseText { StatusBaseText {
width: parent.width width: parent.width
text: root.store.isAddAccountPopup? qsTr("Private key") : qsTr("Enter seed phrase for %1 key pair").arg(root.store.selectedKeypair.name) text: root.store.isAddAccountPopup? qsTr("Private key") : qsTr("Enter recovery phrase for %1 key pair").arg(root.store.selectedKeypair.name)
font.pixelSize: Constants.addAccountPopup.labelFontSize1 font.pixelSize: Constants.addAccountPopup.labelFontSize1
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -120,7 +120,7 @@ Item {
multiline: true multiline: true
leftPadding: Theme.padding leftPadding: Theme.padding
font.pixelSize: Constants.addAccountPopup.labelFontSize2 font.pixelSize: Constants.addAccountPopup.labelFontSize2
text: qsTr("New addresses cannot be derived from an account imported from a private key. Import using a seed phrase if you wish to derive addresses.") text: qsTr("New addresses cannot be derived from an account imported from a private key. Import using a recovery phrase if you wish to derive addresses.")
input.edit.enabled: false input.edit.enabled: false
input.enabled: false input.enabled: false
input.background.color: "transparent" input.background.color: "transparent"

View File

@ -27,7 +27,7 @@ Item {
StatusBaseText { StatusBaseText {
width: parent.width width: parent.width
text: root.store.isAddAccountPopup? qsTr("Enter seed phrase") : qsTr("Enter private key for %1 key pair").arg(root.store.selectedKeypair.name) text: root.store.isAddAccountPopup? qsTr("Enter recovery phrase") : qsTr("Enter private key for %1 key pair").arg(root.store.selectedKeypair.name)
font.pixelSize: Constants.addAccountPopup.labelFontSize1 font.pixelSize: Constants.addAccountPopup.labelFontSize1
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -46,9 +46,9 @@ Item {
} }
root.store.enteredSeedPhraseIsValid = valid root.store.enteredSeedPhraseIsValid = valid
if (!enterSeedPhrase.isSeedPhraseValid(seedPhrase)) { if (!enterSeedPhrase.isSeedPhraseValid(seedPhrase)) {
let err = qsTr("The entered seed phrase is already added") let err = qsTr("The entered recovery phrase is already added")
if (!root.store.isAddAccountPopup) { if (!root.store.isAddAccountPopup) {
err = qsTr("This is not the correct seed phrase for %1 key").arg(root.store.selectedKeypair.name) err = qsTr("This is not the correct recovery phrase for %1 key").arg(root.store.selectedKeypair.name)
} }
enterSeedPhrase.setWrongSeedPhraseMessage(err) enterSeedPhrase.setWrongSeedPhraseMessage(err)
} }

View File

@ -28,9 +28,9 @@ StatusModal {
case Constants.keycardSharedFlow.setupNewKeycard: case Constants.keycardSharedFlow.setupNewKeycard:
return qsTr("Set up a new Keycard with an existing account") return qsTr("Set up a new Keycard with an existing account")
case Constants.keycardSharedFlow.setupNewKeycardNewSeedPhrase: case Constants.keycardSharedFlow.setupNewKeycardNewSeedPhrase:
return qsTr("Create a new Keycard account with a new seed phrase") return qsTr("Create a new Keycard account with a new recovery phrase")
case Constants.keycardSharedFlow.setupNewKeycardOldSeedPhrase: case Constants.keycardSharedFlow.setupNewKeycardOldSeedPhrase:
return qsTr("Import or restore a Keycard via a seed phrase") return qsTr("Import or restore a Keycard via a recovery phrase")
case Constants.keycardSharedFlow.importFromKeycard: case Constants.keycardSharedFlow.importFromKeycard:
return qsTr("Migrate account from Keycard to Status") return qsTr("Migrate account from Keycard to Status")
case Constants.keycardSharedFlow.factoryReset: case Constants.keycardSharedFlow.factoryReset:

View File

@ -659,7 +659,7 @@ QtObject {
case Constants.keycardSharedState.createPin: case Constants.keycardSharedState.createPin:
case Constants.keycardSharedState.repeatPin: case Constants.keycardSharedState.repeatPin:
case Constants.keycardSharedState.pinSet: case Constants.keycardSharedState.pinSet:
return qsTr("Input seed phrase") return qsTr("Input recovery phrase")
case Constants.keycardSharedState.seedPhraseEnterWords: case Constants.keycardSharedState.seedPhraseEnterWords:
return qsTr("Yes, migrate key pair to this Keycard") return qsTr("Yes, migrate key pair to this Keycard")
@ -668,7 +668,7 @@ QtObject {
return qsTr("Yes, migrate key pair to Keycard") return qsTr("Yes, migrate key pair to Keycard")
case Constants.keycardSharedState.wrongSeedPhrase: case Constants.keycardSharedState.wrongSeedPhrase:
return qsTr("Try entering seed phrase again") return qsTr("Try entering recovery phrase again")
case Constants.keycardSharedState.keycardNotEmpty: case Constants.keycardSharedState.keycardNotEmpty:
return qsTr("Check what is stored on this Keycard") return qsTr("Check what is stored on this Keycard")
@ -950,7 +950,7 @@ QtObject {
// to run unlock flow directly. // to run unlock flow directly.
return "" return ""
} }
return qsTr("Unlock using seed phrase") return qsTr("Unlock using recovery phrase")
case Constants.keycardSharedState.createPin: case Constants.keycardSharedState.createPin:
case Constants.keycardSharedState.repeatPin: case Constants.keycardSharedState.repeatPin:
@ -961,7 +961,7 @@ QtObject {
return qsTr("Next") return qsTr("Next")
case Constants.keycardSharedState.wrongSeedPhrase: case Constants.keycardSharedState.wrongSeedPhrase:
return qsTr("Try entering seed phrase again") return qsTr("Try entering recovery phrase again")
case Constants.keycardSharedState.maxPukRetriesReached: case Constants.keycardSharedState.maxPukRetriesReached:
if (root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.disableSeedPhraseForUnlock) { if (root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.disableSeedPhraseForUnlock) {
@ -1123,7 +1123,7 @@ QtObject {
return qsTr("Done") return qsTr("Done")
case Constants.keycardSharedState.wrongSeedPhrase: case Constants.keycardSharedState.wrongSeedPhrase:
return qsTr("Try entering seed phrase again") return qsTr("Try entering recovery phrase again")
case Constants.keycardSharedState.maxPinRetriesReached: case Constants.keycardSharedState.maxPinRetriesReached:
case Constants.keycardSharedState.maxPukRetriesReached: case Constants.keycardSharedState.maxPukRetriesReached:

View File

@ -18,7 +18,7 @@ Item {
property bool wrongSeedPhrase: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wrongSeedPhrase property bool wrongSeedPhrase: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wrongSeedPhrase
onWrongSeedPhraseChanged: { onWrongSeedPhraseChanged: {
seedPhrase.setWrongSeedPhraseMessage(wrongSeedPhrase? qsTr("The phrase youve entered does not match this Keycards seed phrase") : "") seedPhrase.setWrongSeedPhraseMessage(wrongSeedPhrase? qsTr("The phrase youve entered does not match this Keycards recovery phrase") : "")
} }
} }
@ -74,7 +74,7 @@ Item {
text: { text: {
switch (root.sharedKeycardModule.currentState.flowType) { switch (root.sharedKeycardModule.currentState.flowType) {
case Constants.keycardSharedFlow.migrateFromKeycardToApp: case Constants.keycardSharedFlow.migrateFromKeycardToApp:
return qsTr("Enter seed phrase for %1 key pair").arg(root.sharedKeycardModule.keyPairForProcessing.name) return qsTr("Enter recovery phrase for %1 key pair").arg(root.sharedKeycardModule.keyPairForProcessing.name)
} }
return "" return ""
@ -89,7 +89,7 @@ Item {
text: { text: {
switch (root.sharedKeycardModule.currentState.flowType) { switch (root.sharedKeycardModule.currentState.flowType) {
case Constants.keycardSharedFlow.migrateFromKeycardToApp: case Constants.keycardSharedFlow.migrateFromKeycardToApp:
return qsTr("Enter seed phrase for %1 key pair").arg(root.sharedKeycardModule.keyPairForProcessing.name) return qsTr("Enter recovery phrase for %1 key pair").arg(root.sharedKeycardModule.keyPairForProcessing.name)
} }
return "" return ""

View File

@ -62,7 +62,7 @@ Item {
id: title id: title
Layout.preferredHeight: Constants.keycard.general.titleHeight Layout.preferredHeight: Constants.keycard.general.titleHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Confirm seed phrase words") text: qsTr("Confirm recovery phrase words")
font.pixelSize: Constants.keycard.general.fontSize1 font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1

View File

@ -1401,7 +1401,7 @@ Item {
target: title target: title
text: { text: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycardOldSeedPhrase) { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycardOldSeedPhrase) {
return qsTr("This seed phrase has already been imported") return qsTr("This recovery phrase has already been imported")
} }
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.importFromKeycard) { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.importFromKeycard) {
return qsTr("This keycard has already been imported") return qsTr("This keycard has already been imported")
@ -1459,7 +1459,7 @@ Item {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migrateKeypairToApp) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migrateKeypairToApp) {
if (root.sharedKeycardModule.keyPairForProcessing.pairType === Constants.keycard.keyPairType.profile) { if (root.sharedKeycardModule.keyPairForProcessing.pairType === Constants.keycard.keyPairType.profile) {
if (root.sharedKeycardModule.forceFlow) { if (root.sharedKeycardModule.forceFlow) {
return qsTr("In order to continue using this profile on this device, you need to enter the key pairs seed phrase and create a new password to log in with on this device.") return qsTr("In order to continue using this profile on this device, you need to enter the key pairs recovery phrase and create a new password to log in with on this device.")
} }
let t = qsTr("%1 is your default Status key pair.").arg(root.sharedKeycardModule.keyPairForProcessing.name) let t = qsTr("%1 is your default Status key pair.").arg(root.sharedKeycardModule.keyPairForProcessing.name)

View File

@ -45,7 +45,7 @@ Item {
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
Layout.fillHeight: true Layout.fillHeight: true
property var seedPhrase: root.sharedKeycardModule.getMnemonic().split(" ") seedPhrase: root.sharedKeycardModule.getMnemonic().split(" ")
} }
} }
@ -55,14 +55,14 @@ Item {
when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay
PropertyChanges { PropertyChanges {
target: title target: title
text: qsTr("Write down your seed phrase") text: qsTr("Write down your recovery phrase")
font.pixelSize: Constants.keycard.general.fontSize1 font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1
} }
PropertyChanges { PropertyChanges {
target: message target: message
text: qsTr("The next screen contains your seed phrase.<br/><b>Anyone</b> who sees it can use it to access to your funds.") text: qsTr("The next screen contains your recovery phrase.<br/><b>Anyone</b> who sees it can use it to access to your funds.")
font.pixelSize: Constants.keycard.general.fontSize2 font.pixelSize: Constants.keycard.general.fontSize2
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
textFormat: Text.RichText textFormat: Text.RichText

View File

@ -62,7 +62,7 @@ Item {
StatusListItem { StatusListItem {
title: root.store.selectedKeypair.pairType === Constants.keypair.type.seedImport? title: root.store.selectedKeypair.pairType === Constants.keypair.type.seedImport?
qsTr("Import via entering seed phrase") : qsTr("Import via entering recovery phrase") :
qsTr("Import via entering private key") qsTr("Import via entering private key")
asset { asset {

View File

@ -79,11 +79,6 @@ ColumnLayout {
QtObject { QtObject {
id: d id: d
property bool containsLower: false
property bool containsUpper: false
property bool containsNumbers: false
property bool containsSymbols: false
readonly property var validatorRegexp: /^[!-~]+$/ readonly property var validatorRegexp: /^[!-~]+$/
readonly property string validatorErrMessage: qsTr("Only ASCII letters, numbers, and symbols are allowed") readonly property string validatorErrMessage: qsTr("Only ASCII letters, numbers, and symbols are allowed")
readonly property string passTooLongErrMessage: qsTr("Maximum %n character(s)", "", Constants.maxPasswordLength) readonly property string passTooLongErrMessage: qsTr("Maximum %n character(s)", "", Constants.maxPasswordLength)
@ -244,7 +239,7 @@ ColumnLayout {
Layout.alignment: root.contentAlignment Layout.alignment: root.contentAlignment
StatusBaseText { StatusBaseText {
text: qsTr("New password") text: qsTr("Choose password")
} }
StatusPasswordInput { StatusPasswordInput {
@ -255,7 +250,7 @@ ColumnLayout {
Layout.alignment: root.contentAlignment Layout.alignment: root.contentAlignment
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Enter new password") placeholderText: qsTr("Type password")
echoMode: showPassword ? TextInput.Normal : TextInput.Password echoMode: showPassword ? TextInput.Normal : TextInput.Password
rightPadding: showHideNewIcon.width + showHideNewIcon.anchors.rightMargin + Theme.padding / 2 rightPadding: showHideNewIcon.width + showHideNewIcon.anchors.rightMargin + Theme.padding / 2
@ -265,11 +260,6 @@ ColumnLayout {
// Update strength indicator: // Update strength indicator:
strengthInditactor.strength = d.convertStrength(root.passwordStrengthScoreFunction(newPswInput.text)) strengthInditactor.strength = d.convertStrength(root.passwordStrengthScoreFunction(newPswInput.text))
d.containsLower = d.lowerCaseValidator(text)
d.containsUpper = d.upperCaseValidator(text)
d.containsNumbers = d.numbersValidator(text)
d.containsSymbols = d.symbolsValidator(text)
if(!d.validateCharacterSet(text)) return if(!d.validateCharacterSet(text)) return
if (text.length === confirmPswInput.text.length) { if (text.length === confirmPswInput.text.length) {
@ -292,87 +282,11 @@ ColumnLayout {
onClicked: newPswInput.showPassword = !newPswInput.showPassword onClicked: newPswInput.showPassword = !newPswInput.showPassword
} }
} }
StatusPasswordStrengthIndicator {
id: strengthInditactor
Layout.fillWidth: true
value: Math.min(Constants.minPasswordLength, newPswInput.text.length)
from: 0
to: Constants.minPasswordLength
labelVeryWeak: qsTr("Very weak")
labelWeak: qsTr("Weak")
labelSoso: qsTr("So-so")
labelGood: qsTr("Good")
labelGreat: qsTr("Great")
}
}
Rectangle {
Layout.fillWidth: true
Layout.minimumHeight: 80
border.color: Theme.palette.baseColor2
border.width: 1
color: "transparent"
radius: Theme.radius
implicitHeight: strengthColumn.implicitHeight
implicitWidth: strengthColumn.implicitWidth
ColumnLayout {
id: strengthColumn
anchors.fill: parent
anchors.margins: Theme.padding
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.padding
StatusBaseText {
id: strengthenTxt
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
text: root.strengthenText
font.pixelSize: 12
color: Theme.palette.baseColor1
clip: true
}
RowLayout {
spacing: Theme.padding
Layout.alignment: Qt.AlignHCenter
StatusBaseText {
id: lowerCaseTxt
text: "• " + qsTr("Lower case")
font.pixelSize: 12
color: d.containsLower ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: upperCaseTxt
text: "• " + qsTr("Upper case")
font.pixelSize: 12
color: d.containsUpper ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: numbersTxt
text: "• " + qsTr("Numbers")
font.pixelSize: 12
color: d.containsNumbers ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: symbolsTxt
text: "• " + qsTr("Symbols")
font.pixelSize: 12
color: d.containsSymbols ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
}
}
} }
ColumnLayout { ColumnLayout {
StatusBaseText { StatusBaseText {
text: qsTr("Confirm new password") text: qsTr("Repeat password")
} }
StatusPasswordInput { StatusPasswordInput {
@ -384,7 +298,7 @@ ColumnLayout {
z: root.zFront z: root.zFront
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: root.contentAlignment Layout.alignment: root.contentAlignment
placeholderText: qsTr("Enter new password") placeholderText: qsTr("Type password")
echoMode: showPassword ? TextInput.Normal : TextInput.Password echoMode: showPassword ? TextInput.Normal : TextInput.Password
rightPadding: showHideConfirmIcon.width + showHideConfirmIcon.anchors.rightMargin + Theme.padding / 2 rightPadding: showHideConfirmIcon.width + showHideConfirmIcon.anchors.rightMargin + Theme.padding / 2
@ -427,11 +341,53 @@ ColumnLayout {
} }
} }
StatusPasswordStrengthIndicator {
id: strengthInditactor
Layout.fillWidth: true
value: Math.min(Constants.minPasswordLength, newPswInput.text.length)
from: 0
to: Constants.minPasswordLength
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.padding
Layout.alignment: Qt.AlignHCenter
PassIncludesIndicator {
caption: qsTr("Lower case")
checked: d.lowerCaseValidator(newPswInput.text)
}
PassIncludesIndicator {
caption: qsTr("Upper case")
checked: d.upperCaseValidator(newPswInput.text)
}
PassIncludesIndicator {
caption: qsTr("Numbers")
checked: d.numbersValidator(newPswInput.text)
}
PassIncludesIndicator {
caption: qsTr("Symbols")
checked: d.symbolsValidator(newPswInput.text)
}
}
StatusBaseText { StatusBaseText {
id: errorTxt id: errorTxt
Layout.alignment: root.contentAlignment Layout.alignment: root.contentAlignment
Layout.fillHeight: true font.pixelSize: Theme.tertiaryTextFontSize
font.pixelSize: 12
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }
component PassIncludesIndicator: StatusBaseText {
property bool checked
property string caption
text: "%1 %2".arg(checked ? "✓" : "+").arg(caption)
font.pixelSize: Theme.tertiaryTextFontSize
color: checked ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
} }

View File

@ -1346,6 +1346,7 @@ QtObject {
readonly property string welcome: "welcome_view" readonly property string welcome: "welcome_view"
readonly property string privacyAndSecurity: "privacy_and_security_view" readonly property string privacyAndSecurity: "privacy_and_security_view"
readonly property string startApp: "start_app_after_upgrade" readonly property string startApp: "start_app_after_upgrade"
readonly property string onboarding: "onboarding"
} }
enum MutingVariations { enum MutingVariations {

View File

@ -91,6 +91,7 @@ QtObject {
signal openTestnetPopup() signal openTestnetPopup()
signal privacyPolicyRequested() signal privacyPolicyRequested()
signal termsOfUseRequested()
// Swap // Swap
signal openSwapModalRequested(var formDataParams) signal openSwapModalRequested(var formDataParams)

View File

@ -78,11 +78,11 @@ QtObject {
`<a href="${link}">${link}</a>` `<a href="${link}">${link}</a>`
} }
function getStyledLink(linkText, linkUrl, hoveredLink, textColor = Theme.palette.directColor1, linkColor = Theme.palette.primaryColor1) { function getStyledLink(linkText, linkUrl, hoveredLink, textColor = Theme.palette.directColor1, linkColor = Theme.palette.primaryColor1, underlineLink = true) {
return `<style type="text/css">` + return `<style type="text/css">` +
`a {` + `a {` +
`color: ${textColor};` + `color: ${textColor};` +
`text-decoration: underline;` + (underlineLink ? `text-decoration: underline;` : `text-decoration: none;`) +
`}` + `}` +
(hoveredLink === linkUrl ? `a[href="${linkUrl}"] { text-decoration: underline; color: ${linkColor} }` : "") + (hoveredLink === linkUrl ? `a[href="${linkUrl}"] { text-decoration: underline; color: ${linkColor} }` : "") +
`</style>` + `</style>` +