diff --git a/src/app/login/core.nim b/src/app/login/core.nim new file mode 100644 index 0000000000..5f740ec0a1 --- /dev/null +++ b/src/app/login/core.nim @@ -0,0 +1,43 @@ +import NimQml +import ../../status/types as status_types +import ../../signals/types +import eventemitter +import view +import ../../models/accounts as AccountModel +import chronicles +import options +import std/wrapnils + +type LoginController* = ref object of SignalSubscriber + view*: LoginView + variant*: QVariant + appEvents*: EventEmitter + model: AccountModel + +proc newController*(appEvents: EventEmitter): LoginController = + result = LoginController() + result.appEvents = appEvents + result.model = newAccountModel() + result.view = newLoginView(result.model) + result.variant = newQVariant(result.view) + +proc delete*(self: LoginController) = + delete self.view + delete self.variant + +proc init*(self: LoginController, nodeAccounts: seq[NodeAccount]) = + self.model.nodeAccounts = nodeAccounts + for nodeAccount in nodeAccounts: + 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.model.currentAccount != nil: + self.appEvents.emit("login", AccountArgs(account: self.model.currentAccount)) + +method onSignal(self: LoginController, data: Signal) = + if data.signalType == SignalType.NodeLogin: + self.handleNodeLogin(data) + else: + discard \ No newline at end of file diff --git a/src/app/login/view.nim b/src/app/login/view.nim new file mode 100644 index 0000000000..27149e7cf6 --- /dev/null +++ b/src/app/login/view.nim @@ -0,0 +1,86 @@ +import NimQml +import Tables +import json +import nimcrypto +import ../../signals/types +import ../../status/types as status_types +import ../../status/accounts as status_accounts +import strformat +import json_serialization +import core +import ../../models/accounts as AccountModel + +type + AccountRoles {.pure.} = enum + Username = UserRole + 1, + Identicon = UserRole + 2, + Key = UserRole + 3 + +QtObject: + type LoginView* = ref object of QAbstractListModel + accounts: seq[NodeAccount] + lastLoginResponse: string + model*: AccountModel + + proc setup(self: LoginView) = + self.QAbstractListModel.setup + + proc delete*(self: LoginView) = + self.QAbstractListModel.delete + self.accounts = @[] + + proc newLoginView*(model: AccountModel): LoginView = + new(result, delete) + result.accounts = @[] + result.lastLoginResponse = "" + result.model = model + result.setup + + proc addAccountToList*(self: LoginView, account: NodeAccount) = + self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len) + self.accounts.add(account) + self.endInsertRows() + + method rowCount(self: LoginView, index: QModelIndex = nil): int = + return self.accounts.len + + method data(self: LoginView, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.accounts.len: + return + + let asset = self.accounts[index.row] + let assetRole = role.AccountRoles + case assetRole: + of AccountRoles.Username: result = newQVariant(asset.name) + of AccountRoles.Identicon: result = newQVariant(asset.photoPath) + of AccountRoles.Key: result = newQVariant(asset.keyUid) + + method roleNames(self: LoginView): Table[int, string] = + { AccountRoles.Username.int:"username", + AccountRoles.Identicon.int:"identicon", + AccountRoles.Key.int:"key" }.toTable + + proc login(self: LoginView, selectedAccountIndex: int, password: string): string {.slot.} = + try: + result = self.model.login(selectedAccountIndex, password).toJson + except: + let + e = getCurrentException() + msg = getCurrentExceptionMsg() + result = SignalError(error: msg).toJson + + proc lastLoginResponse*(self: LoginView): string = + result = self.lastLoginResponse + + 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 diff --git a/src/app/onboarding/core.nim b/src/app/onboarding/core.nim index e26ea486ff..47b740450f 100644 --- a/src/app/onboarding/core.nim +++ b/src/app/onboarding/core.nim @@ -1,14 +1,18 @@ import NimQml -import ../../models/accounts -import ../../signals/types +import ../../status/types as status_types +import ../../status/accounts as status_accounts +import ../../models/accounts as AccountModel import eventemitter import view +import chronicles +import ../../signals/types +import std/wrapnils -type OnboardingController* = object +type OnboardingController* = ref object of SignalSubscriber view*: OnboardingView variant*: QVariant - model*: AccountModel appEvents*: EventEmitter + model: AccountModel proc newController*(appEvents: EventEmitter): OnboardingController = result = OnboardingController() @@ -16,8 +20,6 @@ proc newController*(appEvents: EventEmitter): OnboardingController = result.model = newAccountModel() result.view = newOnboardingView(result.model) result.variant = newQVariant(result.view) - result.model.events.on("accountsReady") do(a: Args): - appEvents.emit("accountsReady", a) proc delete*(self: OnboardingController) = delete self.view @@ -25,6 +27,17 @@ proc delete*(self: OnboardingController) = proc init*(self: OnboardingController) = let accounts = self.model.generateAddresses() - for account in accounts: - self.view.addAddressToList(account.toAddress()) + 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.model.currentAccount != nil: + self.appEvents.emit("login", AccountArgs(account: self.model.currentAccount)) + +method onSignal(self: OnboardingController, data: Signal) = + if data.signalType == SignalType.NodeLogin: + self.handleNodeLogin(data) + else: + discard diff --git a/src/app/onboarding/view.nim b/src/app/onboarding/view.nim index b1fecc03a8..db5dcd1ea0 100644 --- a/src/app/onboarding/view.nim +++ b/src/app/onboarding/view.nim @@ -2,61 +2,83 @@ import NimQml import Tables import json import nimcrypto -import ../../models/accounts as Models +import ../../status/types as status_types +import ../../signals/types +import strformat +import json_serialization +import ../../models/accounts as AccountModel type - AddressRoles {.pure.} = enum + AccountRoles {.pure.} = enum Username = UserRole + 1, Identicon = UserRole + 2, Key = UserRole + 3 QtObject: type OnboardingView* = ref object of QAbstractListModel - addresses*: seq[Address] - model: AccountModel + accounts*: seq[GeneratedAccount] + lastLoginResponse: string + model*: AccountModel proc setup(self: OnboardingView) = self.QAbstractListModel.setup proc delete*(self: OnboardingView) = self.QAbstractListModel.delete - self.addresses = @[] + self.accounts = @[] proc newOnboardingView*(model: AccountModel): OnboardingView = new(result, delete) + result.accounts = @[] + result.lastLoginResponse = "" result.model = model - result.addresses = @[] result.setup - proc addAddressToList*(self: OnboardingView, address: Address) = - self.beginInsertRows(newQModelIndex(), self.addresses.len, self.addresses.len) - self.addresses.add(address) + proc addAccountToList*(self: OnboardingView, account: GeneratedAccount) = + self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len) + self.accounts.add(account) self.endInsertRows() method rowCount(self: OnboardingView, index: QModelIndex = nil): int = - return self.addresses.len + return self.accounts.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: + if index.row < 0 or index.row >= self.accounts.len: return - let asset = self.addresses[index.row] - let assetRole = role.AddressRoles + let asset = self.accounts[index.row] + let assetRole = role.AccountRoles case assetRole: - of AddressRoles.Username: result = newQVariant(asset.username) - of AddressRoles.Identicon: result = newQVariant(asset.identicon) - of AddressRoles.Key: result = newQVariant(asset.key) + of AccountRoles.Username: result = newQVariant(asset.name) + of AccountRoles.Identicon: result = newQVariant(asset.photoPath) + of AccountRoles.Key: result = newQVariant(asset.keyUid) method roleNames(self: OnboardingView): Table[int, string] = - { AddressRoles.Username.int:"username", - AddressRoles.Identicon.int:"identicon", - AddressRoles.Key.int:"key" }.toTable + { AccountRoles.Username.int:"username", + AccountRoles.Identicon.int:"identicon", + AccountRoles.Key.int:"key" }.toTable - proc storeAccountAndLogin(self: OnboardingView, selectedAccountIndex: int, password: string) {.slot.} = - discard self.model.storeAccountAndLogin(selectedAccountIndex, password) + proc storeAccountAndLogin(self: OnboardingView, selectedAccountIndex: int, password: string): string {.slot.} = + try: + result = self.model.storeAccountAndLogin(selectedAccountIndex, password).toJson + except: + let + e = getCurrentException() + msg = getCurrentExceptionMsg() + result = SignalError(error: msg).toJson - # TODO: this is temporary and will be removed once accounts import and creation is working - proc generateRandomAccountAndLogin*(self: OnboardingView) {.slot.} = - self.model.generateRandomAccountAndLogin() + proc lastLoginResponse*(self: OnboardingView): string = + result = self.lastLoginResponse + + proc loginResponseChanged*(self: OnboardingView, response: string) {.signal.} + + proc setLastLoginResponse*(self: OnboardingView, loginResponse: string) {.slot.} = + self.lastLoginResponse = loginResponse + self.loginResponseChanged(loginResponse) + + QtProperty[string] loginResponse: + read = lastLoginResponse + write = setLastLoginResponse + notify = loginResponseChanged diff --git a/src/models/accounts.nim b/src/models/accounts.nim index b88762f24d..eabdf8876f 100644 --- a/src/models/accounts.nim +++ b/src/models/accounts.nim @@ -1,28 +1,16 @@ -import eventemitter -import json_serialization import ../status/accounts as status_accounts import ../status/types - -type - Address* = ref object - username*, identicon*, key*: string - -proc toAddress*(account: GeneratedAccount): Address = - result = Address(username: account.name, identicon: account.photoPath, key: account.address) +import options type AccountModel* = ref object generatedAddresses*: seq[GeneratedAccount] - events*: EventEmitter + nodeAccounts*: seq[NodeAccount] + currentAccount*: Account proc newAccountModel*(): AccountModel = result = AccountModel() - result.events = createEventEmitter() - result.generatedAddresses = @[] - -proc delete*(self: AccountModel) = - # delete self.generatedAddresses - discard + result.currentAccount = nil proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = var accounts = status_accounts.generateAddresses() @@ -32,13 +20,12 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = self.generatedAddresses.add(account) self.generatedAddresses -# TODO: this is temporary and will be removed once accounts import and creation is working -proc generateRandomAccountAndLogin*(self: AccountModel) = - let generatedAccounts = status_accounts.generateAddresses() - let account = status_accounts.setupAccount(generatedAccounts[0], "qwerty") - self.events.emit("accountsReady", AccountArgs(account: account)) +proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount = + let currentNodeAccount = self.nodeAccounts[selectedAccountIndex] + self.currentAccount = 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.events.emit("accountsReady", AccountArgs(account: result)) + self.currentAccount = generatedAccount.toAccount \ No newline at end of file diff --git a/src/models/profile.nim b/src/models/profile.nim index 5d6a9a4761..db90281894 100644 --- a/src/models/profile.nim +++ b/src/models/profile.nim @@ -1,4 +1,5 @@ import eventemitter +import ../status/types type MailServer* = ref object @@ -11,5 +12,5 @@ type type Profile* = ref object username*, identicon*: string -proc toProfileModel*(obj: object): Profile = - result = Profile(username: obj.name, identicon: obj.photoPath) +proc toProfileModel*(account: Account): Profile = + result = Profile(username: account.name, identicon: account.photoPath) diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index f7437f1842..c7c0bce13f 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -6,13 +6,12 @@ import app/node/core as node import app/profile/core as profile import signals/core as signals import app/onboarding/core as onboarding +import app/login/core as login import state import status/accounts as status_accounts import status/core as status_core -import status/chat as status_chat import status/types as types import status/libstatus -import models/accounts import state import status/types import eventemitter @@ -24,7 +23,7 @@ logScope: topics = "main" proc mainProc() = - let nodeAccounts = Json.decode(status_accounts.initNodeAccounts(), seq[NodeAccount]) # to be used for login + let nodeAccounts = status_accounts.initNodeAccounts() let app = newQApplication() let engine = newQQmlApplicationEngine() let signalController = signals.newController(app) @@ -56,23 +55,33 @@ proc mainProc() = var profile = profile.newController(appEvents) engine.setRootContextProperty("profileModel", profile.variant) - # var accountsModel = newAccountModel() - appEvents.on("accountsReady") do(a: Args): + appEvents.on("login") do(a: Args): var args = AccountArgs(a) status_core.startMessenger() wallet.init() - profile.init(args.account) # TODO: use correct account + profile.init(args.account) - # var onboarding = onboarding.newController(accountsModel) + var login = login.newController(appEvents) var onboarding = onboarding.newController(appEvents) - onboarding.init() - engine.setRootContextProperty("onboardingModel", onboarding.variant) + + # TODO: replace this with routing + let showLogin = nodeAccounts.len > 0 + engine.setRootContextProperty("showLogin", newQVariant(showLogin)) + + if nodeAccounts.len > 0: + login.init(nodeAccounts) + engine.setRootContextProperty("loginModel", login.variant) + else: + onboarding.init() + engine.setRootContextProperty("onboardingModel", onboarding.variant) signalController.init() signalController.addSubscriber(SignalType.Wallet, wallet) signalController.addSubscriber(SignalType.Wallet, node) signalController.addSubscriber(SignalType.Message, chat) signalController.addSubscriber(SignalType.WhisperFilterAdded, chat) + signalController.addSubscriber(SignalType.NodeLogin, login) + signalController.addSubscriber(SignalType.NodeLogin, onboarding) engine.setRootContextProperty("signals", signalController.variant) @@ -81,8 +90,8 @@ proc mainProc() = chat.load(channel.name) ) - # accountsModel.appEvents.on("accountsReady") do(a: Args): - # appEvents.on("accountsReady") do(a: Args): + # accountsModel.appEvents.on("login") do(a: Args): + # appEvents.on("login") do(a: Args): # appState.addChannel("test") # appState.addChannel("test2") # appState.addChannel("status") diff --git a/src/signals/core.nim b/src/signals/core.nim index ad43fbd538..d5e5f1f35f 100644 --- a/src/signals/core.nim +++ b/src/signals/core.nim @@ -6,6 +6,8 @@ import types import messages import chronicles import whisperFilter +import strutils +import json_serialization logScope: topics = "signals" @@ -45,27 +47,35 @@ QtObject: let jsonSignal = (self.statusSignal).parseJson let signalString = $jsonSignal["type"].getStr - var signalType: SignalType - var signal: Signal + trace "Raw signal data", data = $jsonSignal - case signalString: - of "messages.new": - signalType = SignalType.Message + var signalType: SignalType + + try: + signalType = parseEnum[SignalType](signalString) + except: + warn "Unknown signal received", type = signalString + signalType = SignalType.Unknown + return + + var signal: Signal = Signal(signalType: signalType) + + case signalType: + of SignalType.Message: signal = messages.fromEvent(jsonSignal) - of "whisper.filter.added": - signalType = SignalType.WhisperFilterAdded + of SignalType.WhisperFilterAdded: signal = whisperFilter.fromEvent(jsonSignal) - of "wallet": - signalType = SignalType.Wallet + of SignalType.Wallet: signal = WalletSignal(content: $jsonSignal) + of SignalType.NodeLogin: + signal = Json.decode($jsonSignal, NodeSignal) else: - warn "Unhandled signal received", type = signalString - signalType = SignalType.Unknown - return + discard signal.signalType = signalType if not self.signalSubscribers.hasKey(signalType): + warn "Unhandled signal received", type = signalString self.signalSubscribers[signalType] = @[] for subscriber in self.signalSubscribers[signalType]: diff --git a/src/signals/types.nim b/src/signals/types.nim index d76e721e5c..ace1487265 100644 --- a/src/signals/types.nim +++ b/src/signals/types.nim @@ -1,10 +1,17 @@ import chronicles import ../status/types +import json_serialization type SignalSubscriber* = ref object of RootObj type Signal* = ref object of RootObj - signalType*: SignalType + signalType* {.serializedFieldName("type").}: SignalType + +type SignalError* = object + error*: string + +type NodeSignal* = ref object of Signal + event*: SignalError type WalletSignal* = ref object of Signal content*: string diff --git a/src/status/accounts.nim b/src/status/accounts.nim index 88637912c6..77bdbea5a1 100644 --- a/src/status/accounts.nim +++ b/src/status/accounts.nim @@ -34,7 +34,7 @@ proc ensureDir(dirname: string) = # removeDir(dirname) createDir(dirname) -proc initNodeAccounts*(): string = +proc initNodeAccounts*(): seq[NodeAccount] = const datadir = "./data/" const keystoredir = "./data/keystore/" const nobackupdir = "./noBackup/" @@ -44,9 +44,17 @@ proc initNodeAccounts*(): string = ensureDir(nobackupdir) discard $libstatus.initKeystore(keystoredir); - result = $libstatus.openAccounts(datadir); + let strNodeAccounts = $libstatus.openAccounts(datadir); + result = Json.decode(strNodeAccounts, seq[NodeAccount]) -proc saveAccountAndLogin*(multiAccounts: MultiAccounts, alias: string, identicon: string, accountData: string, password: string, configJSON: string, settingsJSON: string): Account = +proc saveAccountAndLogin*( + multiAccounts: MultiAccounts, + alias: string, + identicon: string, + accountData: string, + password: string, + configJSON: string, + settingsJSON: string): Account = let hashedPassword = "0x" & $keccak_256.digest(password) let subaccountData = %* [ { @@ -69,11 +77,14 @@ proc saveAccountAndLogin*(multiAccounts: MultiAccounts, alias: string, identicon var savedResult = $libstatus.saveAccountAndLogin(accountData, hashedPassword, settingsJSON, configJSON, $subaccountData) let parsedSavedResult = savedResult.parseJson + let error = parsedSavedResult["error"].getStr - if parsedSavedResult["error"].getStr == "": + if error == "": debug "Account saved succesfully" + result = Account(name: alias, photoPath: identicon) + return - result = Account(name: alias, photoPath: identicon) + raise newException(LoginError, "Error saving account and logging in: " & error) proc generateMultiAccounts*(account: GeneratedAccount, password: string): MultiAccounts = let hashedPassword = "0x" & $keccak_256.digest(password) @@ -135,3 +146,16 @@ proc setupAccount*(account: GeneratedAccount, password: string): Account = # 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) + let account = nodeAccount.toAccount + let loginResult = $libstatus.login($toJson(account), hashedPassword) + let error = parseJson(loginResult)["error"].getStr + + if error == "": + debug "Login requested", user=nodeAccount.name + result = nodeAccount + return + + raise newException(LoginError, "Error logging in: " & error) diff --git a/src/status/libstatus.nim b/src/status/libstatus.nim index b2b500dcee..4c84315695 100644 --- a/src/status/libstatus.nim +++ b/src/status/libstatus.nim @@ -25,3 +25,5 @@ proc sendTransaction*(jsonArgs: cstring, password: cstring): cstring {.importc: proc generateAlias*(p0: GoString): cstring {.importc: "GenerateAlias".} proc identicon*(p0: GoString): cstring {.importc: "Identicon".} + +proc login*(acctData: cstring, password: cstring): cstring {.importc: "Login".} diff --git a/src/status/types.nim b/src/status/types.nim index 9ef8295f25..594ecf64f1 100644 --- a/src/status/types.nim +++ b/src/status/types.nim @@ -7,13 +7,16 @@ type SignalCallback* = proc(eventMessage: cstring): void {.cdecl.} type SignalType* {.pure.} = enum Message = "messages.new" Wallet = "wallet" + NodeReady = "node.ready" NodeStarted = "node.started" NodeLogin = "node.login" EnvelopeSent = "envelope.sent" EnvelopeExpired = "envelope.expired" MailserverRequestCompleted = "mailserver.request.completed" MailserverRequestExpired = "mailserver.request.expired" - DiscoverSummary = "discover.summary" + DiscoveryStarted = "discovery.started" + DiscoveryStopped = "discovery.stopped" + DiscoverySummary = "discovery.summary" SubscriptionsData = "subscriptions.data" SubscriptionsError = "subscriptions.error" WhisperFilterAdded = "whisper.filter.added" @@ -36,31 +39,38 @@ type MultiAccounts* = object type - Account* = object of RootObj + Account* = ref object of RootObj name*: string keyUid* {.serializedFieldName("key-uid").}: string photoPath* {.serializedFieldName("photo-path").}: string type - NodeAccount* = object + NodeAccount* = ref object of Account timestamp*: int keycardPairing* {.serializedFieldName("keycard-pairing").}: string - # deserialisation does not handle base classes, so flatten - name*: string - keyUid* {.serializedFieldName("key-uid").}: string - photoPath* {.serializedFieldName("photo-path").}: string type - GeneratedAccount* = object + GeneratedAccount* = ref object publicKey*: string address*: string id*: string mnemonic*: string derived*: MultiAccounts - # deserialisation does not handle base classes, so flatten + # FIXME: should inherit from Account but multiAccountGenerateAndDeriveAddresses + # response has a camel-cased properties like "publicKey" and "keyUid", so the + # serializedFieldName pragma would need to be different name*: string keyUid*: string photoPath*: string +proc toAccount*(account: GeneratedAccount): Account = + result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.address) + +proc toAccount*(account: NodeAccount): Account = + result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.keyUid) + type AccountArgs* = ref object of Args account*: Account + +type + LoginError* = object of Exception diff --git a/ui/onboarding/GenKey.qml b/ui/onboarding/GenKey.qml index 65988059b3..86b3c4b8df 100644 --- a/ui/onboarding/GenKey.qml +++ b/ui/onboarding/GenKey.qml @@ -9,10 +9,8 @@ SwipeView { anchors.fill: parent currentIndex: 0 - signal loginDone - onCurrentItemChanged: { - currentItem.txtPassword.focus = true + currentItem.txtPassword.focus = true; } Item { @@ -87,17 +85,16 @@ SwipeView { anchors.fill: parent } - 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: { + swipeView.incrementCurrentIndex(); } + } + } } @@ -135,9 +132,7 @@ SwipeView { anchors.bottom: parent.bottom anchors.bottomMargin: 20 onClicked: { - console.log("password: " + txtPassword.text) - - swipeView.incrementCurrentIndex() + swipeView.incrementCurrentIndex(); } } } @@ -178,9 +173,9 @@ SwipeView { icon: StandardIcon.Warning standardButtons: StandardButton.Ok onAccepted: { - txtConfirmPassword.clear() - swipeView.currentIndex = 1 - txtPassword.focus = true + txtConfirmPassword.clear(); + swipeView.currentIndex = 1; + txtPassword.focus = true; } } @@ -188,27 +183,32 @@ SwipeView { id: storeAccountAndLoginError title: "Error storing account and logging in" text: "An error occurred while storing your account and logging in: " - icon: StandardIcon.Warning + icon: StandardIcon.Critical standardButtons: StandardButton.Ok } + Connections { + target: onboardingModel + onLoginResponseChanged: { + const loginResponse = JSON.parse(response); + if(loginResponse.error){ + storeAccountAndLoginError.text += loginResponse.error; + storeAccountAndLoginError.open() + } + } + } + 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() + return passwordsDontMatchError.open(); } - const selectedAccountIndex = wizardStep2.selectedIndex - onboardingModel.storeAccountAndLogin(selectedAccountIndex, - txtPassword.text) - - swipeView.loginDone() + onboardingModel.storeAccountAndLogin(selectedAccountIndex, txtPassword.text) } } } diff --git a/ui/onboarding/Intro.qml b/ui/onboarding/Intro.qml index a9efc685a9..6b9bb7276c 100644 --- a/ui/onboarding/Intro.qml +++ b/ui/onboarding/Intro.qml @@ -29,21 +29,6 @@ RowLayout { Item { id: itmSlide1 - StyledButton { - id: btnGenRandomAcct - width: 250 - height: 50 - anchors.top: parent.top - anchors.topMargin: 50 - anchors.left: parent.left - anchors.leftMargin: 15 - label: "Generate random account and login" - onClicked: { - onboardingModel.generateRandomAccountAndLogin() - app.visible = true - } - } - Image { id: img1 anchors.horizontalCenter: parent.horizontalCenter diff --git a/ui/onboarding/Login.qml b/ui/onboarding/Login.qml new file mode 100644 index 0000000000..165c3b6fe8 --- /dev/null +++ b/ui/onboarding/Login.qml @@ -0,0 +1,176 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.11 +import QtQuick.Window 2.11 +import QtQuick.Dialogs 1.3 + +SwipeView { + id: swipeView + anchors.fill: parent + currentIndex: 0 + + signal loginDone(response: var) + + onCurrentItemChanged: { + currentItem.txtPassword.focus = true; + } + + Item { + id: wizardStep2 + property int selectedIndex: 0 + + ColumnLayout { + id: columnLayout + width: 620 + height: 427 + + Text { + text: "Login" + font.pointSize: 36 + anchors.top: parent.top + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + } + + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + spacing: 10 + ButtonGroup { + id: accountGroup + } + + Component { + id: addressViewDelegate + + 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; + } + } + Column { + Image { + source: identicon + } + } + Column { + Text { + text: username + } + Text { + text: key + width: 160 + elide: Text.ElideMiddle + } + + } + } + } + + } + + ListView { + id: addressesView + contentWidth: 200 + model: loginModel + delegate: addressViewDelegate + Layout.fillHeight: true + Layout.fillWidth: true + anchors.topMargin: 36 + anchors.fill: parent + } + } + + Button { + text: "Select" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + onClicked: { + swipeView.incrementCurrentIndex(); + } + } + + } + } + + 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 + } + } + + MessageDialog { + id: loginError + title: "Login failed" + text: "Login failed. Please re-enter your password and try again." + icon: StandardIcon.Critical + standardButtons: StandardButton.Ok + onAccepted: { + txtPassword.clear(); + txtPassword.focus = true + } + } + + Connections { + target: loginModel + onLoginResponseChanged: { + const loginResponse = JSON.parse(response); + if(loginResponse.error){ + loginError.open() + } + } + } + + Button { + text: "Finish" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + onClicked: { + const selectedAccountIndex = wizardStep2.selectedIndex + const response = loginModel.login(selectedAccountIndex, txtPassword.text) + // TODO: replace me with something graphical (ie spinner) + console.log("Logging in...") + } + } + } +} + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/ui/onboarding/OnboardingMain.qml b/ui/onboarding/OnboardingMain.qml index 9dfcf667ad..855e900ce1 100644 --- a/ui/onboarding/OnboardingMain.qml +++ b/ui/onboarding/OnboardingMain.qml @@ -9,7 +9,7 @@ Page { DSM.StateMachine { id: stateMachine - initialState: stateIntro + initialState: showLogin ? stateLogin : stateIntro running: onboardingMain.visible DSM.State { @@ -43,25 +43,36 @@ Page { id: existingKeyState onEntered: existingKey.visible = true onExited: existingKey.visible = false - -// DSM.SignalTransition { -// targetState: keysMainState -// signal: keysMain.btnExistingKey.clicked -// } } DSM.State { id: genKeyState - onEntered: { - genKey.visible = true - } + onEntered: genKey.visible = true onExited: genKey.visible = false - DSM.SignalTransition { - targetState: appState - signal: genKey.loginDone - // guard: !response.error - } + DSM.SignalTransition { + targetState: appState + signal: onboardingModel.loginResponseChanged + guard: { + const resp = JSON.parse(response); + return !resp.error + } + } + } + + DSM.State { + id: stateLogin + onEntered: login.visible = true + onExited: login.visible = false + + DSM.SignalTransition { + targetState: appState + signal: loginModel.loginResponseChanged + guard: { + const resp = JSON.parse(response); + return !resp.error + } + } } DSM.FinalState { @@ -74,7 +85,7 @@ Page { Intro { id: intro anchors.fill: parent - visible: true + visible: false } KeysMain { @@ -94,6 +105,12 @@ Page { anchors.fill: parent visible: false } + + Login { + id: login + anchors.fill: parent + visible: false + } } /*##^## diff --git a/vendor/nim-stew b/vendor/nim-stew index d0f5be4971..a99dafab42 160000 --- a/vendor/nim-stew +++ b/vendor/nim-stew @@ -1 +1 @@ -Subproject commit d0f5be4971ad34d115b9749d9fb69bdd2aecf525 +Subproject commit a99dafab420bcbbffee35e9bd847a9014eafaffe