fix(SeedPhrase): Fixing seed phrase validation (#13496)

* fix(SeedPhrase): Fixing seed phrase validation

1. Validate each word after the editing is finished
2. Fixing the seed phrase suggestions where the suggestions box was hidden behind other ui elements
3. Propagate editingFinished signal in StatusBaseInput, StatusInput, StatusSeedPhraseInput
4. Fixing undefined `mnemonicIndex` errors

* fix: Refactoring of SeedPhraseInputView

Remove duplicated code and use EnterSeedPhrase component

+ Added storybook page

* fix(Onboarding): Fixing seed phrase validation on windows

The seed phrase validation fails on windows due to the dictionary line endings

* chore(squish): Update e2e tests to the new enter seed phrase panel construction

* fix: Load english dictionary from local file using StringUtils
This commit is contained in:
Alex Jbanca 2024-02-14 15:50:14 +02:00 committed by GitHub
parent 46e256673c
commit 8a69f3bc63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 777 additions and 435 deletions

View File

@ -0,0 +1,14 @@
import QtQuick 2.15
import shared.panels 1.0
import Storybook 1.0
Item {
EnterSeedPhrase {
anchors.centerIn: parent
}
}
// category: Panels

View File

@ -0,0 +1,72 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import AppLayouts.Onboarding.views 1.0
import AppLayouts.Onboarding.stores 1.0
import utils 1.0
import Storybook 1.0
SplitView {
Logs { id: logs }
SeedPhraseInputView {
SplitView.fillWidth: true
SplitView.fillHeight: true
startupStore: StartupStore {
id: startupStore
function validMnemonic(mnemonic) {
return true
}
property QtObject startupModuleInst: QtObject {
property int keycardData: keycardDataCheckbox.checked ? 0 : Constants.predefinedKeycardData.wrongSeedPhrase
property string flowType: flowTypeComboBox.currentText
}
property QtObject currentStartupState: QtObject {
property string flowType: flowTypeComboBox.currentText
}
function doPrimaryAction() {
logs.logEvent("Primary action clicked")
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
SplitView.preferredWidth: 300
logsView.logText: logs.logText
ColumnLayout {
spacing: 10
width: parent.width
CheckBox {
id: keycardDataCheckbox
text: "Has keycard data"
checked: startupStore.startupModuleInst.keycardData != Constants.predefinedKeycardData.wrongSeedPhrase
}
Label {
text: "Current startup flow type"
}
ComboBox {
id: flowTypeComboBox
Layout.fillWidth: true
model: Object.values(Constants.startupFlow).filter(flow => flow != "" && typeof flow === "string")
}
}
}
}
// category: Panels

View File

@ -0,0 +1,413 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQml 2.15
import QtQml.Models 2.15
import StatusQ.Core.Utils 0.1
import QtTest 1.15
import shared.panels 1.0
import utils 1.0
Item {
id: root
width: 600
height: 400
TestCase {
name: "EnterSeedPhraseTest"
when: windowShown
Component {
id: enterSeedPhraseComponent
EnterSeedPhrase {
id: enterSeedPhrase
anchors.fill: parent
dictionary: ListModel {}
readonly property SignalSpy submitSpy: SignalSpy { target: enterSeedPhrase; signalName: "submitSeedPhrase" }
readonly property SignalSpy seedPhraseUpdatedSpy: SignalSpy { target: enterSeedPhrase; signalName: "seedPhraseUpdated" }
}
}
property EnterSeedPhrase itemUnderTest: null
function generateDictionaryVariation(baseDictionary) {
let dictionaryVariation = baseDictionary.map((word) => word + "a")
dictionaryVariation = baseDictionary.map((word) => word + "b").concat(dictionaryVariation)
dictionaryVariation = baseDictionary.map((word) => word + "c").concat(dictionaryVariation)
dictionaryVariation = baseDictionary.map((word) => word + "d").concat(dictionaryVariation)
dictionaryVariation.sort()
return dictionaryVariation
}
function init() {
itemUnderTest = createTemporaryObject(enterSeedPhraseComponent, root)
waitForItemPolished(itemUnderTest)
waitForRendering(itemUnderTest)
}
function test_componentCreation() {
verify(itemUnderTest !== null, "Component creation failed")
}
// Test seed phrase input by typing on the keyboard
// The seed phrase is valid and the typing is done without any space between words
// This is the most common way to input a seed phrase
function test_seedPhraseKeyboardInput() {
//generate a seed phrase
const expectedSeedPhrase = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
expectedSeedPhrase.sort();
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(expectedSeedPhrase.map((word) => ({seedWord: word})))
//Type the seed phrase. No space is needed between words
const str = expectedSeedPhrase.join("")
for (let i = 0; i < str.length; i++) {
keyPress(str.charAt(i))
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyClick(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 1, "submitSeedPhrase signal was not emitted")
// This signal is emitted multiple times due to the way the seed phrase is updated and validated
// The minimum is the length if the seed phrase
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by typing on the keyboard
// The seed phrase is valid and the typing is done with a space between words
// The space between words is ignored and the seed should be valid
function test_seedPhraseKeyboardInputWithExtraSpace() {
//generate a seed phrase
const expectedSeedPhrase = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
expectedSeedPhrase.sort();
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(expectedSeedPhrase.map((word) => ({seedWord: word})))
//Type the seed phrase. A space is needed between words
const str = expectedSeedPhrase.join(" ")
for (let i = 0; i < str.length; i++) {
keyPress(str.charAt(i))
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyClick(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 1, "submitSeedPhrase signal was not emitted")
// This signal is emitted multiple times due to the way the seed phrase is updated and validated
// The minimum is the length if the seed phrase
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by pasting from clipboard
// The seed phrase is valid and the clipboard seed is space separated
function test_seedPhrasePaste() {
//generate a seed phrase
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
expectedSeedPhrase.sort();
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(expectedSeedPhrase.map((word) => ({seedWord: word})))
const clipboardHelper = createTemporaryQmlObject("import QtQuick 2.15; QtObject { property var getFromClipboard }", root)
clipboardHelper.getFromClipboard = () => expectedSeedPhrase.join(" ")
Utils.globalUtilsInst = clipboardHelper
// Trigger the paste action
keyClick("v", Qt.ControlModifier)
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyClick(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 1, "submitSeedPhrase signal was not emitted")
// This signal is emitted multiple times due to the way the seed phrase is updated and validated
// The minimum is the length if the seed phrase
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test the seed phrase by choosing from the suggestions
// The seed phrase is valid and the user selects the words from the suggestions
function test_seedPhraseChooseFromSuggestions() {
//generate a seed phrase
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
// Suggestions dialog is expected to receive key events when there's multiple suggestions
let downKeyEvents = 0
for (let i = 0; i < expectedSeedPhrase.length; i++) {
keySequence(expectedSeedPhrase[i].substring(0, 4).split('').join(','))
for (let j = 0; j < downKeyEvents; j++) {
keyClick(Qt.Key_Down)
}
downKeyEvents = downKeyEvents === 3 ? 0 : downKeyEvents + 1
keyClick(Qt.Key_Tab)
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyPress(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 1, "submitSeedPhrase signal was not emitted")
// This signal is emitted multiple times due to the way the seed phrase is updated and validated
// The minimum is the length if the seed phrase
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by typing on the keyboard
// The seed phrase is invalidated by the external isSeedPhraseValid
function test_invalidatedSeedPhraseKeyboardInput() {
//generate a seed phrase
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return false
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
//Type the seed phrase
const str = expectedSeedPhrase.join("")
for (let i = 0; i < str.length; i++) {
keyPress(str.charAt(i))
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyClick(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 0, "submitSeedPhrase signal was emitted")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by pasting from clipboard
// The seed phrase is invalidated by the external isSeedPhraseValid
function test_invalidatedSeedPhrasePaste() {
//generate a seed phrase
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return false
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
const clipboardHelper = createTemporaryQmlObject("import QtQuick 2.15; QtObject { property var getFromClipboard }", root)
clipboardHelper.getFromClipboard = () => expectedSeedPhrase.join(" ")
Utils.globalUtilsInst = clipboardHelper
// Trigger the paste action
keyClick("v", Qt.ControlModifier)
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
keyClick(Qt.Key_Enter)
verify(itemUnderTest.submitSpy.count === 0, "submitSeedPhrase signal was emitted")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by typing on the keyboard
// The seed phrase is invalid due to the length
function test_invalidLengthSeedPhrase() {
const expectedSeedPhrase = ["abandona", "abilityb", "ablec"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
//Type the seed phrase
const str = expectedSeedPhrase.join("")
for (let i = 0; i < str.length; i++) {
keyPress(str.charAt(i))
}
keyClick(Qt.Key_Enter)
verify(!isSeedPhraseValidCalled, "isSeedPhraseValid was called when it should not have been")
verify(itemUnderTest.submitSpy.count === 0, "submitSeedPhrase signal was emitted")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test seed phrase input by typing on the keyboard
// The seed phrase is invalid due to the dictionary word
function test_invalidDictionarySeedPhrase() {
const expectedSeedPhrase = ["abandonna", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
// ^^ invalid word
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
verify(seedPhrase === expectedSeedPhrase.join(" "), "Seed phrase is not valid")
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
//Type the seed phrase
const str = expectedSeedPhrase.join("")
for (let i = 0; i < str.length; i++) {
keyPress(str.charAt(i))
if (i === 8) {
// The first word is invalid. Move on to the next word
keyPress(Qt.Key_Tab)
}
}
keyClick(Qt.Key_Enter)
print (itemUnderTest.seedPhraseUpdatedSpy.count)
verify(itemUnderTest.submitSpy.count === 0, "submitSeedPhrase signal was emitted")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test suggestions are active after the seed phrase word is updated
function test_suggestionsActiveAfterUpdatingWord() {
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidentd"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
let lastVerifiedSeedPhrase = ""
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
lastVerifiedSeedPhrase = seedPhrase
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
// Suggestions dialog is expected to receive key events when there's multiple suggestions
let downKeyEvents = 0
for (let i = 0; i < expectedSeedPhrase.length; i++) {
keySequence(expectedSeedPhrase[i].substring(0, 4).split('').join(','))
for (let j = 0; j < downKeyEvents; j++) {
keyClick(Qt.Key_Down)
}
downKeyEvents = downKeyEvents === 3 ? 0 : downKeyEvents + 1
keyClick(Qt.Key_Tab)
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
isSeedPhraseValidCalled = false
keyPress(Qt.Key_Backspace)
wait (500) // Wait for the suggestions to appear
keyClick(Qt.Key_Tab)
keyClick(Qt.Key_Enter)
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
print (lastVerifiedSeedPhrase)
verify(lastVerifiedSeedPhrase === expectedSeedPhrase.join(" ").slice(0, -1) + "a", "Seed phrase is not updated")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
}
// Test suggestions are active after the seed phrase word is fixed
function test_suggestionsActiveAfterFixingWord() {
const expectedSeedPhrase = ["abandona", "abilityb", "ablec", "aboutd", "abovea", "absentb", "absorbc", "abstractd", "absurda", "abuseb", "accessc", "accidenntd"]
expectedSeedPhrase.sort()
const baseDictionary = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident"]
let dictionaryVariation = generateDictionaryVariation(baseDictionary)
let isSeedPhraseValidCalled = false
let lastVerifiedSeedPhrase = ""
itemUnderTest.isSeedPhraseValid = (seedPhrase) => {
lastVerifiedSeedPhrase = seedPhrase
isSeedPhraseValidCalled = true
return true
}
itemUnderTest.dictionary.append(dictionaryVariation.map((word) => ({seedWord: word})))
// Suggestions dialog is expected to receive key events when there's multiple suggestions
let downKeyEvents = 0
for (let i = 0; i < expectedSeedPhrase.length; i++) {
keySequence(expectedSeedPhrase[i].substring(0, 4).split('').join(','))
for (let j = 0; j < downKeyEvents; j++) {
keyClick(Qt.Key_Down)
}
downKeyEvents = downKeyEvents === 3 ? 0 : downKeyEvents + 1
keyClick(Qt.Key_Tab)
}
verify(isSeedPhraseValidCalled, "isSeedPhraseValid is not called")
isSeedPhraseValidCalled = false
for (let i = 0; i < 2; i++) {
keyPress(Qt.Key_Backspace)
}
wait (500) // Wait for the suggestions to appear
keyClick(Qt.Key_Tab)
keyClick(Qt.Key_Enter)
verify(isSeedPhraseValidCalled, "isSeedPhraseValid was not called")
verify(lastVerifiedSeedPhrase === expectedSeedPhrase.join(" ").slice(0, -3) + "ta", "Seed phrase is not updated")
verify(itemUnderTest.seedPhraseUpdatedSpy.count >= expectedSeedPhrase.length, "seedPhraseUpdate signal was not emitted")
verify(itemUnderTest.submitSpy.count === 1, "submitSeedPhrase signal was emitted")
}
}
}

View File

@ -0,0 +1,18 @@
import QtQuick 2.13
ListModel {
id: root
Component.onCompleted: {
var englishWords = [
"apple", "banana", "cat", "dog", "elephant", "fish", "grape", "horse", "ice cream", "jellyfish",
"kiwi", "lemon", "mango", "nut", "orange", "pear", "quail", "rabbit", "strawberry", "turtle",
"umbrella", "violet", "watermelon", "xylophone", "yogurt", "zebra"
// Add more English words here...
];
for (var i = 0; i < englishWords.length; i++) {
root.append({ seedWord: englishWords[i] });
}
}
}

View File

@ -3,3 +3,4 @@ CurrenciesStore 1.0 CurrenciesStore.qml
NetworkConnectionStore 1.0 NetworkConnectionStore.qml NetworkConnectionStore 1.0 NetworkConnectionStore.qml
singleton RootStore 1.0 RootStore.qml singleton RootStore 1.0 RootStore.qml
TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml
BIP39_en 1.0 BIP39_en.qml

View File

@ -83,7 +83,7 @@ class AddNewAccountPopup(BasePopup):
else: else:
raise RuntimeError("Wrong amount of seed words", len(seed_phrase_words)) raise RuntimeError("Wrong amount of seed words", len(seed_phrase_words))
for count, word in enumerate(seed_phrase_words, start=1): for count, word in enumerate(seed_phrase_words, start=1):
self._seed_phrase_word_text_edit.object_name['objectName'] = f'statusSeedPhraseInputField{count}' self._seed_phrase_word_text_edit.object_name['objectName'] = f'enterSeedPhraseInputField{count}'
self._seed_phrase_word_text_edit.text = word self._seed_phrase_word_text_edit.text = word
seed_phrase_name = ''.join([word[0] for word in seed_phrase_words[:10]]) seed_phrase_name = ''.join([word[0] for word in seed_phrase_words[:10]])
self._seed_phrase_phrase_key_name_text_edit.text = seed_phrase_name self._seed_phrase_phrase_key_name_text_edit.text = seed_phrase_name

View File

@ -30,34 +30,34 @@ onboarding_back_button = {"container": statusDesktop_mainWindow, "objectName": "
# Seed phrase form: # Seed phrase form:
import_a_seed_phrase_StatusBaseText = {"container": statusDesktop_mainWindow, "text": "Import a seed phrase", "type": "StatusBaseText", "unnamed": 1, "visible": True} import_a_seed_phrase_StatusBaseText = {"container": statusDesktop_mainWindow, "text": "Import a seed phrase", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWindow_switchTabBar_StatusSwitchTabBar = {"container": statusDesktop_mainWindow, "objectName": "onboardingSeedPhraseSwitchBar", "type": "StatusSwitchTabBar"} mainWindow_switchTabBar_StatusSwitchTabBar = {"container": statusDesktop_mainWindow, "objectName": "enterSeedPhraseSwitchBar", "type": "StatusSwitchTabBar"}
switchTabBar_12_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "12SeedButton", "type": "StatusSwitchTabButton"} switchTabBar_12_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "12SeedButton", "type": "StatusSwitchTabButton"}
switchTabBar_18_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "18SeedButton", "type": "StatusSwitchTabButton"} switchTabBar_18_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "18SeedButton", "type": "StatusSwitchTabButton"}
switchTabBar_24_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "24SeedButton", "type": "StatusSwitchTabButton"} switchTabBar_24_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "24SeedButton", "type": "StatusSwitchTabButton"}
seedPhraseView_Submit_Button = {"container": statusDesktop_mainWindow, "objectName": "seedPhraseViewSubmitButton", "type": "StatusButton"} seedPhraseView_Submit_Button = {"container": statusDesktop_mainWindow, "objectName": "seedPhraseViewSubmitButton", "type": "StatusButton"}
onboarding_InvalidSeed_Text = {"container": statusDesktop_mainWindow, "objectName": "onboardingInvalidSeedText", "type": "StatusBaseText"} onboarding_InvalidSeed_Text = {"container": statusDesktop_mainWindow, "objectName": "onboardingInvalidSeedText", "type": "StatusBaseText"}
onboarding_SeedPhrase_Input_TextField_1 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField1"} onboarding_SeedPhrase_Input_TextField_1 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField1"}
onboarding_SeedPhrase_Input_TextField_2 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField2"} onboarding_SeedPhrase_Input_TextField_2 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField2"}
onboarding_SeedPhrase_Input_TextField_3 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField3"} onboarding_SeedPhrase_Input_TextField_3 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField3"}
onboarding_SeedPhrase_Input_TextField_4 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField4"} onboarding_SeedPhrase_Input_TextField_4 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField4"}
onboarding_SeedPhrase_Input_TextField_5 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField5"} onboarding_SeedPhrase_Input_TextField_5 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField5"}
onboarding_SeedPhrase_Input_TextField_6 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField6"} onboarding_SeedPhrase_Input_TextField_6 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField6"}
onboarding_SeedPhrase_Input_TextField_7 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField7"} onboarding_SeedPhrase_Input_TextField_7 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField7"}
onboarding_SeedPhrase_Input_TextField_8 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField8"} onboarding_SeedPhrase_Input_TextField_8 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField8"}
onboarding_SeedPhrase_Input_TextField_9 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField9"} onboarding_SeedPhrase_Input_TextField_9 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField9"}
onboarding_SeedPhrase_Input_TextField_10 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField10"} onboarding_SeedPhrase_Input_TextField_10 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField10"}
onboarding_SeedPhrase_Input_TextField_11 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField11"} onboarding_SeedPhrase_Input_TextField_11 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField11"}
onboarding_SeedPhrase_Input_TextField_12 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField12"} onboarding_SeedPhrase_Input_TextField_12 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField12"}
onboarding_SeedPhrase_Input_TextField_13 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField13"} onboarding_SeedPhrase_Input_TextField_13 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField13"}
onboarding_SeedPhrase_Input_TextField_14 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField14"} onboarding_SeedPhrase_Input_TextField_14 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField14"}
onboarding_SeedPhrase_Input_TextField_15 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField15"} onboarding_SeedPhrase_Input_TextField_15 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField15"}
onboarding_SeedPhrase_Input_TextField_16 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField16"} onboarding_SeedPhrase_Input_TextField_16 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField16"}
onboarding_SeedPhrase_Input_TextField_17 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField17"} onboarding_SeedPhrase_Input_TextField_17 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField17"}
onboarding_SeedPhrase_Input_TextField_18 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField18"} onboarding_SeedPhrase_Input_TextField_18 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField18"}
onboarding_SeedPhrase_Input_TextField_19 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField19"} onboarding_SeedPhrase_Input_TextField_19 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField19"}
onboarding_SeedPhrase_Input_TextField_20 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField20"} onboarding_SeedPhrase_Input_TextField_20 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField20"}
onboarding_SeedPhrase_Input_TextField_21 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField21"} onboarding_SeedPhrase_Input_TextField_21 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField21"}
onboarding_SeedPhrase_Input_TextField_22 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField22"} onboarding_SeedPhrase_Input_TextField_22 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField22"}
onboarding_SeedPhrase_Input_TextField_23 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField23"} onboarding_SeedPhrase_Input_TextField_23 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField23"}
onboarding_SeedPhrase_Input_TextField_24 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField24"} onboarding_SeedPhrase_Input_TextField_24 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField24"}

View File

@ -126,34 +126,34 @@ mainWallet_AddEditAccountPopup_SeedPhraseWordAtIndex_Placeholder = {"container":
mainWallet_AddEditAccountPopup_EnterSeedPhraseWordComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-EnterSeedPhraseWord", "type": "StatusInput", "visible": True} mainWallet_AddEditAccountPopup_EnterSeedPhraseWordComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-EnterSeedPhraseWord", "type": "StatusInput", "visible": True}
mainWallet_AddEditAccountPopup_EnterSeedPhraseWord = {"container": mainWallet_AddEditAccountPopup_EnterSeedPhraseWordComponent, "type": "TextEdit", "unnamed": 1, "visible": True} mainWallet_AddEditAccountPopup_EnterSeedPhraseWord = {"container": mainWallet_AddEditAccountPopup_EnterSeedPhraseWordComponent, "type": "TextEdit", "unnamed": 1, "visible": True}
confirmSeedPhrasePanel_StatusSeedPhraseInput = {"container": statusDesktop_mainWindow, "type": "StatusSeedPhraseInput", "visible": True} confirmSeedPhrasePanel_StatusSeedPhraseInput = {"container": statusDesktop_mainWindow, "type": "StatusSeedPhraseInput", "visible": True}
mainWallet_AddEditAccountPopup_SPWord = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": RegularExpression("statusSeedPhraseInputField*")} mainWallet_AddEditAccountPopup_SPWord = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": RegularExpression("enterSeedPhraseInputField*")}
mainWallet_AddEditAccountPopup_12WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "12SeedButton", "type": "StatusSwitchTabButton"} mainWallet_AddEditAccountPopup_12WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "12SeedButton", "type": "StatusSwitchTabButton"}
mainWallet_AddEditAccountPopup_18WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "18SeedButton", "type": "StatusSwitchTabButton"} mainWallet_AddEditAccountPopup_18WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "18SeedButton", "type": "StatusSwitchTabButton"}
mainWallet_AddEditAccountPopup_24WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "24SeedButton", "type": "StatusSwitchTabButton"} mainWallet_AddEditAccountPopup_24WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "24SeedButton", "type": "StatusSwitchTabButton"}
mainWallet_AddEditAccountPopup_SPWord_1 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField1"} mainWallet_AddEditAccountPopup_SPWord_1 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField1"}
mainWallet_AddEditAccountPopup_SPWord_2 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField2"} mainWallet_AddEditAccountPopup_SPWord_2 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField2"}
mainWallet_AddEditAccountPopup_SPWord_3 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField3"} mainWallet_AddEditAccountPopup_SPWord_3 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField3"}
mainWallet_AddEditAccountPopup_SPWord_4 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField4"} mainWallet_AddEditAccountPopup_SPWord_4 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField4"}
mainWallet_AddEditAccountPopup_SPWord_5 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField5"} mainWallet_AddEditAccountPopup_SPWord_5 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField5"}
mainWallet_AddEditAccountPopup_SPWord_6 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField6"} mainWallet_AddEditAccountPopup_SPWord_6 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField6"}
mainWallet_AddEditAccountPopup_SPWord_7 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField7"} mainWallet_AddEditAccountPopup_SPWord_7 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField7"}
mainWallet_AddEditAccountPopup_SPWord_8 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField8"} mainWallet_AddEditAccountPopup_SPWord_8 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField8"}
mainWallet_AddEditAccountPopup_SPWord_9 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField9"} mainWallet_AddEditAccountPopup_SPWord_9 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField9"}
mainWallet_AddEditAccountPopup_SPWord_10 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField10"} mainWallet_AddEditAccountPopup_SPWord_10 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField10"}
mainWallet_AddEditAccountPopup_SPWord_11 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField11"} mainWallet_AddEditAccountPopup_SPWord_11 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField11"}
mainWallet_AddEditAccountPopup_SPWord_12 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField12"} mainWallet_AddEditAccountPopup_SPWord_12 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField12"}
mainWallet_AddEditAccountPopup_SPWord_13 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField13"} mainWallet_AddEditAccountPopup_SPWord_13 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField13"}
mainWallet_AddEditAccountPopup_SPWord_14 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField14"} mainWallet_AddEditAccountPopup_SPWord_14 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField14"}
mainWallet_AddEditAccountPopup_SPWord_15 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField15"} mainWallet_AddEditAccountPopup_SPWord_15 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField15"}
mainWallet_AddEditAccountPopup_SPWord_16 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField16"} mainWallet_AddEditAccountPopup_SPWord_16 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField16"}
mainWallet_AddEditAccountPopup_SPWord_17 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField17"} mainWallet_AddEditAccountPopup_SPWord_17 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField17"}
mainWallet_AddEditAccountPopup_SPWord_18 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField18"} mainWallet_AddEditAccountPopup_SPWord_18 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField18"}
mainWallet_AddEditAccountPopup_SPWord_19 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField19"} mainWallet_AddEditAccountPopup_SPWord_19 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField19"}
mainWallet_AddEditAccountPopup_SPWord_20 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField20"} mainWallet_AddEditAccountPopup_SPWord_20 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField20"}
mainWallet_AddEditAccountPopup_SPWord_21 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField21"} mainWallet_AddEditAccountPopup_SPWord_21 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField21"}
mainWallet_AddEditAccountPopup_SPWord_22 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField22"} mainWallet_AddEditAccountPopup_SPWord_22 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField22"}
mainWallet_AddEditAccountPopup_SPWord_23 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField23"} mainWallet_AddEditAccountPopup_SPWord_23 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField23"}
mainWallet_AddEditAccountPopup_SPWord_24 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField24"} mainWallet_AddEditAccountPopup_SPWord_24 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField24"}
# Remove account popup: # Remove account popup:
mainWallet_Remove_Account_Popup_Account_Notification = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-Notification", "type": "StatusBaseText", "visible": True} mainWallet_Remove_Account_Popup_Account_Notification = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-Notification", "type": "StatusBaseText", "visible": True}

View File

@ -4,8 +4,8 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include <QAbstractItemModel>
class QAbstractItemModel;
class QJSEngine; class QJSEngine;
class QQmlEngine; class QQmlEngine;

View File

@ -267,6 +267,11 @@ Item {
This signal is emitted when the text edit is clicked. This signal is emitted when the text edit is clicked.
*/ */
signal editClicked() signal editClicked()
/*!
\qmlsignal editingFinished
This signal is emitted when the text edit loses focus.
*/
signal editingFinished()
onFocusChanged: { onFocusChanged: {
if(focus) edit.forceActiveFocus() if(focus) edit.forceActiveFocus()
@ -368,12 +373,20 @@ Item {
wrapMode: root.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap wrapMode: root.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap
Keys.onReturnPressed: { Keys.onReturnPressed: {
root.keyPressed(event)
event.accepted = !multiline && !acceptReturn event.accepted = !multiline && !acceptReturn
// Special case for single line inputs, where we want to accept the return key, but notify the parend
// Enter and return can be used to accept the input
if (event.accepted) {
root.keyPressed(event)
}
} }
Keys.onEnterPressed: { Keys.onEnterPressed: {
root.keyPressed(event)
event.accepted = !multiline && !acceptReturn event.accepted = !multiline && !acceptReturn
// Special case for single line inputs, where we want to accept the return key, but notify the parend
// Enter and return can be used to accept the input
if (event.accepted) {
root.keyPressed(event)
}
} }
Keys.forwardTo: [root] Keys.forwardTo: [root]
KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem
@ -409,6 +422,9 @@ Item {
previousText = text previousText = text
} }
} }
onEditingFinished: {
root.editingFinished()
}
cursorDelegate: StatusCursorDelegate { cursorDelegate: StatusCursorDelegate {
cursorVisible: edit.cursorVisible cursorVisible: edit.cursorVisible

View File

@ -215,6 +215,11 @@ Item {
This signal is emitted when the text edit is clicked. This signal is emitted when the text edit is clicked.
*/ */
signal editClicked() signal editClicked()
/*!
\qmlsignal editingFinished
This signal is emitted when the text edit loses focus.
*/
signal editingFinished()
/*! /*!
\qmltype ValidationMode \qmltype ValidationMode
@ -453,6 +458,9 @@ Item {
onEditChanged: { onEditChanged: {
root.editClicked(); root.editClicked();
} }
onEditingFinished: {
root.editingFinished()
}
} }
StatusBaseText { StatusBaseText {

View File

@ -97,12 +97,16 @@ Item {
The corresponding handler is \c onEditClicked The corresponding handler is \c onEditClicked
*/ */
signal editClicked() signal editClicked()
/*!
\qmlsignal editingFinished
This signal is emitted when the text edit loses focus.
*/
signal editingFinished()
function setWord(seedWord) { function setWord(seedWord) {
let seedWordTrimmed = seedWord.trim() let seedWordTrimmed = seedWord.trim()
seedWordInput.input.edit.text = seedWordTrimmed seedWordInput.input.edit.text = seedWordTrimmed
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length seedWordInput.input.edit.cursorPosition = seedWordInput.text.length
seedSuggestionsList.model = 0
root.doneInsertingWord(seedWordTrimmed) root.doneInsertingWord(seedWordTrimmed)
} }
@ -134,72 +138,77 @@ Item {
d.isInputValidWord = false d.isInputValidWord = false
filteredList.clear(); filteredList.clear();
let textToCheck = text.trim().toLowerCase() let textToCheck = text.trim().toLowerCase()
if (textToCheck !== "") {
for (var i = 0; i < inputList.count; i++) { if (textToCheck === "") {
if (inputList.get(i).seedWord.startsWith(textToCheck)) { return;
filteredList.insert(filteredList.count, {"seedWord": inputList.get(i).seedWord}); }
if(inputList.get(i).seedWord === textToCheck)
d.isInputValidWord = true for (var i = 0; i < inputList.count; i++) {
} if (inputList.get(i).seedWord.startsWith(textToCheck)) {
filteredList.insert(filteredList.count, {"seedWord": inputList.get(i).seedWord});
if(inputList.get(i).seedWord === textToCheck)
d.isInputValidWord = true
} }
seedSuggestionsList.model = filteredList; }
if (filteredList.count === 1 && input.edit.keyEvent !== Qt.Key_Backspace
&& input.edit.keyEvent !== Qt.Key_Delete if (filteredList.count === 1 && input.edit.keyEvent !== Qt.Key_Backspace
&& filteredList.get(0).seedWord.trim() === textToCheck) { && input.edit.keyEvent !== Qt.Key_Delete
seedWordInput.input.edit.cursorPosition = textToCheck.length; && filteredList.get(0).seedWord.trim() === textToCheck) {
seedSuggestionsList.model = 0; seedWordInput.input.edit.cursorPosition = textToCheck.length;
root.doneInsertingWord(textToCheck); filteredList.clear();
} root.doneInsertingWord(textToCheck);
} else {
seedSuggestionsList.model = 0;
} }
} }
onKeyPressed: { onKeyPressed: {
if (input.edit.keyEvent === Qt.Key_Tab || input.edit.keyEvent === Qt.Key_Return || input.edit.keyEvent === Qt.Key_Enter) { switch (input.edit.keyEvent) {
if (!!text && seedSuggestionsList.count > 0) { case Qt.Key_Tab:
root.setWord(filteredList.get(seedSuggestionsList.currentIndex).seedWord) case Qt.Key_Return:
event.accepted = true case Qt.Key_Enter: {
return if (!!text && filteredList.count > 0) {
root.setWord(filteredList.get(seedSuggestionsList.currentIndex).seedWord)
event.accepted = true
return
}
break;
}
case Qt.Key_Down: {
seedSuggestionsList.incrementCurrentIndex()
input.edit.keyEvent = null
break;
}
case Qt.Key_Up: {
seedSuggestionsList.decrementCurrentIndex()
input.edit.keyEvent = null
break;
}
case Qt.Key_Space: {
event.accepted = !event.text.match(/^[a-zA-Z]$/)
break;
} }
} }
if (input.edit.keyEvent === Qt.Key_Down) {
seedSuggestionsList.incrementCurrentIndex()
input.edit.keyEvent = null
}
if (input.edit.keyEvent === Qt.Key_Up) {
seedSuggestionsList.decrementCurrentIndex()
input.edit.keyEvent = null
}
root.keyPressed(event); root.keyPressed(event);
} }
onEditClicked: { onEditClicked: {
root.editClicked(); root.editClicked();
} }
// Consider word inserted if input looses focus while a valid word is present ("user" clicks outside)
Connections { onEditingFinished: {
target: seedWordInput.input.edit root.editingFinished();
function onActiveFocusChanged() {
if (!seedWordInput.input.edit.activeFocus && d.isInputValidWord) {
// There are so many side effects regarding focus and doneInsertingWord that we need to reset this flag not to be processed again.
d.isInputValidWord = false
root.doneInsertingWord(root.text.trim())
}
}
} }
} }
Item { Popup {
id: suggListContainer id: suggListContainer
width: seedSuggestionsList.width contentWidth: seedSuggestionsList.width
height: (((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34) + 16 contentHeight: ((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34 + 16
anchors.left: parent.left x: 16
anchors.leftMargin: 16 y: seedWordInput.height + 4
anchors.top: seedWordInput.bottom topPadding: 8
anchors.topMargin: 4 bottomPadding: 8
visible: ((seedSuggestionsList.count > 0) && seedWordInput.input.edit.activeFocus) visible: ((filteredList.count > 0) && seedWordInput.input.edit.activeFocus)
Rectangle { background: Rectangle {
id: statusMenuBackgroundContent id: statusMenuBackgroundContent
anchors.fill: parent
color: Theme.palette.statusMenu.backgroundColor color: Theme.palette.statusMenu.backgroundColor
radius: 8 radius: 8
layer.enabled: true layer.enabled: true
@ -219,9 +228,7 @@ Item {
width: ((seedSuggestionsList.contentItem.childrenRect.width + 24) > root.width) ? root.width width: ((seedSuggestionsList.contentItem.childrenRect.width + 24) > root.width) ? root.width
: (seedSuggestionsList.contentItem.childrenRect.width + 24) : (seedSuggestionsList.contentItem.childrenRect.width + 24)
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 8
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8
onCountChanged: { onCountChanged: {
seedSuggestionsList.currentIndex = 0 seedSuggestionsList.currentIndex = 0
@ -229,6 +236,7 @@ Item {
clip: true clip: true
ScrollBar.vertical: ScrollBar { } ScrollBar.vertical: ScrollBar { }
model: root.filteredList
delegate: Item { delegate: Item {
id: txtDelegate id: txtDelegate
width: suggWord.contentWidth width: suggWord.contentWidth

View File

@ -0,0 +1,2 @@
AccountViewDelegate 1.0 AccountViewDelegate.qml
OnboardingBasePage 1.0 OnboardingBasePage.qml

View File

@ -1,35 +1,26 @@
import QtQuick 2.12 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtGraphicalEffects 1.13 import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3 import QtGraphicalEffects 1.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import AppLayouts.Onboarding.controls 1.0
import AppLayouts.Onboarding.stores 1.0
import utils 1.0 import utils 1.0
import shared.panels 1.0
import shared.stores 1.0 import shared.stores 1.0
import shared.controls 1.0 import shared.controls 1.0
import "../controls"
import "../stores"
Item { Item {
id: root id: root
property StartupStore startupStore property StartupStore startupStore
property var mnemonicInput: []
signal seedValidated()
readonly property var tabs: [12, 18, 24]
Timer {
id: timer
}
QtObject { QtObject {
id: d id: d
@ -38,272 +29,43 @@ Item {
onWrongSeedPhraseChanged: { onWrongSeedPhraseChanged: {
if (wrongSeedPhrase) { if (wrongSeedPhrase) {
if (root.startupStore.startupModuleInst.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) { if (root.startupStore.startupModuleInst.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) {
invalidSeedTxt.text = qsTr("Profile keypair for the inserted seed phrase is already set up") seedPhraseView.setWrongSeedPhraseMessage(qsTr("Profile keypair for the inserted seed phrase is already set up"))
return return
} }
invalidSeedTxt.text = qsTr("Seed phrase doesnt match the profile of an existing Keycard user on this device") seedPhraseView.setWrongSeedPhraseMessage(qsTr("Seed phrase doesnt match the profile of an existing Keycard user on this device"))
} }
else { else {
invalidSeedTxt.text = "" seedPhraseView.setWrongSeedPhraseMessage("")
} }
} }
} }
function pasteWords () { ColumnLayout {
const clipboardText = globalUtils.getFromClipboard() width: 565
// Split words separated by commas and or blank spaces (spaces, enters, tabs) implicitHeight: contentItem.implicitHeight
const words = clipboardText.split(/[, \s]+/) anchors.centerIn: parent
spacing: 24
let index = root.tabs.indexOf(words.length)
if (index === -1) {
return false
}
let timeout = 0
if (switchTabBar.currentIndex !== index) {
switchTabBar.currentIndex = index
// Set the teimeout to 100 so the grid has time to generate the new items
timeout = 100
}
root.mnemonicInput = []
timer.setTimeout(() => {
// Populate mnemonicInput
for (let i = 0; i < words.length; i++) {
grid.addWord(i + 1, words[i], true)
}
// Populate grid
for (let j = 0; j < grid.count; j++) {
const item = grid.itemAtIndex(j)
if (!item || !item.leftComponentText) {
// The grid has gaps in it and also sometimes doesn't return the item correctly when offscreen
// in those cases, we just add the word in the array but not in the grid.
// The button will still work and import correctly. The Grid itself will be partly empty, but offscreen
// With the re-design of the grid, this should be fixed
continue
}
const pos = item.mnemonicIndex
item.setWord(words[pos - 1])
}
submitButton.checkMnemonicLength()
}, timeout)
return true
}
Item {
implicitWidth: 565
implicitHeight: parent.height
anchors.horizontalCenter: parent.horizontalCenter
StatusBaseText { StatusBaseText {
id: headlineText id: headlineText
font.pixelSize: 22 font.pixelSize: 22
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.palette.directColor1 color: Theme.palette.directColor1
anchors.top: parent.top Layout.alignment: Qt.AlignHCenter
anchors.topMargin: (root.height - parent.childrenRect.height)/2
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Enter seed phrase") text: qsTr("Enter seed phrase")
} }
StatusSwitchTabBar { EnterSeedPhrase {
id: switchTabBar id: seedPhraseView
objectName: "onboardingSeedPhraseSwitchBar" isSeedPhraseValid: root.startupStore.validMnemonic
anchors.top: headlineText.bottom Layout.alignment: Qt.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 24
Repeater {
model: root.tabs
StatusSwitchTabButton {
text: qsTr("%n word(s)", "", modelData)
id: seedPhraseWords
objectName: `${modelData}SeedButton`
}
}
onCurrentIndexChanged: {
root.mnemonicInput = root.mnemonicInput.filter(function(value) {
return value.pos <= root.tabs[switchTabBar.currentIndex]
})
submitButton.checkMnemonicLength()
}
}
clip: true
StatusGridView {
id: grid
objectName: "seedPhraseGridView"
width: parent.width
readonly property var wordIndex: [
["1", "3", "5", "7", "9", "11", "2", "4", "6", "8", "10", "12"]
,["1", "4", "7", "10", "13", "16", "2", "5", "8",
"11", "14", "17", "3", "6", "9", "12", "15", "18"]
,["1", "5", "9", "13", "17", "21", "2", "6", "10", "14", "18", "22",
"3", "7", "11", "15", "19", "23", "4", "8", "12", "16", "20", "24"]
]
height: 312
clip: false
anchors.left: parent.left
anchors.leftMargin: 12
anchors.top: switchTabBar.bottom
anchors.topMargin: 24
flow: GridView.FlowTopToBottom
cellWidth: (parent.width/(count/6)) - 8
cellHeight: 52
interactive: false
z: 100000
cacheBuffer: 9999
model: switchTabBar.currentItem.text.substring(0,2)
function addWord(pos, word, ignoreGoingNext = false) {
mnemonicInput.push({pos: pos, seed: word.replace(/\s/g, '')})
for (let j = 0; j < mnemonicInput.length; j++) {
if (mnemonicInput[j].pos === pos && mnemonicInput[j].seed !== word) {
mnemonicInput[j].seed = word
break
}
}
//remove duplicates
const valueArr = mnemonicInput.map(item => item.pos)
const isDuplicate = valueArr.some((item, idx) => {
if (valueArr.indexOf(item) !== idx) {
root.mnemonicInput.splice(idx, 1)
}
return valueArr.indexOf(item) !== idx
})
if (!ignoreGoingNext) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex !== (pos + 1)) {
continue
}
grid.currentIndex = grid.itemAtIndex(i).itemIndex
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus()
if (grid.currentIndex !== 12) {
continue
}
grid.positionViewAtEnd()
if (grid.count === 20) {
grid.contentX = 1500
}
}
}
submitButton.checkMnemonicLength()
}
delegate: StatusSeedPhraseInput {
id: seedWordInput
textEdit.input.edit.objectName: `statusSeedPhraseInputField${seedWordInput.leftComponentText}`
width: (grid.cellWidth - 8)
height: (grid.cellHeight - 8)
Behavior on width { NumberAnimation { duration: 180 } }
textEdit.text: {
const pos = seedWordInput.mnemonicIndex
for (let i in root.mnemonicInput) {
const p = root.mnemonicInput[i]
if (p.pos === pos) {
return p.seed
}
}
return ""
}
readonly property int mnemonicIndex: grid.wordIndex[(grid.count / 6) - 2][index]
leftComponentText: mnemonicIndex
inputList: BIP39_en {}
property int itemIndex: index
z: (grid.currentIndex === index) ? 150000000 : 0
onTextChanged: {
invalidSeedTxt.text = ""
}
onDoneInsertingWord: {
grid.addWord(mnemonicIndex, word)
}
onEditClicked: {
grid.currentIndex = index
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus()
}
onKeyPressed: {
grid.currentIndex = index
if (event.key === Qt.Key_Backtab) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex === ((mnemonicIndex - 1) >= 0 ? (mnemonicIndex - 1) : 0)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus(Qt.BacktabFocusReason)
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit
event.accepted = true
break
}
}
} else if (event.key === Qt.Key_Tab) {
for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex === ((mnemonicIndex + 1) <= grid.count ? (mnemonicIndex + 1) : grid.count)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus(Qt.TabFocusReason)
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit
event.accepted = true
break
}
}
}
if (event.matches(StandardKey.Paste)) {
if (root.pasteWords()) {
// Paste was done by splitting the words
event.accepted = true
}
return
}
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
event.accepted = true
if (submitButton.enabled) {
submitButton.clicked(null)
return
}
}
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
const wordIndex = mnemonicInput.findIndex(x => x.pos === mnemonicIndex)
if (wordIndex > -1) {
mnemonicInput.splice(wordIndex, 1)
submitButton.checkMnemonicLength()
}
}
}
Component.onCompleted: {
const item = grid.itemAtIndex(0)
if (item) {
item.textEdit.input.edit.forceActiveFocus()
}
}
}
}
StatusBaseText {
id: invalidSeedTxt
objectName: "onboardingInvalidSeedText"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: grid.bottom
anchors.topMargin: 24
color: Theme.palette.dangerColor1
visible: text !== ""
} }
StatusButton { StatusButton {
id: submitButton id: submitButton
objectName: "seedPhraseViewSubmitButton" objectName: "seedPhraseViewSubmitButton"
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter
anchors.top: invalidSeedTxt.bottom enabled: seedPhraseView.seedPhraseIsValid
anchors.topMargin: 24
enabled: false
function checkMnemonicLength() {
submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex])
}
text: { text: {
if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) { if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) {
return qsTr("Import") return qsTr("Import")
@ -322,21 +84,7 @@ Item {
} }
return "" return ""
} }
onClicked: { onClicked: root.startupStore.doPrimaryAction()
let mnemonicString = ""
const sortTable = mnemonicInput.sort((a, b) => a.pos - b.pos)
for (let i = 0; i < mnemonicInput.length; i++) {
mnemonicString += sortTable[i].seed.toLowerCase() + ((i === (grid.count-1)) ? "" : " ")
}
if (Utils.isMnemonic(mnemonicString) && root.startupStore.validMnemonic(mnemonicString)) {
root.mnemonicInput = []
root.startupStore.doPrimaryAction()
} else {
invalidSeedTxt.text = qsTr("Invalid seed")
enabled = false
}
}
} }
} }
} }

View File

@ -1,2 +1,3 @@
LoginView 1.0 LoginView.qml LoginView 1.0 LoginView.qml
ProfileFetchingView 1.0 ProfileFetchingView.qml ProfileFetchingView 1.0 ProfileFetchingView.qml
SeedPhraseInputView 1.0 SeedPhraseInputView.qml

View File

@ -3,6 +3,7 @@ import QtQuick.Layouts 1.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
@ -16,17 +17,26 @@ ColumnLayout {
//************************************************************************** //**************************************************************************
//* This component is not refactored, just pulled out to a shared location * //* This component is not refactored, just pulled out to a shared location *
//************************************************************************** //**************************************************************************
spacing: Style.current.padding spacing: Style.current.padding
clip: true clip: true
readonly property bool seedPhraseIsValid: d.allEntriesValid && invalidSeedTxt.text === ""
property var isSeedPhraseValid: function (mnemonic) { return false } property var isSeedPhraseValid: function (mnemonic) { return false }
property ListModel dictionary: BIP39_en {}
signal submitSeedPhrase() signal submitSeedPhrase()
signal seedPhraseUpdated(bool valid, string seedPhrase) signal seedPhraseUpdated(bool valid, string seedPhrase)
function setWrongSeedPhraseMessage(message) { function setWrongSeedPhraseMessage(message) {
invalidSeedTxt.text = message invalidSeedTxt.text = message
// Validate again the seed phrase
// This is needed because the message can be set to empty and the seed phrase is still invalid
if (message === "")
d.validate()
}
function getSeedPhraseAsString() {
return d.buildMnemonicString()
} }
QtObject { QtObject {
@ -34,18 +44,18 @@ ColumnLayout {
property bool allEntriesValid: false property bool allEntriesValid: false
property var mnemonicInput: [] property var mnemonicInput: []
property var incorrectWordAtIndex: []
readonly property var tabs: [12, 18, 24] readonly property var tabs: [12, 18, 24]
readonly property ListModel seedPhrases_en: BIP39_en {} readonly property alias seedPhrases_en: root.dictionary
onIncorrectWordAtIndexChanged: d.validate()
onAllEntriesValidChanged: { onAllEntriesValidChanged: {
let mnemonicString = "" let mnemonicString = ""
if (d.allEntriesValid) {
const sortTable = mnemonicInput.sort((a, b) => a.pos - b.pos)
for (let i = 0; i < mnemonicInput.length; i++) {
d.checkWordExistence(sortTable[i].seed)
mnemonicString += sortTable[i].seed + ((i === (grid.count-1)) ? "" : " ")
}
if (d.allEntriesValid) {
mnemonicString = buildMnemonicString()
if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) { if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) {
root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase")) root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase"))
d.allEntriesValid = false d.allEntriesValid = false
@ -54,22 +64,40 @@ ColumnLayout {
root.seedPhraseUpdated(d.allEntriesValid, mnemonicString) root.seedPhraseUpdated(d.allEntriesValid, mnemonicString)
} }
function checkMnemonicLength() { function validate() {
d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex] if (d.incorrectWordAtIndex.length > 0) {
invalidSeedTxt.text = qsTr("The phrase youve entered is invalid")
return
}
invalidSeedTxt.text = ""
} }
function checkWordExistence(word) { function checkMnemonicLength() {
d.allEntriesValid = d.allEntriesValid && d.seedPhrases_en.words.includes(word) d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex] && d.incorrectWordAtIndex.length === 0
if (d.allEntriesValid) { }
root.setWrongSeedPhraseMessage("")
} function buildMnemonicString() {
else { const sortTable = mnemonicInput.sort((a, b) => a.pos - b.pos)
root.setWrongSeedPhraseMessage(qsTr("The phrase youve entered is invalid")) return sortTable.map(x => x.seed).join(" ")
}
function checkWordExistence(word, pos) {
if (word !== "" && !ModelUtils.contains(d.seedPhrases_en, "seedWord", word)) {
const incorrectWordAtIndex = d.incorrectWordAtIndex
incorrectWordAtIndex.push(pos)
d.incorrectWordAtIndex = incorrectWordAtIndex
return
} }
d.incorrectWordAtIndex = d.incorrectWordAtIndex.filter(function(value) {
return value !== pos
})
} }
function pasteWords () { function pasteWords () {
const clipboardText = globalUtils.getFromClipboard() const clipboardText = Utils.getFromClipboard()
// Split words separated by commas and or blank spaces (spaces, enters, tabs) // Split words separated by commas and or blank spaces (spaces, enters, tabs)
const words = clipboardText.trim().split(/[, \s]+/) const words = clipboardText.trim().split(/[, \s]+/)
@ -118,7 +146,8 @@ ColumnLayout {
StatusSwitchTabBar { StatusSwitchTabBar {
id: switchTabBar id: switchTabBar
Layout.preferredWidth: parent.width objectName: "enterSeedPhraseSwitchBar"
Layout.alignment: Qt.AlignHCenter
Repeater { Repeater {
model: d.tabs model: d.tabs
StatusSwitchTabButton { StatusSwitchTabButton {
@ -131,6 +160,9 @@ ColumnLayout {
d.mnemonicInput = d.mnemonicInput.filter(function(value) { d.mnemonicInput = d.mnemonicInput.filter(function(value) {
return value.pos <= d.tabs[switchTabBar.currentIndex] return value.pos <= d.tabs[switchTabBar.currentIndex]
}) })
d.incorrectWordAtIndex = d.incorrectWordAtIndex.filter(function(value) {
return value <= d.tabs[switchTabBar.currentIndex]
})
d.checkMnemonicLength() d.checkMnemonicLength()
} }
} }
@ -144,42 +176,46 @@ ColumnLayout {
,["1", "5", "9", "13", "17", "21", "2", "6", "10", "14", "18", "22", ,["1", "5", "9", "13", "17", "21", "2", "6", "10", "14", "18", "22",
"3", "7", "11", "15", "19", "23", "4", "8", "12", "16", "20", "24"] "3", "7", "11", "15", "19", "23", "4", "8", "12", "16", "20", "24"]
] ]
Layout.preferredWidth: parent.width
objectName: "enterSeedPhraseGridView"
Layout.fillWidth: true
Layout.preferredHeight: 312 Layout.preferredHeight: 312
clip: false clip: false
flow: GridView.FlowTopToBottom flow: GridView.FlowTopToBottom
cellWidth: (parent.width/(count/6)) cellWidth: (parent.width/(count/6))
cellHeight: 52 cellHeight: 52
interactive: false interactive: false
z: 100000
cacheBuffer: 9999
model: switchTabBar.currentItem.text.substring(0,2) model: switchTabBar.currentItem.text.substring(0,2)
function addWord(pos, word, ignoreGoingNext = false) { function addWord(pos, word, ignoreGoingNext = false) {
d.mnemonicInput.push({pos: pos, seed: word.replace(/\s/g, '')})
for (let j = 0; j < d.mnemonicInput.length; j++) { const words = d.mnemonicInput
if (d.mnemonicInput[j].pos === pos && d.mnemonicInput[j].seed !== word) {
d.mnemonicInput[j].seed = word words.push({pos: pos, seed: word.replace(/\s/g, '')})
for (let j = 0; j < words.length; j++) {
if (words[j].pos === pos && words[j].seed !== word) {
words[j].seed = word
break break
} }
} }
//remove duplicates //remove duplicates
const valueArr = d.mnemonicInput.map(item => item.pos) const valueArr = words.map(item => item.pos)
const isDuplicate = valueArr.some((item, idx) => { const isDuplicate = valueArr.some((item, idx) => {
if (valueArr.indexOf(item) !== idx) { if (valueArr.indexOf(item) !== idx) {
d.mnemonicInput.splice(idx, 1) words.splice(idx, 1)
} }
return valueArr.indexOf(item) !== idx return valueArr.indexOf(item) !== idx
}) })
if (!ignoreGoingNext) { if (!ignoreGoingNext) {
for (let i = 0; i < grid.count; i++) { for (let i = 0; i < grid.count; i++) {
if (grid.itemAtIndex(i).mnemonicIndex !== (pos + 1)) { const item = grid.itemAtIndex(i)
if (!item || item.mnemonicIndex !== (pos + 1)) {
continue continue
} }
grid.currentIndex = grid.itemAtIndex(i).itemIndex grid.currentIndex = item.itemIndex
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus() item.textEdit.input.edit.forceActiveFocus()
if (grid.currentIndex !== 12) { if (grid.currentIndex !== 12) {
continue continue
@ -192,12 +228,16 @@ ColumnLayout {
} }
} }
} }
d.mnemonicInput = words
d.checkWordExistence(word, pos)
d.checkMnemonicLength() d.checkMnemonicLength()
root.seedPhraseUpdated(d.allEntriesValid, d.buildMnemonicString())
} }
delegate: StatusSeedPhraseInput { delegate: StatusSeedPhraseInput {
id: seedWordInput id: seedWordInput
textEdit.input.edit.objectName: `statusSeedPhraseInputField${seedWordInput.leftComponentText}`
textEdit.input.edit.objectName: `enterSeedPhraseInputField${seedWordInput.leftComponentText}`
width: (grid.cellWidth - 8) width: (grid.cellWidth - 8)
height: (grid.cellHeight - 8) height: (grid.cellHeight - 8)
Behavior on width { NumberAnimation { duration: 180 } } Behavior on width { NumberAnimation { duration: 180 } }
@ -218,13 +258,16 @@ ColumnLayout {
inputList: d.seedPhrases_en inputList: d.seedPhrases_en
property int itemIndex: index property int itemIndex: index
z: (grid.currentIndex === index) ? 150000000 : 0
onTextChanged: {
d.checkWordExistence(text)
}
onDoneInsertingWord: { onDoneInsertingWord: {
grid.addWord(mnemonicIndex, word) grid.addWord(mnemonicIndex, word)
} }
onEditingFinished: {
if (text === "") {
return
}
grid.addWord(mnemonicIndex, text, true)
}
onEditClicked: { onEditClicked: {
grid.currentIndex = index grid.currentIndex = index
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus() grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus()
@ -287,7 +330,7 @@ ColumnLayout {
StatusBaseText { StatusBaseText {
id: invalidSeedTxt id: invalidSeedTxt
visible: text !== "" objectName: "enterSeedPhraseInvalidSeedText"
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }

View File

@ -1,23 +1,17 @@
import QtQuick 2.13 import QtQuick 2.13
import StatusQ.Core.Utils 0.1
ListModel { ListModel {
id: root id: root
property var words: []
Component.onCompleted: { Component.onCompleted: {
var xhr = new XMLHttpRequest(); const words = StringUtils.readTextFile(":/imports/shared/stores/english.txt").split(/\r?\n|\r/);
xhr.open("GET", "english.txt"); for (var i = 0; i < words.length; i++) {
xhr.onreadystatechange = function () { let word = words[i]
if (xhr.readyState === XMLHttpRequest.DONE) { if (word !== "") {
root.words = xhr.responseText.split('\n'); insert(count, {"seedWord": word});
for (var i = 0; i < root.words.length; i++) {
if (root.words[i] !== "") {
insert(count, {"seedWord": root.words[i]});
}
}
} }
} }
xhr.send();
} }
} }

View File

@ -737,6 +737,10 @@ QtObject {
globalUtilsInst.copyToClipboard(text) globalUtilsInst.copyToClipboard(text)
} }
function getFromClipboard() {
return globalUtilsInst.getFromClipboard()
}
function copyImageToClipboardByUrl(content) { function copyImageToClipboardByUrl(content) {
globalUtilsInst.copyImageToClipboardByUrl(content) globalUtilsInst.copyImageToClipboardByUrl(content)
} }