From 7cd9ceac28eedd1967ea5eea605689057f60b477 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Thu, 21 May 2020 11:25:33 -0400 Subject: [PATCH] refactor: move onboarding logic out of the view --- src/app/onboarding/core.nim | 29 +- src/app/onboarding/view.nim | 81 ++++- src/models/accounts.nim | 53 ++- src/nim_status_client.nim | 2 +- ui/onboarding/GenKey.qml | 542 +++++++++++++++++++------------ ui/onboarding/OnboardingMain.qml | 2 +- 6 files changed, 481 insertions(+), 228 deletions(-) diff --git a/src/app/onboarding/core.nim b/src/app/onboarding/core.nim index fd5df87bad..3e6ef8de49 100644 --- a/src/app/onboarding/core.nim +++ b/src/app/onboarding/core.nim @@ -13,8 +13,14 @@ import uuids import eventemitter import view -proc storeAccountAndLogin(events: EventEmitter, selectedAccount: string, password: string): string = - let account = to(json.parseJson(selectedAccount), Models.GeneratedAccount) +proc storeAccountAndLogin(model: AccountModel, selectedAccountIndex: int, password: string): string = + # 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 multiAccount = %* { "accountID": account.id, @@ -86,7 +92,7 @@ proc storeAccountAndLogin(events: EventEmitter, selectedAccount: string, passwor let saveResult = result.parseJson if saveResult["error"].getStr == "": - events.emit("node:ready", Args()) + model.events.emit("node:ready", Args()) echo "Account saved succesfully" # 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 view*: OnboardingView variant*: QVariant + model*: AccountModel proc newController*(events: EventEmitter): 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) proc delete*(self: OnboardingController) = @@ -108,7 +117,17 @@ proc delete*(self: OnboardingController) = delete self.variant 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) = # echo "new signal received" diff --git a/src/app/onboarding/view.nim b/src/app/onboarding/view.nim index 435f9c9168..c3116b1367 100644 --- a/src/app/onboarding/view.nim +++ b/src/app/onboarding/view.nim @@ -1,5 +1,7 @@ import NimQml +import Tables import json +import eventemitter import ../../status/accounts as status_accounts import nimcrypto import ../../status/utils @@ -7,28 +9,76 @@ import ../../status/libstatus import ../../models/accounts as Models import ../../constants/constants import uuids -import eventemitter 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: - type OnboardingView* = ref object of QObject + type OnboardingView* = ref object of QAbstractListModel + addresses*: seq[Address] + model: AccountModel m_generatedAddresses: string - events: EventEmitter - doStoreAccountAndLogin: proc(events: EventEmitter, selectedAccount: string, password: string): string + doStoreAccountAndLogin: proc(model: AccountModel, selectedAccount: int, password: string): string doGenerateRandomAccountAndLogin: proc(events: EventEmitter) proc setup(self: OnboardingView) = - self.QObject.setup + self.QAbstractListModel.setup 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) - result.events = events + result.model = model result.doStoreAccountAndLogin = doStoreAccountAndLogin 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.} = result = self.m_generatedAddresses @@ -57,9 +107,16 @@ QtObject: proc identicon*(self: OnboardingView, publicKey: string): string {.slot.} = result = $libstatus.identicon(publicKey.toGoString) - proc storeAccountAndLogin(self: OnboardingView, selectedAccount: string, password: string): string {.slot.} = - result = self.doStoreAccountAndLogin(self.events, selectedAccount, password) + # proc storeAccountAndLogin(self: OnboardingView, selectedAccount: string, password: string): string {.slot.} = + 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 proc generateRandomAccountAndLogin*(self: OnboardingView) {.slot.} = - self.doGenerateRandomAccountAndLogin(self.events) + self.doGenerateRandomAccountAndLogin(self.model.events) diff --git a/src/models/accounts.nim b/src/models/accounts.nim index fbaa719a16..816e516399 100644 --- a/src/models/accounts.nim +++ b/src/models/accounts.nim @@ -1,4 +1,9 @@ import json +import eventemitter +import ../status/libstatus +import ../status/accounts as status_accounts +import ../constants/constants +import ../status/utils type GeneratedAccount* = object @@ -7,4 +12,50 @@ type id*: string keyUid*: string mnemonic*: string - derived*: JsonNode \ No newline at end of file + 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 diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index db98a1ec80..9ba3c8301f 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -77,7 +77,7 @@ proc mainProc() = var onboarding = onboarding.newController(events) - # onboarding.init() + onboarding.init() engine.setRootContextProperty("onboardingLogic", onboarding.variant) engine.setRootContextProperty("onboardingModel", onboarding.variant) diff --git a/ui/onboarding/GenKey.qml b/ui/onboarding/GenKey.qml index 82decd01fb..c05c907c04 100644 --- a/ui/onboarding/GenKey.qml +++ b/ui/onboarding/GenKey.qml @@ -5,236 +5,362 @@ import QtQuick.Window 2.11 import QtQuick.Dialogs 1.3 SwipeView { - id: swipeView - anchors.fill: parent - currentIndex: 0 + id: swipeView + anchors.fill: parent + currentIndex: 0 - property string strGeneratedAccounts: onboardingLogic.generatedAddresses - property var generatedAccounts: {} - signal storeAccountAndLoginResult(response: var) + property string strGeneratedAccounts: onboardingLogic.generatedAddresses + // property var generatedAccounts: {} + // signal storeAccountAndLoginResult(response: var) + signal storeAccountAndLoginResult() - onCurrentItemChanged: { - currentItem.txtPassword.focus = true; - } + onCurrentItemChanged: { + currentItem.txtPassword.focus = true; + } - ListModel { - 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 + ListModel { + id: generatedAccountsModel } Item { - anchors.top: parent.top - anchors.topMargin: 50 + id: wizardStep2 + property int selectedIndex: 0 - Column { - spacing: 10 - ButtonGroup { - id: accountGroup - } + ColumnLayout { + id: columnLayout + width: 620 + height: 427 - 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 - } - - } + Text { + text: "Generated accounts" + font.pointSize: 36 + anchors.top: parent.top + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter } - } + +// 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 { - text: "Select" - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - onClicked: { - console.log("button: " + wizardStep2.selectedIndex); + Item { + id: wizardStep3 + property Item txtPassword: txtPassword - 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]; - const storeResponse = onboardingModel.storeAccountAndLogin(JSON.stringify(selectedAccount), txtPassword.text) - const response = JSON.parse(storeResponse); - if (response.error) { - storeAccountAndLoginError.text += response.error; - return storeAccountAndLoginError.open(); + 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 + } } - swipeView.storeAccountAndLoginResult(response); - } - } - } - // 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); - } + Button { + text: "Next" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + onClicked: { + console.log("password: " + txtPassword.text); - // 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) - }); - }); - } + 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 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 { D{i:0;autoSize:true;height:480;width:640} } ##^##*/ - diff --git a/ui/onboarding/OnboardingMain.qml b/ui/onboarding/OnboardingMain.qml index 5a2b927351..eee0c7cb40 100644 --- a/ui/onboarding/OnboardingMain.qml +++ b/ui/onboarding/OnboardingMain.qml @@ -58,7 +58,7 @@ Page { DSM.SignalTransition { targetState: appState signal: genKey.storeAccountAndLoginResult - guard: !response.error + // guard: !response.error } }