diff --git a/storybook/pages/EnterSeedPhrasePage.qml b/storybook/pages/EnterSeedPhrasePage.qml new file mode 100644 index 0000000000..301d8eb661 --- /dev/null +++ b/storybook/pages/EnterSeedPhrasePage.qml @@ -0,0 +1,14 @@ +import QtQuick 2.15 + +import shared.panels 1.0 + +import Storybook 1.0 + + +Item { + EnterSeedPhrase { + anchors.centerIn: parent + } +} + +// category: Panels \ No newline at end of file diff --git a/storybook/pages/SeedPhraseInputViewPage.qml b/storybook/pages/SeedPhraseInputViewPage.qml new file mode 100644 index 0000000000..c950599ce7 --- /dev/null +++ b/storybook/pages/SeedPhraseInputViewPage.qml @@ -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 diff --git a/storybook/qmlTests/tests/tst_EnterSeedPhrase.qml b/storybook/qmlTests/tests/tst_EnterSeedPhrase.qml new file mode 100644 index 0000000000..0b369f0ed3 --- /dev/null +++ b/storybook/qmlTests/tests/tst_EnterSeedPhrase.qml @@ -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") + } + } +} diff --git a/storybook/stubs/shared/stores/BIP39_en.qml b/storybook/stubs/shared/stores/BIP39_en.qml new file mode 100644 index 0000000000..0a116044fb --- /dev/null +++ b/storybook/stubs/shared/stores/BIP39_en.qml @@ -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] }); + } + } +} diff --git a/storybook/stubs/shared/stores/qmldir b/storybook/stubs/shared/stores/qmldir index 863b8d52b9..f7439fc0fd 100644 --- a/storybook/stubs/shared/stores/qmldir +++ b/storybook/stubs/shared/stores/qmldir @@ -3,3 +3,4 @@ CurrenciesStore 1.0 CurrenciesStore.qml NetworkConnectionStore 1.0 NetworkConnectionStore.qml singleton RootStore 1.0 RootStore.qml TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml +BIP39_en 1.0 BIP39_en.qml \ No newline at end of file diff --git a/test/ui-test/src/screens/components/wallet_account_popups.py b/test/ui-test/src/screens/components/wallet_account_popups.py index 5a425619a4..c9e5452008 100644 --- a/test/ui-test/src/screens/components/wallet_account_popups.py +++ b/test/ui-test/src/screens/components/wallet_account_popups.py @@ -83,7 +83,7 @@ class AddNewAccountPopup(BasePopup): else: raise RuntimeError("Wrong amount of seed words", len(seed_phrase_words)) 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 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 diff --git a/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py b/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py index 231faec582..6d70d70f18 100644 --- a/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py +++ b/test/ui-test/testSuites/global_shared/scripts/onboarding_names.py @@ -30,34 +30,34 @@ onboarding_back_button = {"container": statusDesktop_mainWindow, "objectName": " # Seed phrase form: 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_18_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "18SeedButton", "type": "StatusSwitchTabButton"} switchTabBar_24_words_Button = {"container": mainWindow_switchTabBar_StatusSwitchTabBar, "objectName": "24SeedButton", "type": "StatusSwitchTabButton"} seedPhraseView_Submit_Button = {"container": statusDesktop_mainWindow, "objectName": "seedPhraseViewSubmitButton", "type": "StatusButton"} 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_2 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField2"} -onboarding_SeedPhrase_Input_TextField_3 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField3"} -onboarding_SeedPhrase_Input_TextField_4 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField4"} -onboarding_SeedPhrase_Input_TextField_5 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField5"} -onboarding_SeedPhrase_Input_TextField_6 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField6"} -onboarding_SeedPhrase_Input_TextField_7 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField7"} -onboarding_SeedPhrase_Input_TextField_8 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField8"} -onboarding_SeedPhrase_Input_TextField_9 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField9"} -onboarding_SeedPhrase_Input_TextField_10 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField10"} -onboarding_SeedPhrase_Input_TextField_11 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField11"} -onboarding_SeedPhrase_Input_TextField_12 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField12"} -onboarding_SeedPhrase_Input_TextField_13 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField13"} -onboarding_SeedPhrase_Input_TextField_14 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField14"} -onboarding_SeedPhrase_Input_TextField_15 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField15"} -onboarding_SeedPhrase_Input_TextField_16 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField16"} -onboarding_SeedPhrase_Input_TextField_17 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField17"} -onboarding_SeedPhrase_Input_TextField_18 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField18"} -onboarding_SeedPhrase_Input_TextField_19 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField19"} -onboarding_SeedPhrase_Input_TextField_20 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField20"} -onboarding_SeedPhrase_Input_TextField_21 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField21"} -onboarding_SeedPhrase_Input_TextField_22 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField22"} -onboarding_SeedPhrase_Input_TextField_23 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField23"} -onboarding_SeedPhrase_Input_TextField_24 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "statusSeedPhraseInputField24"} \ No newline at end of file +onboarding_SeedPhrase_Input_TextField_1 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField1"} +onboarding_SeedPhrase_Input_TextField_2 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField2"} +onboarding_SeedPhrase_Input_TextField_3 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField3"} +onboarding_SeedPhrase_Input_TextField_4 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField4"} +onboarding_SeedPhrase_Input_TextField_5 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField5"} +onboarding_SeedPhrase_Input_TextField_6 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField6"} +onboarding_SeedPhrase_Input_TextField_7 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField7"} +onboarding_SeedPhrase_Input_TextField_8 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField8"} +onboarding_SeedPhrase_Input_TextField_9 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField9"} +onboarding_SeedPhrase_Input_TextField_10 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField10"} +onboarding_SeedPhrase_Input_TextField_11 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField11"} +onboarding_SeedPhrase_Input_TextField_12 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField12"} +onboarding_SeedPhrase_Input_TextField_13 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField13"} +onboarding_SeedPhrase_Input_TextField_14 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField14"} +onboarding_SeedPhrase_Input_TextField_15 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField15"} +onboarding_SeedPhrase_Input_TextField_16 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField16"} +onboarding_SeedPhrase_Input_TextField_17 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField17"} +onboarding_SeedPhrase_Input_TextField_18 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField18"} +onboarding_SeedPhrase_Input_TextField_19 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField19"} +onboarding_SeedPhrase_Input_TextField_20 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField20"} +onboarding_SeedPhrase_Input_TextField_21 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField21"} +onboarding_SeedPhrase_Input_TextField_22 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField22"} +onboarding_SeedPhrase_Input_TextField_23 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField23"} +onboarding_SeedPhrase_Input_TextField_24 = {"container": statusDesktop_mainWindow, "type": "TextEdit", "objectName": "enterSeedPhraseInputField24"} \ No newline at end of file diff --git a/test/ui-test/testSuites/global_shared/scripts/wallet_names.py b/test/ui-test/testSuites/global_shared/scripts/wallet_names.py index 3dae835948..af90470356 100644 --- a/test/ui-test/testSuites/global_shared/scripts/wallet_names.py +++ b/test/ui-test/testSuites/global_shared/scripts/wallet_names.py @@ -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_EnterSeedPhraseWord = {"container": mainWallet_AddEditAccountPopup_EnterSeedPhraseWordComponent, "type": "TextEdit", "unnamed": 1, "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_18WordsButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "18SeedButton", "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_2 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField2"} -mainWallet_AddEditAccountPopup_SPWord_3 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField3"} -mainWallet_AddEditAccountPopup_SPWord_4 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField4"} -mainWallet_AddEditAccountPopup_SPWord_5 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField5"} -mainWallet_AddEditAccountPopup_SPWord_6 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField6"} -mainWallet_AddEditAccountPopup_SPWord_7 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField7"} -mainWallet_AddEditAccountPopup_SPWord_8 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField8"} -mainWallet_AddEditAccountPopup_SPWord_9 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField9"} -mainWallet_AddEditAccountPopup_SPWord_10 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField10"} -mainWallet_AddEditAccountPopup_SPWord_11 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField11"} -mainWallet_AddEditAccountPopup_SPWord_12 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField12"} -mainWallet_AddEditAccountPopup_SPWord_13 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField13"} -mainWallet_AddEditAccountPopup_SPWord_14 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField14"} -mainWallet_AddEditAccountPopup_SPWord_15 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField15"} -mainWallet_AddEditAccountPopup_SPWord_16 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField16"} -mainWallet_AddEditAccountPopup_SPWord_17 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField17"} -mainWallet_AddEditAccountPopup_SPWord_18 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField18"} -mainWallet_AddEditAccountPopup_SPWord_19 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField19"} -mainWallet_AddEditAccountPopup_SPWord_20 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField20"} -mainWallet_AddEditAccountPopup_SPWord_21 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField21"} -mainWallet_AddEditAccountPopup_SPWord_22 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField22"} -mainWallet_AddEditAccountPopup_SPWord_23 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField23"} -mainWallet_AddEditAccountPopup_SPWord_24 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "statusSeedPhraseInputField24"} +mainWallet_AddEditAccountPopup_SPWord_1 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField1"} +mainWallet_AddEditAccountPopup_SPWord_2 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField2"} +mainWallet_AddEditAccountPopup_SPWord_3 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField3"} +mainWallet_AddEditAccountPopup_SPWord_4 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField4"} +mainWallet_AddEditAccountPopup_SPWord_5 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField5"} +mainWallet_AddEditAccountPopup_SPWord_6 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField6"} +mainWallet_AddEditAccountPopup_SPWord_7 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField7"} +mainWallet_AddEditAccountPopup_SPWord_8 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField8"} +mainWallet_AddEditAccountPopup_SPWord_9 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField9"} +mainWallet_AddEditAccountPopup_SPWord_10 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField10"} +mainWallet_AddEditAccountPopup_SPWord_11 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField11"} +mainWallet_AddEditAccountPopup_SPWord_12 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField12"} +mainWallet_AddEditAccountPopup_SPWord_13 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField13"} +mainWallet_AddEditAccountPopup_SPWord_14 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField14"} +mainWallet_AddEditAccountPopup_SPWord_15 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField15"} +mainWallet_AddEditAccountPopup_SPWord_16 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField16"} +mainWallet_AddEditAccountPopup_SPWord_17 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField17"} +mainWallet_AddEditAccountPopup_SPWord_18 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField18"} +mainWallet_AddEditAccountPopup_SPWord_19 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField19"} +mainWallet_AddEditAccountPopup_SPWord_20 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField20"} +mainWallet_AddEditAccountPopup_SPWord_21 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField21"} +mainWallet_AddEditAccountPopup_SPWord_22 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField22"} +mainWallet_AddEditAccountPopup_SPWord_23 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField23"} +mainWallet_AddEditAccountPopup_SPWord_24 = {"container": mainWallet_AddEditAccountPopup_Content, "type": "TextEdit", "objectName": "enterSeedPhraseInputField24"} # Remove account popup: mainWallet_Remove_Account_Popup_Account_Notification = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-Notification", "type": "StatusBaseText", "visible": True} diff --git a/ui/StatusQ/include/StatusQ/modelutilsinternal.h b/ui/StatusQ/include/StatusQ/modelutilsinternal.h index 96d4e0afcc..feb40b8e58 100644 --- a/ui/StatusQ/include/StatusQ/modelutilsinternal.h +++ b/ui/StatusQ/include/StatusQ/modelutilsinternal.h @@ -4,8 +4,8 @@ #include #include #include +#include -class QAbstractItemModel; class QJSEngine; class QQmlEngine; diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml index 456bbc7d95..00f913b826 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml @@ -267,6 +267,11 @@ Item { This signal is emitted when the text edit is clicked. */ signal editClicked() + /*! + \qmlsignal editingFinished + This signal is emitted when the text edit loses focus. + */ + signal editingFinished() onFocusChanged: { if(focus) edit.forceActiveFocus() @@ -368,12 +373,20 @@ Item { wrapMode: root.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap Keys.onReturnPressed: { - root.keyPressed(event) 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: { - root.keyPressed(event) 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] KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem @@ -409,6 +422,9 @@ Item { previousText = text } } + onEditingFinished: { + root.editingFinished() + } cursorDelegate: StatusCursorDelegate { cursorVisible: edit.cursorVisible diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml index 8ce0c93563..52a2127c47 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml @@ -215,6 +215,11 @@ Item { This signal is emitted when the text edit is clicked. */ signal editClicked() + /*! + \qmlsignal editingFinished + This signal is emitted when the text edit loses focus. + */ + signal editingFinished() /*! \qmltype ValidationMode @@ -453,6 +458,9 @@ Item { onEditChanged: { root.editClicked(); } + onEditingFinished: { + root.editingFinished() + } } StatusBaseText { diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml index 63ca85af64..5e8f516479 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml @@ -97,12 +97,16 @@ Item { The corresponding handler is \c onEditClicked */ signal editClicked() + /*! + \qmlsignal editingFinished + This signal is emitted when the text edit loses focus. + */ + signal editingFinished() function setWord(seedWord) { let seedWordTrimmed = seedWord.trim() seedWordInput.input.edit.text = seedWordTrimmed seedWordInput.input.edit.cursorPosition = seedWordInput.text.length - seedSuggestionsList.model = 0 root.doneInsertingWord(seedWordTrimmed) } @@ -134,72 +138,77 @@ Item { d.isInputValidWord = false filteredList.clear(); let textToCheck = text.trim().toLowerCase() - if (textToCheck !== "") { - 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 - } + + if (textToCheck === "") { + return; + } + + 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 - && filteredList.get(0).seedWord.trim() === textToCheck) { - seedWordInput.input.edit.cursorPosition = textToCheck.length; - seedSuggestionsList.model = 0; - root.doneInsertingWord(textToCheck); - } - } else { - seedSuggestionsList.model = 0; + } + + if (filteredList.count === 1 && input.edit.keyEvent !== Qt.Key_Backspace + && input.edit.keyEvent !== Qt.Key_Delete + && filteredList.get(0).seedWord.trim() === textToCheck) { + seedWordInput.input.edit.cursorPosition = textToCheck.length; + filteredList.clear(); + root.doneInsertingWord(textToCheck); } } onKeyPressed: { - if (input.edit.keyEvent === Qt.Key_Tab || input.edit.keyEvent === Qt.Key_Return || input.edit.keyEvent === Qt.Key_Enter) { - if (!!text && seedSuggestionsList.count > 0) { - root.setWord(filteredList.get(seedSuggestionsList.currentIndex).seedWord) - event.accepted = true - return + switch (input.edit.keyEvent) { + case Qt.Key_Tab: + case Qt.Key_Return: + case Qt.Key_Enter: { + 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); } onEditClicked: { root.editClicked(); } - // Consider word inserted if input looses focus while a valid word is present ("user" clicks outside) - Connections { - target: seedWordInput.input.edit - 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()) - } - } + + onEditingFinished: { + root.editingFinished(); } } - Item { + Popup { id: suggListContainer - width: seedSuggestionsList.width - height: (((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34) + 16 - anchors.left: parent.left - anchors.leftMargin: 16 - anchors.top: seedWordInput.bottom - anchors.topMargin: 4 - visible: ((seedSuggestionsList.count > 0) && seedWordInput.input.edit.activeFocus) - Rectangle { + contentWidth: seedSuggestionsList.width + contentHeight: ((seedSuggestionsList.count <= 5) ? seedSuggestionsList.count : 5) *34 + 16 + x: 16 + y: seedWordInput.height + 4 + topPadding: 8 + bottomPadding: 8 + visible: ((filteredList.count > 0) && seedWordInput.input.edit.activeFocus) + background: Rectangle { id: statusMenuBackgroundContent - anchors.fill: parent color: Theme.palette.statusMenu.backgroundColor radius: 8 layer.enabled: true @@ -219,9 +228,7 @@ Item { width: ((seedSuggestionsList.contentItem.childrenRect.width + 24) > root.width) ? root.width : (seedSuggestionsList.contentItem.childrenRect.width + 24) anchors.top: parent.top - anchors.topMargin: 8 anchors.bottom: parent.bottom - anchors.bottomMargin: 8 onCountChanged: { seedSuggestionsList.currentIndex = 0 @@ -229,6 +236,7 @@ Item { clip: true ScrollBar.vertical: ScrollBar { } + model: root.filteredList delegate: Item { id: txtDelegate width: suggWord.contentWidth diff --git a/ui/app/AppLayouts/Onboarding/controls/qmldir b/ui/app/AppLayouts/Onboarding/controls/qmldir new file mode 100644 index 0000000000..87da99d316 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding/controls/qmldir @@ -0,0 +1,2 @@ +AccountViewDelegate 1.0 AccountViewDelegate.qml +OnboardingBasePage 1.0 OnboardingBasePage.qml \ No newline at end of file diff --git a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml index 1b35992031..30f70e0d1d 100644 --- a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml +++ b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml @@ -1,35 +1,26 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.14 -import QtGraphicalEffects 1.13 -import QtQuick.Dialogs 1.3 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 import StatusQ.Controls 0.1 import StatusQ.Popups 0.1 import StatusQ.Core 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 shared.panels 1.0 import shared.stores 1.0 import shared.controls 1.0 -import "../controls" -import "../stores" - Item { id: root property StartupStore startupStore - property var mnemonicInput: [] - - signal seedValidated() - - readonly property var tabs: [12, 18, 24] - - Timer { - id: timer - } - QtObject { id: d @@ -38,272 +29,43 @@ Item { onWrongSeedPhraseChanged: { if (wrongSeedPhrase) { 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 } - invalidSeedTxt.text = qsTr("Seed phrase doesn’t match the profile of an existing Keycard user on this device") + seedPhraseView.setWrongSeedPhraseMessage(qsTr("Seed phrase doesn’t match the profile of an existing Keycard user on this device")) } else { - invalidSeedTxt.text = "" + seedPhraseView.setWrongSeedPhraseMessage("") } } } - function pasteWords () { - const clipboardText = globalUtils.getFromClipboard() - // Split words separated by commas and or blank spaces (spaces, enters, tabs) - const words = clipboardText.split(/[, \s]+/) - - 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 + ColumnLayout { + width: 565 + implicitHeight: contentItem.implicitHeight + anchors.centerIn: parent + spacing: 24 StatusBaseText { id: headlineText font.pixelSize: 22 font.weight: Font.Bold color: Theme.palette.directColor1 - anchors.top: parent.top - anchors.topMargin: (root.height - parent.childrenRect.height)/2 - anchors.horizontalCenter: parent.horizontalCenter + Layout.alignment: Qt.AlignHCenter text: qsTr("Enter seed phrase") } - StatusSwitchTabBar { - id: switchTabBar - objectName: "onboardingSeedPhraseSwitchBar" - anchors.top: headlineText.bottom - 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 !== "" + EnterSeedPhrase { + id: seedPhraseView + isSeedPhraseValid: root.startupStore.validMnemonic + Layout.alignment: Qt.AlignHCenter } StatusButton { id: submitButton objectName: "seedPhraseViewSubmitButton" - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: invalidSeedTxt.bottom - anchors.topMargin: 24 - enabled: false - function checkMnemonicLength() { - submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex]) - } + Layout.alignment: Qt.AlignHCenter + enabled: seedPhraseView.seedPhraseIsValid text: { if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) { return qsTr("Import") @@ -322,21 +84,7 @@ Item { } return "" } - onClicked: { - 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 - } - } + onClicked: root.startupStore.doPrimaryAction() } } } diff --git a/ui/app/AppLayouts/Onboarding/views/qmldir b/ui/app/AppLayouts/Onboarding/views/qmldir index cafd427e45..d37b11fb4d 100644 --- a/ui/app/AppLayouts/Onboarding/views/qmldir +++ b/ui/app/AppLayouts/Onboarding/views/qmldir @@ -1,2 +1,3 @@ LoginView 1.0 LoginView.qml -ProfileFetchingView 1.0 ProfileFetchingView.qml \ No newline at end of file +ProfileFetchingView 1.0 ProfileFetchingView.qml +SeedPhraseInputView 1.0 SeedPhraseInputView.qml \ No newline at end of file diff --git a/ui/imports/shared/panels/EnterSeedPhrase.qml b/ui/imports/shared/panels/EnterSeedPhrase.qml index 41a872ca91..f8450ac1a6 100644 --- a/ui/imports/shared/panels/EnterSeedPhrase.qml +++ b/ui/imports/shared/panels/EnterSeedPhrase.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 import StatusQ.Controls 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 * //************************************************************************** - spacing: Style.current.padding clip: true + readonly property bool seedPhraseIsValid: d.allEntriesValid && invalidSeedTxt.text === "" property var isSeedPhraseValid: function (mnemonic) { return false } + property ListModel dictionary: BIP39_en {} signal submitSeedPhrase() signal seedPhraseUpdated(bool valid, string seedPhrase) function setWrongSeedPhraseMessage(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 { @@ -34,18 +44,18 @@ ColumnLayout { property bool allEntriesValid: false property var mnemonicInput: [] + property var incorrectWordAtIndex: [] 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: { 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)) { root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase")) d.allEntriesValid = false @@ -54,25 +64,43 @@ ColumnLayout { root.seedPhraseUpdated(d.allEntriesValid, mnemonicString) } - function checkMnemonicLength() { - d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex] + function validate() { + if (d.incorrectWordAtIndex.length > 0) { + invalidSeedTxt.text = qsTr("The phrase you’ve entered is invalid") + return + } + + invalidSeedTxt.text = "" } - function checkWordExistence(word) { - d.allEntriesValid = d.allEntriesValid && d.seedPhrases_en.words.includes(word) - if (d.allEntriesValid) { - root.setWrongSeedPhraseMessage("") - } - else { - root.setWrongSeedPhraseMessage(qsTr("The phrase you’ve entered is invalid")) + function checkMnemonicLength() { + d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex] && d.incorrectWordAtIndex.length === 0 + } + + function buildMnemonicString() { + const sortTable = mnemonicInput.sort((a, b) => a.pos - b.pos) + 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 () { - const clipboardText = globalUtils.getFromClipboard() + const clipboardText = Utils.getFromClipboard() + // Split words separated by commas and or blank spaces (spaces, enters, tabs) const words = clipboardText.trim().split(/[, \s]+/) - + let index = d.tabs.indexOf(words.length) if (index === -1) { return false @@ -118,7 +146,8 @@ ColumnLayout { StatusSwitchTabBar { id: switchTabBar - Layout.preferredWidth: parent.width + objectName: "enterSeedPhraseSwitchBar" + Layout.alignment: Qt.AlignHCenter Repeater { model: d.tabs StatusSwitchTabButton { @@ -131,6 +160,9 @@ ColumnLayout { d.mnemonicInput = d.mnemonicInput.filter(function(value) { return value.pos <= d.tabs[switchTabBar.currentIndex] }) + d.incorrectWordAtIndex = d.incorrectWordAtIndex.filter(function(value) { + return value <= d.tabs[switchTabBar.currentIndex] + }) d.checkMnemonicLength() } } @@ -144,42 +176,46 @@ ColumnLayout { ,["1", "5", "9", "13", "17", "21", "2", "6", "10", "14", "18", "22", "3", "7", "11", "15", "19", "23", "4", "8", "12", "16", "20", "24"] ] - Layout.preferredWidth: parent.width + + objectName: "enterSeedPhraseGridView" + Layout.fillWidth: true Layout.preferredHeight: 312 clip: false flow: GridView.FlowTopToBottom cellWidth: (parent.width/(count/6)) cellHeight: 52 interactive: false - z: 100000 - cacheBuffer: 9999 model: switchTabBar.currentItem.text.substring(0,2) 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++) { - if (d.mnemonicInput[j].pos === pos && d.mnemonicInput[j].seed !== word) { - d.mnemonicInput[j].seed = word + const words = d.mnemonicInput + + 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 } } //remove duplicates - const valueArr = d.mnemonicInput.map(item => item.pos) + const valueArr = words.map(item => item.pos) const isDuplicate = valueArr.some((item, idx) => { if (valueArr.indexOf(item) !== idx) { - d.mnemonicInput.splice(idx, 1) + words.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)) { + const item = grid.itemAtIndex(i) + if (!item || item.mnemonicIndex !== (pos + 1)) { continue } - grid.currentIndex = grid.itemAtIndex(i).itemIndex - grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus() + grid.currentIndex = item.itemIndex + item.textEdit.input.edit.forceActiveFocus() if (grid.currentIndex !== 12) { continue @@ -192,12 +228,16 @@ ColumnLayout { } } } + d.mnemonicInput = words + d.checkWordExistence(word, pos) d.checkMnemonicLength() + root.seedPhraseUpdated(d.allEntriesValid, d.buildMnemonicString()) } delegate: StatusSeedPhraseInput { id: seedWordInput - textEdit.input.edit.objectName: `statusSeedPhraseInputField${seedWordInput.leftComponentText}` + + textEdit.input.edit.objectName: `enterSeedPhraseInputField${seedWordInput.leftComponentText}` width: (grid.cellWidth - 8) height: (grid.cellHeight - 8) Behavior on width { NumberAnimation { duration: 180 } } @@ -218,13 +258,16 @@ ColumnLayout { inputList: d.seedPhrases_en property int itemIndex: index - z: (grid.currentIndex === index) ? 150000000 : 0 - onTextChanged: { - d.checkWordExistence(text) - } onDoneInsertingWord: { grid.addWord(mnemonicIndex, word) } + onEditingFinished: { + if (text === "") { + return + } + + grid.addWord(mnemonicIndex, text, true) + } onEditClicked: { grid.currentIndex = index grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus() @@ -287,7 +330,7 @@ ColumnLayout { StatusBaseText { id: invalidSeedTxt - visible: text !== "" + objectName: "enterSeedPhraseInvalidSeedText" Layout.alignment: Qt.AlignHCenter color: Theme.palette.dangerColor1 } diff --git a/ui/imports/shared/stores/BIP39_en.qml b/ui/imports/shared/stores/BIP39_en.qml index fa6941cc73..7fb49b05b2 100644 --- a/ui/imports/shared/stores/BIP39_en.qml +++ b/ui/imports/shared/stores/BIP39_en.qml @@ -1,23 +1,17 @@ import QtQuick 2.13 +import StatusQ.Core.Utils 0.1 + ListModel { id: root - property var words: [] - Component.onCompleted: { - var xhr = new XMLHttpRequest(); - xhr.open("GET", "english.txt"); - xhr.onreadystatechange = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { - root.words = xhr.responseText.split('\n'); - for (var i = 0; i < root.words.length; i++) { - if (root.words[i] !== "") { - insert(count, {"seedWord": root.words[i]}); - } - } + const words = StringUtils.readTextFile(":/imports/shared/stores/english.txt").split(/\r?\n|\r/); + for (var i = 0; i < words.length; i++) { + let word = words[i] + if (word !== "") { + insert(count, {"seedWord": word}); } } - xhr.send(); } } diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 2944e64790..1c514c5435 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -737,6 +737,10 @@ QtObject { globalUtilsInst.copyToClipboard(text) } + function getFromClipboard() { + return globalUtilsInst.getFromClipboard() + } + function copyImageToClipboardByUrl(content) { globalUtilsInst.copyImageToClipboardByUrl(content) }