2023-03-22 15:48:44 +00:00
|
|
|
|
import QtQuick 2.14
|
|
|
|
|
import QtQuick.Layouts 1.14
|
|
|
|
|
|
2024-08-27 21:48:34 +00:00
|
|
|
|
import StatusQ 0.1
|
2023-03-22 15:48:44 +00:00
|
|
|
|
import StatusQ.Core 0.1
|
|
|
|
|
import StatusQ.Core.Theme 0.1
|
2024-02-14 13:50:14 +00:00
|
|
|
|
import StatusQ.Core.Utils 0.1
|
2023-03-22 15:48:44 +00:00
|
|
|
|
import StatusQ.Controls 0.1
|
|
|
|
|
import StatusQ.Controls.Validators 0.1
|
|
|
|
|
|
|
|
|
|
import utils 1.0
|
|
|
|
|
import shared.stores 1.0
|
|
|
|
|
import shared.controls 1.0
|
|
|
|
|
|
|
|
|
|
ColumnLayout {
|
|
|
|
|
id: root
|
|
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
|
|
|
//* This component is not refactored, just pulled out to a shared location *
|
|
|
|
|
//**************************************************************************
|
|
|
|
|
spacing: Style.current.padding
|
|
|
|
|
clip: true
|
|
|
|
|
|
2024-02-14 13:50:14 +00:00
|
|
|
|
readonly property bool seedPhraseIsValid: d.allEntriesValid && invalidSeedTxt.text === ""
|
2023-03-22 15:48:44 +00:00
|
|
|
|
property var isSeedPhraseValid: function (mnemonic) { return false }
|
2024-02-14 13:50:14 +00:00
|
|
|
|
property ListModel dictionary: BIP39_en {}
|
2023-03-22 15:48:44 +00:00
|
|
|
|
|
|
|
|
|
signal submitSeedPhrase()
|
|
|
|
|
signal seedPhraseUpdated(bool valid, string seedPhrase)
|
|
|
|
|
|
|
|
|
|
function setWrongSeedPhraseMessage(message) {
|
|
|
|
|
invalidSeedTxt.text = message
|
2024-02-14 13:50:14 +00:00
|
|
|
|
// 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()
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QtObject {
|
|
|
|
|
id: d
|
|
|
|
|
|
|
|
|
|
property bool allEntriesValid: false
|
|
|
|
|
property var mnemonicInput: []
|
2024-02-14 13:50:14 +00:00
|
|
|
|
property var incorrectWordAtIndex: []
|
2023-03-22 15:48:44 +00:00
|
|
|
|
readonly property var tabs: [12, 18, 24]
|
2024-02-14 13:50:14 +00:00
|
|
|
|
readonly property alias seedPhrases_en: root.dictionary
|
|
|
|
|
|
|
|
|
|
onIncorrectWordAtIndexChanged: d.validate()
|
2023-03-22 15:48:44 +00:00
|
|
|
|
|
|
|
|
|
onAllEntriesValidChanged: {
|
|
|
|
|
let mnemonicString = ""
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
2023-03-22 15:48:44 +00:00
|
|
|
|
if (d.allEntriesValid) {
|
2024-02-14 13:50:14 +00:00
|
|
|
|
mnemonicString = buildMnemonicString()
|
2023-03-22 15:48:44 +00:00
|
|
|
|
if (!Utils.isMnemonic(mnemonicString) || !root.isSeedPhraseValid(mnemonicString)) {
|
|
|
|
|
root.setWrongSeedPhraseMessage(qsTr("Invalid seed phrase"))
|
|
|
|
|
d.allEntriesValid = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
root.seedPhraseUpdated(d.allEntriesValid, mnemonicString)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 13:50:14 +00:00
|
|
|
|
function validate() {
|
|
|
|
|
if (d.incorrectWordAtIndex.length > 0) {
|
|
|
|
|
invalidSeedTxt.text = qsTr("The phrase you’ve entered is invalid")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
invalidSeedTxt.text = ""
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 15:48:44 +00:00
|
|
|
|
function checkMnemonicLength() {
|
2024-02-14 13:50:14 +00:00
|
|
|
|
d.allEntriesValid = d.mnemonicInput.length === d.tabs[switchTabBar.currentIndex] && d.incorrectWordAtIndex.length === 0
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 13:50:14 +00:00
|
|
|
|
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
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
|
|
|
|
d.incorrectWordAtIndex = d.incorrectWordAtIndex.filter(function(value) {
|
|
|
|
|
return value !== pos
|
|
|
|
|
})
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pasteWords () {
|
2024-08-27 21:48:34 +00:00
|
|
|
|
const clipboardText = ClipboardUtils.text
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
2023-03-22 15:48:44 +00:00
|
|
|
|
// Split words separated by commas and or blank spaces (spaces, enters, tabs)
|
2023-08-04 12:41:57 +00:00
|
|
|
|
const words = clipboardText.trim().split(/[, \s]+/)
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
2023-03-22 15:48:44 +00:00
|
|
|
|
let index = d.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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.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])
|
|
|
|
|
}
|
|
|
|
|
d.checkMnemonicLength()
|
|
|
|
|
}, timeout)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
|
id: timer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StatusSwitchTabBar {
|
|
|
|
|
id: switchTabBar
|
2024-02-14 13:50:14 +00:00
|
|
|
|
objectName: "enterSeedPhraseSwitchBar"
|
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
2023-03-22 15:48:44 +00:00
|
|
|
|
Repeater {
|
|
|
|
|
model: d.tabs
|
|
|
|
|
StatusSwitchTabButton {
|
|
|
|
|
text: qsTr("%n word(s)", "", modelData)
|
|
|
|
|
id: seedPhraseWords
|
|
|
|
|
objectName: `${modelData}SeedButton`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onCurrentIndexChanged: {
|
|
|
|
|
d.mnemonicInput = d.mnemonicInput.filter(function(value) {
|
|
|
|
|
return value.pos <= d.tabs[switchTabBar.currentIndex]
|
|
|
|
|
})
|
2024-02-14 13:50:14 +00:00
|
|
|
|
d.incorrectWordAtIndex = d.incorrectWordAtIndex.filter(function(value) {
|
|
|
|
|
return value <= d.tabs[switchTabBar.currentIndex]
|
|
|
|
|
})
|
2023-03-22 15:48:44 +00:00
|
|
|
|
d.checkMnemonicLength()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StatusGridView {
|
|
|
|
|
id: grid
|
|
|
|
|
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"]
|
|
|
|
|
]
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
|
|
|
|
objectName: "enterSeedPhraseGridView"
|
|
|
|
|
Layout.fillWidth: true
|
2023-03-22 15:48:44 +00:00
|
|
|
|
Layout.preferredHeight: 312
|
|
|
|
|
clip: false
|
|
|
|
|
flow: GridView.FlowTopToBottom
|
|
|
|
|
cellWidth: (parent.width/(count/6))
|
|
|
|
|
cellHeight: 52
|
|
|
|
|
interactive: false
|
|
|
|
|
model: switchTabBar.currentItem.text.substring(0,2)
|
|
|
|
|
|
|
|
|
|
function addWord(pos, word, ignoreGoingNext = false) {
|
|
|
|
|
|
2024-02-14 13:50:14 +00:00
|
|
|
|
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
|
2023-03-22 15:48:44 +00:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//remove duplicates
|
2024-02-14 13:50:14 +00:00
|
|
|
|
const valueArr = words.map(item => item.pos)
|
2023-03-22 15:48:44 +00:00
|
|
|
|
const isDuplicate = valueArr.some((item, idx) => {
|
|
|
|
|
if (valueArr.indexOf(item) !== idx) {
|
2024-02-14 13:50:14 +00:00
|
|
|
|
words.splice(idx, 1)
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
|
|
|
|
return valueArr.indexOf(item) !== idx
|
|
|
|
|
})
|
|
|
|
|
if (!ignoreGoingNext) {
|
|
|
|
|
for (let i = 0; i < grid.count; i++) {
|
2024-02-14 13:50:14 +00:00
|
|
|
|
const item = grid.itemAtIndex(i)
|
|
|
|
|
if (!item || item.mnemonicIndex !== (pos + 1)) {
|
2023-03-22 15:48:44 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 13:50:14 +00:00
|
|
|
|
grid.currentIndex = item.itemIndex
|
|
|
|
|
item.textEdit.input.edit.forceActiveFocus()
|
2023-03-22 15:48:44 +00:00
|
|
|
|
|
|
|
|
|
if (grid.currentIndex !== 12) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grid.positionViewAtEnd()
|
|
|
|
|
|
|
|
|
|
if (grid.count === 20) {
|
|
|
|
|
grid.contentX = 1500
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-14 13:50:14 +00:00
|
|
|
|
d.mnemonicInput = words
|
|
|
|
|
d.checkWordExistence(word, pos)
|
2023-03-22 15:48:44 +00:00
|
|
|
|
d.checkMnemonicLength()
|
2024-02-14 13:50:14 +00:00
|
|
|
|
root.seedPhraseUpdated(d.allEntriesValid, d.buildMnemonicString())
|
2023-03-22 15:48:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delegate: StatusSeedPhraseInput {
|
|
|
|
|
id: seedWordInput
|
2024-02-14 13:50:14 +00:00
|
|
|
|
|
|
|
|
|
textEdit.input.edit.objectName: `enterSeedPhraseInputField${seedWordInput.leftComponentText}`
|
2023-03-22 15:48:44 +00:00
|
|
|
|
width: (grid.cellWidth - 8)
|
|
|
|
|
height: (grid.cellHeight - 8)
|
|
|
|
|
Behavior on width { NumberAnimation { duration: 180 } }
|
|
|
|
|
textEdit.text: {
|
|
|
|
|
const pos = seedWordInput.mnemonicIndex
|
|
|
|
|
for (let i in d.mnemonicInput) {
|
|
|
|
|
const p = d.mnemonicInput[i]
|
|
|
|
|
if (p.pos === pos) {
|
|
|
|
|
return p.seed
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readonly property int mnemonicIndex: grid.wordIndex[(grid.count / 6) - 2][index]
|
|
|
|
|
|
|
|
|
|
leftComponentText: mnemonicIndex
|
|
|
|
|
inputList: d.seedPhrases_en
|
|
|
|
|
|
|
|
|
|
property int itemIndex: index
|
|
|
|
|
onDoneInsertingWord: {
|
|
|
|
|
grid.addWord(mnemonicIndex, word)
|
|
|
|
|
}
|
2024-02-14 13:50:14 +00:00
|
|
|
|
onEditingFinished: {
|
|
|
|
|
if (text === "") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grid.addWord(mnemonicIndex, text, true)
|
|
|
|
|
}
|
2023-03-22 15:48:44 +00:00
|
|
|
|
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 (d.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 (d.allEntriesValid) {
|
|
|
|
|
root.submitSeedPhrase()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
|
|
|
|
const wordIndex = d.mnemonicInput.findIndex(x => x.pos === mnemonicIndex)
|
|
|
|
|
if (wordIndex > -1) {
|
|
|
|
|
d.mnemonicInput.splice(wordIndex, 1)
|
|
|
|
|
d.checkMnemonicLength()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Component.onCompleted: {
|
|
|
|
|
const item = grid.itemAtIndex(0)
|
|
|
|
|
if (item) {
|
|
|
|
|
item.textEdit.input.edit.forceActiveFocus()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StatusBaseText {
|
|
|
|
|
id: invalidSeedTxt
|
2024-02-14 13:50:14 +00:00
|
|
|
|
objectName: "enterSeedPhraseInvalidSeedText"
|
2023-03-22 15:48:44 +00:00
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
|
|
color: Theme.palette.dangerColor1
|
|
|
|
|
}
|
|
|
|
|
}
|