refactor: move onboarding logic out of the view

This commit is contained in:
Iuri Matias 2020-05-21 11:25:33 -04:00
parent 6e2f61b08e
commit 7cd9ceac28
6 changed files with 481 additions and 228 deletions

View File

@ -13,8 +13,14 @@ import uuids
import eventemitter import eventemitter
import view import view
proc storeAccountAndLogin(events: EventEmitter, selectedAccount: string, password: string): string = proc storeAccountAndLogin(model: AccountModel, selectedAccountIndex: int, password: string): string =
let account = to(json.parseJson(selectedAccount), Models.GeneratedAccount) # let account = to(json.parseJson(selectedAccount), Models.GeneratedAccount)
echo "account index selected is "
echo selectedAccountIndex
let account: GeneratedAccount = model.generatedAddresses[selectedAccountIndex]
echo "account selected is "
echo account
let password = "0x" & $keccak_256.digest(password) let password = "0x" & $keccak_256.digest(password)
let multiAccount = %* { let multiAccount = %* {
"accountID": account.id, "accountID": account.id,
@ -86,7 +92,7 @@ proc storeAccountAndLogin(events: EventEmitter, selectedAccount: string, passwor
let saveResult = result.parseJson let saveResult = result.parseJson
if saveResult["error"].getStr == "": if saveResult["error"].getStr == "":
events.emit("node:ready", Args()) model.events.emit("node:ready", Args())
echo "Account saved succesfully" echo "Account saved succesfully"
# TODO: this is temporary and will be removed once accounts import and creation is working # TODO: this is temporary and will be removed once accounts import and creation is working
@ -97,10 +103,13 @@ proc generateRandomAccountAndLogin*(events: EventEmitter) =
type OnboardingController* = ref object of SignalSubscriber type OnboardingController* = ref object of SignalSubscriber
view*: OnboardingView view*: OnboardingView
variant*: QVariant variant*: QVariant
model*: AccountModel
proc newController*(events: EventEmitter): OnboardingController = proc newController*(events: EventEmitter): OnboardingController =
result = OnboardingController() result = OnboardingController()
result.view = newOnboardingView(events, storeAccountAndLogin, generateRandomAccountAndLogin) # TODO: events should be specific to the model itself
result.model = newAccountModel(events)
result.view = newOnboardingView(result.model, storeAccountAndLogin, generateRandomAccountAndLogin)
result.variant = newQVariant(result.view) result.variant = newQVariant(result.view)
proc delete*(self: OnboardingController) = proc delete*(self: OnboardingController) =
@ -108,7 +117,17 @@ proc delete*(self: OnboardingController) =
delete self.variant delete self.variant
proc init*(self: OnboardingController) = proc init*(self: OnboardingController) =
discard # let addresses = parseJson(status_accounts.generateAddresses())
let accounts = self.model.generateAddresses()
for account in accounts:
# self.view.addAddressToList("account.username", "account.identicon", "account.key")
self.view.addAddressToList(account.username, account.identicon, account.key)
# echo address
# var username = $libstatus.generateAlias(address["publicKey"].str.toGoString)
# var identicon = $libstatus.identicon(address["publicKey"].str.toGoString)
# var generatedAddress = address["address"].str
# self.view.addAddressToList(username, identicon, generatedAddress)
# method onSignal(self: OnboardingController, data: Signal) = # method onSignal(self: OnboardingController, data: Signal) =
# echo "new signal received" # echo "new signal received"

View File

@ -1,5 +1,7 @@
import NimQml import NimQml
import Tables
import json import json
import eventemitter
import ../../status/accounts as status_accounts import ../../status/accounts as status_accounts
import nimcrypto import nimcrypto
import ../../status/utils import ../../status/utils
@ -7,28 +9,76 @@ import ../../status/libstatus
import ../../models/accounts as Models import ../../models/accounts as Models
import ../../constants/constants import ../../constants/constants
import uuids import uuids
import eventemitter
import ../../status/test as status_test import ../../status/test as status_test
type
AddressRoles {.pure.} = enum
Username = UserRole + 1,
Identicon = UserRole + 2,
Key = UserRole + 3
type
Address* = ref object of QObject
username*, identicon*, key*: string
QtObject: QtObject:
type OnboardingView* = ref object of QObject type OnboardingView* = ref object of QAbstractListModel
addresses*: seq[Address]
model: AccountModel
m_generatedAddresses: string m_generatedAddresses: string
events: EventEmitter doStoreAccountAndLogin: proc(model: AccountModel, selectedAccount: int, password: string): string
doStoreAccountAndLogin: proc(events: EventEmitter, selectedAccount: string, password: string): string
doGenerateRandomAccountAndLogin: proc(events: EventEmitter) doGenerateRandomAccountAndLogin: proc(events: EventEmitter)
proc setup(self: OnboardingView) = proc setup(self: OnboardingView) =
self.QObject.setup self.QAbstractListModel.setup
proc delete*(self: OnboardingView) = proc delete*(self: OnboardingView) =
self.QObject.delete self.QAbstractListModel.delete
for address in self.addresses:
address.delete
self.addresses = @[]
proc newOnboardingView*(events: EventEmitter, doStoreAccountAndLogin: proc, doGenerateRandomAccountAndLogin: proc(events: EventEmitter)): OnboardingView = proc newOnboardingView*(model: AccountModel, doStoreAccountAndLogin: proc, doGenerateRandomAccountAndLogin: proc(events: EventEmitter)): OnboardingView =
new(result, delete) new(result, delete)
result.events = events result.model = model
result.doStoreAccountAndLogin = doStoreAccountAndLogin result.doStoreAccountAndLogin = doStoreAccountAndLogin
result.doGenerateRandomAccountAndLogin = doGenerateRandomAccountAndLogin result.doGenerateRandomAccountAndLogin = doGenerateRandomAccountAndLogin
result.setup() result.addresses = @[]
result.setup
proc addAddressToList*(self: OnboardingView, username: string, identicon: string, key: string) {.slot.} =
self.beginInsertRows(newQModelIndex(), self.addresses.len, self.addresses.len)
self.addresses.add(Address(username : username,
identicon : identicon,
key : key))
self.endInsertRows()
method rowCount(self: OnboardingView, index: QModelIndex = nil): int =
return self.addresses.len
method data(self: OnboardingView, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.addresses.len:
return
let asset = self.addresses[index.row]
let assetRole = role.AddressRoles
case assetRole:
of AddressRoles.Username: result = newQVariant(asset.username)
of AddressRoles.Identicon: result = newQVariant(asset.identicon)
of AddressRoles.Key: result = newQVariant(asset.key)
method roleNames(self: OnboardingView): Table[int, string] =
{ AddressRoles.Username.int:"username",
AddressRoles.Identicon.int:"identicon",
AddressRoles.Key.int:"key" }.toTable
proc getGeneratedAddresses*(self: OnboardingView): string {.slot.} = proc getGeneratedAddresses*(self: OnboardingView): string {.slot.} =
result = self.m_generatedAddresses result = self.m_generatedAddresses
@ -57,9 +107,16 @@ QtObject:
proc identicon*(self: OnboardingView, publicKey: string): string {.slot.} = proc identicon*(self: OnboardingView, publicKey: string): string {.slot.} =
result = $libstatus.identicon(publicKey.toGoString) result = $libstatus.identicon(publicKey.toGoString)
proc storeAccountAndLogin(self: OnboardingView, selectedAccount: string, password: string): string {.slot.} = # proc storeAccountAndLogin(self: OnboardingView, selectedAccount: string, password: string): string {.slot.} =
result = self.doStoreAccountAndLogin(self.events, selectedAccount, password) proc storeAccountAndLogin(self: OnboardingView, selectedAccountIndex: int, password: string): string {.slot.} =
echo "--------------------"
echo "--------------------"
echo selectedAccountIndex
echo "--------------------"
echo "--------------------"
# var selectedAccountIndex = self.addresses
result = self.doStoreAccountAndLogin(self.model, selectedAccountIndex, password)
# TODO: this is temporary and will be removed once accounts import and creation is working # TODO: this is temporary and will be removed once accounts import and creation is working
proc generateRandomAccountAndLogin*(self: OnboardingView) {.slot.} = proc generateRandomAccountAndLogin*(self: OnboardingView) {.slot.} =
self.doGenerateRandomAccountAndLogin(self.events) self.doGenerateRandomAccountAndLogin(self.model.events)

View File

@ -1,4 +1,9 @@
import json import json
import eventemitter
import ../status/libstatus
import ../status/accounts as status_accounts
import ../constants/constants
import ../status/utils
type type
GeneratedAccount* = object GeneratedAccount* = object
@ -7,4 +12,50 @@ type
id*: string id*: string
keyUid*: string keyUid*: string
mnemonic*: string mnemonic*: string
derived*: JsonNode derived*: JsonNode
username*: string
key*: string
identicon*: string
type
AccountModel* = ref object
generatedAddresses*: seq[GeneratedAccount]
events*: EventEmitter
proc newAccountModel*(events: EventEmitter): AccountModel =
result = AccountModel()
result.events = events
result.generatedAddresses = @[]
proc delete*(self: AccountModel) =
# delete self.generatedAddresses
discard
proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
let accounts = parseJson(status_accounts.generateAddresses())
echo "----- generating accounts"
for account in accounts:
echo account
var generatedAccount = GeneratedAccount()
generatedAccount.publicKey = account["publicKey"].str
generatedAccount.address = account["address"].str
generatedAccount.id = account["id"].str
generatedAccount.keyUid = account["keyUid"].str
generatedAccount.mnemonic = account["mnemonic"].str
generatedAccount.derived = account["derived"]
generatedAccount.username = $libstatus.generateAlias(account["publicKey"].str.toGoString)
generatedAccount.identicon = $libstatus.identicon(account["publicKey"].str.toGoString)
generatedAccount.key = account["address"].str
# var generatedAccount = cast[GeneratedAccount](account.to(GeneratedAccountBase))
# generatedAccount.username = $libstatus.generateAlias(account["publicKey"].str.toGoString)
# generatedAccount.identicon = $libstatus.identicon(account["publicKey"].str.toGoString)
# generatedAccount.key = account["address"].str
self.generatedAddresses.add(generatedAccount)
self.generatedAddresses

View File

@ -77,7 +77,7 @@ proc mainProc() =
var onboarding = onboarding.newController(events) var onboarding = onboarding.newController(events)
# onboarding.init() onboarding.init()
engine.setRootContextProperty("onboardingLogic", onboarding.variant) engine.setRootContextProperty("onboardingLogic", onboarding.variant)
engine.setRootContextProperty("onboardingModel", onboarding.variant) engine.setRootContextProperty("onboardingModel", onboarding.variant)

View File

@ -5,236 +5,362 @@ import QtQuick.Window 2.11
import QtQuick.Dialogs 1.3 import QtQuick.Dialogs 1.3
SwipeView { SwipeView {
id: swipeView id: swipeView
anchors.fill: parent anchors.fill: parent
currentIndex: 0 currentIndex: 0
property string strGeneratedAccounts: onboardingLogic.generatedAddresses property string strGeneratedAccounts: onboardingLogic.generatedAddresses
property var generatedAccounts: {} // property var generatedAccounts: {}
signal storeAccountAndLoginResult(response: var) // signal storeAccountAndLoginResult(response: var)
signal storeAccountAndLoginResult()
onCurrentItemChanged: { onCurrentItemChanged: {
currentItem.txtPassword.focus = true; currentItem.txtPassword.focus = true;
} }
ListModel { ListModel {
id: generatedAccountsModel id: generatedAccountsModel
}
Item {
id: wizardStep2
property int selectedIndex: 0
Text {
text: "Generated accounts"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
} }
Item { Item {
anchors.top: parent.top id: wizardStep2
anchors.topMargin: 50 property int selectedIndex: 0
Column { ColumnLayout {
spacing: 10 id: columnLayout
ButtonGroup { width: 620
id: accountGroup height: 427
}
Repeater { Text {
model: generatedAccountsModel text: "Generated accounts"
Rectangle { font.pointSize: 36
height: 32 anchors.top: parent.top
width: 32 anchors.topMargin: 20
anchors.leftMargin: 20 anchors.horizontalCenter: parent.horizontalCenter
anchors.rightMargin: 20
Row {
RadioButton {
checked: index == 0 ? true : false
ButtonGroup.group: accountGroup
onClicked: {
wizardStep2.selectedIndex = index;
}
}
Column {
Image {
source: identicon
}
}
Column {
Text {
text: alias
}
Text {
text: publicKey
width: 160
elide: Text.ElideMiddle
}
}
} }
}
// Item {
// Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
// transformOrigin: Item.Center
// anchors.top: parent.top
// anchors.topMargin: 50
Row {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 10
ButtonGroup {
id: accountGroup
}
Component {
id: addressViewDelegate
// Item {
// id: addressViewContainer
// height: 56
// anchors.right: parent.right
// anchors.rightMargin: 0
// anchors.left: parent.left
// anchors.leftMargin: 0
// Text {
// text: "address"
// font.pointSize: 24
// anchors.verticalCenter: parent.verticalCenter
// font.pixelSize: 14
// font.strikeout: false
// anchors.left: parent.left
// anchors.leftMargin: 72
// }
// }
Item {
height: 56
// anchors.leftMargin: 20
// anchors.rightMargin: 20
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
// Text {
// id: keyValue
// text: key
// anchors.verticalCenter: parent.verticalCenter
// font.pixelSize: 14
// font.strikeout: false
// anchors.left: parent.left
// anchors.leftMargin: 72
// }
Row {
RadioButton {
// checked: index == 0 ? true : false
checked: false
ButtonGroup.group: accountGroup
// onClicked: {
// wizardStep2.selectedIndex = index;
// }
}
Column {
Image {
source: identicon
// source: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAF0lEQVR42mPk+c9Qz0ACYBzVMKoBOwAA3IgShVgwlIUAAAAASUVORK5CYII="
}
}
Column {
Text {
text: username
}
Text {
text: key
width: 160
elide: Text.ElideMiddle
}
}
}
}
}
ListView {
id: addressesView
contentWidth: 200
model: onboardingModel
delegate: addressViewDelegate
Layout.fillHeight: true
Layout.fillWidth: true
anchors.topMargin: 36
anchors.fill: parent
// model: ListModel {
// ListElement {
// username: "Bill Smith"
// key: "0x123"
// }
// ListElement {
// username: "Slushy Welltodo Woodborer"
// key: "0x234"
// }
// }
}
// Repeater {
// model: generatedAccountsModel
// Rectangle {
// height: 32
// width: 32
// anchors.leftMargin: 20
// anchors.rightMargin: 20
// Row {
// RadioButton {
// checked: index == 0 ? true : false
// ButtonGroup.group: accountGroup
// onClicked: {
// wizardStep2.selectedIndex = index;
// }
// }
// Column {
// Image {
// source: identicon
// }
// }
// Column {
// Text {
// text: alias
// }
// Text {
// text: publicKey
// width: 160
// elide: Text.ElideMiddle
// }
// }
// }
// }
// }
}
// }
Button {
text: "Select"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("button: " + wizardStep2.selectedIndex);
swipeView.incrementCurrentIndex();
}
}
} }
}
} }
Button { Item {
text: "Select" id: wizardStep3
anchors.horizontalCenter: parent.horizontalCenter property Item txtPassword: txtPassword
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("button: " + wizardStep2.selectedIndex);
swipeView.incrementCurrentIndex(); Text {
} text: "Enter password"
} font.pointSize: 36
} anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: wizardStep3
property Item txtPassword: txtPassword
Text {
text: "Enter password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
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
}
}
Button {
text: "Next"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("password: " + txtPassword.text);
swipeView.incrementCurrentIndex();
}
}
}
Item {
id: wizardStep4
property Item txtPassword: txtConfirmPassword
Text {
text: "Confirm password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
TextInput {
id: txtConfirmPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
}
}
MessageDialog {
id: passwordsDontMatchError
title: "Error"
text: "Passwords don't match"
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok
onAccepted: {
txtConfirmPassword.clear();
swipeView.currentIndex = 1
txtPassword.focus = true
}
}
MessageDialog {
id: storeAccountAndLoginError
title: "Error storing account and logging in"
text: "An error occurred while storing your account and logging in: "
icon: StandardIcon.Error
standardButtons: StandardButton.Ok
}
Button {
text: "Finish"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("confirm clicked " + txtConfirmPassword.text + " : " + txtPassword.text);
if (txtConfirmPassword.text != txtPassword.text) {
return passwordsDontMatchError.open();
} }
const selectedAccount = swipeView.generatedAccounts[wizardStep2.selectedIndex]; Rectangle {
const storeResponse = onboardingModel.storeAccountAndLogin(JSON.stringify(selectedAccount), txtPassword.text) color: "#EEEEEE"
const response = JSON.parse(storeResponse); anchors.left: parent.left
if (response.error) { anchors.right: parent.right
storeAccountAndLoginError.text += response.error; anchors.centerIn: parent
return storeAccountAndLoginError.open(); height: 32
width: parent.width - 40
TextInput {
id: txtPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
}
} }
swipeView.storeAccountAndLoginResult(response);
}
}
}
// handle the serialised result coming from node and deserialise into JSON Button {
// TODO: maybe we should figure out a clever to avoid this? text: "Next"
onStrGeneratedAccountsChanged: { anchors.horizontalCenter: parent.horizontalCenter
if (generatedAccounts === null || generatedAccounts === "") { anchors.bottom: parent.bottom
return; anchors.bottomMargin: 20
} onClicked: {
swipeView.generatedAccounts = JSON.parse(strGeneratedAccounts); console.log("password: " + txtPassword.text);
}
// handle deserialised data coming from the node swipeView.incrementCurrentIndex();
onGeneratedAccountsChanged: { }
generatedAccountsModel.clear(); }
generatedAccounts.forEach(acc => { }
generatedAccountsModel.append({
publicKey: acc.publicKey, Item {
alias: onboardingLogic.generateAlias(acc.publicKey), id: wizardStep4
identicon: onboardingLogic.identicon(acc.publicKey) property Item txtPassword: txtConfirmPassword
});
}); Text {
} text: "Confirm password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
TextInput {
id: txtConfirmPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
}
}
MessageDialog {
id: passwordsDontMatchError
title: "Error"
text: "Passwords don't match"
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok
onAccepted: {
txtConfirmPassword.clear();
swipeView.currentIndex = 1
txtPassword.focus = true
}
}
MessageDialog {
id: storeAccountAndLoginError
title: "Error storing account and logging in"
text: "An error occurred while storing your account and logging in: "
// icon: StandardIcon.Error
standardButtons: StandardButton.Ok
}
Button {
text: "Finish"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("confirm clicked " + txtConfirmPassword.text + " : " + txtPassword.text);
if (txtConfirmPassword.text != txtPassword.text) {
return passwordsDontMatchError.open();
}
const selectedAccountIndex = wizardStep2.selectedIndex
const storeResponse = onboardingModel.storeAccountAndLogin(selectedAccountIndex, txtPassword.text)
// const storeResponse = onboardingModel.storeAccountAndLogin(JSON.stringify(selectedAccount), txtPassword.text)
// const selectedAccount = swipeView.generatedAccounts[wizardStep2.selectedIndex];
// const storeResponse = onboardingModel.storeAccountAndLogin(JSON.stringify(selectedAccount), txtPassword.text)
const response = JSON.parse(storeResponse);
// if (response.error) {
// storeAccountAndLoginError.text += response.error;
// return storeAccountAndLoginError.open();
// }
console.log("=======");
console.log(storeResponse);
console.log("=======")
// swipeView.storeAccountAndLoginResult(response);
swipeView.storeAccountAndLoginResult();
}
}
}
// handle the serialised result coming from node and deserialise into JSON
// TODO: maybe we should figure out a clever to avoid this?
// onStrGeneratedAccountsChanged: {
// if (generatedAccounts === null || generatedAccounts === "") {
// return;
// }
// swipeView.generatedAccounts = JSON.parse(strGeneratedAccounts);
// }
// handle deserialised data coming from the node
// onGeneratedAccountsChanged: {
// generatedAccountsModel.clear();
// generatedAccounts.forEach(acc => {
// generatedAccountsModel.append({
// publicKey: acc.publicKey,
// alias: onboardingLogic.generateAlias(acc.publicKey),
// identicon: onboardingLogic.identicon(acc.publicKey)
// });
// });
// }
} }
/*##^## /*##^##
Designer { Designer {
D{i:0;autoSize:true;height:480;width:640} D{i:0;autoSize:true;height:480;width:640}
} }
##^##*/ ##^##*/

View File

@ -58,7 +58,7 @@ Page {
DSM.SignalTransition { DSM.SignalTransition {
targetState: appState targetState: appState
signal: genKey.storeAccountAndLoginResult signal: genKey.storeAccountAndLoginResult
guard: !response.error // guard: !response.error
} }
} }