diff --git a/libs/StatusQ/qml/Status/Core/StatusBaseText.qml b/libs/StatusQ/qml/Status/Core/StatusBaseText.qml index bc128ebfac..5cc724774a 100644 --- a/libs/StatusQ/qml/Status/Core/StatusBaseText.qml +++ b/libs/StatusQ/qml/Status/Core/StatusBaseText.qml @@ -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 diff --git a/storybook/pages/ColorsPage.qml b/storybook/pages/ColorsPage.qml index c8d4b68639..fc05a4c3e5 100644 --- a/storybook/pages/ColorsPage.qml +++ b/storybook/pages/ColorsPage.qml @@ -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 { diff --git a/storybook/pages/OnboardingLayoutPage.qml b/storybook/pages/OnboardingLayoutPage.qml new file mode 100644 index 0000000000..93fc82aee9 --- /dev/null +++ b/storybook/pages/OnboardingLayoutPage.qml @@ -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 diff --git a/storybook/pages/StatusPinInputPage.qml b/storybook/pages/StatusPinInputPage.qml new file mode 100644 index 0000000000..b33b4e65b8 --- /dev/null +++ b/storybook/pages/StatusPinInputPage.qml @@ -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 diff --git a/storybook/stubs/AppLayouts/Profile/stores/PrivacyStore.qml b/storybook/stubs/AppLayouts/Profile/stores/PrivacyStore.qml index dcece8a991..2587cd419c 100644 --- a/storybook/stubs/AppLayouts/Profile/stores/PrivacyStore.qml +++ b/storybook/stubs/AppLayouts/Profile/stores/PrivacyStore.qml @@ -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 {} diff --git a/storybook/stubs/shared/stores/BIP39_en.qml b/storybook/stubs/shared/stores/BIP39_en.qml index b275a1e685..7301449ae4 100644 --- a/storybook/stubs/shared/stores/BIP39_en.qml +++ b/storybook/stubs/shared/stores/BIP39_en.qml @@ -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... diff --git a/storybook/stubs/shared/stores/MetricsStore.qml b/storybook/stubs/shared/stores/MetricsStore.qml new file mode 100644 index 0000000000..2587cd419c --- /dev/null +++ b/storybook/stubs/shared/stores/MetricsStore.qml @@ -0,0 +1,3 @@ +import QtQml 2.15 + +QtObject {} diff --git a/storybook/stubs/shared/stores/qmldir b/storybook/stubs/shared/stores/qmldir index d7a633428c..b907b1108b 100644 --- a/storybook/stubs/shared/stores/qmldir +++ b/storybook/stubs/shared/stores/qmldir @@ -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 diff --git a/ui/StatusQ/src/StatusQ/Components/StatusImage.qml b/ui/StatusQ/src/StatusQ/Components/StatusImage.qml index 608414d4ab..c2b1915c42 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusImage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusImage.qml @@ -1,5 +1,4 @@ -import QtQuick 2.13 -import QtQuick.Window 2.15 +import QtQuick 2.15 /*! \qmltype StatusImage diff --git a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml index 90180869ca..1644ff7b13 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusSmartIdenticon.qml @@ -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 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusItemDelegate.qml b/ui/StatusQ/src/StatusQ/Controls/StatusItemDelegate.qml index 50bf3f2402..8a942e83a6 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusItemDelegate.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusItemDelegate.qml @@ -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 } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusPasswordStrengthIndicator.qml b/ui/StatusQ/src/StatusQ/Controls/StatusPasswordStrengthIndicator.qml index d8c58f95e6..013de9caf7 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusPasswordStrengthIndicator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusPasswordStrengthIndicator.qml @@ -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 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusPinInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusPinInput.qml index 2a7e16e9cc..dedebe274f 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusPinInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusPinInput.qml @@ -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 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml index c252a2691f..1f13b9a1e1 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml @@ -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 diff --git a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusIntValidator.qml b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusIntValidator.qml index b3f1ba7e51..1ee9d15577 100644 --- a/ui/StatusQ/src/StatusQ/Controls/Validators/StatusIntValidator.qml +++ b/ui/StatusQ/src/StatusQ/Controls/Validators/StatusIntValidator.qml @@ -1,4 +1,4 @@ -import QtQuick 2.14 +import QtQuick 2.15 import StatusQ.Controls 0.1 diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml b/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml index abb02f68d9..b6ee9b6d1f 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/StatusColors.qml @@ -86,5 +86,8 @@ QtObject { 'lightDesktopBlue10': '#ECEFFB', 'darkDesktopBlue10': '#273251', + + // new/mobile colors + 'neutral-95': '#0D1625' } } diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml index 0ecce225bb..6aa9d57d28 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml @@ -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) + } } diff --git a/ui/StatusQ/src/StatusQ/Popups/StatusSimpleTextPopup.qml b/ui/StatusQ/src/StatusQ/Popups/StatusSimpleTextPopup.qml new file mode 100644 index 0000000000..a2a31c9c1b --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Popups/StatusSimpleTextPopup.qml @@ -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 + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Popups/qmldir b/ui/StatusQ/src/StatusQ/Popups/qmldir index a5acf21cb0..db0110b4a8 100644 --- a/ui/StatusQ/src/StatusQ/Popups/qmldir +++ b/ui/StatusQ/src/StatusQ/Popups/qmldir @@ -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 diff --git a/ui/StatusQ/src/assets.qrc b/ui/StatusQ/src/assets.qrc index 8f7d43516f..d6610e3a52 100644 --- a/ui/StatusQ/src/assets.qrc +++ b/ui/StatusQ/src/assets.qrc @@ -8339,6 +8339,23 @@ assets/png/onboarding/profile_fetching_in_progress.png assets/png/onboarding/seed-phrase.png assets/png/onboarding/welcome.png + assets/png/onboarding/status_totebag_artwork_1.png + assets/png/onboarding/status_generate_keys.png + assets/png/onboarding/status_generate_keycard.png + assets/png/onboarding/create_profile_seed.png + assets/png/onboarding/create_profile_keycard.png + assets/png/onboarding/status_chat.png + assets/png/onboarding/status_key.png + assets/png/onboarding/status_keycard.png + assets/png/onboarding/status_keycard_multiple.png + assets/png/onboarding/status_seedphrase.png + assets/png/onboarding/enable_biometrics.png + assets/png/onboarding/keycard/empty.png + assets/png/onboarding/keycard/insert.png + assets/png/onboarding/keycard/invalid.png + assets/png/onboarding/keycard/reading.png + assets/png/onboarding/keycard/error.png + assets/png/onboarding/keycard/success.png assets/png/onRampProviders/latamex.png assets/png/onRampProviders/mercuryo.png assets/png/onRampProviders/moonPay.png diff --git a/ui/StatusQ/src/assets/png/onboarding/create_profile_keycard.png b/ui/StatusQ/src/assets/png/onboarding/create_profile_keycard.png new file mode 100644 index 0000000000..993e3bacf3 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/create_profile_keycard.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/create_profile_seed.png b/ui/StatusQ/src/assets/png/onboarding/create_profile_seed.png new file mode 100644 index 0000000000..ba8cf2fc3e Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/create_profile_seed.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/enable_biometrics.png b/ui/StatusQ/src/assets/png/onboarding/enable_biometrics.png new file mode 100644 index 0000000000..83538879b0 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/enable_biometrics.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/empty.png b/ui/StatusQ/src/assets/png/onboarding/keycard/empty.png new file mode 100644 index 0000000000..aa50cf9098 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/empty.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/error.png b/ui/StatusQ/src/assets/png/onboarding/keycard/error.png new file mode 100644 index 0000000000..245ea6610b Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/error.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/insert.png b/ui/StatusQ/src/assets/png/onboarding/keycard/insert.png new file mode 100644 index 0000000000..4ea5822092 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/insert.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/invalid.png b/ui/StatusQ/src/assets/png/onboarding/keycard/invalid.png new file mode 100644 index 0000000000..b9e1258c4c Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/invalid.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/reading.png b/ui/StatusQ/src/assets/png/onboarding/keycard/reading.png new file mode 100644 index 0000000000..afcd019ee4 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/reading.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/keycard/success.png b/ui/StatusQ/src/assets/png/onboarding/keycard/success.png new file mode 100644 index 0000000000..80f41fc28c Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/keycard/success.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_chat.png b/ui/StatusQ/src/assets/png/onboarding/status_chat.png new file mode 100644 index 0000000000..7757a05b6d Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_chat.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_generate_keycard.png b/ui/StatusQ/src/assets/png/onboarding/status_generate_keycard.png new file mode 100644 index 0000000000..c56732d540 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_generate_keycard.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_generate_keys.png b/ui/StatusQ/src/assets/png/onboarding/status_generate_keys.png new file mode 100644 index 0000000000..4c34857933 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_generate_keys.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_key.png b/ui/StatusQ/src/assets/png/onboarding/status_key.png new file mode 100644 index 0000000000..37d2b2ac4f Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_key.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_keycard.png b/ui/StatusQ/src/assets/png/onboarding/status_keycard.png new file mode 100644 index 0000000000..68755a7c5a Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_keycard.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_keycard_multiple.png b/ui/StatusQ/src/assets/png/onboarding/status_keycard_multiple.png new file mode 100644 index 0000000000..965e41663e Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_keycard_multiple.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_seedphrase.png b/ui/StatusQ/src/assets/png/onboarding/status_seedphrase.png new file mode 100644 index 0000000000..c76c843d30 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_seedphrase.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/status_totebag_artwork_1.png b/ui/StatusQ/src/assets/png/onboarding/status_totebag_artwork_1.png new file mode 100644 index 0000000000..1cea12b449 Binary files /dev/null and b/ui/StatusQ/src/assets/png/onboarding/status_totebag_artwork_1.png differ diff --git a/ui/StatusQ/src/assets/png/onboarding/welcome.png b/ui/StatusQ/src/assets/png/onboarding/welcome.png index 2f37f7147a..22668b7759 100644 Binary files a/ui/StatusQ/src/assets/png/onboarding/welcome.png and b/ui/StatusQ/src/assets/png/onboarding/welcome.png differ diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index b8adcf5354..b0e3df4a70 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -245,6 +245,7 @@ StatusQ/Popups/StatusSearchLocationMenu.qml StatusQ/Popups/StatusSearchPopup.qml StatusQ/Popups/StatusSearchPopupMenuItem.qml + StatusQ/Popups/StatusSimpleTextPopup.qml StatusQ/Popups/StatusStackModal.qml StatusQ/Popups/StatusSuccessAction.qml StatusQ/Popups/qmldir diff --git a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml index 04c21bd7ce..e4a2f9d5b3 100644 --- a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml +++ b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml @@ -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 { diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml new file mode 100644 index 0000000000..5fecbb8a8f --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml @@ -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 + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/components/NewsCarousel.qml b/ui/app/AppLayouts/Onboarding2/components/NewsCarousel.qml new file mode 100644 index 0000000000..78b4c44a71 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/components/NewsCarousel.qml @@ -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 + } + } + } + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml b/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml new file mode 100644 index 0000000000..0e08511d77 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/components/SeedphraseVerifyInput.qml @@ -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 + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/components/StepIndicator.qml b/ui/app/AppLayouts/Onboarding2/components/StepIndicator.qml new file mode 100644 index 0000000000..818f4d60d8 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/components/StepIndicator.qml @@ -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 + } +} diff --git a/ui/app/AppLayouts/Onboarding2/components/qmldir b/ui/app/AppLayouts/Onboarding2/components/qmldir new file mode 100644 index 0000000000..db34fbf6bf --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/components/qmldir @@ -0,0 +1,3 @@ +NewsCarousel 1.0 NewsCarousel.qml +SeedphraseVerifyInput 1.0 SeedphraseVerifyInput.qml +StepIndicator 1.0 StepIndicator.qml diff --git a/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml b/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.qml new file mode 100644 index 0000000000..c28b16b703 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/controls/ListItemButton.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 + } + ] +} diff --git a/ui/app/AppLayouts/Onboarding2/controls/OnboardingFrame.qml b/ui/app/AppLayouts/Onboarding2/controls/OnboardingFrame.qml new file mode 100644 index 0000000000..abd22549a5 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/controls/OnboardingFrame.qml @@ -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) + } +} diff --git a/ui/app/AppLayouts/Onboarding2/controls/qmldir b/ui/app/AppLayouts/Onboarding2/controls/qmldir new file mode 100644 index 0000000000..b513ee4081 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/controls/qmldir @@ -0,0 +1,2 @@ +OnboardingFrame 1.0 OnboardingFrame.qml +ListItemButton 1.0 ListItemButton.qml diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.qml new file mode 100644 index 0000000000..9597d83dc1 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseAcks.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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml new file mode 100644 index 0000000000..94fdc77c4d --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseIntro.qml @@ -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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml new file mode 100644 index 0000000000..11c6c359d3 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseOutro.qml @@ -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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml new file mode 100644 index 0000000000..414506344a --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml @@ -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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml new file mode 100644 index 0000000000..9a7f53ab78 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseVerify.qml @@ -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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml new file mode 100644 index 0000000000..94750192bc --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml @@ -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() + } + } + } + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml new file mode 100644 index 0000000000..1041eea266 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/CreatePasswordPage.qml @@ -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.") + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml new file mode 100644 index 0000000000..879f2023a0 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/CreateProfilePage.qml @@ -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() + } + } + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml b/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml new file mode 100644 index 0000000000..5a21d96a0c --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/EnableBiometricsPage.qml @@ -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) + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml b/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml new file mode 100644 index 0000000000..4adb4d4a18 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/HelpUsImproveStatusPage.qml @@ -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 + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml new file mode 100644 index 0000000000..866f1367ba --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardBasePage.qml @@ -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 + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml new file mode 100644 index 0000000000..ad13826e07 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml @@ -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() + } + } + } + ] +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml new file mode 100644 index 0000000000..1d1c417ac2 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml @@ -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: "".arg(Theme.palette.dangerColor1) + qsTr("Keycard locked") + "" + } + 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 + } + } + } + ] +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml new file mode 100644 index 0000000000..a2a74b2084 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml @@ -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() + } + } + ] +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml b/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml new file mode 100644 index 0000000000..5a6a81f04f --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/OnboardingPage.qml @@ -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 + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml new file mode 100644 index 0000000000..13b7923b5a --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml @@ -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() + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml b/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml new file mode 100644 index 0000000000..740394e393 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/WelcomePage.qml @@ -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
%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 + } + } +} diff --git a/ui/app/AppLayouts/Onboarding2/pages/qmldir b/ui/app/AppLayouts/Onboarding2/pages/qmldir new file mode 100644 index 0000000000..5cc71f3440 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/pages/qmldir @@ -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 diff --git a/ui/app/AppLayouts/Onboarding2/qmldir b/ui/app/AppLayouts/Onboarding2/qmldir new file mode 100644 index 0000000000..ac7c41394a --- /dev/null +++ b/ui/app/AppLayouts/Onboarding2/qmldir @@ -0,0 +1 @@ +OnboardingLayout 1.0 OnboardingLayout.qml diff --git a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml index 350f66989e..ab44ec774b 100644 --- a/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.qml +++ b/ui/app/AppLayouts/Profile/helpers/SettingsEntriesModel.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.
If they do, please use the Enter a Seed Phrase feature instead.") + experimentalTooltip: qsTr("Connection problems can happen.
If they do, please use the Enter a Recovery Phrase feature instead.") }, { subsection: Constants.settingsSubsection.messaging, diff --git a/ui/app/AppLayouts/Profile/popups/RemoveKeypairPopup.qml b/ui/app/AppLayouts/Profile/popups/RemoveKeypairPopup.qml index fb69669c80..f12731a395 100644 --- a/ui/app/AppLayouts/Profile/popups/RemoveKeypairPopup.qml +++ b/ui/app/AppLayouts/Profile/popups/RemoveKeypairPopup.qml @@ -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 } diff --git a/ui/app/AppLayouts/Profile/popups/backupseed/Acknowledgements.qml b/ui/app/AppLayouts/Profile/popups/backupseed/Acknowledgements.qml index 76482285f8..cf859e1e16 100644 --- a/ui/app/AppLayouts/Profile/popups/backupseed/Acknowledgements.qml +++ b/ui/app/AppLayouts/Profile/popups/backupseed/Acknowledgements.qml @@ -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 must back it up. The simplest way is to write it down offline and store it somewhere secure.") + text: qsTr("Your recovery phrase cannot be recovered if lost. Therefore, you must back it up. The simplest way is to write it down offline and store it somewhere secure.") 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 { diff --git a/ui/app/AppLayouts/Profile/popups/backupseed/BackupSeedStepBase.qml b/ui/app/AppLayouts/Profile/popups/backupseed/BackupSeedStepBase.qml index f5a87401f9..25f704b62f 100644 --- a/ui/app/AppLayouts/Profile/popups/backupseed/BackupSeedStepBase.qml +++ b/ui/app/AppLayouts/Profile/popups/backupseed/BackupSeedStepBase.qml @@ -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 diff --git a/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmSeedPhrasePanel.qml b/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmSeedPhrasePanel.qml index 19ee9d15bd..3f236eb7e1 100644 --- a/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmSeedPhrasePanel.qml +++ b/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmSeedPhrasePanel.qml @@ -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.\nAnyone who sees it can use it to access to your funds.") + text: qsTr("The next screen contains your recovery phrase.\nAnyone who sees it can use it to access to your funds.") Layout.fillWidth: true } } diff --git a/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmStoringSeedPhrasePanel.qml b/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmStoringSeedPhrasePanel.qml index 3a9566626d..ff1650481a 100644 --- a/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmStoringSeedPhrasePanel.qml +++ b/ui/app/AppLayouts/Profile/popups/backupseed/ConfirmStoringSeedPhrasePanel.qml @@ -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 } diff --git a/ui/app/AppLayouts/Profile/views/SyncingView.qml b/ui/app/AppLayouts/Profile/views/SyncingView.qml index 81370cccae..8fca6312d6 100644 --- a/ui/app/AppLayouts/Profile/views/SyncingView.qml +++ b/ui/app/AppLayouts/Profile/views/SyncingView.qml @@ -187,7 +187,7 @@ SettingsContentBase { anchors.left: parent.right anchors.leftMargin: 8 anchors.verticalCenter: parent.verticalCenter - tooltipText: qsTr("Connection problems can happen.
If they do, please use the Enter a Seed Phrase feature instead.") + tooltipText: qsTr("Connection problems can happen.
If they do, please use the Enter a Recovery Phrase feature instead.") } } } diff --git a/ui/app/AppLayouts/Profile/views/keycard/MainView.qml b/ui/app/AppLayouts/Profile/views/keycard/MainView.qml index fad9c1f529..8aa69932f2 100644 --- a/ui/app/AppLayouts/Profile/views/keycard/MainView.qml +++ b/ui/app/AppLayouts/Profile/views/keycard/MainView.qml @@ -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 { diff --git a/ui/app/AppLayouts/Wallet/panels/SeedPhraseBackupWarning.qml b/ui/app/AppLayouts/Wallet/panels/SeedPhraseBackupWarning.qml index 75023b3108..ea94788358 100644 --- a/ui/app/AppLayouts/Wallet/panels/SeedPhraseBackupWarning.qml +++ b/ui/app/AppLayouts/Wallet/panels/SeedPhraseBackupWarning.qml @@ -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 diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index e373822fd3..c22ffeb62e 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -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() diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 79a7cff2bb..0002501e75 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -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 - textFormat: Text.MarkdownText - text: SQUtils.StringUtils.readTextFile(":/imports/assets/docs/privacy.mdwn") - onLinkActivated: Global.openLinkWithConfirmation(link, SQUtils.StringUtils.extractDomainFromLink(link)) - } + 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 } } diff --git a/ui/imports/shared/panels/EnterSeedPhrase.qml b/ui/imports/shared/panels/EnterSeedPhrase.qml index 5e42aaaef8..7e8d9a6008 100644 --- a/ui/imports/shared/panels/EnterSeedPhrase.qml +++ b/ui/imports/shared/panels/EnterSeedPhrase.qml @@ -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 } } diff --git a/ui/imports/shared/popups/addaccount/AddAccountPopup.qml b/ui/imports/shared/popups/addaccount/AddAccountPopup.qml index 3afc8bc9fb..88349ea325 100644 --- a/ui/imports/shared/popups/addaccount/AddAccountPopup.qml +++ b/ui/imports/shared/popups/addaccount/AddAccountPopup.qml @@ -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 "" diff --git a/ui/imports/shared/popups/addaccount/panels/SelectOrigin.qml b/ui/imports/shared/popups/addaccount/panels/SelectOrigin.qml index 87ac5e341f..dfd85b6c74 100644 --- a/ui/imports/shared/popups/addaccount/panels/SelectOrigin.qml +++ b/ui/imports/shared/popups/addaccount/panels/SelectOrigin.qml @@ -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") } diff --git a/ui/imports/shared/popups/addaccount/panels/WatchOnlyAddressSection.qml b/ui/imports/shared/popups/addaccount/panels/WatchOnlyAddressSection.qml index cb53786cff..f1ab663ed3 100644 --- a/ui/imports/shared/popups/addaccount/panels/WatchOnlyAddressSection.qml +++ b/ui/imports/shared/popups/addaccount/panels/WatchOnlyAddressSection.qml @@ -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 } } diff --git a/ui/imports/shared/popups/addaccount/states/ConfirmAddingNewMasterKey.qml b/ui/imports/shared/popups/addaccount/states/ConfirmAddingNewMasterKey.qml index c8bdd3da87..40f88a2b20 100644 --- a/ui/imports/shared/popups/addaccount/states/ConfirmAddingNewMasterKey.qml +++ b/ui/imports/shared/popups/addaccount/states/ConfirmAddingNewMasterKey.qml @@ -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.

Your seed phrase cannot be recovered if lost. Therefore, you must back it up. The simplest way is to write it down offline and store it somewhere secure.") + text: qsTr("Your recovery phrase is a 12-word passcode to your funds.

Your recovery phrase cannot be recovered if lost. Therefore, you must back it up. The simplest way is to write it down offline and store it somewhere secure.") } 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.") } } } diff --git a/ui/imports/shared/popups/addaccount/states/ConfirmSeedPhraseBackup.qml b/ui/imports/shared/popups/addaccount/states/ConfirmSeedPhraseBackup.qml index 0d5529e116..f077644dda 100644 --- a/ui/imports/shared/popups/addaccount/states/ConfirmSeedPhraseBackup.qml +++ b/ui/imports/shared/popups/addaccount/states/ConfirmSeedPhraseBackup.qml @@ -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 } diff --git a/ui/imports/shared/popups/addaccount/states/DisplaySeedPhrase.qml b/ui/imports/shared/popups/addaccount/states/DisplaySeedPhrase.qml index 64a6ec86a0..02493128dc 100644 --- a/ui/imports/shared/popups/addaccount/states/DisplaySeedPhrase.qml +++ b/ui/imports/shared/popups/addaccount/states/DisplaySeedPhrase.qml @@ -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.
Anyone who sees it can use it to access to your funds.") + text: qsTr("The next screen contains your recovery phrase.
Anyone who sees it can use it to access to your funds.") } } } diff --git a/ui/imports/shared/popups/addaccount/states/EnterSeedPhraseWord.qml b/ui/imports/shared/popups/addaccount/states/EnterSeedPhraseWord.qml index 3d9355b4e5..91d5370854 100644 --- a/ui/imports/shared/popups/addaccount/states/EnterSeedPhraseWord.qml +++ b/ui/imports/shared/popups/addaccount/states/EnterSeedPhraseWord.qml @@ -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) } diff --git a/ui/imports/shared/popups/addaccount/states/SelectMasterKey.qml b/ui/imports/shared/popups/addaccount/states/SelectMasterKey.qml index d7898cdea2..69297f8742 100644 --- a/ui/imports/shared/popups/addaccount/states/SelectMasterKey.qml +++ b/ui/imports/shared/popups/addaccount/states/SelectMasterKey.qml @@ -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 diff --git a/ui/imports/shared/popups/common/EnterPrivateKey.qml b/ui/imports/shared/popups/common/EnterPrivateKey.qml index 99df8272fb..e4491aa16d 100644 --- a/ui/imports/shared/popups/common/EnterPrivateKey.qml +++ b/ui/imports/shared/popups/common/EnterPrivateKey.qml @@ -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" diff --git a/ui/imports/shared/popups/common/EnterSeedPhrase.qml b/ui/imports/shared/popups/common/EnterSeedPhrase.qml index 697fff190b..4949d6e5da 100644 --- a/ui/imports/shared/popups/common/EnterSeedPhrase.qml +++ b/ui/imports/shared/popups/common/EnterSeedPhrase.qml @@ -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) } diff --git a/ui/imports/shared/popups/keycard/KeycardPopup.qml b/ui/imports/shared/popups/keycard/KeycardPopup.qml index 9ea4a9f342..be365535ee 100644 --- a/ui/imports/shared/popups/keycard/KeycardPopup.qml +++ b/ui/imports/shared/popups/keycard/KeycardPopup.qml @@ -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: diff --git a/ui/imports/shared/popups/keycard/KeycardPopupDetails.qml b/ui/imports/shared/popups/keycard/KeycardPopupDetails.qml index ea78f3fd55..b179bdd42a 100644 --- a/ui/imports/shared/popups/keycard/KeycardPopupDetails.qml +++ b/ui/imports/shared/popups/keycard/KeycardPopupDetails.qml @@ -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: diff --git a/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml b/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml index 9c862ccc3e..dcc91ff32b 100644 --- a/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml +++ b/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml @@ -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 "" diff --git a/ui/imports/shared/popups/keycard/states/EnterSeedPhraseWords.qml b/ui/imports/shared/popups/keycard/states/EnterSeedPhraseWords.qml index 0686cca146..b341aff4e7 100644 --- a/ui/imports/shared/popups/keycard/states/EnterSeedPhraseWords.qml +++ b/ui/imports/shared/popups/keycard/states/EnterSeedPhraseWords.qml @@ -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 diff --git a/ui/imports/shared/popups/keycard/states/KeycardInit.qml b/ui/imports/shared/popups/keycard/states/KeycardInit.qml index 2bf7da129a..e2240df2ed 100644 --- a/ui/imports/shared/popups/keycard/states/KeycardInit.qml +++ b/ui/imports/shared/popups/keycard/states/KeycardInit.qml @@ -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) diff --git a/ui/imports/shared/popups/keycard/states/SeedPhrase.qml b/ui/imports/shared/popups/keycard/states/SeedPhrase.qml index 40211437f8..22b0cacfb6 100644 --- a/ui/imports/shared/popups/keycard/states/SeedPhrase.qml +++ b/ui/imports/shared/popups/keycard/states/SeedPhrase.qml @@ -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.
Anyone who sees it can use it to access to your funds.") + text: qsTr("The next screen contains your recovery phrase.
Anyone who sees it can use it to access to your funds.") font.pixelSize: Constants.keycard.general.fontSize2 wrapMode: Text.WordWrap textFormat: Text.RichText diff --git a/ui/imports/shared/popups/keypairimport/states/SelectImportMethod.qml b/ui/imports/shared/popups/keypairimport/states/SelectImportMethod.qml index e7759d033a..e5dc10bee3 100644 --- a/ui/imports/shared/popups/keypairimport/states/SelectImportMethod.qml +++ b/ui/imports/shared/popups/keypairimport/states/SelectImportMethod.qml @@ -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 { diff --git a/ui/imports/shared/views/PasswordView.qml b/ui/imports/shared/views/PasswordView.qml index ed5fc8b875..ca50e64fd8 100644 --- a/ui/imports/shared/views/PasswordView.qml +++ b/ui/imports/shared/views/PasswordView.qml @@ -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 + } } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 9f04a462b2..4b22c5ce12 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -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 { diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index e1c8af3102..75e26d4156 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -91,6 +91,7 @@ QtObject { signal openTestnetPopup() signal privacyPolicyRequested() + signal termsOfUseRequested() // Swap signal openSwapModalRequested(var formDataParams) diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 1c26cb7a19..abce08f651 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -78,11 +78,11 @@ QtObject { `${link}` } - 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 `` +