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
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-03-15 22:27:36 +00:00
import "../controls"
import "../stores"
OnboardingBasePage {
id: root
state: "existingUser"
property bool existingUser: ( root . state === "existingUser" )
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-04-14 19:10:25 +00:00
Connections {
target: OnboardingStore . onboardingModuleInst
onAccountImportError: {
if ( error === Constants . existingAccountError ) {
importSeedError . title = qsTr ( "Keys for this account already exist" )
importSeedError . text = qsTr ( "Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase" )
} else {
importSeedError . title = qsTr ( "Error importing seed" )
importSeedError . text = error
}
importSeedError . open ( )
}
onAccountImportSuccess: {
root . seedValidated ( )
}
}
MessageDialog {
id: importSeedError
icon: StandardIcon . Critical
standardButtons: StandardButton . Ok
}
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-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
objectName: qsTr ( "%1SeedButton" ) . arg ( modelData )
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
GridView {
id: grid
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-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-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-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 ) ) {
grid . itemAtIndex ( i ) . textEdit . input . edit . forceActiveFocus ( Qt . TabFocusReason ) ;
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
}
}
grid . currentIndex = index ;
}
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
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
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-05-10 16:04:25 +00:00
text: root . existingUser ? qsTr ( "Restore Status Profile" ) : qsTr ( "Import" )
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-05-23 11:08:00 +00:00
if ( Utils . isMnemonic ( mnemonicString ) && ! OnboardingStore . validateMnemonic ( mnemonicString ) ) {
OnboardingStore . importMnemonic ( mnemonicString )
2022-05-10 16:04:25 +00:00
root . mnemonicInput = [ ] ;
} else {
invalidSeedTxt . visible = true ;
enabled = false ;
2022-03-15 22:27:36 +00:00
}
}
}
}
onBackClicked: {
2022-05-10 16:04:25 +00:00
root . mnemonicInput = [ ] ;
root . exit ( ) ;
2022-03-15 22:27:36 +00:00
}
}