From 4ec593baedf351054e627b6ae5301611c1da36e8 Mon Sep 17 00:00:00 2001 From: emizzle Date: Thu, 4 Jun 2020 17:38:24 +1000 Subject: [PATCH] feat: Add logout functionality Move the onboarding/login state machine to the top level in main.qml, so that logout events can trigger new states. Add Loader to statemachine so that each component is lazy-loaded. Initial tests saved 50MB of memory on startup. Currently, logging out, then logging back in to the same or different account results in a doubling-up of chats/messages/wallet accounts. These need to be reset, however I need help doing that and it would delayed and blown out this PR further. This reset has been done for Onboarding and Login, but needs to be done for chats, wallet, mailservers, etc. --- src/app/login/core.nim | 20 ++- src/app/login/view.nim | 7 + src/app/onboarding/core.nim | 7 +- src/app/onboarding/view.nim | 5 + src/app/profile/core.nim | 2 +- src/app/profile/view.nim | 9 +- src/nim_status_client.nim | 41 +++-- src/status/accounts.nim | 14 +- src/status/libstatus/accounts.nim | 20 +-- src/status/libstatus/accounts/constants.nim | 6 +- src/status/libstatus/libstatus.nim | 2 + src/status/libstatus/types.nim | 1 + src/status/profile.nim | 11 ++ src/status/status.nim | 22 ++- .../Profile/Sections/SignoutContainer.qml | 14 +- ui/main.qml | 148 ++++++++++++++++-- ui/onboarding/ExistingKey.qml | 13 ++ ui/onboarding/OnboardingMain.qml | 130 --------------- 18 files changed, 300 insertions(+), 172 deletions(-) delete mode 100644 ui/onboarding/OnboardingMain.qml diff --git a/src/app/login/core.nim b/src/app/login/core.nim index d740a4942c..56d0acefb2 100644 --- a/src/app/login/core.nim +++ b/src/app/login/core.nim @@ -3,6 +3,7 @@ import ../../status/libstatus/types as status_types import ../../signals/types import ../../status/status import view +import ../../status/accounts as status_accounts type LoginController* = ref object of SignalSubscriber status*: Status @@ -19,11 +20,19 @@ proc delete*(self: LoginController) = delete self.view delete self.variant -proc init*(self: LoginController, nodeAccounts: seq[NodeAccount]) = +proc init*(self: LoginController) = + let nodeAccounts = self.status.accounts.openAccounts() self.status.accounts.nodeAccounts = nodeAccounts for nodeAccount in nodeAccounts: self.view.addAccountToList(nodeAccount) +proc reset*(self: LoginController) = + self.view.removeAccounts() + +proc handleNodeStopped(self: LoginController, data: Signal) = + self.status.events.emit("nodeStopped", Args()) + self.view.onLoggedOut() + proc handleNodeLogin(self: LoginController, data: Signal) = let response = NodeSignal(data) if self.view.currentAccount.account != nil: @@ -31,8 +40,13 @@ proc handleNodeLogin(self: LoginController, data: Signal) = if ?.response.event.error == "": self.status.events.emit("login", AccountArgs(account: self.view.currentAccount.account.toAccount)) +proc handleNodeReady(self: LoginController, data: Signal) = + self.status.events.emit("nodeReady", Args()) + method onSignal(self: LoginController, data: Signal) = - if data.signalType == SignalType.NodeLogin: - self.handleNodeLogin(data) + case data.signalType: + of SignalType.NodeLogin: handleNodeLogin(self, data) + of SignalType.NodeStopped: handleNodeStopped(self, data) + of SignalType.NodeReady: handleNodeReady(self, data) else: discard diff --git a/src/app/login/view.nim b/src/app/login/view.nim index e1fca179ac..f397cce96c 100644 --- a/src/app/login/view.nim +++ b/src/app/login/view.nim @@ -43,6 +43,11 @@ QtObject: self.accounts.add(account) self.endInsertRows() + proc removeAccounts*(self: LoginView) = + self.beginRemoveRows(newQModelIndex(), self.accounts.len, self.accounts.len) + self.accounts = @[] + self.endRemoveRows() + method rowCount(self: LoginView, index: QModelIndex = nil): int = return self.accounts.len @@ -86,3 +91,5 @@ QtObject: proc setLastLoginResponse*(self: LoginView, loginResponse: StatusGoError) = self.loginResponseChanged(loginResponse.error) + + proc onLoggedOut*(self: LoginView) {.signal.} \ No newline at end of file diff --git a/src/app/onboarding/core.nim b/src/app/onboarding/core.nim index 7a8a9538b4..0e24b13d47 100644 --- a/src/app/onboarding/core.nim +++ b/src/app/onboarding/core.nim @@ -29,6 +29,9 @@ proc init*(self: OnboardingController) = for account in accounts: self.view.addAccountToList(account) +proc reset*(self: OnboardingController) = + self.view.removeAccounts() + proc handleNodeLogin(self: OnboardingController, data: Signal) = let response = NodeSignal(data) if self.view.currentAccount.account != nil: @@ -37,7 +40,7 @@ proc handleNodeLogin(self: OnboardingController, data: Signal) = self.status.events.emit("login", AccountArgs(account: self.view.currentAccount.account.toAccount)) method onSignal(self: OnboardingController, data: Signal) = - if data.signalType == SignalType.NodeLogin: - self.handleNodeLogin(data) + case data.signalType: + of SignalType.NodeLogin: handleNodeLogin(self, data) else: discard diff --git a/src/app/onboarding/view.nim b/src/app/onboarding/view.nim index 6492da0269..40a063dd43 100644 --- a/src/app/onboarding/view.nim +++ b/src/app/onboarding/view.nim @@ -40,6 +40,11 @@ QtObject: self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len) self.accounts.add(account) self.endInsertRows() + + proc removeAccounts*(self: OnboardingView) = + self.beginResetModel() + self.accounts = @[] + self.endResetModel() method rowCount(self: OnboardingView, index: QModelIndex = nil): int = return self.accounts.len diff --git a/src/app/profile/core.nim b/src/app/profile/core.nim index 580854a97d..699960440d 100644 --- a/src/app/profile/core.nim +++ b/src/app/profile/core.nim @@ -15,7 +15,7 @@ type ProfileController* = object proc newController*(status: Status): ProfileController = result = ProfileController() result.status = status - result.view = newProfileView() + result.view = newProfileView(status) result.variant = newQVariant(result.view) proc delete*(self: ProfileController) = diff --git a/src/app/profile/view.nim b/src/app/profile/view.nim index e3a9be2096..39c0462dd7 100644 --- a/src/app/profile/view.nim +++ b/src/app/profile/view.nim @@ -3,12 +3,15 @@ import views/mailservers_list import views/contact_list import views/profile_info import ../../status/profile +import ../../status/accounts as status_accounts +import ../../status/status QtObject: type ProfileView* = ref object of QObject profile*: ProfileInfoView mailserversList*: MailServersList contactList*: ContactList + status*: Status proc setup(self: ProfileView) = self.QObject.setup @@ -16,12 +19,13 @@ QtObject: proc delete*(self: ProfileView) = self.QObject.delete - proc newProfileView*(): ProfileView = + proc newProfileView*(status: Status): ProfileView = new(result, delete) result = ProfileView() result.profile = newProfileInfoView() result.mailserversList = newMailServersList() result.contactList = newContactList() + result.status = status result.setup proc addMailServerToList*(self: ProfileView, mailserver: MailServer) = @@ -50,3 +54,6 @@ QtObject: QtProperty[QVariant] profile: read = getProfile + + proc logout*(self: ProfileView) {.slot.} = + self.status.profile.logout() diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index ba77d8dc79..488f00bdba 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -21,7 +21,7 @@ logScope: proc mainProc() = let status = statuslib.newStatusInstance() - let nodeAccounts = status.initNodeAccounts() + status.initNode() let app = newQApplication() let engine = newQQmlApplicationEngine() @@ -43,13 +43,12 @@ proc mainProc() = engine.setRootContextProperty("chatsModel", chat.variant) var node = node.newController(status) - node.init() engine.setRootContextProperty("nodeModel", node.variant) var profile = profile.newController(status) engine.setRootContextProperty("profileModel", profile.variant) - status.events.once("login") do(a: Args): + status.events.on("login") do(a: Args): var args = AccountArgs(a) status.startMessenger() chat.init() @@ -59,16 +58,37 @@ proc mainProc() = var login = login.newController(status) var onboarding = onboarding.newController(status) - # TODO: replace this with routing - let showLogin = nodeAccounts.len > 0 - engine.setRootContextProperty("showLogin", newQVariant(showLogin)) - - login.init(nodeAccounts) engine.setRootContextProperty("loginModel", login.variant) - onboarding.init() engine.setRootContextProperty("onboardingModel", onboarding.variant) + # Initialize only controllers whose init functions + # do not need a running node + proc initControllers() = + node.init() + login.init() + onboarding.init() + + initControllers() + + # Handle node.stopped signal when user has logged out + status.events.on("nodeStopped") do(a: Args): + # TODO: remove this once accounts are not tracked in the AccountsModel + status.reset() + + # 1. Reset controller data + login.reset() + onboarding.reset() + # TODO: implement all controller resets + # chat.reset() + # node.reset() + # wallet.reset() + # profile.reset() + + # 2. Re-init controllers that don't require a running node + initControllers() + + signalController.init() signalController.addSubscriber(SignalType.Wallet, wallet) signalController.addSubscriber(SignalType.Wallet, node) @@ -76,6 +96,9 @@ proc mainProc() = signalController.addSubscriber(SignalType.DiscoverySummary, chat) signalController.addSubscriber(SignalType.NodeLogin, login) signalController.addSubscriber(SignalType.NodeLogin, onboarding) + signalController.addSubscriber(SignalType.NodeStopped, login) + signalController.addSubscriber(SignalType.NodeStarted, login) + signalController.addSubscriber(SignalType.NodeReady, login) engine.setRootContextProperty("signals", signalController.variant) diff --git a/src/status/accounts.nim b/src/status/accounts.nim index 11bca8bbac..63652b433a 100644 --- a/src/status/accounts.nim +++ b/src/status/accounts.nim @@ -1,14 +1,17 @@ import libstatus/accounts as status_accounts import libstatus/types import options +import eventemitter type AccountModel* = ref object generatedAddresses*: seq[GeneratedAccount] nodeAccounts*: seq[NodeAccount] + events: EventEmitter -proc newAccountModel*(): AccountModel = +proc newAccountModel*(events: EventEmitter): AccountModel = result = AccountModel() + result.events = events proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = var accounts = status_accounts.generateAddresses() @@ -16,7 +19,10 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = account.name = status_accounts.generateAlias(account.derived.whisper.publicKey) account.photoPath = status_accounts.generateIdenticon(account.derived.whisper.publicKey) self.generatedAddresses.add(account) - self.generatedAddresses + result = self.generatedAddresses + +proc openAccounts*(self: AccountModel): seq[NodeAccount] = + result = status_accounts.openAccounts() proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount = let currentNodeAccount = self.nodeAccounts[selectedAccountIndex] @@ -35,3 +41,7 @@ proc importMnemonic*(self: AccountModel, mnemonic: string): GeneratedAccount = importedAccount.name = status_accounts.generateAlias(importedAccount.derived.whisper.publicKey) importedAccount.photoPath = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey) result = importedAccount + +proc reset*(self: AccountModel) = + self.nodeAccounts = @[] + self.generatedAddresses = @[] diff --git a/src/status/libstatus/accounts.nim b/src/status/libstatus/accounts.nim index f1be3034b6..29184cf180 100644 --- a/src/status/libstatus/accounts.nim +++ b/src/status/libstatus/accounts.nim @@ -36,17 +36,15 @@ proc ensureDir(dirname: string) = # removeDir(dirname) createDir(dirname) -proc initNodeAccounts*(): seq[NodeAccount] = - const datadir = "./data/" - const keystoredir = "./data/keystore/" - const nobackupdir = "./noBackup/" +proc initNode*() = + ensureDir(DATADIR) + ensureDir(KEYSTOREDIR) + ensureDir(NOBACKUPDIR) - ensureDir(datadir) - ensureDir(keystoredir) - ensureDir(nobackupdir) + discard $libstatus.initKeystore(KEYSTOREDIR) - discard $libstatus.initKeystore(keystoredir); - let strNodeAccounts = $libstatus.openAccounts(datadir); +proc openAccounts*(): seq[NodeAccount] = + let strNodeAccounts = $libstatus.openAccounts(DATADIR) result = Json.decode(strNodeAccounts, seq[NodeAccount]) proc saveAccountAndLogin*( @@ -215,6 +213,8 @@ proc deriveAccounts*(accountId: string): MultiAccounts = "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) + +proc logout*(): StatusGoError = + result = Json.decode($libstatus.logout(), StatusGoError) diff --git a/src/status/libstatus/accounts/constants.nim b/src/status/libstatus/accounts/constants.nim index e704ec195b..a0db92e80d 100644 --- a/src/status/libstatus/accounts/constants.nim +++ b/src/status/libstatus/accounts/constants.nim @@ -174,4 +174,8 @@ let NODE_CONFIG* = %* { "WalletConfig": { "Enabled": true } -} \ No newline at end of file +} + +const DATADIR* = "./data/" +const KEYSTOREDIR* = "./data/keystore/" +const NOBACKUPDIR* = "./noBackup/" \ No newline at end of file diff --git a/src/status/libstatus/libstatus.nim b/src/status/libstatus/libstatus.nim index 895240992f..6a9895fcec 100644 --- a/src/status/libstatus/libstatus.nim +++ b/src/status/libstatus/libstatus.nim @@ -33,3 +33,5 @@ proc generateAlias*(p0: GoString): cstring {.importc: "GenerateAlias".} proc identicon*(p0: GoString): cstring {.importc: "Identicon".} proc login*(acctData: cstring, password: cstring): cstring {.importc: "Login".} + +proc logout*(): cstring {.importc: "Logout".} diff --git a/src/status/libstatus/types.nim b/src/status/libstatus/types.nim index 2c57e34d6f..bbf92aa960 100644 --- a/src/status/libstatus/types.nim +++ b/src/status/libstatus/types.nim @@ -9,6 +9,7 @@ type SignalType* {.pure.} = enum Wallet = "wallet" NodeReady = "node.ready" NodeStarted = "node.started" + NodeStopped = "node.stopped" NodeLogin = "node.login" EnvelopeSent = "envelope.sent" EnvelopeExpired = "envelope.expired" diff --git a/src/status/profile.nim b/src/status/profile.nim index 07dbaf4eb2..e7ec747c0e 100644 --- a/src/status/profile.nim +++ b/src/status/profile.nim @@ -2,6 +2,7 @@ import json import eventemitter import libstatus/types import libstatus/core as libstatus_core +import libstatus/accounts as status_accounts type MailServer* = ref object @@ -45,3 +46,13 @@ proc getContactByID*(id: string): Profile = let val = parseJSON($response) result = toProfileModel(val) +type + ProfileModel* = ref object + +proc newProfileModel*(): ProfileModel = + result = ProfileModel() + +proc logout*(self: ProfileModel) = + discard status_accounts.logout() + + diff --git a/src/status/status.nim b/src/status/status.nim index 8172a2c210..dbd92b14ed 100644 --- a/src/status/status.nim +++ b/src/status/status.nim @@ -9,6 +9,8 @@ import accounts as accounts import wallet as wallet import node as node import mailservers as mailservers +import profile +import ../signals/types as signal_types type Status* = ref object events*: EventEmitter @@ -17,19 +19,33 @@ type Status* = ref object accounts*: AccountModel wallet*: WalletModel node*: NodeModel + profile*: ProfileModel proc newStatusInstance*(): Status = result = Status() result.events = createEventEmitter() result.chat = chat.newChatModel(result.events) - result.accounts = accounts.newAccountModel() + result.accounts = accounts.newAccountModel(result.events) result.wallet = wallet.newWalletModel(result.events) result.wallet.initEvents() result.node = node.newNodeModel() result.mailservers = mailservers.newMailserverModel(result.events) + result.profile = profile.newProfileModel() -proc initNodeAccounts*(self: Status): seq[NodeAccount] = - libstatus_accounts.initNodeAccounts() +proc initNode*(self: Status) = + libstatus_accounts.initNode() proc startMessenger*(self: Status) = libstatus_core.startMessenger() + +proc reset*(self: Status) = + # TODO: remove this once accounts are not tracked in the AccountsModel + self.accounts.reset() + + # NOT NEEDED self.chat.reset() + # NOT NEEDED self.wallet.reset() + # NOT NEEDED self.node.reset() + # NOT NEEDED self.mailservers.reset() + # NOT NEEDED self.profile.reset() + + # TODO: add all resets here diff --git a/ui/app/AppLayouts/Profile/Sections/SignoutContainer.qml b/ui/app/AppLayouts/Profile/Sections/SignoutContainer.qml index 799c7cd9e3..afb4bc5c4f 100644 --- a/ui/app/AppLayouts/Profile/Sections/SignoutContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/SignoutContainer.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.3 import "../../../../imports" +import "../../../../shared" Item { id: signoutContainer @@ -11,7 +12,7 @@ Item { Layout.fillWidth: true Text { - id: element10 + id: txtTitle text: qsTr("Sign out controls") anchors.left: parent.left anchors.leftMargin: 24 @@ -20,4 +21,15 @@ Item { font.weight: Font.Bold font.pixelSize: 20 } + + StyledButton { + id: btnLogout + anchors.top: txtTitle.bottom + anchors.topMargin: Theme.padding + label: qsTr("Logout") + + onClicked: { + profileModel.logout(); + } + } } \ No newline at end of file diff --git a/ui/main.qml b/ui/main.qml index 11d0fb1e5f..7a6f21fee3 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import Qt.labs.platform 1.1 +import QtQml.StateMachine 1.14 as DSM import "./onboarding" import "./app" @@ -13,6 +14,8 @@ ApplicationWindow { visible: true font.family: "Inter" + signal navigateTo(string path) + SystemTrayIcon { visible: true icon.source: "shared/img/status-logo.png" @@ -30,18 +33,145 @@ ApplicationWindow { } } - OnboardingMain { - id: onboarding - visible: !app.visible + DSM.StateMachine { + id: stateMachine + initialState: onboardingState + running: true + + DSM.State { + id: onboardingState + initialState: loginModel.rowCount() ? stateLogin : stateIntro + + DSM.State { + id: stateIntro + onEntered: loader.sourceComponent = intro + + DSM.SignalTransition { + targetState: keysMainState + signal: applicationWindow.navigateTo + guard: path === "KeysMain" + } + } + + DSM.State { + id: keysMainState + onEntered: loader.sourceComponent = keysMain + + DSM.SignalTransition { + targetState: existingKeyState + signal: applicationWindow.navigateTo + guard: path === "ExistingKey" + } + + DSM.SignalTransition { + targetState: genKeyState + signal: applicationWindow.navigateTo + guard: path === "GenKey" + } + } + + DSM.State { + id: existingKeyState + onEntered: loader.sourceComponent = existingKey + + DSM.SignalTransition { + targetState: appState + signal: onboardingModel.loginResponseChanged + guard: !error + } + } + + DSM.State { + id: genKeyState + onEntered: loader.sourceComponent = genKey + + DSM.SignalTransition { + targetState: appState + signal: onboardingModel.loginResponseChanged + guard: !error + } + + DSM.SignalTransition { + targetState: existingKeyState + signal: applicationWindow.navigateTo + guard: path === "ExistingKey" + } + } + + DSM.State { + id: stateLogin + onEntered: loader.sourceComponent = login + + DSM.SignalTransition { + targetState: appState + signal: loginModel.loginResponseChanged + guard: !error + } + + DSM.SignalTransition { + targetState: genKeyState + signal: applicationWindow.navigateTo + guard: path === "GenKey" + } + } + + DSM.FinalState { + id: onboardingDoneState + } + } + + DSM.State { + id: appState + onEntered: loader.sourceComponent = app + + DSM.SignalTransition { + targetState: stateLogin + signal: loginModel.onLoggedOut + } + } + } + + Loader { + id: loader anchors.fill: parent } - AppMain { + Component { id: app - // TODO: Set this to a logic result determining when we need to show the onboarding screens - // Set to true to hide the onboarding screens manually - // Set to false to show the onboarding screens manually - visible: false // logic.accountResult !== "" - anchors.fill: parent + AppMain {} + } + + Component { + id: intro + Intro { + btnGetStarted.onClicked: applicationWindow.navigateTo("KeysMain") + } + } + + Component { + id: keysMain + KeysMain { + btnGenKey.onClicked: applicationWindow.navigateTo("GenKey") + btnExistingKey.onClicked: applicationWindow.navigateTo("ExistingKey") + } + } + + Component { + id: existingKey + ExistingKey {} + } + + Component { + id: genKey + GenKey { + btnExistingKey.onClicked: applicationWindow.navigateTo("ExistingKey") + } + } + + Component { + id: login + Login { + btnGenKey.onClicked: applicationWindow.navigateTo("GenKey") + } } } diff --git a/ui/onboarding/ExistingKey.qml b/ui/onboarding/ExistingKey.qml index 39be34cf14..c5b2a82b23 100644 --- a/ui/onboarding/ExistingKey.qml +++ b/ui/onboarding/ExistingKey.qml @@ -192,6 +192,19 @@ SwipeView { standardButtons: StandardButton.Ok } + 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; + } + } + Connections { target: onboardingModel ignoreUnknownSignals: true diff --git a/ui/onboarding/OnboardingMain.qml b/ui/onboarding/OnboardingMain.qml deleted file mode 100644 index 508ee1ad22..0000000000 --- a/ui/onboarding/OnboardingMain.qml +++ /dev/null @@ -1,130 +0,0 @@ -import QtQuick 2.3 -import QtQml.StateMachine 1.14 as DSM -import QtQuick.Controls 2.3 - -Page { - id: onboardingMain - property string state - anchors.fill: parent - - DSM.StateMachine { - id: stateMachine - initialState: showLogin ? stateLogin : stateIntro - running: onboardingMain.visible - - DSM.State { - id: stateIntro - onEntered: intro.visible = true - onExited: intro.visible = false - - DSM.SignalTransition { - targetState: keysMainState - signal: intro.btnGetStarted.clicked - } - } - - DSM.State { - id: keysMainState - onEntered: keysMain.visible = true - onExited: keysMain.visible = false - - DSM.SignalTransition { - targetState: existingKeyState - signal: keysMain.btnExistingKey.clicked - } - - DSM.SignalTransition { - targetState: genKeyState - signal: keysMain.btnGenKey.clicked - } - } - - DSM.State { - id: existingKeyState - onEntered: existingKey.visible = true - onExited: existingKey.visible = false - - DSM.SignalTransition { - targetState: appState - signal: onboardingModel.loginResponseChanged - guard: !error - } - } - - DSM.State { - id: genKeyState - onEntered: genKey.visible = true - onExited: genKey.visible = false - - DSM.SignalTransition { - targetState: appState - signal: onboardingModel.loginResponseChanged - guard: !error - } - - DSM.SignalTransition { - targetState: existingKeyState - signal: genKey.btnExistingKey.clicked - } - } - - DSM.State { - id: stateLogin - onEntered: login.visible = true - onExited: login.visible = false - - DSM.SignalTransition { - targetState: appState - signal: loginModel.loginResponseChanged - guard: !error - } - - DSM.SignalTransition { - targetState: genKeyState - signal: login.btnGenKey.clicked - } - } - - DSM.FinalState { - id: appState - onEntered: app.visible = true - onExited: app.visible = false - } - } - - Intro { - id: intro - anchors.fill: parent - visible: false - } - - KeysMain { - id: keysMain - anchors.fill: parent - visible: false - } - - ExistingKey { - id: existingKey - anchors.fill: parent - visible: false - } - - GenKey { - id: genKey - anchors.fill: parent - visible: false - } - - Login { - id: login - anchors.fill: parent - visible: false - } -} - -/*##^## -Designer { - D{i:0;autoSize:true;height:770;width:1232} -} -##^##*/