feat: Add import account functionality

Allow user to import an existing mnemonic.

TODO: add mnemonic validation with the `validateMnemonic` status-go function.
This commit is contained in:
emizzle 2020-06-02 18:41:24 +10:00 committed by Iuri Matias
parent dc6793a0f0
commit 691717990d
15 changed files with 522 additions and 182 deletions

View File

@ -25,10 +25,11 @@ proc init*(self: LoginController, nodeAccounts: seq[NodeAccount]) =
self.view.addAccountToList(nodeAccount)
proc handleNodeLogin(self: LoginController, data: Signal) =
var response = NodeSignal(data)
self.view.setLastLoginResponse($response.event.toJson)
if ?.response.event.error == "" and self.status.accounts.currentAccount != nil:
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentAccount))
let response = NodeSignal(data)
if self.status.accounts.currentLoginAccount != nil:
self.view.setLastLoginResponse(response.event)
if ?.response.event.error == "":
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentLoginAccount))
method onSignal(self: LoginController, data: Signal) =
if data.signalType == SignalType.NodeLogin:

View File

@ -23,7 +23,6 @@ QtObject:
type LoginView* = ref object of QAbstractListModel
status: Status
accounts: seq[NodeAccount]
lastLoginResponse: string
proc setup(self: LoginView) =
self.QAbstractListModel.setup
@ -35,7 +34,6 @@ QtObject:
proc newLoginView*(status: Status): LoginView =
new(result, delete)
result.accounts = @[]
result.lastLoginResponse = ""
result.status = status
result.setup
@ -72,18 +70,9 @@ QtObject:
let
e = getCurrentException()
msg = getCurrentExceptionMsg()
result = SignalError(error: msg).toJson
result = StatusGoError(error: msg).toJson
proc lastLoginResponse*(self: LoginView): string =
result = self.lastLoginResponse
proc loginResponseChanged*(self: LoginView, error: string) {.signal.}
proc loginResponseChanged*(self: LoginView, response: string) {.signal.}
proc setLastLoginResponse*(self: LoginView, loginResponse: string) {.slot.} =
self.lastLoginResponse = loginResponse
self.loginResponseChanged(loginResponse)
QtProperty[string] loginResponse:
read = lastLoginResponse
write = setLastLoginResponse
notify = loginResponseChanged
proc setLastLoginResponse*(self: LoginView, loginResponse: StatusGoError) =
self.loginResponseChanged(loginResponse.error)

View File

@ -7,7 +7,6 @@ import view
import chronicles
import ../../signals/types
import std/wrapnils
import ../../status/status
type OnboardingController* = ref object of SignalSubscriber
@ -31,10 +30,11 @@ proc init*(self: OnboardingController) =
self.view.addAccountToList(account)
proc handleNodeLogin(self: OnboardingController, data: Signal) =
var response = NodeSignal(data)
self.view.setLastLoginResponse($response.event.toJson)
if ?.response.event.error == "" and self.status.accounts.currentAccount != nil:
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentAccount))
let response = NodeSignal(data)
if self.status.accounts.currentOnboardingAccount != nil:
self.view.setLastLoginResponse(response.event)
if ?.response.event.error == "":
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentOnboardingAccount))
method onSignal(self: OnboardingController, data: Signal) =
if data.signalType == SignalType.NodeLogin:

View File

@ -6,6 +6,8 @@ import ../../signals/types
import strformat
import json_serialization
import ../../status/accounts as AccountModel
import views/account_info
import strutils
import ../../status/status
type
@ -17,7 +19,7 @@ type
QtObject:
type OnboardingView* = ref object of QAbstractListModel
accounts*: seq[GeneratedAccount]
lastLoginResponse: string
importedAccount: AccountInfoView
status*: Status
proc setup(self: OnboardingView) =
@ -30,7 +32,7 @@ QtObject:
proc newOnboardingView*(status: Status): OnboardingView =
new(result, delete)
result.accounts = @[]
result.lastLoginResponse = ""
result.importedAccount = newAccountInfoView()
result.status = status
result.setup
@ -53,7 +55,7 @@ QtObject:
case assetRole:
of AccountRoles.Username: result = newQVariant(asset.name)
of AccountRoles.Identicon: result = newQVariant(asset.photoPath)
of AccountRoles.Key: result = newQVariant(asset.keyUid)
of AccountRoles.Key: result = newQVariant(asset.derived.whisper.address)
method roleNames(self: OnboardingView): Table[int, string] =
{ AccountRoles.Username.int:"username",
@ -67,18 +69,35 @@ QtObject:
let
e = getCurrentException()
msg = getCurrentExceptionMsg()
result = SignalError(error: msg).toJson
result = StatusGoError(error: msg).toJson
proc lastLoginResponse*(self: OnboardingView): string =
result = self.lastLoginResponse
proc getImportedAccount*(self: OnboardingView): QVariant {.slot.} =
result = newQVariant(self.importedAccount)
proc loginResponseChanged*(self: OnboardingView, response: string) {.signal.}
proc setImportedAccount*(self: OnboardingView, importedAccount: GeneratedAccount) =
self.importedAccount.setAccount(importedAccount)
proc setLastLoginResponse*(self: OnboardingView, loginResponse: string) {.slot.} =
self.lastLoginResponse = loginResponse
self.loginResponseChanged(loginResponse)
QtProperty[QVariant] importedAccount:
read = getImportedAccount
QtProperty[string] loginResponse:
read = lastLoginResponse
write = setLastLoginResponse
notify = loginResponseChanged
proc importMnemonic(self: OnboardingView, mnemonic: string): string {.slot.} =
try:
let importResult = self.status.accounts.importMnemonic(mnemonic)
result = importResult.toJson
self.setImportedAccount(importResult)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc storeDerivedAndLogin(self: OnboardingView, password: string): string {.slot.} =
try:
result = self.status.accounts.storeDerivedAndLogin(self.importedAccount.account, password).toJson
except StatusGoException as e:
var msg = e.msg
if e.msg.contains("account already exists"):
msg = "Account already exists. Please try importing another account."
result = StatusGoError(error: msg).toJson
proc loginResponseChanged*(self: OnboardingView, error: string) {.signal.}
proc setLastLoginResponse*(self: OnboardingView, loginResponse: StatusGoError) =
self.loginResponseChanged(loginResponse.error)

View File

@ -0,0 +1,44 @@
import NimQml
import ../../../status/libstatus/types
import std/wrapnils
QtObject:
type AccountInfoView* = ref object of QObject
account*: GeneratedAccount
proc setup(self: AccountInfoView) =
self.QObject.setup
proc delete*(self: AccountInfoView) =
self.QObject.delete
proc newAccountInfoView*(): AccountInfoView =
new(result, delete)
result = AccountInfoView()
result.setup
proc accountChanged*(self: AccountInfoView) {.signal.}
proc setAccount*(self: AccountInfoView, account: GeneratedAccount) =
self.account = account
self.accountChanged()
proc username*(self: AccountInfoView): string {.slot.} = result = ?.self.account.name
QtProperty[string] username:
read = username
notify = accountChanged
proc identicon*(self: AccountInfoView): string {.slot.} = result = ?.self.account.photoPath
QtProperty[string] identicon:
read = identicon
notify = accountChanged
proc address*(self: AccountInfoView): string {.slot.} = result = ?.self.account.derived.whisper.publicKey
QtProperty[string] address:
read = address
notify = accountChanged
proc id*(self: AccountInfoView): string {.slot.} = result = ?.self.account.id
QtProperty[string] id:
read = id
notify = accountChanged

View File

@ -1,7 +1,6 @@
import NimQml
import eventemitter
import chronicles
import json_serialization
import app/chat/core as chat
import app/wallet/core as wallet

View File

@ -8,11 +8,11 @@ type SignalSubscriber* = ref object of RootObj
type Signal* = ref object of RootObj
signalType* {.serializedFieldName("type").}: SignalType
type SignalError* = object
type StatusGoError* = object
error*: string
type NodeSignal* = ref object of Signal
event*: SignalError
event*: StatusGoError
type WalletSignal* = ref object of Signal
content*: string

View File

@ -6,11 +6,13 @@ type
AccountModel* = ref object
generatedAddresses*: seq[GeneratedAccount]
nodeAccounts*: seq[NodeAccount]
currentAccount*: Account
currentLoginAccount*: Account
currentOnboardingAccount*: Account
proc newAccountModel*(): AccountModel =
result = AccountModel()
result.currentAccount = nil
result.currentLoginAccount = nil
result.currentOnboardingAccount = nil
proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
var accounts = status_accounts.generateAddresses()
@ -22,10 +24,21 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount =
let currentNodeAccount = self.nodeAccounts[selectedAccountIndex]
self.currentAccount = currentNodeAccount.toAccount
self.currentLoginAccount = currentNodeAccount.toAccount
result = status_accounts.login(currentNodeAccount, password)
proc storeAccountAndLogin*(self: AccountModel, selectedAccountIndex: int, password: string): Account =
let generatedAccount: GeneratedAccount = self.generatedAddresses[selectedAccountIndex]
result = status_accounts.setupAccount(generatedAccount, password)
self.currentAccount = generatedAccount.toAccount
self.currentOnboardingAccount = generatedAccount.toAccount
proc storeDerivedAndLogin*(self: AccountModel, importedAccount: GeneratedAccount, password: string): Account =
result = status_accounts.setupAccount(importedAccount, password)
self.currentOnboardingAccount = importedAccount.toAccount
proc importMnemonic*(self: AccountModel, mnemonic: string): GeneratedAccount =
let importedAccount = status_accounts.multiAccountImportMnemonic(mnemonic)
importedAccount.derived = status_accounts.deriveAccounts(importedAccount.id)
importedAccount.name = status_accounts.generateAlias(importedAccount.derived.whisper.publicKey)
importedAccount.photoPath = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey)
result = importedAccount

View File

@ -9,6 +9,7 @@ import uuids
import types
import json_serialization
import chronicles
import ../../signals/types as signal_types
proc queryAccounts*(): string =
var response = callPrivateRPC("eth_accounts")
@ -19,9 +20,10 @@ proc generateAddresses*(): seq[GeneratedAccount] =
"n": 5,
"mnemonicPhraseLength": 12,
"bip39Passphrase": "",
"paths": [PATH_WHISPER, PATH_WALLET_ROOT, PATH_DEFAULT_WALLET]
"paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET]
}
result = Json.decode($libstatus.multiAccountGenerateAndDeriveAddresses($multiAccountConfig), seq[GeneratedAccount])
let generatedAccounts = $libstatus.multiAccountGenerateAndDeriveAddresses($multiAccountConfig)
result = Json.decode(generatedAccounts, seq[GeneratedAccount])
proc generateAlias*(publicKey: string): string =
result = $libstatus.generateAlias(publicKey.toGoString)
@ -48,9 +50,7 @@ proc initNodeAccounts*(): seq[NodeAccount] =
result = Json.decode(strNodeAccounts, seq[NodeAccount])
proc saveAccountAndLogin*(
multiAccounts: MultiAccounts,
alias: string,
identicon: string,
account: GeneratedAccount,
accountData: string,
password: string,
configJSON: string,
@ -58,18 +58,18 @@ proc saveAccountAndLogin*(
let hashedPassword = "0x" & $keccak_256.digest(password)
let subaccountData = %* [
{
"public-key": multiAccounts.defaultWallet.publicKey,
"address": multiAccounts.defaultWallet.address,
"public-key": account.derived.defaultWallet.publicKey,
"address": account.derived.defaultWallet.address,
"color": "#4360df",
"wallet": true,
"path": constants.PATH_DEFAULT_WALLET,
"name": "Status account"
},
{
"public-key": multiAccounts.whisper.publicKey,
"address": multiAccounts.whisper.address,
"name": alias,
"photo-path": identicon,
"public-key": account.derived.whisper.publicKey,
"address": account.derived.whisper.address,
"name": account.name,
"photo-path": account.photoPath,
"path": constants.PATH_WHISPER,
"chat": true
}
@ -81,47 +81,52 @@ proc saveAccountAndLogin*(
if error == "":
debug "Account saved succesfully"
result = Account(name: alias, photoPath: identicon)
result = account.toAccount
return
raise newException(LoginError, "Error saving account and logging in: " & error)
raise newException(StatusGoException, "Error saving account and logging in: " & error)
proc generateMultiAccounts*(account: GeneratedAccount, password: string): MultiAccounts =
proc storeDerivedAccounts*(account: GeneratedAccount, password: string): MultiAccounts =
let hashedPassword = "0x" & $keccak_256.digest(password)
let multiAccount = %* {
"accountID": account.id,
"paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET],
"password": hashedPassword
}
var response = $libstatus.multiAccountStoreDerivedAccounts($multiAccount);
result = Json.decode($response, MultiAccounts)
let response = $libstatus.multiAccountStoreDerivedAccounts($multiAccount);
try:
result = Json.decode($response, MultiAccounts)
except:
let err = Json.decode($response, StatusGoError)
raise newException(StatusGoException, "Error storing multiaccount derived accounts: " & err.error)
proc getAccountData*(account: GeneratedAccount, alias: string, identicon: string): JsonNode =
proc getAccountData*(account: GeneratedAccount): JsonNode =
result = %* {
"name": alias,
"name": account.name,
"address": account.address,
"photo-path": identicon,
"photo-path": account.photoPath,
"key-uid": account.keyUid,
"keycard-pairing": nil
}
proc getAccountSettings*(account: GeneratedAccount, alias: string, identicon: string, multiAccounts: MultiAccounts, defaultNetworks: JsonNode): JsonNode =
proc getAccountSettings*(account: GeneratedAccount, defaultNetworks: JsonNode): JsonNode =
result = %* {
"key-uid": account.keyUid,
"mnemonic": account.mnemonic,
"public-key": multiAccounts.whisper.publicKey,
"name": alias,
"public-key": account.derived.whisper.publicKey,
"name": account.name,
"address": account.address,
"eip1581-address": multiAccounts.eip1581.address,
"dapps-address": multiAccounts.defaultWallet.address,
"wallet-root-address": multiAccounts.walletRoot.address,
"eip1581-address": account.derived.eip1581.address,
"dapps-address": account.derived.defaultWallet.address,
"wallet-root-address": account.derived.walletRoot.address,
"preview-privacy?": true,
"signing-phrase": generateSigningPhrase(3),
"log-level": "INFO",
"latest-derived-path": 0,
"networks/networks": defaultNetworks,
"currency": "usd",
"photo-path": identicon,
"photo-path": account.photoPath,
"waku-enabled": true,
"wallet/visible-tokens": {
"mainnet": ["SNT"]
@ -132,20 +137,20 @@ proc getAccountSettings*(account: GeneratedAccount, alias: string, identicon: st
}
proc setupAccount*(account: GeneratedAccount, password: string): Account =
let multiAccounts = generateMultiAccounts(account, password)
try:
let storeDerivedResult = storeDerivedAccounts(account, password)
let accountData = getAccountData(account)
var settingsJSON = getAccountSettings(account, constants.DEFAULT_NETWORKS)
let whisperPubKey = account.derived.whisper.publicKey
let alias = generateAlias(whisperPubKey)
let identicon =generateIdenticon(whisperPubKey)
result = saveAccountAndLogin(account, $accountData, password, $constants.NODE_CONFIG, $settingsJSON)
let accountData = getAccountData(account, alias, identicon)
var settingsJSON = getAccountSettings(account, alias, identicon, multiAccounts, constants.DEFAULT_NETWORKS)
result = saveAccountAndLogin(multiAccounts, alias, identicon, $accountData, password, $constants.NODE_CONFIG, $settingsJSON)
# TODO this is needed for now for the retrieving of past messages. We'll either move or remove it later
let peer = "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443"
discard libstatus.addPeer(peer)
except StatusGoException as e:
raise newException(StatusGoException, "Error setting up account: " & e.msg)
finally:
# TODO this is needed for now for the retrieving of past messages. We'll either move or remove it later
let peer = "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443"
discard libstatus.addPeer(peer)
proc login*(nodeAccount: NodeAccount, password: string): NodeAccount =
let hashedPassword = "0x" & $keccak_256.digest(password)
@ -158,4 +163,22 @@ proc login*(nodeAccount: NodeAccount, password: string): NodeAccount =
result = nodeAccount
return
raise newException(LoginError, "Error logging in: " & error)
raise newException(StatusGoException, "Error logging in: " & error)
proc multiAccountImportMnemonic*(mnemonic: string): GeneratedAccount =
let mnemonicJson = %* {
"mnemonicPhrase": mnemonic,
"Bip39Passphrase": ""
}
# libstatus.multiAccountImportMnemonic never results in an error given ANY input
let importResult = $libstatus.multiAccountImportMnemonic($mnemonicJson)
result = Json.decode(importResult, GeneratedAccount)
proc deriveAccounts*(accountId: string): MultiAccounts =
let deriveJson = %* {
"accountID": accountId,
"paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET]
}
# libstatus.multiAccountImportMnemonic never results in an error given ANY input
let deriveResult = $libstatus.multiAccountDeriveAddresses($deriveJson)
result = Json.decode(deriveResult, MultiAccounts)

View File

@ -10,6 +10,10 @@ proc multiAccountGenerateAndDeriveAddresses*(paramsJSON: cstring): cstring {.imp
proc multiAccountStoreDerivedAccounts*(paramsJSON: cstring): cstring {.importc: "MultiAccountStoreDerivedAccounts".}
proc multiAccountImportMnemonic*(paramsJSON: cstring): cstring {.importc: "MultiAccountImportMnemonic".}
proc multiAccountDeriveAddresses*(paramsJSON: cstring): cstring {.importc: "MultiAccountDeriveAddresses".}
proc saveAccountAndLogin*(accountData: cstring, password: cstring, settingsJSON: cstring, configJSON: cstring, subaccountData: cstring): cstring {.importc: "SaveAccountAndLogin".}
proc callRPC*(inputJSON: cstring): cstring {.importc: "CallRPC".}

View File

@ -73,4 +73,4 @@ type AccountArgs* = ref object of Args
account*: Account
type
LoginError* = object of Exception
StatusGoException* = object of Exception

View File

@ -3,16 +3,230 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Window 2.11
import QtQuick.Dialogs 1.3
import "../shared"
import "../imports"
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: 0
onCurrentItemChanged: {
currentItem.txtPassword.textField.focus = true;
}
Item {
id: wizardStep1
property Item txtPassword: txtMnemonic
width: 620
height: 427
Text {
text: "Existing key flow [COMING SOON]"
font.pointSize: 36
anchors.centerIn: parent
Text {
id: title
text: "Enter mnemonic"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Input {
id: txtMnemonic
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.padding
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
placeholderText: "Enter 12, 15, 21 or 24 words. Separate words by a single space."
Keys.onReturnPressed: {
btnImport.clicked()
}
}
StyledButton {
id: btnImport
label: "Next"
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.padding
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
onboardingModel.importMnemonic(txtMnemonic.textField.text);
swipeView.incrementCurrentIndex();
}
}
}
Item {
id: wizardStep2
property Item txtPassword: txtPassword
Text {
id: step2Title
text: "Enter password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: step2Title.bottom
anchors.topMargin: 30
Column {
Image {
source: onboardingModel.importedAccount.identicon
}
}
Column {
Text {
text: onboardingModel.importedAccount.username
}
Text {
text: onboardingModel.importedAccount.address
width: 160
elide: Text.ElideMiddle
}
}
}
Input {
id: txtPassword
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.padding
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
placeholderText: "Enter password"
Component.onCompleted: {
this.textField.echoMode = TextInput.Password
}
Keys.onReturnPressed: {
btnNext.clicked()
}
}
StyledButton {
id: btnNext
label: "Next"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
swipeView.incrementCurrentIndex();
}
}
}
Item {
id: wizardStep3
property Item txtPassword: txtConfirmPassword
Text {
id: step3Title
text: "Confirm password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: step3Title.bottom
anchors.topMargin: 30
Column {
Image {
source: onboardingModel.importedAccount.identicon
}
}
Column {
Text {
text: onboardingModel.importedAccount.username
}
Text {
text: onboardingModel.importedAccount.address
width: 160
elide: Text.ElideMiddle
}
}
}
Input {
id: txtConfirmPassword
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.padding
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
placeholderText: "Confirm entered password"
Component.onCompleted: {
this.textField.echoMode = TextInput.Password
}
Keys.onReturnPressed: {
btnFinish.clicked()
}
}
MessageDialog {
id: importError
title: "Error importing account"
text: "An error occurred while importing your account: "
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
onAccepted: {
swipeView.currentIndex = 0
}
}
MessageDialog {
id: importLoginError
title: "Login failed"
text: "Login failed. Please re-enter your password and try again."
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
Connections {
target: onboardingModel
ignoreUnknownSignals: true
onLoginResponseChanged: {
if(error){
importLoginError.open()
}
}
}
StyledButton {
id: btnFinish
label: "Finish"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
if (txtConfirmPassword.textField.text != txtPassword.textField.text) {
return passwordsDontMatchError.open();
}
const result = onboardingModel.storeDerivedAndLogin(txtConfirmPassword.textField.text);
const error = JSON.parse(result).error;
if (error) {
importError.text += error;
importError.open();
}
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -3,6 +3,7 @@ import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Window 2.11
import QtQuick.Dialogs 1.3
import "../imports"
import "../shared"
SwipeView {
@ -10,15 +11,17 @@ SwipeView {
anchors.fill: parent
currentIndex: 0
property alias btnExistingKey: btnExistingKey
onCurrentItemChanged: {
currentItem.txtPassword.focus = true;
currentItem.txtPassword.textField.focus = true;
}
Item {
id: wizardStep2
id: wizardStep1
property int selectedIndex: 0
width: 620
height: 427
Layout.fillHeight: true
Layout.fillWidth: true
Text {
id: title
@ -37,62 +40,79 @@ SwipeView {
anchors.topMargin: 20
ButtonGroup {
id: accountGroup
}
ButtonGroup {
id: accountGroup
}
Component {
id: addressViewDelegate
Component {
id: addressViewDelegate
Item {
height: 56
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
Item {
height: 56
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
Row {
RadioButton {
checked: index == 0 ? true : false
ButtonGroup.group: accountGroup
onClicked: {
wizardStep2.selectedIndex = index
}
Row {
RadioButton {
checked: index == 0 ? true : false
ButtonGroup.group: accountGroup
onClicked: {
wizardStep1.selectedIndex = index
}
Column {
Image {
source: identicon
}
}
Column {
Image {
source: identicon
}
Column {
Text {
text: username
}
Text {
text: key
width: 160
elide: Text.ElideMiddle
}
}
Column {
Text {
text: username
}
Text {
text: key
width: 160
elide: Text.ElideMiddle
}
}
}
}
}
ListView {
id: addressesView
contentWidth: 200
model: onboardingModel
delegate: addressViewDelegate
anchors.fill: parent
ListView {
id: addressesView
contentWidth: 200
model: onboardingModel
delegate: addressViewDelegate
anchors.fill: parent
Layout.fillHeight: true
Layout.fillWidth: true
}
Item {
id: footer
width: btnExistingKey.width + selectBtn.width + Theme.padding
height: btnExistingKey.height
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.padding
anchors.horizontalCenter: parent.horizontalCenter
StyledButton {
id: btnExistingKey
label: "Access existing key"
}
StyledButton {
label: "Select"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
swipeView.incrementCurrentIndex();
StyledButton {
id: selectBtn
anchors.left: btnExistingKey.right
anchors.leftMargin: Theme.padding
label: "Select"
onClicked: {
swipeView.incrementCurrentIndex()
}
}
}
@ -100,7 +120,7 @@ SwipeView {
}
Item {
id: wizardStep3
id: wizardStep2
property Item txtPassword: txtPassword
Text {
@ -111,23 +131,25 @@ SwipeView {
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
Input {
id: txtPassword
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.padding
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
TextInput {
id: txtPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
placeholderText: "Enter password"
Component.onCompleted: {
this.textField.echoMode = TextInput.Password
}
Keys.onReturnPressed: {
btnNext.clicked()
}
}
StyledButton {
id: btnNext
label: "Next"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
@ -139,7 +161,7 @@ SwipeView {
}
Item {
id: wizardStep4
id: wizardStep3
property Item txtPassword: txtConfirmPassword
Text {
@ -150,20 +172,20 @@ SwipeView {
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
Input {
id: txtConfirmPassword
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.padding
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
placeholderText: "Confirm entered password"
TextInput {
id: txtConfirmPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
Component.onCompleted: {
this.textField.echoMode = TextInput.Password
}
Keys.onReturnPressed: {
btnFinish.clicked()
}
}
@ -190,26 +212,27 @@ SwipeView {
Connections {
target: onboardingModel
ignoreUnknownSignals: true
onLoginResponseChanged: {
const loginResponse = JSON.parse(response);
if(loginResponse.error){
storeAccountAndLoginError.text += loginResponse.error;
if(error){
storeAccountAndLoginError.text += error;
storeAccountAndLoginError.open()
}
}
}
StyledButton {
id: btnFinish
label: "Finish"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
if (txtConfirmPassword.text != txtPassword.text) {
if (txtConfirmPassword.textField.text != txtPassword.textField.text) {
return passwordsDontMatchError.open();
}
const selectedAccountIndex = wizardStep2.selectedIndex
onboardingModel.storeAccountAndLogin(selectedAccountIndex, txtPassword.text)
const selectedAccountIndex = wizardStep1.selectedIndex
onboardingModel.storeAccountAndLogin(selectedAccountIndex, txtPassword.textField.text)
}
}
}

View File

@ -12,9 +12,12 @@ SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: 0
interactive: false
onCurrentItemChanged: {
currentItem.txtPassword.textField.focus = true
if(currentItem.txtPassword) {
currentItem.txtPassword.textField.focus = true
}
}
Item {
@ -22,8 +25,6 @@ SwipeView {
property int selectedIndex: 0
Layout.fillHeight: true
Layout.fillWidth: true
// width: parent.width
// height: parent.height
Text {
id: title
@ -90,6 +91,10 @@ SwipeView {
delegate: addressViewDelegate
Layout.fillHeight: true
Layout.fillWidth: true
focus: true
Keys.onReturnPressed: {
selectBtn.clicked()
}
}
Item {
@ -137,6 +142,7 @@ SwipeView {
anchors.leftMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
placeholderText: "Enter password"
Component.onCompleted: {
this.textField.echoMode = TextInput.Password
@ -160,11 +166,11 @@ SwipeView {
Connections {
target: loginModel
ignoreUnknownSignals: true
onLoginResponseChanged: {
const loginResponse = JSON.parse(response)
if (loginResponse.error) {
loginError.open()
}
if(error){
loginError.open()
}
}
}

View File

@ -43,6 +43,12 @@ Page {
id: existingKeyState
onEntered: existingKey.visible = true
onExited: existingKey.visible = false
DSM.SignalTransition {
targetState: appState
signal: onboardingModel.loginResponseChanged
guard: !error
}
}
DSM.State {
@ -53,10 +59,12 @@ Page {
DSM.SignalTransition {
targetState: appState
signal: onboardingModel.loginResponseChanged
guard: {
const resp = JSON.parse(response);
return !resp.error
}
guard: !error
}
DSM.SignalTransition {
targetState: existingKeyState
signal: genKey.btnExistingKey.clicked
}
}
@ -68,10 +76,7 @@ Page {
DSM.SignalTransition {
targetState: appState
signal: loginModel.loginResponseChanged
guard: {
const resp = JSON.parse(response);
return !resp.error
}
guard: !error
}
DSM.SignalTransition {