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:
parent
dc6793a0f0
commit
691717990d
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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".}
|
||||
|
|
|
@ -73,4 +73,4 @@ type AccountArgs* = ref object of Args
|
|||
account*: Account
|
||||
|
||||
type
|
||||
LoginError* = object of Exception
|
||||
StatusGoException* = object of Exception
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
##^##*/
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue