2022-03-15 22:27:36 +00:00
|
|
|
import QtQuick 2.12
|
2022-05-10 15:42:35 +00:00
|
|
|
import QtQuick.Controls 2.14
|
2022-03-15 22:27:36 +00:00
|
|
|
import QtGraphicalEffects 1.13
|
2022-04-14 19:10:25 +00:00
|
|
|
import QtQuick.Dialogs 1.3
|
2022-03-15 22:27:36 +00:00
|
|
|
|
|
|
|
import StatusQ.Controls 0.1
|
|
|
|
import StatusQ.Popups 0.1
|
|
|
|
import StatusQ.Core 0.1
|
|
|
|
import StatusQ.Core.Theme 0.1
|
2022-07-20 14:54:30 +00:00
|
|
|
|
2022-03-15 22:27:36 +00:00
|
|
|
import utils 1.0
|
2022-03-31 11:46:25 +00:00
|
|
|
import shared.stores 1.0
|
2022-05-05 15:16:05 +00:00
|
|
|
import shared.controls 1.0
|
2022-07-20 14:54:30 +00:00
|
|
|
|
2022-03-15 22:27:36 +00:00
|
|
|
import "../controls"
|
|
|
|
import "../stores"
|
|
|
|
|
2022-07-20 12:34:44 +00:00
|
|
|
Item {
|
2022-03-15 22:27:36 +00:00
|
|
|
id: root
|
|
|
|
|
2022-07-20 12:34:44 +00:00
|
|
|
property StartupStore startupStore
|
2022-03-15 22:27:36 +00:00
|
|
|
|
|
|
|
property var mnemonicInput: []
|
|
|
|
|
|
|
|
signal seedValidated()
|
|
|
|
|
2022-05-05 15:16:05 +00:00
|
|
|
readonly property var tabs: ([12, 18, 24])
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
id: timer
|
|
|
|
}
|
|
|
|
|
|
|
|
function pasteWords () {
|
|
|
|
const clipboardText = globalUtils.getFromClipboard()
|
|
|
|
// Split words separated by commas and or blank spaces (spaces, enters, tabs)
|
|
|
|
let 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(function() {
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
let pos = parseInt(item.leftComponentText)
|
|
|
|
item.setWord(words[pos - 1])
|
|
|
|
}
|
|
|
|
submitButton.checkMnemonicLength()
|
|
|
|
}, timeout);
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-03-15 22:27:36 +00:00
|
|
|
Item {
|
2022-05-10 16:04:25 +00:00
|
|
|
implicitWidth: 565
|
|
|
|
implicitHeight: parent.height
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
2022-04-28 07:32:17 +00:00
|
|
|
|
|
|
|
StatusBaseText {
|
|
|
|
id: headlineText
|
|
|
|
font.pixelSize: 22
|
|
|
|
font.weight: Font.Bold
|
|
|
|
color: Theme.palette.directColor1
|
2022-05-10 16:04:25 +00:00
|
|
|
anchors.top: parent.top
|
|
|
|
anchors.topMargin: (root.height - parent.childrenRect.height)/2
|
2022-04-28 07:32:17 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
text: qsTr("Enter seed phrase")
|
|
|
|
}
|
|
|
|
|
2022-03-15 22:27:36 +00:00
|
|
|
StatusSwitchTabBar {
|
|
|
|
id: switchTabBar
|
2022-08-03 17:31:59 +00:00
|
|
|
objectName: "onboardingSeedPhraseSwitchBar"
|
2022-04-28 07:32:17 +00:00
|
|
|
anchors.top: headlineText.bottom
|
2022-03-15 22:27:36 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
2022-04-28 07:32:17 +00:00
|
|
|
anchors.topMargin: 24
|
2022-05-05 15:16:05 +00:00
|
|
|
Repeater {
|
|
|
|
model: root.tabs
|
|
|
|
StatusSwitchTabButton {
|
2022-05-10 16:04:25 +00:00
|
|
|
text: qsTr("%1 words").arg(modelData)
|
2022-06-29 01:12:35 +00:00
|
|
|
id: seedPhraseWords
|
2022-08-03 17:31:59 +00:00
|
|
|
objectName: `${modelData}SeedButton`
|
2022-05-05 15:16:05 +00:00
|
|
|
}
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
onCurrentIndexChanged: {
|
2022-05-23 11:08:00 +00:00
|
|
|
root.mnemonicInput = root.mnemonicInput.filter(function(value) {
|
|
|
|
return value.pos <= root.tabs[switchTabBar.currentIndex]
|
|
|
|
})
|
|
|
|
submitButton.checkMnemonicLength()
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
clip: true
|
|
|
|
|
2022-07-20 14:54:30 +00:00
|
|
|
StatusGridView {
|
2022-03-15 22:27:36 +00:00
|
|
|
id: grid
|
2022-08-03 17:31:59 +00:00
|
|
|
objectName: "seedPhraseGridView"
|
2022-03-15 22:27:36 +00:00
|
|
|
width: parent.width
|
2022-05-10 16:04:25 +00:00
|
|
|
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
|
2022-08-02 09:29:47 +00:00
|
|
|
clip: false
|
2022-03-15 22:27:36 +00:00
|
|
|
anchors.left: parent.left
|
|
|
|
anchors.leftMargin: 12
|
|
|
|
anchors.top: switchTabBar.bottom
|
2022-05-10 16:04:25 +00:00
|
|
|
anchors.topMargin: 24
|
2022-03-15 22:27:36 +00:00
|
|
|
flow: GridView.FlowTopToBottom
|
2022-05-10 16:04:25 +00:00
|
|
|
cellWidth: (parent.width/(count/6)) - 8
|
|
|
|
cellHeight: 52
|
2022-03-15 22:27:36 +00:00
|
|
|
interactive: false
|
|
|
|
z: 100000
|
2022-05-05 15:16:05 +00:00
|
|
|
cacheBuffer: 9999
|
2022-05-10 16:04:25 +00:00
|
|
|
model: switchTabBar.currentItem.text.substring(0,2)
|
2022-05-10 15:42:35 +00:00
|
|
|
|
|
|
|
function addWord(pos, word, ignoreGoingNext) {
|
2022-05-05 15:16:05 +00:00
|
|
|
root.mnemonicInput.push({pos: parseInt(pos), seed: word.replace(/\s/g, '')});
|
2022-05-10 15:42:35 +00:00
|
|
|
for (var j = 0; j < mnemonicInput.length; j++) {
|
|
|
|
if (mnemonicInput[j].pos === pos && mnemonicInput[j].seed !== word) {
|
|
|
|
mnemonicInput[j].seed = word;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//remove duplicates
|
|
|
|
var valueArr = mnemonicInput.map(function(item){ return item.pos });
|
|
|
|
var isDuplicate = valueArr.some(function(item, idx){
|
|
|
|
if (valueArr.indexOf(item) !== idx) {
|
|
|
|
root.mnemonicInput.splice(idx, 1);
|
|
|
|
}
|
|
|
|
return valueArr.indexOf(item) !== idx
|
|
|
|
});
|
|
|
|
if (!ignoreGoingNext) {
|
|
|
|
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
|
|
|
if (parseInt(grid.itemAtIndex(i).leftComponentText) !== (parseInt(pos)+1)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
grid.currentIndex = grid.itemAtIndex(i).itemIndex;
|
|
|
|
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
|
|
|
|
|
|
|
if (grid.currentIndex !== 12) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
grid.positionViewAtEnd();
|
2022-05-05 15:16:05 +00:00
|
|
|
|
2022-05-10 15:42:35 +00:00
|
|
|
if (grid.count === 20) {
|
|
|
|
grid.contentX = 1500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
submitButton.checkMnemonicLength()
|
|
|
|
}
|
|
|
|
|
2022-03-15 22:27:36 +00:00
|
|
|
delegate: StatusSeedPhraseInput {
|
|
|
|
id: seedWordInput
|
2022-08-03 17:31:59 +00:00
|
|
|
textEdit.input.edit.objectName: `statusSeedPhraseInputField${seedWordInput.leftComponentText}`
|
2022-05-10 16:04:25 +00:00
|
|
|
width: (grid.cellWidth - 8)
|
|
|
|
height: (grid.cellHeight - 8)
|
|
|
|
Behavior on width { NumberAnimation { duration: 180 } }
|
2022-04-07 16:17:30 +00:00
|
|
|
textEdit.text: {
|
2022-05-05 15:16:05 +00:00
|
|
|
let pos = parseInt(seedWordInput.leftComponentText)
|
2022-04-07 16:17:30 +00:00
|
|
|
for (var i in root.mnemonicInput) {
|
|
|
|
let p = root.mnemonicInput[i]
|
2022-05-05 15:16:05 +00:00
|
|
|
if (p.pos === pos) {
|
2022-04-07 16:17:30 +00:00
|
|
|
return p.seed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2022-05-10 16:04:25 +00:00
|
|
|
leftComponentText: grid.wordIndex[(grid.count/6)-2][index]
|
2022-03-15 22:27:36 +00:00
|
|
|
inputList: BIP39_en { }
|
|
|
|
property int itemIndex: index
|
|
|
|
z: (grid.currentIndex === index) ? 150000000 : 0
|
|
|
|
onTextChanged: {
|
|
|
|
invalidSeedTxt.visible = false;
|
|
|
|
}
|
|
|
|
onDoneInsertingWord: {
|
2022-05-10 15:42:35 +00:00
|
|
|
grid.addWord(leftComponentText, word)
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
onEditClicked: {
|
|
|
|
grid.currentIndex = index;
|
|
|
|
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
|
|
|
|
}
|
|
|
|
onKeyPressed: {
|
2022-07-15 14:58:26 +00:00
|
|
|
grid.currentIndex = index;
|
|
|
|
|
2022-05-10 15:42:35 +00:00
|
|
|
if (event.key === Qt.Key_Backtab) {
|
2022-05-10 16:04:25 +00:00
|
|
|
for (var i = 0; i < grid.count; i++) {
|
2022-05-10 15:42:35 +00:00
|
|
|
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
|
2022-07-15 14:58:26 +00:00
|
|
|
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus(Qt.BacktabFocusReason);
|
2022-03-15 22:27:36 +00:00
|
|
|
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
|
2022-05-10 15:42:35 +00:00
|
|
|
event.accepted = true
|
|
|
|
break
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-10 15:42:35 +00:00
|
|
|
} else if (event.key === Qt.Key_Tab) {
|
2022-05-10 16:04:25 +00:00
|
|
|
for (var i = 0; i < grid.count; i++) {
|
2022-05-10 15:42:35 +00:00
|
|
|
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+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
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-10 15:42:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (event.matches(StandardKey.Paste)) {
|
2022-05-05 15:16:05 +00:00
|
|
|
if (root.pasteWords()) {
|
|
|
|
// Paste was done by splitting the words
|
|
|
|
event.accepted = true
|
2022-05-10 15:42:35 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
|
|
event.accepted = true
|
|
|
|
if (submitButton.enabled) {
|
|
|
|
submitButton.clicked(null)
|
|
|
|
return
|
|
|
|
}
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
2022-05-05 15:16:05 +00:00
|
|
|
var wordIndex = mnemonicInput.findIndex(x => x.pos === parseInt(leftComponentText));
|
2022-03-15 22:27:36 +00:00
|
|
|
if (wordIndex > -1) {
|
|
|
|
mnemonicInput.splice(wordIndex , 1);
|
2022-05-10 15:42:35 +00:00
|
|
|
submitButton.checkMnemonicLength()
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-05 15:16:05 +00:00
|
|
|
Component.onCompleted: {
|
|
|
|
let item = grid.itemAtIndex(0)
|
|
|
|
if (item) {
|
|
|
|
item.textEdit.input.edit.forceActiveFocus();
|
|
|
|
}
|
|
|
|
}
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusBaseText {
|
|
|
|
id: invalidSeedTxt
|
2022-08-03 17:31:59 +00:00
|
|
|
objectName: "onboardingInvalidSeedText"
|
2022-03-15 22:27:36 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
anchors.top: grid.bottom
|
2022-05-10 16:04:25 +00:00
|
|
|
anchors.topMargin: 24
|
2022-03-15 22:27:36 +00:00
|
|
|
color: Theme.palette.dangerColor1
|
|
|
|
visible: false
|
|
|
|
text: qsTr("Invalid seed")
|
|
|
|
}
|
|
|
|
|
|
|
|
StatusButton {
|
|
|
|
id: submitButton
|
2022-07-29 22:51:34 +00:00
|
|
|
objectName: "seedPhraseViewSubmitButton"
|
2022-03-15 22:27:36 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
anchors.top: invalidSeedTxt.bottom
|
|
|
|
anchors.topMargin: 24
|
|
|
|
enabled: false
|
2022-05-10 15:42:35 +00:00
|
|
|
function checkMnemonicLength() {
|
2022-05-23 11:08:00 +00:00
|
|
|
submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex])
|
2022-05-10 15:42:35 +00:00
|
|
|
}
|
2022-07-21 11:29:18 +00:00
|
|
|
text: {
|
|
|
|
if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) {
|
|
|
|
return qsTr("Import")
|
|
|
|
}
|
|
|
|
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) {
|
|
|
|
return qsTr("Restore Status Profile")
|
|
|
|
}
|
|
|
|
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserKeycardImport ||
|
|
|
|
root.startupStore.currentStartupState.flowType === Constants.startupFlow.appLogin) {
|
|
|
|
return qsTr("Recover Keycard")
|
|
|
|
}
|
|
|
|
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard) {
|
|
|
|
return qsTr("Next")
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2022-03-15 22:27:36 +00:00
|
|
|
onClicked: {
|
2022-05-23 11:08:00 +00:00
|
|
|
let mnemonicString = "";
|
2022-05-10 16:04:25 +00:00
|
|
|
var sortTable = mnemonicInput.sort(function (a, b) {
|
|
|
|
return a.pos - b.pos;
|
|
|
|
});
|
|
|
|
for (var i = 0; i < mnemonicInput.length; i++) {
|
2022-05-23 11:08:00 +00:00
|
|
|
mnemonicString += sortTable[i].seed + ((i === (grid.count-1)) ? "" : " ");
|
2022-05-10 16:04:25 +00:00
|
|
|
}
|
2022-07-20 12:34:44 +00:00
|
|
|
if (Utils.isMnemonic(mnemonicString) && root.startupStore.validMnemonic(mnemonicString)) {
|
2022-05-10 16:04:25 +00:00
|
|
|
root.mnemonicInput = [];
|
2022-07-20 12:34:44 +00:00
|
|
|
root.startupStore.doPrimaryAction()
|
2022-05-10 16:04:25 +00:00
|
|
|
} else {
|
|
|
|
invalidSeedTxt.visible = true;
|
|
|
|
enabled = false;
|
2022-03-15 22:27:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|