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) self.view.addAccountToList(nodeAccount)
proc handleNodeLogin(self: LoginController, data: Signal) = proc handleNodeLogin(self: LoginController, data: Signal) =
var response = NodeSignal(data) let response = NodeSignal(data)
self.view.setLastLoginResponse($response.event.toJson) if self.status.accounts.currentLoginAccount != nil:
if ?.response.event.error == "" and self.status.accounts.currentAccount != nil: self.view.setLastLoginResponse(response.event)
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentAccount)) if ?.response.event.error == "":
self.status.events.emit("login", AccountArgs(account: self.status.accounts.currentLoginAccount))
method onSignal(self: LoginController, data: Signal) = method onSignal(self: LoginController, data: Signal) =
if data.signalType == SignalType.NodeLogin: if data.signalType == SignalType.NodeLogin:

View File

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

View File

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

View File

@ -6,6 +6,8 @@ import ../../signals/types
import strformat import strformat
import json_serialization import json_serialization
import ../../status/accounts as AccountModel import ../../status/accounts as AccountModel
import views/account_info
import strutils
import ../../status/status import ../../status/status
type type
@ -17,7 +19,7 @@ type
QtObject: QtObject:
type OnboardingView* = ref object of QAbstractListModel type OnboardingView* = ref object of QAbstractListModel
accounts*: seq[GeneratedAccount] accounts*: seq[GeneratedAccount]
lastLoginResponse: string importedAccount: AccountInfoView
status*: Status status*: Status
proc setup(self: OnboardingView) = proc setup(self: OnboardingView) =
@ -30,7 +32,7 @@ QtObject:
proc newOnboardingView*(status: Status): OnboardingView = proc newOnboardingView*(status: Status): OnboardingView =
new(result, delete) new(result, delete)
result.accounts = @[] result.accounts = @[]
result.lastLoginResponse = "" result.importedAccount = newAccountInfoView()
result.status = status result.status = status
result.setup result.setup
@ -53,7 +55,7 @@ QtObject:
case assetRole: case assetRole:
of AccountRoles.Username: result = newQVariant(asset.name) of AccountRoles.Username: result = newQVariant(asset.name)
of AccountRoles.Identicon: result = newQVariant(asset.photoPath) 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] = method roleNames(self: OnboardingView): Table[int, string] =
{ AccountRoles.Username.int:"username", { AccountRoles.Username.int:"username",
@ -67,18 +69,35 @@ QtObject:
let let
e = getCurrentException() e = getCurrentException()
msg = getCurrentExceptionMsg() msg = getCurrentExceptionMsg()
result = SignalError(error: msg).toJson result = StatusGoError(error: msg).toJson
proc lastLoginResponse*(self: OnboardingView): string = proc getImportedAccount*(self: OnboardingView): QVariant {.slot.} =
result = self.lastLoginResponse 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.} = QtProperty[QVariant] importedAccount:
self.lastLoginResponse = loginResponse read = getImportedAccount
self.loginResponseChanged(loginResponse)
QtProperty[string] loginResponse: proc importMnemonic(self: OnboardingView, mnemonic: string): string {.slot.} =
read = lastLoginResponse try:
write = setLastLoginResponse let importResult = self.status.accounts.importMnemonic(mnemonic)
notify = loginResponseChanged 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 NimQml
import eventemitter import eventemitter
import chronicles import chronicles
import json_serialization
import app/chat/core as chat import app/chat/core as chat
import app/wallet/core as wallet import app/wallet/core as wallet

View File

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

View File

@ -6,11 +6,13 @@ type
AccountModel* = ref object AccountModel* = ref object
generatedAddresses*: seq[GeneratedAccount] generatedAddresses*: seq[GeneratedAccount]
nodeAccounts*: seq[NodeAccount] nodeAccounts*: seq[NodeAccount]
currentAccount*: Account currentLoginAccount*: Account
currentOnboardingAccount*: Account
proc newAccountModel*(): AccountModel = proc newAccountModel*(): AccountModel =
result = AccountModel() result = AccountModel()
result.currentAccount = nil result.currentLoginAccount = nil
result.currentOnboardingAccount = nil
proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
var accounts = status_accounts.generateAddresses() var accounts = status_accounts.generateAddresses()
@ -22,10 +24,21 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount = proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount =
let currentNodeAccount = self.nodeAccounts[selectedAccountIndex] let currentNodeAccount = self.nodeAccounts[selectedAccountIndex]
self.currentAccount = currentNodeAccount.toAccount self.currentLoginAccount = currentNodeAccount.toAccount
result = status_accounts.login(currentNodeAccount, password) result = status_accounts.login(currentNodeAccount, password)
proc storeAccountAndLogin*(self: AccountModel, selectedAccountIndex: int, password: string): Account = proc storeAccountAndLogin*(self: AccountModel, selectedAccountIndex: int, password: string): Account =
let generatedAccount: GeneratedAccount = self.generatedAddresses[selectedAccountIndex] let generatedAccount: GeneratedAccount = self.generatedAddresses[selectedAccountIndex]
result = status_accounts.setupAccount(generatedAccount, password) 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 types
import json_serialization import json_serialization
import chronicles import chronicles
import ../../signals/types as signal_types
proc queryAccounts*(): string = proc queryAccounts*(): string =
var response = callPrivateRPC("eth_accounts") var response = callPrivateRPC("eth_accounts")
@ -19,9 +20,10 @@ proc generateAddresses*(): seq[GeneratedAccount] =
"n": 5, "n": 5,
"mnemonicPhraseLength": 12, "mnemonicPhraseLength": 12,
"bip39Passphrase": "", "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 = proc generateAlias*(publicKey: string): string =
result = $libstatus.generateAlias(publicKey.toGoString) result = $libstatus.generateAlias(publicKey.toGoString)
@ -48,9 +50,7 @@ proc initNodeAccounts*(): seq[NodeAccount] =
result = Json.decode(strNodeAccounts, seq[NodeAccount]) result = Json.decode(strNodeAccounts, seq[NodeAccount])
proc saveAccountAndLogin*( proc saveAccountAndLogin*(
multiAccounts: MultiAccounts, account: GeneratedAccount,
alias: string,
identicon: string,
accountData: string, accountData: string,
password: string, password: string,
configJSON: string, configJSON: string,
@ -58,18 +58,18 @@ proc saveAccountAndLogin*(
let hashedPassword = "0x" & $keccak_256.digest(password) let hashedPassword = "0x" & $keccak_256.digest(password)
let subaccountData = %* [ let subaccountData = %* [
{ {
"public-key": multiAccounts.defaultWallet.publicKey, "public-key": account.derived.defaultWallet.publicKey,
"address": multiAccounts.defaultWallet.address, "address": account.derived.defaultWallet.address,
"color": "#4360df", "color": "#4360df",
"wallet": true, "wallet": true,
"path": constants.PATH_DEFAULT_WALLET, "path": constants.PATH_DEFAULT_WALLET,
"name": "Status account" "name": "Status account"
}, },
{ {
"public-key": multiAccounts.whisper.publicKey, "public-key": account.derived.whisper.publicKey,
"address": multiAccounts.whisper.address, "address": account.derived.whisper.address,
"name": alias, "name": account.name,
"photo-path": identicon, "photo-path": account.photoPath,
"path": constants.PATH_WHISPER, "path": constants.PATH_WHISPER,
"chat": true "chat": true
} }
@ -81,47 +81,52 @@ proc saveAccountAndLogin*(
if error == "": if error == "":
debug "Account saved succesfully" debug "Account saved succesfully"
result = Account(name: alias, photoPath: identicon) result = account.toAccount
return 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 hashedPassword = "0x" & $keccak_256.digest(password)
let multiAccount = %* { let multiAccount = %* {
"accountID": account.id, "accountID": account.id,
"paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET], "paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET],
"password": hashedPassword "password": hashedPassword
} }
var response = $libstatus.multiAccountStoreDerivedAccounts($multiAccount); let response = $libstatus.multiAccountStoreDerivedAccounts($multiAccount);
result = Json.decode($response, MultiAccounts)
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 = %* { result = %* {
"name": alias, "name": account.name,
"address": account.address, "address": account.address,
"photo-path": identicon, "photo-path": account.photoPath,
"key-uid": account.keyUid, "key-uid": account.keyUid,
"keycard-pairing": nil "keycard-pairing": nil
} }
proc getAccountSettings*(account: GeneratedAccount, alias: string, identicon: string, multiAccounts: MultiAccounts, defaultNetworks: JsonNode): JsonNode = proc getAccountSettings*(account: GeneratedAccount, defaultNetworks: JsonNode): JsonNode =
result = %* { result = %* {
"key-uid": account.keyUid, "key-uid": account.keyUid,
"mnemonic": account.mnemonic, "mnemonic": account.mnemonic,
"public-key": multiAccounts.whisper.publicKey, "public-key": account.derived.whisper.publicKey,
"name": alias, "name": account.name,
"address": account.address, "address": account.address,
"eip1581-address": multiAccounts.eip1581.address, "eip1581-address": account.derived.eip1581.address,
"dapps-address": multiAccounts.defaultWallet.address, "dapps-address": account.derived.defaultWallet.address,
"wallet-root-address": multiAccounts.walletRoot.address, "wallet-root-address": account.derived.walletRoot.address,
"preview-privacy?": true, "preview-privacy?": true,
"signing-phrase": generateSigningPhrase(3), "signing-phrase": generateSigningPhrase(3),
"log-level": "INFO", "log-level": "INFO",
"latest-derived-path": 0, "latest-derived-path": 0,
"networks/networks": defaultNetworks, "networks/networks": defaultNetworks,
"currency": "usd", "currency": "usd",
"photo-path": identicon, "photo-path": account.photoPath,
"waku-enabled": true, "waku-enabled": true,
"wallet/visible-tokens": { "wallet/visible-tokens": {
"mainnet": ["SNT"] "mainnet": ["SNT"]
@ -132,20 +137,20 @@ proc getAccountSettings*(account: GeneratedAccount, alias: string, identicon: st
} }
proc setupAccount*(account: GeneratedAccount, password: string): Account = 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 result = saveAccountAndLogin(account, $accountData, password, $constants.NODE_CONFIG, $settingsJSON)
let alias = generateAlias(whisperPubKey)
let identicon =generateIdenticon(whisperPubKey)
let accountData = getAccountData(account, alias, identicon) except StatusGoException as e:
var settingsJSON = getAccountSettings(account, alias, identicon, multiAccounts, constants.DEFAULT_NETWORKS) raise newException(StatusGoException, "Error setting up account: " & e.msg)
result = saveAccountAndLogin(multiAccounts, alias, identicon, $accountData, password, $constants.NODE_CONFIG, $settingsJSON) finally:
# TODO this is needed for now for the retrieving of past messages. We'll either move or remove it later
# 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"
let peer = "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443" discard libstatus.addPeer(peer)
discard libstatus.addPeer(peer)
proc login*(nodeAccount: NodeAccount, password: string): NodeAccount = proc login*(nodeAccount: NodeAccount, password: string): NodeAccount =
let hashedPassword = "0x" & $keccak_256.digest(password) let hashedPassword = "0x" & $keccak_256.digest(password)
@ -158,4 +163,22 @@ proc login*(nodeAccount: NodeAccount, password: string): NodeAccount =
result = nodeAccount result = nodeAccount
return 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 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 saveAccountAndLogin*(accountData: cstring, password: cstring, settingsJSON: cstring, configJSON: cstring, subaccountData: cstring): cstring {.importc: "SaveAccountAndLogin".}
proc callRPC*(inputJSON: cstring): cstring {.importc: "CallRPC".} proc callRPC*(inputJSON: cstring): cstring {.importc: "CallRPC".}

View File

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

View File

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

View File

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