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
|
@ -17,7 +17,7 @@ import Status.Core.Theme
|
|||
width: 240
|
||||
text: qsTr("Hello World!")
|
||||
font.pixelSize: 24
|
||||
color: Theme.pallete.directColor1
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
\endqml
|
||||
|
||||
|
|
|
@ -146,6 +146,10 @@ SplitView {
|
|||
enabled: searchField.searchText !== ""
|
||||
onClicked: searchField.clear()
|
||||
}
|
||||
Label {
|
||||
text: "INFO: Reload the page after selecting 'Dark mode'"
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
ColorFlow {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,28 +1,3 @@
|
|||
import QtQuick 2.15
|
||||
import QtQml 2.15
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
QtObject {}
|
||||
|
|
|
@ -5,7 +5,7 @@ ListModel {
|
|||
|
||||
Component.onCompleted: {
|
||||
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",
|
||||
"umbrella", "violet", "watermelon", "xylophone", "yogurt", "zebra"
|
||||
// Add more English words here...
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import QtQml 2.15
|
||||
|
||||
QtObject {}
|
|
@ -8,3 +8,4 @@ PermissionsStore 1.0 PermissionsStore.qml
|
|||
ProfileStore 1.0 ProfileStore.qml
|
||||
RootStore 1.0 RootStore.qml
|
||||
UtilsStore 1.0 UtilsStore.qml
|
||||
MetricsStore 1.0 MetricsStore.qml
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick 2.15
|
||||
|
||||
/*!
|
||||
\qmltype StatusImage
|
||||
|
|
|
@ -53,6 +53,7 @@ Loader {
|
|||
objectName: "statusRoundImage"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: asset.bgRadius
|
||||
image.source: root.asset.isImage ? root.asset.name : ""
|
||||
showLoadingIndicator: true
|
||||
border.width: root.asset.imgIsIdenticon ? 1 : 0
|
||||
|
|
|
@ -11,6 +11,7 @@ ItemDelegate {
|
|||
property bool centerTextHorizontally: false
|
||||
property int radius: 0
|
||||
property int cursorShape: Qt.PointingHandCursor
|
||||
property color highlightColor: Theme.palette.statusMenu.hoverBackgroundColor
|
||||
|
||||
padding: 8
|
||||
spacing: 8
|
||||
|
@ -19,7 +20,7 @@ ItemDelegate {
|
|||
icon.height: 16
|
||||
|
||||
font.family: Theme.baseFont.name
|
||||
font.pixelSize: 15
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: root.spacing
|
||||
|
@ -40,7 +41,7 @@ ItemDelegate {
|
|||
text: root.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
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 {
|
||||
when: root.centerTextHorizontally
|
||||
|
@ -50,16 +51,11 @@ ItemDelegate {
|
|||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: root.highlighted
|
||||
? Theme.palette.statusMenu.hoverBackgroundColor
|
||||
: "transparent"
|
||||
|
||||
color: root.highlighted ? root.highlightColor : "transparent"
|
||||
radius: root.radius
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: root.cursorShape
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: root.cursorShape
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ StatusProgressBar {
|
|||
|
||||
Default value: "So-so"
|
||||
*/
|
||||
property string labelSoso: qsTr("So-so")
|
||||
property string labelSoso: qsTr("Okay")
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelGood
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Good.
|
||||
|
@ -88,7 +88,7 @@ StatusProgressBar {
|
|||
|
||||
Default value: "Great"
|
||||
*/
|
||||
property string labelGreat: qsTr("Great")
|
||||
property string labelGreat: qsTr("Very strong")
|
||||
|
||||
enum Strength {
|
||||
None, // 0
|
||||
|
|
|
@ -42,7 +42,7 @@ Item {
|
|||
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
|
||||
input which leaves the pinInput property in an acceptable state.
|
||||
|
||||
|
@ -59,6 +59,13 @@ Item {
|
|||
*/
|
||||
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
|
||||
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
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
|
|
|
@ -75,6 +75,9 @@ Item {
|
|||
input text.
|
||||
*/
|
||||
property ListModel filteredList: ListModel { }
|
||||
|
||||
readonly property bool suggestionsOpened: suggListContainer.opened
|
||||
|
||||
/*!
|
||||
\qmlsignal doneInsertingWord
|
||||
This signal is emitted when the user selects a word from the suggestions list
|
||||
|
@ -117,9 +120,10 @@ Item {
|
|||
Component {
|
||||
id: seedInputLeftComponent
|
||||
StatusBaseText {
|
||||
leftPadding: 4
|
||||
rightPadding: 6
|
||||
leftPadding: text.length == 1 ? 10 : 6
|
||||
rightPadding: 4
|
||||
text: root.leftComponentText
|
||||
font.family: Theme.monoFont.name
|
||||
color: seedWordInput.input.edit.activeFocus ?
|
||||
Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
||||
}
|
||||
|
@ -197,7 +201,7 @@ Item {
|
|||
id: suggListContainer
|
||||
contentWidth: seedSuggestionsList.width
|
||||
contentHeight: ((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34
|
||||
x: 16
|
||||
x: 0
|
||||
y: seedWordInput.height + 4
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
|
|
|
@ -86,5 +86,8 @@ QtObject {
|
|||
|
||||
'lightDesktopBlue10': '#ECEFFB',
|
||||
'darkDesktopBlue10': '#273251',
|
||||
|
||||
// new/mobile colors
|
||||
'neutral-95': '#0D1625'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,4 +279,20 @@ QtObject {
|
|||
function stripHttpsAndwwwFromUrl(text) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,5 +14,6 @@ StatusModalDivider 0.1 StatusModalDivider.qml
|
|||
StatusSearchLocationMenu 0.1 StatusSearchLocationMenu.qml
|
||||
StatusSearchPopup 0.1 StatusSearchPopup.qml
|
||||
StatusSearchPopupMenuItem 0.1 StatusSearchPopupMenuItem.qml
|
||||
StatusSimpleTextPopup 0.1 StatusSimpleTextPopup.qml
|
||||
StatusStackModal 0.1 StatusStackModal.qml
|
||||
StatusSuccessAction 0.1 StatusSuccessAction.qml
|
||||
|
|
|
@ -8339,6 +8339,23 @@
|
|||
<file>assets/png/onboarding/profile_fetching_in_progress.png</file>
|
||||
<file>assets/png/onboarding/seed-phrase.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/mercuryo.png</file>
|
||||
<file>assets/png/onRampProviders/moonPay.png</file>
|
||||
|
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 133 KiB |
After Width: | Height: | Size: 257 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 498 KiB |
Before Width: | Height: | Size: 778 KiB After Width: | Height: | Size: 776 KiB |
|
@ -245,6 +245,7 @@
|
|||
<file>StatusQ/Popups/StatusSearchLocationMenu.qml</file>
|
||||
<file>StatusQ/Popups/StatusSearchPopup.qml</file>
|
||||
<file>StatusQ/Popups/StatusSearchPopupMenuItem.qml</file>
|
||||
<file>StatusQ/Popups/StatusSimpleTextPopup.qml</file>
|
||||
<file>StatusQ/Popups/StatusStackModal.qml</file>
|
||||
<file>StatusQ/Popups/StatusSuccessAction.qml</file>
|
||||
<file>StatusQ/Popups/qmldir</file>
|
||||
|
|
|
@ -29,10 +29,10 @@ Item {
|
|||
onWrongSeedPhraseChanged: {
|
||||
if (wrongSeedPhrase) {
|
||||
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
|
||||
}
|
||||
seedPhraseView.setWrongSeedPhraseMessage(qsTr("Seed phrase doesn’t match the profile of an existing Keycard user on this device"))
|
||||
seedPhraseView.setWrongSeedPhraseMessage(qsTr("Recovery phrase doesn’t match the profile of an existing Keycard user on this device"))
|
||||
}
|
||||
else {
|
||||
seedPhraseView.setWrongSeedPhraseMessage("")
|
||||
|
@ -52,7 +52,7 @@ Item {
|
|||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Enter seed phrase")
|
||||
text: qsTr("Enter recovery phrase")
|
||||
}
|
||||
|
||||
EnterSeedPhrase {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
NewsCarousel 1.0 NewsCarousel.qml
|
||||
SeedphraseVerifyInput 1.0 SeedphraseVerifyInput.qml
|
||||
StepIndicator 1.0 StepIndicator.qml
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
OnboardingFrame 1.0 OnboardingFrame.qml
|
||||
ListItemButton 1.0 ListItemButton.qml
|
|
@ -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 I’ll 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("Let’s 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 can’t 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.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("Let’s 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("We’ll collect anonymous analytics and diagnostics from your app to enhance Status’s 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 don’t 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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("I’ve 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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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("I’ve 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 isn’t a Keycard")
|
||||
subtitle: qsTr("Remove card and insert a Keycard")
|
||||
image.source: Theme.png("onboarding/keycard/invalid")
|
||||
}
|
||||
PropertyChanges {
|
||||
target: btnFactoryReset
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "occupied"
|
||||
extend: "error"
|
||||
when: root.keycardState === Constants.startupState.keycardMaxPairingSlotsReached
|
||||
PropertyChanges {
|
||||
target: root
|
||||
title: qsTr("All pairing slots occupied")
|
||||
subtitle: qsTr("Factory reset this Keycard or insert a different one")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "locked"
|
||||
extend: "error"
|
||||
when: root.keycardState === Constants.startupState.keycardLocked
|
||||
PropertyChanges {
|
||||
target: root
|
||||
title: qsTr("Keycard locked")
|
||||
subtitle: qsTr("The Keycard you have inserted is locked, you will need to factory reset it or insert a different one")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "notEmpty"
|
||||
extend: "error"
|
||||
when: root.keycardState === Constants.startupState.keycardNotEmpty
|
||||
PropertyChanges {
|
||||
target: root
|
||||
title: qsTr("Keycard is not empty")
|
||||
subtitle: qsTr("You can’t use it to store new keys right now")
|
||||
}
|
||||
PropertyChanges {
|
||||
target: btnLogin
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
// success/exit state
|
||||
State {
|
||||
name: "emptyDetected"
|
||||
when: root.keycardState === Constants.startupState.keycardEmpty
|
||||
StateChangeScript {
|
||||
script: root.emptyKeycardDetected()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
OnboardingLayout 1.0 OnboardingLayout.qml
|
|
@ -43,7 +43,7 @@ SortFilterProxyModel {
|
|||
readonly property var entries: [
|
||||
{
|
||||
subsection: Constants.settingsSubsection.backUpSeed,
|
||||
text: qsTr("Back up seed phrase"),
|
||||
text: qsTr("Back up recovery phrase"),
|
||||
icon: "seed-phrase"
|
||||
},
|
||||
{
|
||||
|
@ -73,7 +73,7 @@ SortFilterProxyModel {
|
|||
text: qsTr("Syncing"),
|
||||
icon: "rotate",
|
||||
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,
|
||||
|
|
|
@ -31,7 +31,7 @@ StatusDialog {
|
|||
|
||||
StatusBaseText {
|
||||
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
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ ColumnLayout {
|
|||
font.pixelSize: Theme.primaryTextFontSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ ColumnLayout {
|
|||
textFormat: Text.RichText
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ ColumnLayout {
|
|||
id: writeDown
|
||||
objectName: "Acknowledgements_writeDown"
|
||||
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
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ ColumnLayout {
|
|||
wrapMode: Text.WordWrap
|
||||
color: Theme.palette.dangerColor1
|
||||
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 {
|
||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
|
|||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ BackupSeedStepBase {
|
|||
property bool hideSeed: true
|
||||
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 {
|
||||
implicitHeight: 304
|
||||
|
@ -69,7 +69,7 @@ BackupSeedStepBase {
|
|||
anchors.centerIn: parent
|
||||
visible: hideSeed
|
||||
icon.name: "view"
|
||||
text: qsTr("Reveal seed phrase")
|
||||
text: qsTr("Reveal recovery phrase")
|
||||
onClicked: {
|
||||
privacyStore.mnemonicWasShown();
|
||||
hideSeed = false;
|
||||
|
@ -86,7 +86,7 @@ BackupSeedStepBase {
|
|||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ BackupSeedStepBase {
|
|||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
lineHeight: 1.2
|
||||
text: qsTr("By completing this process, you will remove your seed phrase from this application’s storage. This makes your funds more secure.")
|
||||
text: qsTr("By completing this process, you will remove your recovery phrase from this application’s storage. This makes your funds more secure.")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ BackupSeedStepBase {
|
|||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ BackupSeedStepBase {
|
|||
objectName: "ConfirmStoringSeedPhrasePanel_storeCheck"
|
||||
spacing: Theme.padding
|
||||
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.topMargin: Theme.bigPadding
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ SettingsContentBase {
|
|||
anchors.left: parent.right
|
||||
anchors.leftMargin: 8
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ ColumnLayout {
|
|||
|
||||
StatusListItem {
|
||||
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"
|
||||
components: [
|
||||
StatusIcon {
|
||||
|
@ -143,7 +143,7 @@ ColumnLayout {
|
|||
|
||||
StatusListItem {
|
||||
Layout.fillWidth: true
|
||||
title: qsTr("Import or restore via a seed phrase")
|
||||
title: qsTr("Import or restore via a recovery phrase")
|
||||
objectName: "importRestoreKeycard"
|
||||
components: [
|
||||
StatusIcon {
|
||||
|
|
|
@ -22,7 +22,7 @@ Rectangle {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Back up your seed phrase")
|
||||
text: qsTr("Back up your recovery phrase")
|
||||
font.pixelSize: 13
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Theme.palette.white
|
||||
|
|
|
@ -984,7 +984,7 @@ Item {
|
|||
active: !appMain.rootStore.profileSectionStore.profileStore.userDeclinedBackupBanner
|
||||
&& !appMain.rootStore.profileSectionStore.profileStore.privacyStore.mnemonicBackedUp
|
||||
type: ModuleWarning.Danger
|
||||
text: qsTr("Secure your seed phrase")
|
||||
text: qsTr("Secure your recovery phrase")
|
||||
buttonText: qsTr("Back up now")
|
||||
delay: false
|
||||
onClicked: popups.openBackUpSeedPopup()
|
||||
|
|
|
@ -103,6 +103,7 @@ QtObject {
|
|||
Global.openSwapModalRequested.connect(openSwapModal)
|
||||
Global.openBuyCryptoModalRequested.connect(openBuyCryptoModal)
|
||||
Global.privacyPolicyRequested.connect(() => openPopup(privacyPolicyPopupComponent))
|
||||
Global.termsOfUseRequested.connect(() => openPopup(termsOfUsePopupComponent))
|
||||
}
|
||||
|
||||
property var currentPopup
|
||||
|
@ -1253,23 +1254,25 @@ QtObject {
|
|||
},
|
||||
Component {
|
||||
id: privacyPolicyPopupComponent
|
||||
StatusDialog {
|
||||
width: 600
|
||||
padding: 0
|
||||
StatusSimpleTextPopup {
|
||||
title: qsTr("Status Software Privacy Policy")
|
||||
StatusScrollView {
|
||||
id: privacyDialogScrollView
|
||||
anchors.fill: parent
|
||||
contentWidth: availableWidth
|
||||
StatusBaseText {
|
||||
width: privacyDialogScrollView.availableWidth
|
||||
wrapMode: Text.Wrap
|
||||
content {
|
||||
textFormat: Text.MarkdownText
|
||||
text: SQUtils.StringUtils.readTextFile(":/imports/assets/docs/privacy.mdwn")
|
||||
onLinkActivated: Global.openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link))
|
||||
}
|
||||
destroyOnClose: true
|
||||
}
|
||||
},
|
||||
Component {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ ColumnLayout {
|
|||
if (d.allEntriesValid) {
|
||||
mnemonicString = buildMnemonicString()
|
||||
if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) {
|
||||
root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase"))
|
||||
root.setWrongSeedPhraseMessage(qsTr("Invalid recovery phrase"))
|
||||
d.allEntriesValid = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,9 +325,9 @@ StatusModal {
|
|||
case Constants.addAccountPopup.state.enterKeypairName:
|
||||
return qsTr("Continue")
|
||||
case Constants.addAccountPopup.state.confirmAddingNewMasterKey:
|
||||
return qsTr("Reveal seed phrase")
|
||||
return qsTr("Reveal recovery phrase")
|
||||
case Constants.addAccountPopup.state.displaySeedPhrase:
|
||||
return qsTr("Confirm seed phrase")
|
||||
return qsTr("Confirm recovery phrase")
|
||||
}
|
||||
|
||||
return ""
|
||||
|
|
|
@ -86,7 +86,7 @@ StatusSelect {
|
|||
subTitle: {
|
||||
if (menu.isOption) {
|
||||
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)
|
||||
return qsTr("Any ETH address")
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ Column {
|
|||
AddressDetails {
|
||||
width: parent.width
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ Item {
|
|||
textFormat: Text.RichText
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
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 {
|
||||
|
@ -96,7 +96,7 @@ Item {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Theme.padding
|
||||
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 {
|
||||
|
@ -126,7 +126,7 @@ Item {
|
|||
wrapMode: Text.WordWrap
|
||||
color: Theme.palette.dangerColor1
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ Item {
|
|||
font.pixelSize: Theme.primaryTextFontSize
|
||||
lineHeight: 1.2
|
||||
color: Theme.palette.directColor1
|
||||
text: qsTr("By completing this process, you will remove your seed phrase from this application’s 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 application’s storage. This makes your funds more secure.\n\nYou will remain logged in, and your recovery phrase will be entirely in your hands.")
|
||||
}
|
||||
|
||||
StatusCheckBox {
|
||||
|
@ -84,7 +84,7 @@ Item {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Theme.padding
|
||||
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: {
|
||||
root.store.seedPhraseBackupConfirmed = checked
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ Item {
|
|||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Constants.addAccountPopup.labelFontSize1
|
||||
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 {
|
||||
|
@ -69,7 +69,7 @@ Item {
|
|||
textFormat: Text.RichText
|
||||
wrapMode: Text.WordWrap
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ Item {
|
|||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Constants.addAccountPopup.labelFontSize1
|
||||
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.seedPhraseWord2WordNumber + 1)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ Item {
|
|||
|
||||
StatusListItem {
|
||||
objectName: "AddAccountPopup-ImportUsingSeedPhrase"
|
||||
title: qsTr("Import using seed phrase")
|
||||
title: qsTr("Import using recovery phrase")
|
||||
asset {
|
||||
name: "key_pair_seed_phrase"
|
||||
color: Theme.palette.primaryColor1
|
||||
|
|
|
@ -37,7 +37,7 @@ Item {
|
|||
|
||||
StatusBaseText {
|
||||
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
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ Item {
|
|||
multiline: true
|
||||
leftPadding: Theme.padding
|
||||
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.enabled: false
|
||||
input.background.color: "transparent"
|
||||
|
|
|
@ -27,7 +27,7 @@ Item {
|
|||
|
||||
StatusBaseText {
|
||||
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
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ Item {
|
|||
}
|
||||
root.store.enteredSeedPhraseIsValid = valid
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ StatusModal {
|
|||
case Constants.keycardSharedFlow.setupNewKeycard:
|
||||
return qsTr("Set up a new Keycard with an existing account")
|
||||
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:
|
||||
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:
|
||||
return qsTr("Migrate account from Keycard to Status")
|
||||
case Constants.keycardSharedFlow.factoryReset:
|
||||
|
|
|
@ -659,7 +659,7 @@ QtObject {
|
|||
case Constants.keycardSharedState.createPin:
|
||||
case Constants.keycardSharedState.repeatPin:
|
||||
case Constants.keycardSharedState.pinSet:
|
||||
return qsTr("Input seed phrase")
|
||||
return qsTr("Input recovery phrase")
|
||||
|
||||
case Constants.keycardSharedState.seedPhraseEnterWords:
|
||||
return qsTr("Yes, migrate key pair to this Keycard")
|
||||
|
@ -668,7 +668,7 @@ QtObject {
|
|||
return qsTr("Yes, migrate key pair to Keycard")
|
||||
|
||||
case Constants.keycardSharedState.wrongSeedPhrase:
|
||||
return qsTr("Try entering seed phrase again")
|
||||
return qsTr("Try entering recovery phrase again")
|
||||
|
||||
case Constants.keycardSharedState.keycardNotEmpty:
|
||||
return qsTr("Check what is stored on this Keycard")
|
||||
|
@ -950,7 +950,7 @@ QtObject {
|
|||
// to run unlock flow directly.
|
||||
return ""
|
||||
}
|
||||
return qsTr("Unlock using seed phrase")
|
||||
return qsTr("Unlock using recovery phrase")
|
||||
|
||||
case Constants.keycardSharedState.createPin:
|
||||
case Constants.keycardSharedState.repeatPin:
|
||||
|
@ -961,7 +961,7 @@ QtObject {
|
|||
return qsTr("Next")
|
||||
|
||||
case Constants.keycardSharedState.wrongSeedPhrase:
|
||||
return qsTr("Try entering seed phrase again")
|
||||
return qsTr("Try entering recovery phrase again")
|
||||
|
||||
case Constants.keycardSharedState.maxPukRetriesReached:
|
||||
if (root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.disableSeedPhraseForUnlock) {
|
||||
|
@ -1123,7 +1123,7 @@ QtObject {
|
|||
return qsTr("Done")
|
||||
|
||||
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.maxPukRetriesReached:
|
||||
|
|
|
@ -18,7 +18,7 @@ Item {
|
|||
|
||||
property bool wrongSeedPhrase: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.wrongSeedPhrase
|
||||
onWrongSeedPhraseChanged: {
|
||||
seedPhrase.setWrongSeedPhraseMessage(wrongSeedPhrase? qsTr("The phrase you’ve entered does not match this Keycard’s seed phrase") : "")
|
||||
seedPhrase.setWrongSeedPhraseMessage(wrongSeedPhrase? qsTr("The phrase you’ve entered does not match this Keycard’s recovery phrase") : "")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ Item {
|
|||
text: {
|
||||
switch (root.sharedKeycardModule.currentState.flowType) {
|
||||
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 ""
|
||||
|
@ -89,7 +89,7 @@ Item {
|
|||
text: {
|
||||
switch (root.sharedKeycardModule.currentState.flowType) {
|
||||
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 ""
|
||||
|
|
|
@ -62,7 +62,7 @@ Item {
|
|||
id: title
|
||||
Layout.preferredHeight: Constants.keycard.general.titleHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Confirm seed phrase words")
|
||||
text: qsTr("Confirm recovery phrase words")
|
||||
font.pixelSize: Constants.keycard.general.fontSize1
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
|
|
|
@ -1401,7 +1401,7 @@ Item {
|
|||
target: title
|
||||
text: {
|
||||
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) {
|
||||
return qsTr("This keycard has already been imported")
|
||||
|
@ -1459,7 +1459,7 @@ Item {
|
|||
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migrateKeypairToApp) {
|
||||
if (root.sharedKeycardModule.keyPairForProcessing.pairType === Constants.keycard.keyPairType.profile) {
|
||||
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)
|
||||
|
|
|
@ -45,7 +45,7 @@ Item {
|
|||
Layout.preferredWidth: parent.width
|
||||
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
|
||||
PropertyChanges {
|
||||
target: title
|
||||
text: qsTr("Write down your seed phrase")
|
||||
text: qsTr("Write down your recovery phrase")
|
||||
font.pixelSize: Constants.keycard.general.fontSize1
|
||||
font.weight: Font.Bold
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
PropertyChanges {
|
||||
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
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
|
|
|
@ -62,7 +62,7 @@ Item {
|
|||
|
||||
StatusListItem {
|
||||
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")
|
||||
|
||||
asset {
|
||||
|
|
|
@ -79,11 +79,6 @@ ColumnLayout {
|
|||
QtObject {
|
||||
id: d
|
||||
|
||||
property bool containsLower: false
|
||||
property bool containsUpper: false
|
||||
property bool containsNumbers: false
|
||||
property bool containsSymbols: false
|
||||
|
||||
readonly property var validatorRegexp: /^[!-~]+$/
|
||||
readonly property string validatorErrMessage: qsTr("Only ASCII letters, numbers, and symbols are allowed")
|
||||
readonly property string passTooLongErrMessage: qsTr("Maximum %n character(s)", "", Constants.maxPasswordLength)
|
||||
|
@ -244,7 +239,7 @@ ColumnLayout {
|
|||
Layout.alignment: root.contentAlignment
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("New password")
|
||||
text: qsTr("Choose password")
|
||||
}
|
||||
|
||||
StatusPasswordInput {
|
||||
|
@ -255,7 +250,7 @@ ColumnLayout {
|
|||
|
||||
Layout.alignment: root.contentAlignment
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Enter new password")
|
||||
placeholderText: qsTr("Type password")
|
||||
echoMode: showPassword ? TextInput.Normal : TextInput.Password
|
||||
rightPadding: showHideNewIcon.width + showHideNewIcon.anchors.rightMargin + Theme.padding / 2
|
||||
|
||||
|
@ -265,11 +260,6 @@ ColumnLayout {
|
|||
// Update strength indicator:
|
||||
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 (text.length === confirmPswInput.text.length) {
|
||||
|
@ -292,87 +282,11 @@ ColumnLayout {
|
|||
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 {
|
||||
StatusBaseText {
|
||||
text: qsTr("Confirm new password")
|
||||
text: qsTr("Repeat password")
|
||||
}
|
||||
|
||||
StatusPasswordInput {
|
||||
|
@ -384,7 +298,7 @@ ColumnLayout {
|
|||
z: root.zFront
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: root.contentAlignment
|
||||
placeholderText: qsTr("Enter new password")
|
||||
placeholderText: qsTr("Type password")
|
||||
echoMode: showPassword ? TextInput.Normal : TextInput.Password
|
||||
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 {
|
||||
id: errorTxt
|
||||
Layout.alignment: root.contentAlignment
|
||||
Layout.fillHeight: true
|
||||
font.pixelSize: 12
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1346,6 +1346,7 @@ QtObject {
|
|||
readonly property string welcome: "welcome_view"
|
||||
readonly property string privacyAndSecurity: "privacy_and_security_view"
|
||||
readonly property string startApp: "start_app_after_upgrade"
|
||||
readonly property string onboarding: "onboarding"
|
||||
}
|
||||
|
||||
enum MutingVariations {
|
||||
|
|
|
@ -91,6 +91,7 @@ QtObject {
|
|||
signal openTestnetPopup()
|
||||
|
||||
signal privacyPolicyRequested()
|
||||
signal termsOfUseRequested()
|
||||
|
||||
// Swap
|
||||
signal openSwapModalRequested(var formDataParams)
|
||||
|
|
|
@ -78,11 +78,11 @@ QtObject {
|
|||
`<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">` +
|
||||
`a {` +
|
||||
`color: ${textColor};` +
|
||||
`text-decoration: underline;` +
|
||||
(underlineLink ? `text-decoration: underline;` : `text-decoration: none;`) +
|
||||
`}` +
|
||||
(hoveredLink === linkUrl ? `a[href="${linkUrl}"] { text-decoration: underline; color: ${linkColor} }` : "") +
|
||||
`</style>` +
|
||||
|
|