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.
This commit is contained in:
emizzle 2020-06-04 17:38:24 +10:00 committed by Iuri Matias
parent bd8d743385
commit 4ec593baed
18 changed files with 300 additions and 172 deletions

View File

@ -3,6 +3,7 @@ import ../../status/libstatus/types as status_types
import ../../signals/types import ../../signals/types
import ../../status/status import ../../status/status
import view import view
import ../../status/accounts as status_accounts
type LoginController* = ref object of SignalSubscriber type LoginController* = ref object of SignalSubscriber
status*: Status status*: Status
@ -19,11 +20,19 @@ proc delete*(self: LoginController) =
delete self.view delete self.view
delete self.variant 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 self.status.accounts.nodeAccounts = nodeAccounts
for nodeAccount in nodeAccounts: for nodeAccount in nodeAccounts:
self.view.addAccountToList(nodeAccount) 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) = proc handleNodeLogin(self: LoginController, data: Signal) =
let response = NodeSignal(data) let response = NodeSignal(data)
if self.view.currentAccount.account != nil: if self.view.currentAccount.account != nil:
@ -31,8 +40,13 @@ proc handleNodeLogin(self: LoginController, data: Signal) =
if ?.response.event.error == "": if ?.response.event.error == "":
self.status.events.emit("login", AccountArgs(account: self.view.currentAccount.account.toAccount)) 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) = method onSignal(self: LoginController, data: Signal) =
if data.signalType == SignalType.NodeLogin: case data.signalType:
self.handleNodeLogin(data) of SignalType.NodeLogin: handleNodeLogin(self, data)
of SignalType.NodeStopped: handleNodeStopped(self, data)
of SignalType.NodeReady: handleNodeReady(self, data)
else: else:
discard discard

View File

@ -43,6 +43,11 @@ QtObject:
self.accounts.add(account) self.accounts.add(account)
self.endInsertRows() 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 = method rowCount(self: LoginView, index: QModelIndex = nil): int =
return self.accounts.len return self.accounts.len
@ -86,3 +91,5 @@ QtObject:
proc setLastLoginResponse*(self: LoginView, loginResponse: StatusGoError) = proc setLastLoginResponse*(self: LoginView, loginResponse: StatusGoError) =
self.loginResponseChanged(loginResponse.error) self.loginResponseChanged(loginResponse.error)
proc onLoggedOut*(self: LoginView) {.signal.}

View File

@ -29,6 +29,9 @@ proc init*(self: OnboardingController) =
for account in accounts: for account in accounts:
self.view.addAccountToList(account) self.view.addAccountToList(account)
proc reset*(self: OnboardingController) =
self.view.removeAccounts()
proc handleNodeLogin(self: OnboardingController, data: Signal) = proc handleNodeLogin(self: OnboardingController, data: Signal) =
let response = NodeSignal(data) let response = NodeSignal(data)
if self.view.currentAccount.account != nil: 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)) self.status.events.emit("login", AccountArgs(account: self.view.currentAccount.account.toAccount))
method onSignal(self: OnboardingController, data: Signal) = method onSignal(self: OnboardingController, data: Signal) =
if data.signalType == SignalType.NodeLogin: case data.signalType:
self.handleNodeLogin(data) of SignalType.NodeLogin: handleNodeLogin(self, data)
else: else:
discard discard

View File

@ -41,6 +41,11 @@ QtObject:
self.accounts.add(account) self.accounts.add(account)
self.endInsertRows() self.endInsertRows()
proc removeAccounts*(self: OnboardingView) =
self.beginResetModel()
self.accounts = @[]
self.endResetModel()
method rowCount(self: OnboardingView, index: QModelIndex = nil): int = method rowCount(self: OnboardingView, index: QModelIndex = nil): int =
return self.accounts.len return self.accounts.len

View File

@ -15,7 +15,7 @@ type ProfileController* = object
proc newController*(status: Status): ProfileController = proc newController*(status: Status): ProfileController =
result = ProfileController() result = ProfileController()
result.status = status result.status = status
result.view = newProfileView() result.view = newProfileView(status)
result.variant = newQVariant(result.view) result.variant = newQVariant(result.view)
proc delete*(self: ProfileController) = proc delete*(self: ProfileController) =

View File

@ -3,12 +3,15 @@ import views/mailservers_list
import views/contact_list import views/contact_list
import views/profile_info import views/profile_info
import ../../status/profile import ../../status/profile
import ../../status/accounts as status_accounts
import ../../status/status
QtObject: QtObject:
type ProfileView* = ref object of QObject type ProfileView* = ref object of QObject
profile*: ProfileInfoView profile*: ProfileInfoView
mailserversList*: MailServersList mailserversList*: MailServersList
contactList*: ContactList contactList*: ContactList
status*: Status
proc setup(self: ProfileView) = proc setup(self: ProfileView) =
self.QObject.setup self.QObject.setup
@ -16,12 +19,13 @@ QtObject:
proc delete*(self: ProfileView) = proc delete*(self: ProfileView) =
self.QObject.delete self.QObject.delete
proc newProfileView*(): ProfileView = proc newProfileView*(status: Status): ProfileView =
new(result, delete) new(result, delete)
result = ProfileView() result = ProfileView()
result.profile = newProfileInfoView() result.profile = newProfileInfoView()
result.mailserversList = newMailServersList() result.mailserversList = newMailServersList()
result.contactList = newContactList() result.contactList = newContactList()
result.status = status
result.setup result.setup
proc addMailServerToList*(self: ProfileView, mailserver: MailServer) = proc addMailServerToList*(self: ProfileView, mailserver: MailServer) =
@ -50,3 +54,6 @@ QtObject:
QtProperty[QVariant] profile: QtProperty[QVariant] profile:
read = getProfile read = getProfile
proc logout*(self: ProfileView) {.slot.} =
self.status.profile.logout()

View File

@ -21,7 +21,7 @@ logScope:
proc mainProc() = proc mainProc() =
let status = statuslib.newStatusInstance() let status = statuslib.newStatusInstance()
let nodeAccounts = status.initNodeAccounts() status.initNode()
let app = newQApplication() let app = newQApplication()
let engine = newQQmlApplicationEngine() let engine = newQQmlApplicationEngine()
@ -43,13 +43,12 @@ proc mainProc() =
engine.setRootContextProperty("chatsModel", chat.variant) engine.setRootContextProperty("chatsModel", chat.variant)
var node = node.newController(status) var node = node.newController(status)
node.init()
engine.setRootContextProperty("nodeModel", node.variant) engine.setRootContextProperty("nodeModel", node.variant)
var profile = profile.newController(status) var profile = profile.newController(status)
engine.setRootContextProperty("profileModel", profile.variant) engine.setRootContextProperty("profileModel", profile.variant)
status.events.once("login") do(a: Args): status.events.on("login") do(a: Args):
var args = AccountArgs(a) var args = AccountArgs(a)
status.startMessenger() status.startMessenger()
chat.init() chat.init()
@ -59,16 +58,37 @@ proc mainProc() =
var login = login.newController(status) var login = login.newController(status)
var onboarding = onboarding.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) engine.setRootContextProperty("loginModel", login.variant)
onboarding.init()
engine.setRootContextProperty("onboardingModel", onboarding.variant) 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.init()
signalController.addSubscriber(SignalType.Wallet, wallet) signalController.addSubscriber(SignalType.Wallet, wallet)
signalController.addSubscriber(SignalType.Wallet, node) signalController.addSubscriber(SignalType.Wallet, node)
@ -76,6 +96,9 @@ proc mainProc() =
signalController.addSubscriber(SignalType.DiscoverySummary, chat) signalController.addSubscriber(SignalType.DiscoverySummary, chat)
signalController.addSubscriber(SignalType.NodeLogin, login) signalController.addSubscriber(SignalType.NodeLogin, login)
signalController.addSubscriber(SignalType.NodeLogin, onboarding) 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) engine.setRootContextProperty("signals", signalController.variant)

View File

@ -1,14 +1,17 @@
import libstatus/accounts as status_accounts import libstatus/accounts as status_accounts
import libstatus/types import libstatus/types
import options import options
import eventemitter
type type
AccountModel* = ref object AccountModel* = ref object
generatedAddresses*: seq[GeneratedAccount] generatedAddresses*: seq[GeneratedAccount]
nodeAccounts*: seq[NodeAccount] nodeAccounts*: seq[NodeAccount]
events: EventEmitter
proc newAccountModel*(): AccountModel = proc newAccountModel*(events: EventEmitter): AccountModel =
result = AccountModel() result = AccountModel()
result.events = events
proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] = proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
var accounts = status_accounts.generateAddresses() 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.name = status_accounts.generateAlias(account.derived.whisper.publicKey)
account.photoPath = status_accounts.generateIdenticon(account.derived.whisper.publicKey) account.photoPath = status_accounts.generateIdenticon(account.derived.whisper.publicKey)
self.generatedAddresses.add(account) 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 = proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount =
let currentNodeAccount = self.nodeAccounts[selectedAccountIndex] 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.name = status_accounts.generateAlias(importedAccount.derived.whisper.publicKey)
importedAccount.photoPath = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey) importedAccount.photoPath = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey)
result = importedAccount result = importedAccount
proc reset*(self: AccountModel) =
self.nodeAccounts = @[]
self.generatedAddresses = @[]

View File

@ -36,17 +36,15 @@ proc ensureDir(dirname: string) =
# removeDir(dirname) # removeDir(dirname)
createDir(dirname) createDir(dirname)
proc initNodeAccounts*(): seq[NodeAccount] = proc initNode*() =
const datadir = "./data/" ensureDir(DATADIR)
const keystoredir = "./data/keystore/" ensureDir(KEYSTOREDIR)
const nobackupdir = "./noBackup/" ensureDir(NOBACKUPDIR)
ensureDir(datadir) discard $libstatus.initKeystore(KEYSTOREDIR)
ensureDir(keystoredir)
ensureDir(nobackupdir)
discard $libstatus.initKeystore(keystoredir); proc openAccounts*(): seq[NodeAccount] =
let strNodeAccounts = $libstatus.openAccounts(datadir); let strNodeAccounts = $libstatus.openAccounts(DATADIR)
result = Json.decode(strNodeAccounts, seq[NodeAccount]) result = Json.decode(strNodeAccounts, seq[NodeAccount])
proc saveAccountAndLogin*( proc saveAccountAndLogin*(
@ -215,6 +213,8 @@ proc deriveAccounts*(accountId: string): MultiAccounts =
"accountID": accountId, "accountID": accountId,
"paths": [PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET] "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) let deriveResult = $libstatus.multiAccountDeriveAddresses($deriveJson)
result = Json.decode(deriveResult, MultiAccounts) result = Json.decode(deriveResult, MultiAccounts)
proc logout*(): StatusGoError =
result = Json.decode($libstatus.logout(), StatusGoError)

View File

@ -175,3 +175,7 @@ let NODE_CONFIG* = %* {
"Enabled": true "Enabled": true
} }
} }
const DATADIR* = "./data/"
const KEYSTOREDIR* = "./data/keystore/"
const NOBACKUPDIR* = "./noBackup/"

View File

@ -33,3 +33,5 @@ proc generateAlias*(p0: GoString): cstring {.importc: "GenerateAlias".}
proc identicon*(p0: GoString): cstring {.importc: "Identicon".} proc identicon*(p0: GoString): cstring {.importc: "Identicon".}
proc login*(acctData: cstring, password: cstring): cstring {.importc: "Login".} proc login*(acctData: cstring, password: cstring): cstring {.importc: "Login".}
proc logout*(): cstring {.importc: "Logout".}

View File

@ -9,6 +9,7 @@ type SignalType* {.pure.} = enum
Wallet = "wallet" Wallet = "wallet"
NodeReady = "node.ready" NodeReady = "node.ready"
NodeStarted = "node.started" NodeStarted = "node.started"
NodeStopped = "node.stopped"
NodeLogin = "node.login" NodeLogin = "node.login"
EnvelopeSent = "envelope.sent" EnvelopeSent = "envelope.sent"
EnvelopeExpired = "envelope.expired" EnvelopeExpired = "envelope.expired"

View File

@ -2,6 +2,7 @@ import json
import eventemitter import eventemitter
import libstatus/types import libstatus/types
import libstatus/core as libstatus_core import libstatus/core as libstatus_core
import libstatus/accounts as status_accounts
type type
MailServer* = ref object MailServer* = ref object
@ -45,3 +46,13 @@ proc getContactByID*(id: string): Profile =
let val = parseJSON($response) let val = parseJSON($response)
result = toProfileModel(val) result = toProfileModel(val)
type
ProfileModel* = ref object
proc newProfileModel*(): ProfileModel =
result = ProfileModel()
proc logout*(self: ProfileModel) =
discard status_accounts.logout()

View File

@ -9,6 +9,8 @@ import accounts as accounts
import wallet as wallet import wallet as wallet
import node as node import node as node
import mailservers as mailservers import mailservers as mailservers
import profile
import ../signals/types as signal_types
type Status* = ref object type Status* = ref object
events*: EventEmitter events*: EventEmitter
@ -17,19 +19,33 @@ type Status* = ref object
accounts*: AccountModel accounts*: AccountModel
wallet*: WalletModel wallet*: WalletModel
node*: NodeModel node*: NodeModel
profile*: ProfileModel
proc newStatusInstance*(): Status = proc newStatusInstance*(): Status =
result = Status() result = Status()
result.events = createEventEmitter() result.events = createEventEmitter()
result.chat = chat.newChatModel(result.events) result.chat = chat.newChatModel(result.events)
result.accounts = accounts.newAccountModel() result.accounts = accounts.newAccountModel(result.events)
result.wallet = wallet.newWalletModel(result.events) result.wallet = wallet.newWalletModel(result.events)
result.wallet.initEvents() result.wallet.initEvents()
result.node = node.newNodeModel() result.node = node.newNodeModel()
result.mailservers = mailservers.newMailserverModel(result.events) result.mailservers = mailservers.newMailserverModel(result.events)
result.profile = profile.newProfileModel()
proc initNodeAccounts*(self: Status): seq[NodeAccount] = proc initNode*(self: Status) =
libstatus_accounts.initNodeAccounts() libstatus_accounts.initNode()
proc startMessenger*(self: Status) = proc startMessenger*(self: Status) =
libstatus_core.startMessenger() 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

View File

@ -2,6 +2,7 @@ import QtQuick 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import "../../../../imports" import "../../../../imports"
import "../../../../shared"
Item { Item {
id: signoutContainer id: signoutContainer
@ -11,7 +12,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
id: element10 id: txtTitle
text: qsTr("Sign out controls") text: qsTr("Sign out controls")
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 24 anchors.leftMargin: 24
@ -20,4 +21,15 @@ Item {
font.weight: Font.Bold font.weight: Font.Bold
font.pixelSize: 20 font.pixelSize: 20
} }
StyledButton {
id: btnLogout
anchors.top: txtTitle.bottom
anchors.topMargin: Theme.padding
label: qsTr("Logout")
onClicked: {
profileModel.logout();
}
}
} }

View File

@ -2,6 +2,7 @@ import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import QtQml.StateMachine 1.14 as DSM
import "./onboarding" import "./onboarding"
import "./app" import "./app"
@ -13,6 +14,8 @@ ApplicationWindow {
visible: true visible: true
font.family: "Inter" font.family: "Inter"
signal navigateTo(string path)
SystemTrayIcon { SystemTrayIcon {
visible: true visible: true
icon.source: "shared/img/status-logo.png" icon.source: "shared/img/status-logo.png"
@ -30,18 +33,145 @@ ApplicationWindow {
} }
} }
OnboardingMain { DSM.StateMachine {
id: onboarding id: stateMachine
visible: !app.visible 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 anchors.fill: parent
} }
AppMain { Component {
id: app id: app
// TODO: Set this to a logic result determining when we need to show the onboarding screens AppMain {}
// Set to true to hide the onboarding screens manually }
// Set to false to show the onboarding screens manually
visible: false // logic.accountResult !== "" Component {
anchors.fill: parent 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")
}
} }
} }

View File

@ -192,6 +192,19 @@ SwipeView {
standardButtons: StandardButton.Ok 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 { Connections {
target: onboardingModel target: onboardingModel
ignoreUnknownSignals: true ignoreUnknownSignals: true

View File

@ -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}
}
##^##*/