fix(@desktop/onboarding): Onboarding/Login flow improvements

- startup, login and onboarding modules merged into the single one
- `State` class introduced which is the base class for all states, every state
  determines what is the next state in each of 3 possible actions, and what
  is the previous state, if it has previous state
- `StateWrapper` class is introduced as a convenient way to expose
  `State`'s props and deal with them on the qml side
- startup module maintains states as a linked list and there are few convenient
  methods to move through the list `onBackActionClicked`, `onNextPrimaryActionClicked`
  `onNextSecondaryActionClicked`, `onNextTertiaryActionClicked`
- redundant code removed

Fixes: #6473
This commit is contained in:
Sale Djenic 2022-07-20 14:34:44 +02:00 committed by saledjenic
parent 1178b4f8c4
commit f9244e2c9f
63 changed files with 1377 additions and 1577 deletions

View File

@ -188,7 +188,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
statusFoundation.events,
result.keychainService,
result.accountsService,
result.generalService
result.generalService,
result.profileService
)
result.mainModule = main_module.newModule[AppController](
result,

View File

@ -4,9 +4,9 @@ import ./utils/qrcodegen
# Services as instances shouldn't be used in this class, just some general/global procs
import ../../app_service/common/conversion
import ../../app_service/service/accounts/service as procs_from_accounts
import ../../app_service/service/visual_identity/service as procs_from_visual_identity_service
include ../../app_service/service/accounts/utils
QtObject:
type Utils* = ref object of QObject
@ -28,7 +28,7 @@ QtObject:
result.removePrefix('/')
proc isAlias*(self: Utils, value: string): bool {.slot.} =
result = procs_from_accounts.isAlias(value)
result = isAlias(value)
proc urlFromUserInput*(self: Utils, input: string): string {.slot.} =
result = url_fromUserInput(input)
@ -71,7 +71,7 @@ QtObject:
return $stint.fromHex(StUint[256], value)
proc generateAlias*(self: Utils, pk: string): string {.slot.} =
return procs_from_accounts.generateAliasFromPk(pk)
return generateAliasFromPk(pk)
proc getFileSize*(self: Utils, filename: string): string {.slot.} =
var f: File = nil
@ -146,7 +146,7 @@ QtObject:
int(procs_from_visual_identity_service.colorIdOf(publicKey))
proc getCompressedPk*(self: Utils, publicKey: string): string {.slot.} =
procs_from_accounts.compressPk(publicKey)
compressPk(publicKey)
proc isCompressedPubKey*(self: Utils, publicKey: string): bool {.slot.} =
conversion.isCompressedPubKey(publicKey)

View File

@ -85,15 +85,11 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_MAILSERVER_NOT_WORKING) do(e: Args):
self.delegate.emitMailserverNotWorking()
if(defined(macosx)):
let account = self.accountsService.getLoggedInAccount()
singletonInstance.localAccountSettings.setFileName(account.name)
self.events.on("keychainServiceSuccess") do(e:Args):
self.events.on(SIGNAL_KEYCHAIN_SERVICE_SUCCESS) do(e:Args):
let args = KeyChainServiceArg(e)
self.delegate.emitStoringPasswordSuccess()
self.events.on("keychainServiceError") do(e:Args):
self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e)
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitStoringPasswordError(args.errDescription)

View File

@ -2,28 +2,52 @@ import chronicles
import io_interface
import ../../global/global_singleton
import ../../core/signals/types
import ../../core/eventemitter
import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/accounts/service as accounts_service
import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/profile/service as profile_service
logScope:
topics = "startup-controller"
type ProfileImageDetails = object
url*: string
x1*: int
y1*: int
x2*: int
y2*: int
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
generalService: general_service.Service
accountsService: accounts_service.Service
keychainService: keychain_service.Service
profileService: profile_service.Service
tmpProfileImageDetails: ProfileImageDetails
tmpDisplayName: string
tmpPassword: string
tmpMnemonic: string
tmpSelectedLoginAccountKeyUid: string
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
accountsService: accounts_service.Service):
generalService: general_service.Service,
accountsService: accounts_service.Service,
keychainService: keychain_service.Service,
profileService: profile_service.Service):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.generalService = generalService
result.accountsService = accountsService
result.keychainService = keychainService
result.profileService = profileService
proc delete*(self: Controller) =
discard
@ -31,10 +55,7 @@ proc delete*(self: Controller) =
proc init*(self: Controller) =
self.events.on(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e)
if signal.event.error == "":
self.delegate.userLoggedIn()
else:
error "error: ", methodName="init", errDesription = "login error " & signal.event.error
self.delegate.onNodeLogin(signal.event.error)
self.events.on(SignalType.NodeStopped.event) do(e:Args):
self.events.emit("nodeStopped", Args())
@ -44,5 +65,136 @@ proc init*(self: Controller) =
self.events.on(SignalType.NodeReady.event) do(e:Args):
self.events.emit("nodeReady", Args())
self.events.on(SIGNAL_KEYCHAIN_SERVICE_SUCCESS) do(e:Args):
let args = KeyChainServiceArg(e)
self.delegate.emitObtainingPasswordSuccess(args.data)
self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e)
# We are notifying user only about keychain errors.
if (args.errType == ERROR_TYPE_AUTHENTICATION):
return
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitObtainingPasswordError(args.errDescription)
proc shouldStartWithOnboardingScreen*(self: Controller): bool =
return self.accountsService.openedAccounts().len == 0
proc getGeneratedAccounts*(self: Controller): seq[GeneratedAccountDto] =
return self.accountsService.generatedAccounts()
proc getImportedAccount*(self: Controller): GeneratedAccountDto =
return self.accountsService.getImportedAccount()
proc generateImages*(self: Controller, image: string, aX: int, aY: int, bX: int, bY: int): seq[general_service.Image] =
return self.generalService.generateImages(image, aX, aY, bX, bY)
proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)
proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
let formatedImg = singletonInstance.utils.formatImagePath(imageUrl)
let images = self.generateImages(formatedImg, aX, aY, bX, bY)
if(images.len == 0):
return
for img in images:
if(img.imgType == "large"):
self.tmpProfileImageDetails = ProfileImageDetails(url: imageUrl, x1: aX, y1: aY, x2: bX, y2: bY)
return img.uri
proc setDisplayName*(self: Controller, value: string) =
self.tmpDisplayName = value
proc getDisplayName*(self: Controller): string =
return self.tmpDisplayName
proc setPassword*(self: Controller, value: string) =
self.tmpPassword = value
proc getPassword*(self: Controller): string =
return self.tmpPassword
proc storePasswordToKeychain(self: Controller) =
let account = self.accountsService.getLoggedInAccount()
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE or account.name.len == 0):
return
self.keychainService.storePassword(account.name, self.tmpPassword)
proc storeIdentityImage*(self: Controller) =
if self.tmpProfileImageDetails.url.len == 0:
return
let account = self.accountsService.getLoggedInAccount()
let image = singletonInstance.utils.formatImagePath(self.tmpProfileImageDetails.url)
self.profileService.storeIdentityImage(account.keyUid, image, self.tmpProfileImageDetails.x1,
self.tmpProfileImageDetails.y1, self.tmpProfileImageDetails.x2, self.tmpProfileImageDetails.y2)
self.tmpProfileImageDetails = ProfileImageDetails()
proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool) =
let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName)
if error != "":
self.delegate.setupAccountError(error)
else:
if storeToKeychain:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_STORE)
self.storePasswordToKeychain()
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
self.setPassword("")
self.setDisplayName("")
proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accounts = self.getGeneratedAccounts()
if accounts.len == 0:
error "list of generated accounts is empty"
return
let accountId = accounts[0].id
self.setupAccount(accountId, storeToKeychain)
proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accountId = self.getImportedAccount().id
self.setupAccount(accountId, storeToKeychain)
proc validMnemonic*(self: Controller, mnemonic: string): bool =
let err = self.accountsService.validateMnemonic(mnemonic)
if err.len == 0:
self.tmpMnemonic = mnemonic
return true
return false
proc importMnemonic*(self: Controller): bool =
let error = self.accountsService.importMnemonic(self.tmpMnemonic)
if(error.len == 0):
self.delegate.importAccountSuccess()
return true
else:
self.delegate.importAccountError(error)
return false
proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
return self.accountsService.openedAccounts()
proc getSelectedLoginAccount(self: Controller): AccountDto =
let openedAccounts = self.getOpenedAccounts()
for acc in openedAccounts:
if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid):
return acc
proc setSelectedLoginAccountKeyUid*(self: Controller, keyUid: string) =
self.tmpSelectedLoginAccountKeyUid = keyUid
# Dealing with Keychain is the MacOS only feature
if(not defined(macosx)):
return
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE):
return
self.keychainService.tryToObtainPassword(selectedAccount.name)
proc login*(self: Controller) =
let selectedAccount = self.getSelectedLoginAccount()
let error = self.accountsService.login(selectedAccount, self.tmpPassword)
self.setPassword("")
if(error.len > 0):
self.delegate.emitAccountLoginError(error)

View File

@ -0,0 +1,38 @@
import state
import ../controller
type
BiometricsState* = ref object of State
proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState =
result = BiometricsState()
result.setup(flowType, StateType.Biometrics, backState)
proc delete*(self: BiometricsState) =
self.State.delete
method moveToNextPrimaryState*(self: BiometricsState): bool =
return false
method moveToNextSecondaryState*(self: BiometricsState): bool =
return false
method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os

View File

@ -0,0 +1,25 @@
import state
import ../controller
import welcome_state_new_user, welcome_state_old_user
type
LoginState* = ref object of State
proc newLoginState*(flowType: FlowType, backState: State): LoginState =
result = LoginState()
result.setup(flowType, StateType.Login, backState)
proc delete*(self: LoginState) =
self.State.delete
method moveToNextPrimaryState*(self: LoginState): bool =
return false
method executePrimaryCommand*(self: LoginState, controller: Controller) =
controller.login()
method getNextSecondaryState*(self: LoginState): State =
return newWelcomeStateNewUser(FlowType.General, self)
method getNextTertiaryState*(self: LoginState): State =
return newWelcomeStateOldUser(FlowType.General, self)

View File

@ -0,0 +1,14 @@
import state, welcome_state
type
NotificationState* = ref object of State
proc newNotificationState*(flowType: FlowType, backState: State): NotificationState =
result = NotificationState()
result.setup(flowType, StateType.AllowNotifications, backState)
proc delete*(self: NotificationState) =
self.State.delete
method getNextPrimaryState*(self: NotificationState): State =
return newWelcomeState(FlowType.General, nil)

View File

@ -0,0 +1,111 @@
import ../controller
type FlowType* {.pure.} = enum
General = "General"
FirstRunNewUserNewKeys = "FirstRunNewUserNewKeys"
FirstRunNewUserNewKeycardKeys = "FirstRunNewUserNewKeycardKeys"
FirstRunNewUserImportSeedPhrase = "FirstRunNewUserImportSeedPhrase"
FirstRunOldUserSyncCode = "FirstRunOldUserSyncCode"
FirstRunOldUserKeycardImport = "FirstRunOldUserKeycardImport"
FirstRunOldUserImportSeedPhrase = "FirstRunOldUserImportSeedPhrase"
AppLogin = "AppLogin"
type StateType* {.pure.} = enum
NoState = "NoState"
AllowNotifications = "AllowNotifications"
Welcome = "Welcome"
WelcomeNewStatusUser = "WelcomeNewStatusUser"
WelcomeOldStatusUser = "WelcomeOldStatusUser"
UserProfileCreate = "UserProfileCreate"
UserProfileChatKey = "UserProfileChatKey"
UserProfileCreatePassword = "UserProfileCreatePassword"
UserProfileConfirmPassword = "UserProfileConfirmPassword"
UserProfileImportSeedPhrase = "UserProfileImportSeedPhrase"
UserProfileEnterSeedPhrase = "UserProfileEnterSeedPhrase"
Biometrics = "Biometrics"
Login = "Login"
## This is the base class for all state we may have in onboarding/login flow.
## We should not instance of this class (in c++ this will be an abstract class).
## For now each `State` inherited instance supports up to 3 different actions (e.g. 3 buttons on the UI).
type
State* {.pure inheritable.} = ref object of RootObj
flowType: FlowType
stateType: StateType
backState: State
proc setup*(self: State, flowType: FlowType, stateType: StateType, backState: State) =
self.flowType = flowType
self.stateType = stateType
self.backState = backState
## `flowType` - detemines the flow this instance belongs to
## `stateType` - detemines the state this instance describes
## `backState` - the sate (instance) we're moving to if user clicks "back" button,
## in case we should not display "back" button for this state, set it to `nil`
proc newState*(self: State, flowType: FlowType, stateType: StateType, backState: State): State =
result = State()
result.setup(flowType, stateType, backState)
proc delete*(self: State) =
discard
## Returns flow type
method flowType*(self: State): FlowType {.inline base.} =
self.flowType
## Returns state type
method stateType*(self: State): StateType {.inline base.} =
self.stateType
## Returns back state instance
method getBackState*(self: State): State {.inline base.} =
self.backState
## Returns true if we should display "back" button, otherwise false
method displayBackButton*(self: State): bool {.inline base.} =
return not self.backState.isNil
## Returns next state instance in case the "primary" action is triggered
method getNextPrimaryState*(self: State): State {.inline base.} =
return nil
## Returns next state instance in case the "secondary" action is triggered
method getNextSecondaryState*(self: State): State {.inline base.} =
return nil
## Returns next state instance in case the "tertiary" action is triggered
method getNextTertiaryState*(self: State): State {.inline base.} =
return nil
## This method is executed in case "back" button is clicked
method executeBackCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is executed in case "primary" action is triggered
method executePrimaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is executed in case "secondary" action is triggered
method executeSecondaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## This method is executed in case "tertiary" action is triggered
method executeTertiaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## Returns true if we should move from this state immediatelly when the "primary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextPrimaryState*(self: State): bool {.inline base.} =
return true
## Returns true if we should move from this state immediatelly when the "secondary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextSecondaryState*(self: State): bool {.inline base.} =
return true
## Returns true if we should move from this state immediatelly when the "tertiary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextTertiaryState*(self: State): bool {.inline base.} =
return true

View File

@ -0,0 +1,62 @@
import NimQml
import state
QtObject:
type StateWrapper* = ref object of QObject
stateObj: State
proc delete*(self: StateWrapper) =
self.QObject.delete
proc newStateWrapper*(): StateWrapper =
new(result, delete)
result.QObject.setup()
proc stateWrapperChanged*(self:StateWrapper) {.signal.}
proc setStateObj*(self: StateWrapper, stateObj: State) =
self.stateObj = stateObj
self.stateWrapperChanged()
proc getStateObj*(self: StateWrapper): State =
return self.stateObj
proc getFlowType(self: StateWrapper): string {.slot.} =
if(self.stateObj.isNil):
return $FlowType.General
return $self.stateObj.flowType()
QtProperty[string] flowType:
read = getFlowType
notify = stateWrapperChanged
proc getStateType(self: StateWrapper): string {.slot.} =
if(self.stateObj.isNil):
return $StateType.NoState
return $self.stateObj.stateType()
QtProperty[string] stateType:
read = getStateType
notify = stateWrapperChanged
proc getDisplayBackButton(self: StateWrapper): bool {.slot.} =
if(self.stateObj.isNil):
return false
return self.stateObj.displayBackButton()
QtProperty[bool] displayBackButton:
read = getDisplayBackButton
notify = stateWrapperChanged
proc backActionClicked*(self: StateWrapper) {.signal.}
proc backAction*(self: StateWrapper) {.slot.} =
self.backActionClicked()
proc primaryActionClicked*(self: StateWrapper) {.signal.}
proc doPrimaryAction*(self: StateWrapper) {.slot.} =
self.primaryActionClicked()
proc secondaryActionClicked*(self: StateWrapper) {.signal.}
proc doSecondaryAction*(self: StateWrapper) {.slot.} =
self.secondaryActionClicked()
proc tertiaryActionClicked*(self: StateWrapper) {.signal.}
proc doTertiaryAction*(self: StateWrapper) {.slot.} =
self.tertiaryActionClicked()

View File

@ -0,0 +1,15 @@
import state
import user_profile_create_password_state
type
UserProfileChatKeyState* = ref object of State
proc newUserProfileChatKeyState*(flowType: FlowType, backState: State): UserProfileChatKeyState =
result = UserProfileChatKeyState()
result.setup(flowType, StateType.UserProfileChatKey, backState)
proc delete*(self: UserProfileChatKeyState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileChatKeyState): State =
return newUserProfileCreatePasswordState(self.State.flowType, self)

View File

@ -0,0 +1,34 @@
import state
import biometrics_state
import ../controller
type
UserProfileConfirmPasswordState* = ref object of State
proc newUserProfileConfirmPasswordState*(flowType: FlowType, backState: State): UserProfileConfirmPasswordState =
result = UserProfileConfirmPasswordState()
result.setup(flowType, StateType.UserProfileConfirmPassword, backState)
proc delete*(self: UserProfileConfirmPasswordState) =
self.State.delete
method moveToNextPrimaryState*(self: UserProfileConfirmPasswordState): bool =
return defined(macosx)
method getNextPrimaryState*(self: UserProfileConfirmPasswordState): State =
if not self.moveToNextPrimaryState():
return nil
return newBiometricsState(self.State.flowType, nil)
method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) =
if self.moveToNextPrimaryState():
return
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os

View File

@ -0,0 +1,19 @@
import state
import ../controller
import user_profile_confirm_password_state
type
UserProfileCreatePasswordState* = ref object of State
proc newUserProfileCreatePasswordState*(flowType: FlowType, backState: State): UserProfileCreatePasswordState =
result = UserProfileCreatePasswordState()
result.setup(flowType, StateType.UserProfileCreatePassword, backState)
proc delete*(self: UserProfileCreatePasswordState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileCreatePasswordState): State =
return newUserProfileConfirmPasswordState(self.State.flowType, self)
method executeBackCommand*(self: UserProfileCreatePasswordState, controller: Controller) =
controller.setPassword("")

View File

@ -0,0 +1,19 @@
import state
import ../controller
import user_profile_chat_key_state
type
UserProfileCreateState* = ref object of State
proc newUserProfileCreateState*(flowType: FlowType, backState: State): UserProfileCreateState =
result = UserProfileCreateState()
result.setup(flowType, StateType.UserProfileCreate, backState)
proc delete*(self: UserProfileCreateState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileCreateState): State =
return newUserProfileChatKeyState(self.State.flowType, self)
method executeBackCommand*(self: UserProfileCreateState, controller: Controller) =
controller.setDisplayName("")

View File

@ -0,0 +1,26 @@
import state
import ../controller
import user_profile_create_state
type
UserProfileEnterSeedPhraseState* = ref object of State
successfulImport: bool
proc newUserProfileEnterSeedPhraseState*(flowType: FlowType, backState: State): UserProfileEnterSeedPhraseState =
result = UserProfileEnterSeedPhraseState()
result.setup(flowType, StateType.UserProfileEnterSeedPhrase, backState)
result.successfulImport = false
proc delete*(self: UserProfileEnterSeedPhraseState) =
self.State.delete
method moveToNextPrimaryState*(self: UserProfileEnterSeedPhraseState): bool =
return self.successfulImport
method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState): State =
if not self.moveToNextPrimaryState():
return nil
return newUserProfileCreateState(self.State.flowType, self)
method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller: Controller) =
self.successfulImport = controller.importMnemonic()

View File

@ -0,0 +1,15 @@
import state
import user_profile_enter_seed_phrase_state
type
UserProfileImportSeedPhraseState* = ref object of State
proc newUserProfileImportSeedPhraseState*(flowType: FlowType, backState: State): UserProfileImportSeedPhraseState =
result = UserProfileImportSeedPhraseState()
result.setup(flowType, StateType.UserProfileImportSeedPhrase, backState)
proc delete*(self: UserProfileImportSeedPhraseState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileImportSeedPhraseState): State =
return newUserProfileEnterSeedPhraseState(self.State.flowType, self)

View File

@ -0,0 +1,18 @@
import state
import welcome_state_new_user, welcome_state_old_user
type
WelcomeState* = ref object of State
proc newWelcomeState*(flowType: FlowType, backState: State): WelcomeState =
result = WelcomeState()
result.setup(flowType, StateType.Welcome, backState)
proc delete*(self: WelcomeState) =
self.State.delete
method getNextPrimaryState*(self: WelcomeState): State =
return newWelcomeStateNewUser(FlowType.General, self)
method getNextSecondaryState*(self: WelcomeState): State =
return newWelcomeStateOldUser(FlowType.General, self)

View File

@ -0,0 +1,24 @@
import state
import user_profile_create_state, user_profile_import_seed_phrase_state
type
WelcomeStateNewUser* = ref object of State
proc newWelcomeStateNewUser*(flowType: FlowType, backState: State): WelcomeStateNewUser =
result = WelcomeStateNewUser()
result.setup(flowType, StateType.WelcomeNewStatusUser, backState)
proc delete*(self: WelcomeStateNewUser) =
self.State.delete
method getNextPrimaryState*(self: WelcomeStateNewUser): State =
return newUserProfileCreateState(FlowType.FirstRunNewUserNewKeys, self)
method getNextSecondaryState*(self: WelcomeStateNewUser): State =
# We will handle here a click on `Generate keys for a new Keycard`
discard
method getNextTertiaryState*(self: WelcomeStateNewUser): State =
return newUserProfileImportSeedPhraseState(FlowType.FirstRunNewUserImportSeedPhrase, self)

View File

@ -0,0 +1,29 @@
import state
import user_profile_enter_seed_phrase_state
type
WelcomeStateOldUser* = ref object of State
proc newWelcomeStateOldUser*(flowType: FlowType, backState: State): WelcomeStateOldUser =
result = WelcomeStateOldUser()
result.setup(flowType, StateType.WelcomeOldStatusUser, backState)
proc delete*(self: WelcomeStateOldUser) =
self.State.delete
method getNextPrimaryState*(self: WelcomeStateOldUser): State =
# We will handle here a click on `Scan sync code`
discard
method getNextSecondaryState*(self: WelcomeStateOldUser): State =
# We will handle here a click on `Login with Keycard`
discard
method getNextTertiaryState*(self: WelcomeStateOldUser): State =
## This is added as next state in case of import seed for an old user, but this doesn't match the flow
## in the design. Need to be fixed correctly.
## Why it's not fixed now???
## -> Cause this is just a improving and moving to a better form what we currently have, fixing will be done in another issue
## and need to be discussed as we haven't had that flow implemented ever before
return newUserProfileEnterSeedPhraseState(FlowType.FirstRunOldUserImportSeedPhrase, self)

View File

@ -1,3 +1,6 @@
import ../../../app_service/service/accounts/service
import models/login_account_item as login_acc_item
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -10,25 +13,71 @@ method load*(self: AccessInterface) {.base.} =
method moveToAppState*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method startUpUIRaised*(self: AccessInterface) {.base.} =
method onBackActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onPrimaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method userLoggedIn*(self: AccessInterface) {.base.} =
method onSecondaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onTertiaryActionClicked*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method startUpUIRaised*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method emitLogOut*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method loginDidLoad*(self: AccessInterface) {.base.} =
method getImportedAccount*(self: AccessInterface): GeneratedAccountDto {.base.} =
raise newException(ValueError, "No implementation available")
method onboardingDidLoad*(self: AccessInterface) {.base.} =
method generateImage*(self: AccessInterface, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.base.} =
raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} =
method setDisplayName*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getDisplayName*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setPassword*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getPassword*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method getPasswordStrengthScore*(self: AccessInterface, password: string, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available")
method setupAccountError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method validMnemonic*(self: AccessInterface, mnemonic: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method importAccountError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method importAccountSuccess*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setSelectedLoginAccount*(self: AccessInterface, item: login_acc_item.Item) {.base.} =
raise newException(ValueError, "No implementation available")
method onNodeLogin*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitAccountLoginError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordError*(self: AccessInterface, errorDescription: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordSuccess*(self: AccessInterface, password: string) {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type

View File

@ -1,81 +0,0 @@
import NimQml, Tables
import io_interface
import ../../../global/global_singleton
import ../../../core/signals/types
import ../../../core/eventemitter
import ../../../../app_service/service/keychain/service as keychain_service
import ../../../../app_service/service/accounts/service as accounts_service
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
keychainService: keychain_service.Service
accountsService: accounts_service.Service
selectedAccountKeyUid: string
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
keychainService: keychain_service.Service,
accountsService: accounts_service.Service):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.keychainService = keychainService
result.accountsService = accountsService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e)
if signal.event.error != "":
self.delegate.emitAccountLoginError(signal.event.error)
self.events.on("keychainServiceSuccess") do(e:Args):
let args = KeyChainServiceArg(e)
self.delegate.emitObtainingPasswordSuccess(args.data)
self.events.on("keychainServiceError") do(e:Args):
let args = KeyChainServiceArg(e)
# We are notifying user only about keychain errors.
if (args.errType == ERROR_TYPE_AUTHENTICATION):
return
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitObtainingPasswordError(args.errDescription)
proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
return self.accountsService.openedAccounts()
proc getSelectedAccount(self: Controller): AccountDto =
let openedAccounts = self.getOpenedAccounts()
for acc in openedAccounts:
if(acc.keyUid == self.selectedAccountKeyUid):
return acc
proc setSelectedAccountKeyUid*(self: Controller, keyUid: string) =
self.selectedAccountKeyUid = keyUid
# Dealing with Keychain is the MacOS only feature
if(not defined(macosx)):
return
let selectedAccount = self.getSelectedAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE):
return
self.keychainService.tryToObtainPassword(selectedAccount.name)
proc login*(self: Controller, password: string) =
let selectedAccount = self.getSelectedAccount()
let error = self.accountsService.login(selectedAccount, password)
if(error.len > 0):
self.delegate.emitAccountLoginError(error)

View File

@ -1,33 +0,0 @@
import item
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method emitAccountLoginError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordError*(self: AccessInterface, errorDescription: string)
{.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordSuccess*(self: AccessInterface, password: string)
{.base.} =
raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setSelectedAccount*(self: AccessInterface, item: Item) {.base.} =
raise newException(ValueError, "No implementation available")
method login*(self: AccessInterface, password: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,88 +0,0 @@
import NimQml
import io_interface
import ../io_interface as delegate_interface
import view, controller, item
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/keychain/service as keychain_service
import ../../../../app_service/service/accounts/service as accounts_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter,
keychainService: keychain_service.Service,
accountsService: accounts_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, keychainService,
accountsService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
proc extractImages(self: Module, account: AccountDto, thumbnailImage: var string,
largeImage: var string) =
for img in account.images:
if(img.imgType == "thumbnail"):
thumbnailImage = img.uri
elif(img.imgType == "large"):
largeImage = img.uri
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("loginModule", self.viewVariant)
self.controller.init()
self.view.load()
let openedAccounts = self.controller.getOpenedAccounts()
if(openedAccounts.len > 0):
var items: seq[Item]
for acc in openedAccounts:
var thumbnailImage: string
var largeImage: string
self.extractImages(acc, thumbnailImage, largeImage)
items.add(initItem(acc.name, thumbnailImage, largeImage,
acc.keyUid, acc.colorHash, acc.colorId))
self.view.setModelItems(items)
# set the first account as slected one
self.controller.setSelectedAccountKeyUid(items[0].getKeyUid())
self.setSelectedAccount(items[0])
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.loginDidLoad()
method setSelectedAccount*(self: Module, item: Item) =
self.controller.setSelectedAccountKeyUid(item.getKeyUid())
self.view.setSelectedAccount(item)
method login*(self: Module, password: string) =
self.controller.login(password)
method emitAccountLoginError*(self: Module, error: string) =
self.view.emitAccountLoginError(error)
method emitObtainingPasswordError*(self: Module, errorDescription: string) =
self.view.emitObtainingPasswordError(errorDescription)
method emitObtainingPasswordSuccess*(self: Module, password: string) =
self.view.emitObtainingPasswordSuccess(password)

View File

@ -1,79 +0,0 @@
import NimQml
import model, item, selected_account
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
selectedAccount: SelectedAccount
selectedAccountVariant: QVariant
model: Model
modelVariant: QVariant
proc delete*(self: View) =
self.selectedAccount.delete
self.selectedAccountVariant.delete
self.model.delete
self.modelVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.selectedAccount = newSelectedAccount()
result.selectedAccountVariant = newQVariant(result.selectedAccount)
result.model = newModel()
result.modelVariant = newQVariant(result.model)
proc load*(self: View) =
self.delegate.viewDidLoad()
proc selectedAccountChanged*(self: View) {.signal.}
proc getSelectedAccount(self: View): QVariant {.slot.} =
return self.selectedAccountVariant
proc setSelectedAccount*(self: View, item: Item) =
self.selectedAccount.setSelectedAccountData(item)
self.selectedAccountChanged()
proc setSelectedAccountByIndex*(self: View, index: int) {.slot.} =
let item = self.model.getItemAtIndex(index)
self.delegate.setSelectedAccount(item)
QtProperty[QVariant] selectedAccount:
read = getSelectedAccount
notify = selectedAccountChanged
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
return self.modelVariant
proc setModelItems*(self: View, accounts: seq[Item]) =
self.model.setItems(accounts)
self.modelChanged()
QtProperty[QVariant] accountsModel:
read = getModel
notify = modelChanged
proc login*(self: View, password: string) {.slot.} =
self.delegate.login(password)
proc accountLoginError*(self: View, error: string) {.signal.}
proc emitAccountLoginError*(self: View, error: string) =
self.accountLoginError(error)
proc obtainingPasswordError*(self:View, errorDescription: string) {.signal.}
proc emitObtainingPasswordError*(self: View, errorDescription: string) =
self.obtainingPasswordError(errorDescription)
proc obtainingPasswordSuccess*(self:View, password: string) {.signal.}
proc emitObtainingPasswordSuccess*(self: View, password: string) =
self.obtainingPasswordSuccess(password)

View File

@ -1,6 +1,6 @@
import NimQml, Tables, strutils
import item
import generated_account_item
type
ModelRole {.pure.} = enum

View File

@ -1,6 +1,6 @@
import NimQml, Tables, strutils, strformat
import NimQml, Tables, strutils
import item
import login_account_item
type
ModelRole {.pure.} = enum

View File

@ -1,85 +1,90 @@
import NimQml
import NimQml, chronicles
import io_interface
import view, controller
import internal/[state, notification_state, welcome_state, login_state]
import models/generated_account_item as gen_acc_item
import models/login_account_item as login_acc_item
import ../../global/global_singleton
import ../../core/eventemitter
import onboarding/module as onboarding_module
import login/module as login_module
import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/accounts/service as accounts_service
import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/profile/service as profile_service
export io_interface
logScope:
topics = "startup-module"
type
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
delegate: T
view: View
viewVariant: QVariant
controller: Controller
onboardingModule: onboarding_module.AccessInterface
loginModule: login_module.AccessInterface
proc newModule*[T](delegate: T,
events: EventEmitter,
keychainService: keychain_service.Service,
accountsService: accounts_service.Service,
generalService: general_service.Service):
generalService: general_service.Service,
profileService: profile_service.Service):
Module[T] =
result = Module[T]()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, accountsService)
# Submodules
result.onboardingModule = onboarding_module.newModule(result, events, accountsService, generalService)
result.loginModule = login_module.newModule(result, events, keychainService,
accountsService)
result.controller = controller.newController(result, events, generalService, accountsService, keychainService,
profileService)
method delete*[T](self: Module[T]) =
self.onboardingModule.delete
self.loginModule.delete
self.view.delete
self.viewVariant.delete
self.controller.delete
proc extractImages(self: Module, account: AccountDto, thumbnailImage: var string,
largeImage: var string) =
for img in account.images:
if(img.imgType == "thumbnail"):
thumbnailImage = img.uri
elif(img.imgType == "large"):
largeImage = img.uri
method load*[T](self: Module[T]) =
singletonInstance.engine.setRootContextProperty("startupModule", self.viewVariant)
self.controller.init()
self.view.load()
var initialAppState = AppState.OnboardingState
if(not self.controller.shouldStartWithOnboardingScreen()):
initialAppState = AppState.LoginState
self.view.setAppState(initialAppState)
self.onboardingModule.load()
self.loginModule.load()
proc checkIfModuleDidLoad[T](self: Module[T]) =
if(not self.onboardingModule.isLoaded()):
return
if(not self.loginModule.isLoaded()):
return
let generatedAccounts = self.controller.getGeneratedAccounts()
var accounts: seq[gen_acc_item.Item]
for acc in generatedAccounts:
accounts.add(gen_acc_item.initItem(acc.id, acc.alias, acc.address, acc.derivedAccounts.whisper.publicKey, acc.keyUid))
self.view.setGeneratedAccountList(accounts)
if(self.controller.shouldStartWithOnboardingScreen()):
if defined(macosx):
self.view.setCurrentStartupState(newNotificationState(FlowType.General, nil))
else:
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
else:
let openedAccounts = self.controller.getOpenedAccounts()
if(openedAccounts.len > 0):
var items: seq[login_acc_item.Item]
for acc in openedAccounts:
var thumbnailImage: string
var largeImage: string
self.extractImages(acc, thumbnailImage, largeImage)
items.add(login_acc_item.initItem(acc.name, thumbnailImage, largeImage, acc.keyUid, acc.colorHash, acc.colorId))
self.view.setLoginAccountsModelItems(items)
# set the first account as slected one
if items.len == 0:
error "cannot run the app in login flow cause list of login accounts is empty"
quit() # quit the app
self.setSelectedLoginAccount(items[0])
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil))
self.delegate.startupDidLoad()
method viewDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method onboardingDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method loginDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method userLoggedIn*[T](self: Module[T]) =
self.delegate.userLoggedIn()
method moveToAppState*[T](self: Module[T]) =
self.view.setAppState(AppState.MainAppState)
@ -88,3 +93,98 @@ method startUpUIRaised*[T](self: Module[T]) =
method emitLogOut*[T](self: Module[T]) =
self.view.emitLogOut()
method onBackActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
currStateObj.executeBackCommand(self.controller)
let backState = currStateObj.getBackState()
self.view.setCurrentStartupState(backState)
currStateObj.delete()
method onPrimaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
currStateObj.executePrimaryCommand(self.controller)
if currStateObj.moveToNextPrimaryState():
let nextState = currStateObj.getNextPrimaryState()
self.view.setCurrentStartupState(nextState)
method onSecondaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
currStateObj.executeSecondaryCommand(self.controller)
if currStateObj.moveToNextSecondaryState():
let nextState = currStateObj.getNextSecondaryState()
self.view.setCurrentStartupState(nextState)
method onTertiaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
currStateObj.executeTertiaryCommand(self.controller)
if currStateObj.moveToNextTertiaryState():
let nextState = currStateObj.getNextTertiaryState()
self.view.setCurrentStartupState(nextState)
method getImportedAccount*[T](self: Module[T]): GeneratedAccountDto =
return self.controller.getImportedAccount()
method generateImage*[T](self: Module[T], imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
return self.controller.generateImage(imageUrl, aX, aY, bX, bY)
method setDisplayName*[T](self: Module[T], value: string) =
self.controller.setDisplayName(value)
method getDisplayName*[T](self: Module[T]): string =
return self.controller.getDisplayName()
method setPassword*[T](self: Module[T], value: string) =
self.controller.setPassword(value)
method getPassword*[T](self: Module[T]): string =
return self.controller.getPassword()
method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int =
return self.controller.getPasswordStrengthScore(password, userName)
method setupAccountError*[T](self: Module[T], error: string) =
self.view.setupAccountError(error)
method validMnemonic*[T](self: Module[T], mnemonic: string): bool =
return self.controller.validMnemonic(mnemonic)
method importAccountError*[T](self: Module[T], error: string) =
self.view.importAccountError(error)
method importAccountSuccess*[T](self: Module[T]) =
self.view.importAccountSuccess()
method setSelectedLoginAccount*[T](self: Module[T], item: login_acc_item.Item) =
self.controller.setSelectedLoginAccountKeyUid(item.getKeyUid())
self.view.setSelectedLoginAccount(item)
method emitAccountLoginError*[T](self: Module[T], error: string) =
self.view.emitAccountLoginError(error)
method emitObtainingPasswordError*[T](self: Module[T], errorDescription: string) =
self.view.emitObtainingPasswordError(errorDescription)
method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) =
self.view.emitObtainingPasswordSuccess(password)
method onNodeLogin*[T](self: Module[T], error: string) =
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "error: cannot determine current startup state"
quit() # quit the app
if error.len == 0:
self.delegate.userLoggedIn()
if currStateObj.flowType() != FlowType.AppLogin:
self.controller.storeIdentityImage()
else:
if currStateObj.flowType() == FlowType.AppLogin:
self.emitAccountLoginError(error)
else:
self.setupAccountError(error)
error "error: ", methodName="onNodeLogin", errDesription =error

View File

@ -1,75 +0,0 @@
import Tables, chronicles
import io_interface
import ../../../core/signals/types
import ../../../core/eventemitter
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/general/service as general_service
logScope:
topics = "onboarding-controller"
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
accountsService: accounts_service.Service
generalService: general_service.Service
selectedAccountId: string
displayName: string
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
accountsService: accounts_service.Service,
generalService: general_service.Service):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.accountsService = accountsService
result.generalService = generalService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e)
if signal.event.error != "":
self.delegate.setupAccountError(signal.event.error)
proc getGeneratedAccounts*(self: Controller): seq[GeneratedAccountDto] =
return self.accountsService.generatedAccounts()
proc getImportedAccount*(self: Controller): GeneratedAccountDto =
return self.accountsService.getImportedAccount()
proc setSelectedAccountByIndex*(self: Controller, index: int) =
let accounts = self.getGeneratedAccounts()
self.selectedAccountId = accounts[index].id
proc setDisplayName*(self: Controller, displayName: string) =
self.displayName = displayName
proc storeSelectedAccountAndLogin*(self: Controller, password: string) =
let error = self.accountsService.setupAccount(self.selectedAccountId, password, self.displayName)
if error != "":
self.delegate.setupAccountError(error)
proc validateMnemonic*(self: Controller, mnemonic: string): string =
return self.accountsService.validateMnemonic(mnemonic)
proc importMnemonic*(self: Controller, mnemonic: string) =
let error = self.accountsService.importMnemonic(mnemonic)
if(error == ""):
self.selectedAccountId = self.getImportedAccount().id
self.delegate.importAccountSuccess()
else:
self.delegate.importAccountError(error)
proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)
proc generateImages*(self: Controller, image: string, aX: int, aY: int, bX: int, bY: int): seq[general_service.Image] =
return self.generalService.generateImages(image, aX, aY, bX, bY)

View File

@ -1,51 +0,0 @@
import ../../../../app_service/service/accounts/service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method setupAccountError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method importAccountError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method importAccountSuccess*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setSelectedAccountByIndex*(self: AccessInterface, index: int) {.base.} =
raise newException(ValueError, "No implementation available")
method storeSelectedAccountAndLogin*(self: AccessInterface, password: string)
{.base.} =
raise newException(ValueError, "No implementation available")
method getImportedAccount*(self: AccessInterface): GeneratedAccountDto {.base.} =
raise newException(ValueError, "No implementation available")
method validateMnemonic*(self: AccessInterface, mnemonic: string):
string {.base.} =
raise newException(ValueError, "No implementation available")
method importMnemonic*(self: AccessInterface, mnemonic: string) {.base.} =
raise newException(ValueError, "No implementation available")
method setDisplayName*(self: AccessInterface, displayName: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getPasswordStrengthScore*(self: AccessInterface, password: string, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available")
method generateImage*(self: AccessInterface, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,93 +0,0 @@
import NimQml
import io_interface
import ../io_interface as delegate_interface
import view, controller, item
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/general/service as general_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter,
accountsService: accounts_service.Service,
generalService: general_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, accountsService, generalService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("onboardingModule", self.viewVariant)
self.controller.init()
self.view.load()
let generatedAccounts = self.controller.getGeneratedAccounts()
var accounts: seq[Item]
for acc in generatedAccounts:
accounts.add(initItem(acc.id, acc.alias, acc.address, acc.derivedAccounts.whisper.publicKey, acc.keyUid))
self.view.setAccountList(accounts)
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.onboardingDidLoad()
method setSelectedAccountByIndex*(self: Module, index: int) =
self.controller.setSelectedAccountByIndex(index)
method setDisplayName*(self: Module, displayName: string) =
self.controller.setDisplayName(displayName)
method storeSelectedAccountAndLogin*(self: Module, password: string) =
self.controller.storeSelectedAccountAndLogin(password)
method setupAccountError*(self: Module, error: string) =
self.view.setupAccountError(error)
method getImportedAccount*(self: Module): GeneratedAccountDto =
return self.controller.getImportedAccount()
method validateMnemonic*(self: Module, mnemonic: string): string =
return self.controller.validateMnemonic(mnemonic)
method importMnemonic*(self: Module, mnemonic: string) =
self.controller.importMnemonic(mnemonic)
method importAccountError*(self: Module, error: string) =
self.view.importAccountError(error)
method importAccountSuccess*(self: Module) =
self.view.importAccountSuccess()
method getPasswordStrengthScore*(self: Module, password, userName: string): int =
return self.controller.getPasswordStrengthScore(password, userName)
method generateImage*(self: Module, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
let formatedImg = singletonInstance.utils.formatImagePath(imageUrl)
let images = self.controller.generateImages(formatedImg, aX, aY, bX, bY)
if(images.len == 0):
return
for img in images:
if(img.imgType == "large"):
return img.uri

View File

@ -1,100 +0,0 @@
import NimQml
import model, item
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
model: Model
modelVariant: QVariant
proc delete*(self: View) =
self.model.delete
self.modelVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.model = newModel()
result.modelVariant = newQVariant(result.model)
proc load*(self: View) =
self.delegate.viewDidLoad()
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
return self.modelVariant
proc setAccountList*(self: View, accounts: seq[Item]) =
self.model.setItems(accounts)
self.modelChanged()
QtProperty[QVariant] accountsModel:
read = getModel
notify = modelChanged
proc importedAccountChanged*(self: View) {.signal.}
proc getImportedAccountAlias*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().alias
QtProperty[string] importedAccountAlias:
read = getImportedAccountAlias
notify = importedAccountChanged
proc getImportedAccountAddress*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().address
QtProperty[string] importedAccountAddress:
read = getImportedAccountAddress
notify = importedAccountChanged
proc getImportedAccountPubKey*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().derivedAccounts.whisper.publicKey
QtProperty[string] importedAccountPubKey:
read = getImportedAccountPubKey
notify = importedAccountChanged
proc setDisplayName*(self: View, displayName: string) {.slot.} =
self.delegate.setDisplayName(displayName)
proc setSelectedAccountByIndex*(self: View, index: int) {.slot.} =
self.delegate.setSelectedAccountByIndex(index)
proc storeSelectedAccountAndLogin*(self: View, password: string) {.slot.} =
self.delegate.storeSelectedAccountAndLogin(password)
proc accountSetupError*(self: View, error: string) {.signal.}
proc setupAccountError*(self: View, error: string) =
self.accountSetupError(error)
proc validateMnemonic*(self: View, mnemonic: string): string {.slot.} =
return self.delegate.validateMnemonic(mnemonic)
proc importMnemonic*(self: View, mnemonic: string) {.slot.} =
self.delegate.importMnemonic(mnemonic)
proc accountImportError*(self: View, error: string) {.signal.}
proc importAccountError*(self: View, error: string) =
# In QML we can connect to this signal and notify a user
# before refactoring we didn't have this signal
self.accountImportError(error)
proc accountImportSuccess*(self: View) {.signal.}
proc importAccountSuccess*(self: View) =
self.importedAccountChanged()
self.accountImportSuccess()
proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName)
proc generateImage*(self: View, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
self.delegate.generateImage(imageUrl, aX, aY, bX, bY)

View File

@ -1,54 +1,54 @@
import NimQml
import item
import models/login_account_item
QtObject:
type SelectedAccount* = ref object of QObject
type SelectedLoginAccount* = ref object of QObject
item: Item
proc setup(self: SelectedAccount) =
proc setup(self: SelectedLoginAccount) =
self.QObject.setup
proc delete*(self: SelectedAccount) =
proc delete*(self: SelectedLoginAccount) =
self.QObject.delete
proc newSelectedAccount*(): SelectedAccount =
proc newSelectedLoginAccount*(): SelectedLoginAccount =
new(result, delete)
result.setup
proc setSelectedAccountData*(self: SelectedAccount, item: Item) =
proc setData*(self: SelectedLoginAccount, item: Item) =
self.item = item
proc getName(self: SelectedAccount): string {.slot.} =
proc getName(self: SelectedLoginAccount): string {.slot.} =
return self.item.getName()
QtProperty[string] username:
read = getName
proc getKeyUid(self: SelectedAccount): string {.slot.} =
proc getKeyUid(self: SelectedLoginAccount): string {.slot.} =
return self.item.getKeyUid()
QtProperty[string] keyUid:
read = getKeyUid
proc getColorHash(self: SelectedAccount): QVariant {.slot.} =
proc getColorHash(self: SelectedLoginAccount): QVariant {.slot.} =
return self.item.getColorHashVariant()
QtProperty[QVariant] colorHash:
read = getColorHash
proc getColorId(self: SelectedAccount): int {.slot.} =
proc getColorId(self: SelectedLoginAccount): int {.slot.} =
return self.item.getColorId()
QtProperty[int] colorId:
read = getColorId
proc getThumbnailImage(self: SelectedAccount): string {.slot.} =
proc getThumbnailImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getThumbnailImage()
QtProperty[string] thumbnailImage:
read = getThumbnailImage
proc getLargeImage(self: SelectedAccount): string {.slot.} =
proc getLargeImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getLargeImage()
QtProperty[string] largeImage:

View File

@ -1,49 +1,203 @@
import NimQml
import io_interface
import selected_login_account
import internal/[state, state_wrapper]
import models/generated_account_model as gen_acc_model
import models/generated_account_item as gen_acc_item
import models/login_account_model as login_acc_model
import models/login_account_item as login_acc_item
type
AppState* {.pure.} = enum
OnboardingState = 0
LoginState
StartupState = 0
MainAppState
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
showBeforeGetStartedPopup: bool
currentStartupState: StateWrapper
currentStartupStateVariant: QVariant
generatedAccountsModel: gen_acc_model.Model
generatedAccountsModelVariant: QVariant
selectedLoginAccount: SelectedLoginAccount
selectedLoginAccountVariant: QVariant
loginAccountsModel: login_acc_model.Model
loginAccountsModelVariant: QVariant
appState: AppState
proc delete*(self: View) =
self.currentStartupStateVariant.delete
self.currentStartupState.delete
self.generatedAccountsModel.delete
self.generatedAccountsModelVariant.delete
self.selectedLoginAccount.delete
self.selectedLoginAccountVariant.delete
self.loginAccountsModel.delete
self.loginAccountsModelVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.appState = AppState.OnboardingState
result.showBeforeGetStartedPopup = true
result.appState = AppState.StartupState
result.currentStartupState = newStateWrapper()
result.currentStartupStateVariant = newQVariant(result.currentStartupState)
result.generatedAccountsModel = gen_acc_model.newModel()
result.generatedAccountsModelVariant = newQVariant(result.generatedAccountsModel)
result.selectedLoginAccount = newSelectedLoginAccount()
result.selectedLoginAccountVariant = newQVariant(result.selectedLoginAccount)
result.loginAccountsModel = login_acc_model.newModel()
result.loginAccountsModelVariant = newQVariant(result.loginAccountsModel)
proc load*(self: View) =
# In some point, here, we will setup some exposed main module related things.
self.delegate.viewDidLoad()
signalConnect(result.currentStartupState, "backActionClicked()", result, "onBackActionClicked()", 2)
signalConnect(result.currentStartupState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)
signalConnect(result.currentStartupState, "secondaryActionClicked()", result, "onSecondaryActionClicked()", 2)
signalConnect(result.currentStartupState, "tertiaryActionClicked()", result, "onTertiaryActionClicked()", 2)
proc currentStartupStateObj*(self: View): State =
return self.currentStartupState.getStateObj()
proc setCurrentStartupState*(self: View, state: State) =
self.currentStartupState.setStateObj(state)
proc getCurrentStartupState(self: View): QVariant {.slot.} =
return self.currentStartupStateVariant
QtProperty[QVariant] currentStartupState:
read = getCurrentStartupState
proc onBackActionClicked*(self: View) {.slot.} =
self.delegate.onBackActionClicked()
proc onPrimaryActionClicked*(self: View) {.slot.} =
self.delegate.onPrimaryActionClicked()
proc onSecondaryActionClicked*(self: View) {.slot.} =
self.delegate.onSecondaryActionClicked()
proc onTertiaryActionClicked*(self: View) {.slot.} =
self.delegate.onTertiaryActionClicked()
proc startUpUIRaised*(self: View) {.signal.}
proc appStateChanged*(self: View, state: int) {.signal.}
proc showBeforeGetStartedPopup*(self: View): bool {.slot.} =
return self.showBeforeGetStartedPopup
proc beforeGetStartedPopupAccepted*(self: View) {.slot.} =
self.showBeforeGetStartedPopup = false
proc appStateChanged*(self: View, state: int) {.signal.}
proc getAppState(self: View): int {.slot.} =
return self.appState.int
proc setAppState*(self: View, state: AppState) =
if(self.appState == state):
return
self.appState = state
self.appStateChanged(self.appState.int)
QtProperty[int] appState:
read = getAppState
notify = appStateChanged
proc logOut*(self: View) {.signal.}
proc emitLogOut*(self: View) =
self.logOut()
proc generatedAccountsModelChanged*(self: View) {.signal.}
proc getGeneratedAccountsModel(self: View): QVariant {.slot.} =
return self.generatedAccountsModelVariant
proc setGeneratedAccountList*(self: View, accounts: seq[gen_acc_item.Item]) =
self.generatedAccountsModel.setItems(accounts)
self.generatedAccountsModelChanged()
QtProperty[QVariant] generatedAccountsModel:
read = getGeneratedAccountsModel
notify = generatedAccountsModelChanged
proc importedAccountChanged*(self: View) {.signal.}
proc getImportedAccountAlias*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().alias
QtProperty[string] importedAccountAlias:
read = getImportedAccountAlias
notify = importedAccountChanged
proc getImportedAccountAddress*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().address
QtProperty[string] importedAccountAddress:
read = getImportedAccountAddress
notify = importedAccountChanged
proc getImportedAccountPubKey*(self: View): string {.slot.} =
return self.delegate.getImportedAccount().derivedAccounts.whisper.publicKey
QtProperty[string] importedAccountPubKey:
read = getImportedAccountPubKey
notify = importedAccountChanged
proc generateImage*(self: View, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
self.delegate.generateImage(imageUrl, aX, aY, bX, bY)
proc setDisplayName*(self: View, value: string) {.slot.} =
self.delegate.setDisplayName(value)
proc getDisplayName*(self: View): string {.slot.} =
return self.delegate.getDisplayName()
proc setPassword*(self: View, value: string) {.slot.} =
self.delegate.setPassword(value)
proc getPassword*(self: View): string {.slot.} =
return self.delegate.getPassword()
proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName)
proc accountSetupError*(self: View, error: string) {.signal.}
proc setupAccountError*(self: View, error: string) =
self.accountSetupError(error)
proc validMnemonic*(self: View, mnemonic: string): bool {.slot.} =
return self.delegate.validMnemonic(mnemonic)
proc accountImportError*(self: View, error: string) {.signal.}
proc importAccountError*(self: View, error: string) =
# In QML we can connect to this signal and notify user, before refactoring we didn't have this signal
self.accountImportError(error)
proc accountImportSuccess*(self: View) {.signal.}
proc importAccountSuccess*(self: View) =
self.importedAccountChanged()
self.accountImportSuccess()
proc selectedLoginAccountChanged*(self: View) {.signal.}
proc getSelectedLoginAccount(self: View): QVariant {.slot.} =
return self.selectedLoginAccountVariant
proc setSelectedLoginAccount*(self: View, item: login_acc_item.Item) =
self.selectedLoginAccount.setData(item)
self.selectedLoginAccountChanged()
proc setSelectedLoginAccountByIndex*(self: View, index: int) {.slot.} =
let item = self.loginAccountsModel.getItemAtIndex(index)
self.delegate.setSelectedLoginAccount(item)
QtProperty[QVariant] selectedLoginAccount:
read = getSelectedLoginAccount
notify = selectedLoginAccountChanged
proc loginAccountsModelChanged*(self: View) {.signal.}
proc getLoginAccountsModel(self: View): QVariant {.slot.} =
return self.loginAccountsModelVariant
proc setLoginAccountsModelItems*(self: View, accounts: seq[login_acc_item.Item]) =
self.loginAccountsModel.setItems(accounts)
self.loginAccountsModelChanged()
QtProperty[QVariant] loginAccountsModel:
read = getLoginAccountsModel
notify = loginAccountsModelChanged
proc accountLoginError*(self: View, error: string) {.signal.}
proc emitAccountLoginError*(self: View, error: string) =
self.accountLoginError(error)
proc obtainingPasswordError*(self:View, errorDescription: string) {.signal.}
proc emitObtainingPasswordError*(self: View, errorDescription: string) =
self.obtainingPasswordError(errorDescription)
proc obtainingPasswordSuccess*(self:View, password: string) {.signal.}
proc emitObtainingPasswordSuccess*(self: View, password: string) =
self.obtainingPasswordSuccess(password)

View File

@ -44,7 +44,8 @@ proc toAccountDto*(jsonObj: JsonNode): AccountDto =
discard jsonObj.getProp("keycard-pairing", result.keycardPairing)
discard jsonObj.getProp("key-uid", result.keyUid)
discard jsonObj.getProp("colorId", result.colorId)
result.colorHash = toColorHashDto(jsonObj["colorHash"])
if jsonObj.hasKey("colorHash"):
result.colorHash = toColorHashDto(jsonObj["colorHash"])
var imagesObj: JsonNode
if(jsonObj.getProp("images", imagesObj) and imagesObj.kind == JArray):

View File

@ -1,9 +1,9 @@
import os, json, sequtils, strutils, uuids
import json_serialization, chronicles
import ../../../app/global/global_singleton
import ./dto/accounts as dto_accounts
import ./dto/generated_accounts as dto_generated_accounts
import ../../../backend/accounts as status_account
import ../../../backend/general as status_general
import ../../../backend/core as status_core
@ -21,6 +21,8 @@ logScope:
const PATHS = @[PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET]
const ACCOUNT_ALREADY_EXISTS_ERROR = "account already exists"
include utils
type
Service* = ref object of RootObj
fleetConfiguration: FleetConfiguration
@ -53,22 +55,6 @@ proc setKeyStoreDir(self: Service, key: string) =
self.keyStoreDir = joinPath(main_constants.ROOTKEYSTOREDIR, key) & main_constants.sep
discard status_general.initKeystore(self.keyStoreDir)
proc compressPk*(publicKey: string): string =
try:
let response = status_account.compressPk(publicKey)
if(not response.error.isNil):
error "error compressPk: ", errDescription = response.error.message
result = response.result
except Exception as e:
error "error: ", procName="compressPk", errName = e.name, errDesription = e.msg
proc generateAliasFromPk*(publicKey: string): string =
return status_account.generateAlias(publicKey).result.getStr
proc isAlias*(value: string): bool =
return status_account.isAlias(value)
proc init*(self: Service) =
try:
let response = status_account.generateAddresses(PATHS)
@ -269,6 +255,10 @@ proc getDefaultNodeConfig*(self: Service, installationId: string): JsonNode =
result["KeyStoreDir"] = newJString(self.keyStoreDir.replace(main_constants.STATUSGODIR, ""))
proc setLocalAccountSettingsFile(self: Service) =
if(defined(macosx) and self.getLoggedInAccount.isValid()):
singletonInstance.localAccountSettings.setFileName(self.getLoggedInAccount.name)
proc setupAccount*(self: Service, accountId, password, displayName: string): string =
try:
let installationId = $genUUID()
@ -292,6 +282,7 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): str
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson,
subaccountDataJson, settingsJson, nodeConfigJson)
self.setLocalAccountSettingsFile()
if self.getLoggedInAccount.isValid():
return ""
@ -377,6 +368,7 @@ proc login*(self: Service, account: AccountDto, password: string): string =
if error == "":
debug "Account logged in"
self.loggedInAccount = account
self.setLocalAccountSettingsFile()
return error

View File

@ -0,0 +1,18 @@
import json
import ../../../backend/accounts as status_account
proc compressPk*(publicKey: string): string =
try:
let response = status_account.compressPk(publicKey)
if(not response.error.isNil):
echo "error compressPk: " & response.error.message
result = response.result
except Exception as e:
echo "error: `compressPk` " & $e.name & " msg: " & $e.msg
proc generateAliasFromPk*(publicKey: string): string =
return status_account.generateAlias(publicKey).result.getStr
proc isAlias*(value: string): bool =
return status_account.isAlias(value)

View File

@ -8,6 +8,9 @@ logScope:
const ERROR_TYPE_AUTHENTICATION* = "authentication"
const ERROR_TYPE_KEYCHAIN* = "keychain"
const SIGNAL_KEYCHAIN_SERVICE_SUCCESS* = "keychainServiceSuccess"
const SIGNAL_KEYCHAIN_SERVICE_ERROR* = "keychainServiceError"
type
KeyChainServiceArg* = ref object of Args
data*: string
@ -53,9 +56,9 @@ QtObject:
let arg = KeyChainServiceArg(errCode: errorCode, errType: errorType,
errDescription: errorDescription)
self.events.emit("keychainServiceError", arg)
self.events.emit("", arg)
proc onKeychainManagerSuccess*(self: Service, data: string) {.slot.} =
## This slot is called in case a password is successfully retrieved from the
## Keychain. In this case @data contains required password.
self.events.emit("keychainServiceSuccess", KeyChainServiceArg(data: data))
self.events.emit(SIGNAL_KEYCHAIN_SERVICE_SUCCESS, KeyChainServiceArg(data: data))

View File

@ -1,4 +1,4 @@
import json, json_serialization, chronicles, nimcrypto, strutils
import json, json_serialization, chronicles, strutils
import ./core, ./utils
import ./response_type

View File

@ -1,307 +1,172 @@
import QtQuick 2.12
import QtQml.StateMachine 1.14 as DSM
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Dialogs 1.3
import utils 1.0
import "controls"
import "views"
import "stores"
QtObject {
OnboardingBasePage {
id: root
property bool hasAccounts
property string keysMainSetState: ""
property string prevState: ""
signal loadApp()
signal onBoardingStepChanged(var view, string state)
property var startupStore: StartupStore {}
property var stateMachine: DSM.StateMachine {
id: stateMachine
initialState: onboardingState
running: true
backButtonVisible: root.startupStore.currentStartupState.displayBackButton
DSM.State {
id: onboardingState
initialState: root.hasAccounts ? stateLogin : (Qt.platform.os === "osx" ? allowNotificationsState : welcomeMainState)
onBackClicked: {
root.startupStore.backAction()
}
DSM.State {
id: allowNotificationsState
onEntered: { onBoardingStepChanged(allowNotificationsMain, ""); }
function unload() {
loader.sourceComponent = undefined
}
DSM.SignalTransition {
targetState: welcomeMainState
signal: Global.applicationWindow.navigateTo
guard: path === "WelcomeMain"
}
Loader {
id: loader
anchors.fill: parent
sourceComponent: {
if (root.startupStore.currentStartupState.stateType === Constants.startupState.allowNotifications)
{
return allowNotificationsViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcome)
{
return welcomeViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeNewStatusUser ||
root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeOldStatusUser ||
root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileImportSeedPhrase)
{
return keysMainViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate ||
root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey)
{
return insertDetailsViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreatePassword)
{
return createPasswordViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileConfirmPassword)
{
return confirmPasswordViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.biometrics)
{
return touchIdAuthViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileEnterSeedPhrase)
{
return seedPhraseInputViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.login)
{
return loginViewComponent
}
DSM.State {
id: welcomeMainState
onEntered: { onBoardingStepChanged(welcomeMain, ""); }
DSM.SignalTransition {
targetState: keysMainState
signal: Global.applicationWindow.navigateTo
guard: path === "KeyMain"
}
}
DSM.State {
id: keysMainState
onEntered: { onBoardingStepChanged(keysMain, root.keysMainSetState); }
DSM.SignalTransition {
targetState: genKeyState
signal: Global.applicationWindow.navigateTo
guard: path === "GenKey"
}
DSM.SignalTransition {
targetState: importSeedState
signal: Global.applicationWindow.navigateTo
guard: path === "ImportSeed"
}
DSM.SignalTransition {
targetState: welcomeMainState
signal: Global.applicationWindow.navigateTo
guard: path === "Welcome"
}
DSM.SignalTransition {
targetState: stateLogin
signal: Global.applicationWindow.navigateTo
guard: path === "LogIn"
}
}
DSM.State {
id: genKeyState
onEntered: { onBoardingStepChanged(genKey, ""); }
DSM.SignalTransition {
targetState: welcomeMainState
signal: Global.applicationWindow.navigateTo
guard: path === "Welcome"
}
DSM.SignalTransition {
targetState: appState
signal: Global.applicationWindow.navigateTo
guard: path === "LoggedIn"
}
DSM.SignalTransition {
targetState: stateLogin
signal: Global.applicationWindow.navigateTo
guard: path === "LogIn"
}
DSM.SignalTransition {
targetState: importSeedState
signal: Global.applicationWindow.navigateTo
guard: path === "ImportSeed"
}
}
DSM.State {
id: importSeedState
property string seedInputState: "existingUser"
onEntered: { onBoardingStepChanged(seedPhrase, seedInputState); }
DSM.SignalTransition {
targetState: keysMainState
signal: Global.applicationWindow.navigateTo
guard: path === "KeyMain"
}
DSM.SignalTransition {
targetState: genKeyState
signal: Global.applicationWindow.navigateTo
guard: path === "GenKey"
}
}
DSM.State {
id: keycardState
onEntered: { onBoardingStepChanged(keycardFlowSelection, ""); }
DSM.SignalTransition {
targetState: appState
signal: startupModule.appStateChanged
guard: state === Constants.appState.main
}
}
DSM.State {
id: stateLogin
onEntered: { onBoardingStepChanged(login, ""); }
DSM.SignalTransition {
targetState: appState
signal: startupModule.appStateChanged
guard: state === Constants.appState.main
}
DSM.SignalTransition {
targetState: genKeyState
signal: Global.applicationWindow.navigateTo
guard: path === "GenKey"
}
}
DSM.SignalTransition {
targetState: root.hasAccounts ? stateLogin : keysMainState
signal: Global.applicationWindow.navigateTo
guard: path === "InitialState"
}
DSM.SignalTransition {
targetState: keysMainState
signal: Global.applicationWindow.navigateTo
guard: path === "KeysMain"
}
DSM.SignalTransition {
targetState: keycardState
signal: Global.applicationWindow.navigateTo
guard: path === "KeycardFlowSelection"
}
DSM.FinalState {
id: onboardingDoneState
}
}
DSM.State {
id: appState
onEntered: loadApp();
DSM.SignalTransition {
targetState: stateLogin
signal: startupModule.logOut
}
return undefined
}
}
property var allowNotificationsComponent: Component {
id: allowNotificationsMain
Connections {
target: root.startupStore.startupModuleInst
onAccountSetupError: {
if (error === Constants.existingAccountError) {
msgDialog.title = qsTr("Keys for this account already exist")
msgDialog.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase")
} else {
msgDialog.title = qsTr("Login failed")
msgDialog.text = qsTr("Login failed. Please re-enter your password and try again.")
}
msgDialog.open()
}
onAccountImportError: {
if (error === Constants.existingAccountError) {
msgDialog.title = qsTr("Keys for this account already exist")
msgDialog.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase")
} else {
msgDialog.title = qsTr("Error importing seed")
msgDialog.text = error
}
msgDialog.open()
}
}
MessageDialog {
id: msgDialog
title: qsTr("Login failed")
text: qsTr("Login failed. Please re-enter your password and try again.")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
onAccepted: {
console.log("TODO: restart flow...")
}
}
Component {
id: allowNotificationsViewComponent
AllowNotificationsView {
onBtnOkClicked: {
Global.applicationWindow.navigateTo("WelcomeMain");
}
startupStore: root.startupStore
}
}
property var welcomeComponent: Component {
id: welcomeMain
Component {
id: welcomeViewComponent
WelcomeView {
onBtnNewUserClicked: {
root.keysMainSetState = "getkeys";
Global.applicationWindow.navigateTo("KeyMain");
}
onBtnExistingUserClicked: {
root.keysMainSetState = "connectkeys";
Global.applicationWindow.navigateTo("KeyMain");
}
startupStore: root.startupStore
}
}
property var keysMainComponent: Component {
id: keysMain
Component {
id: keysMainViewComponent
KeysMainView {
onButtonClicked: {
if (state === "importseed") {
importSeedState.seedInputState = "existingUser";
Global.applicationWindow.navigateTo("ImportSeed");
} else {
importSeedState.seedInputState = "newUser";
Global.applicationWindow.navigateTo("GenKey");
}
}
onKeycardLinkClicked: {
Global.applicationWindow.navigateTo("KeycardFlowSelection");
}
onSeedLinkClicked: {
if (state === "getkeys") {
importSeedState.seedInputState = "newUser";
state = "importseed";
} else {
importSeedState.seedInputState = "existingUser";
Global.applicationWindow.navigateTo("ImportSeed");
}
}
onBackClicked: {
if (state === "importseed") {
state = "getkeys";
} else if ((root.keysMainSetState === "connectkeys" && LoginStore.currentAccount.username !== "") || root.prevState === "LogIn") {
Global.applicationWindow.navigateTo("LogIn");
} else {
Global.applicationWindow.navigateTo("Welcome");
}
}
startupStore: root.startupStore
}
}
property var seedPhraseInputComponent: Component {
id: seedPhrase
Component {
id: insertDetailsViewComponent
InsertDetailsView {
startupStore: root.startupStore
}
}
Component {
id: createPasswordViewComponent
CreatePasswordView {
startupStore: root.startupStore
}
}
Component {
id: confirmPasswordViewComponent
ConfirmPasswordView {
startupStore: root.startupStore
}
}
Component {
id: touchIdAuthViewComponent
TouchIDAuthView {
startupStore: root.startupStore
}
}
Component {
id: seedPhraseInputViewComponent
SeedPhraseInputView {
onExit: {
if (root.keysMainSetState !== "connectkeys") {
root.keysMainSetState = "importseed";
}
Global.applicationWindow.navigateTo("KeyMain");
}
onSeedValidated: {
root.keysMainSetState = "importseed";
Global.applicationWindow.navigateTo("GenKey");
}
startupStore: root.startupStore
}
}
property var genKeyComponent: Component {
id: genKey
GenKeyView {
onExit: {
if (root.keysMainSetState === "importseed") {
root.keysMainSetState = "connectkeys"
Global.applicationWindow.navigateTo("ImportSeed");
} else if (LoginStore.currentAccount.username !== "" && importSeedState.seedInputState === "existingUser") {
Global.applicationWindow.navigateTo("LogIn");
} else {
Global.applicationWindow.navigateTo("KeysMain");
}
}
onKeysGenerated: {
Global.applicationWindow.navigateTo("LoggedIn")
}
}
}
property var keycardFlowSelectionComponent: Component {
id: keycardFlowSelection
KeycardFlowSelectionView {
onClosed: {
if (root.hasAccounts) {
Global.applicationWindow.navigateTo("InitialState")
} else {
Global.applicationWindow.navigateTo("KeysMain")
}
}
}
}
property var loginComponent: Component {
id: login
Component {
id: loginViewComponent
LoginView {
onAddNewUserClicked: {
root.keysMainSetState = "getkeys";
root.prevState = "LogIn"
Global.applicationWindow.navigateTo("KeysMain");
}
onAddExistingKeyClicked: {
root.keysMainSetState = "connectkeys";
root.prevState = "LogIn"
Global.applicationWindow.navigateTo("KeysMain");
}
startupStore: root.startupStore
}
}
}

View File

@ -10,7 +10,6 @@ Page {
property alias backButtonVisible: backButton.visible
signal exit()
signal backClicked()
background: Rectangle {

View File

@ -12,23 +12,27 @@ import "../stores"
// TODO: replace with StatusModal
ModalPopup {
id: root
property StartupStore startupStore
signal accountSelected(int index)
signal openModalClicked()
id: popup
title: qsTr("Your keys")
AccountListPanel {
id: accountList
anchors.fill: parent
model: LoginStore.loginModuleInst.accountsModel
model: root.startupStore.startupModuleInst.loginAccountsModel
isSelected: function (index, keyUid) {
return LoginStore.currentAccount.keyUid === keyUid
return root.startupStore.selectedLoginAccount.keyUid === keyUid
}
onAccountSelect: function(index) {
popup.accountSelected(index)
popup.close()
root.accountSelected(index)
root.close()
}
}
@ -40,7 +44,7 @@ ModalPopup {
onClicked : {
openModalClicked()
popup.close()
root.close()
}
}
}

View File

@ -1,86 +0,0 @@
import QtQuick 2.13
import QtQuick.Dialogs 1.3
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import "../stores"
StatusModal {
id: popup
height: 510
header.title: qsTr("Upload profile picture")
property string currentProfileImg: ""
property string croppedImg: ""
signal setProfileImage(string image)
// Internals
//
onOpened: imageEditor.userSelectedImage = false
onClosed: popup.croppedImg = ""
contentItem: Item {
anchors.fill: parent
EditCroppedImagePanel {
id: imageEditor
width: 160
height: 160
anchors.centerIn: parent
imageFileDialogTitle: qsTr("Choose an image for profile picture")
title: qsTr("Profile picture")
acceptButtonText: qsTr("Make this my profile picture")
aspectRatio: 1
dataImage: popup.currentProfileImg
NoImageUploadedPanel {
anchors.centerIn: parent
visible: imageEditor.nothingToShow
}
}
}
rightButtons: [
StatusFlatButton {
visible: !!popup.currentProfileImg
type: StatusBaseButton.Type.Danger
text: qsTr("Remove")
onClicked: {
OnboardingStore.clearImageProps()
popup.setProfileImage("")
close();
}
},
StatusButton {
id: uploadBtn
text: imageEditor.userSelectedImage ? qsTr("Upload") : qsTr("Done")
onClicked: {
if (imageEditor.userSelectedImage) {
popup.croppedImg = OnboardingStore.generateImage(imageEditor.source,
imageEditor.cropRect.x.toFixed(),
imageEditor.cropRect.y.toFixed(),
(imageEditor.cropRect.x + imageEditor.cropRect.width).toFixed(),
(imageEditor.cropRect.y + imageEditor.cropRect.height).toFixed())
popup.setProfileImage(popup.croppedImg)
}
close();
}
}
]
}

View File

@ -1,20 +0,0 @@
pragma Singleton
import QtQuick 2.13
QtObject {
// Not Refactored Yet
// property var keycardModelInst: keycardModel
function startConnection() {
// keycardModel.startConnection()
}
function init(pin) {
// keycardModel.init(pin)
}
function recoverAccount() {
// keycardModel.recoverAccount()
}
}

View File

@ -1,20 +0,0 @@
pragma Singleton
import QtQuick 2.13
QtObject {
property var loginModuleInst: loginModule
property var currentAccount: loginModuleInst.selectedAccount
function login(password) {
loginModuleInst.login(password)
}
function setCurrentAccount(index) {
loginModuleInst.setSelectedAccountByIndex(index)
}
function rowCount() {
return loginModuleInst.accountsModel.rowCount()
}
}

View File

@ -1,102 +0,0 @@
pragma Singleton
import QtQuick 2.13
import utils 1.0
QtObject {
id: root
property var profileSectionModuleInst: profileSectionModule
property var profileModule: profileSectionModuleInst.profileModule
property var onboardingModuleInst: onboardingModule
property var mainModuleInst: !!mainModule ? mainModule : undefined
property var accountSettings: localAccountSettings
property var privacyModule: profileSectionModuleInst.privacyModule
property string displayName: userProfile !== undefined ? userProfile.displayName : ""
property url profImgUrl: ""
property real profImgAX: 0.0
property real profImgAY: 0.0
property real profImgBX: 0.0
property real profImgBY: 0.0
property bool accountCreated: false
property bool showBeforeGetStartedPopup: true
function generateImage(source, aX, aY, bX, bY) {
profImgUrl = source
profImgAX = aX
profImgAY = aY
profImgBX = bX
profImgBY = bY
return onboardingModuleInst.generateImage(source, aX, aY, bX, bY)
}
function importMnemonic(mnemonic) {
onboardingModuleInst.importMnemonic(mnemonic)
}
function setCurrentAccountAndDisplayName(displayName) {
onboardingModuleInst.setDisplayName(displayName);
if (!onboardingModuleInst.importedAccountPubKey) {
onboardingModuleInst.setSelectedAccountByIndex(0);
}
}
function updatedDisplayName(displayName) {
if (displayName !== root.displayName) {
print(displayName, root.displayName)
root.profileModule.setDisplayName(displayName);
}
}
function saveImage() {
root.profileModule.upload(root.profImgUrl, root.profImgAX, root.profImgAY, root.profImgBX, root.profImgBY);
}
function setImageProps(source, aX, aY, bX, bY) {
root.profImgUrl = source;
root.profImgAX = aX;
root.profImgAY = aY;
root.profImgBX = bX;
root.profImgBY = bY;
}
function clearImageProps() {
root.profImgUrl = "";
root.profImgAX = 0.0;
root.profImgAY = 0.0;
root.profImgBX = 0.0;
root.profImgBY = 0.0;
}
function removeImage() {
return root.profileModule.remove();
}
function finishCreatingAccount(pass) {
root.onboardingModuleInst.storeSelectedAccountAndLogin(pass);
}
function storeToKeyChain(pass) {
mainModule.storePassword(pass);
}
function changePassword(password, newPassword) {
root.privacyModule.changePassword(password, newPassword);
}
function validateMnemonic(text) {
return root.onboardingModuleInst.validateMnemonic(text);
}
property ListModel accountsSampleData: ListModel {
ListElement {
username: "Ferocious Herringbone Sinewave2"
address: "0x123456789009876543211234567890"
}
ListElement {
username: "Another Account"
address: "0x123456789009876543211234567890"
}
}
}

View File

@ -0,0 +1,66 @@
import QtQuick 2.14
QtObject {
id: root
property var startupModuleInst: startupModule
property var currentStartupState: startupModuleInst.currentStartupState
property var selectedLoginAccount: startupModuleInst.selectedLoginAccount
function backAction() {
root.currentStartupState.backAction()
}
function doPrimaryAction() {
root.currentStartupState.doPrimaryAction()
}
function doSecondaryAction() {
root.currentStartupState.doSecondaryAction()
}
function doTertiaryAction() {
root.currentStartupState.doTertiaryAction()
}
function showBeforeGetStartedPopup() {
return root.startupModuleInst.showBeforeGetStartedPopup()
}
function beforeGetStartedPopupAccepted() {
root.startupModuleInst.beforeGetStartedPopupAccepted()
}
function generateImage(source, aX, aY, bX, bY) {
return root.startupModuleInst.generateImage(source, aX, aY, bX, bY)
}
function setDisplayName(value) {
root.startupModuleInst.setDisplayName(value)
}
function getDisplayName() {
return root.startupModuleInst.getDisplayName()
}
function setPassword(value) {
root.startupModuleInst.setPassword(value)
}
function getPassword() {
return root.startupModuleInst.getPassword()
}
function getPasswordStrengthScore(password) {
let userName = root.startupModuleInst.importedAccountAlias
return root.startupModuleInst.getPasswordStrengthScore(password, userName)
}
function validMnemonic(mnemonic) {
return root.startupModuleInst.validMnemonic(mnemonic)
}
function setSelectedLoginAccountByIndex(index) {
root.startupModuleInst.setSelectedLoginAccountByIndex(index)
}
}

View File

@ -1,3 +0,0 @@
singleton OnboardingStore 1.0 OnboardingStore.qml
singleton LoginStore 1.0 LoginStore.qml
singleton KeycardStore 1.0 KeycardStore.qml

View File

@ -8,14 +8,12 @@ import shared.panels 1.0
import utils 1.0
import "../controls"
import "../stores"
OnboardingBasePage {
id: page
Item {
id: root
signal btnOkClicked()
backButtonVisible: false
property StartupStore startupStore
QtObject {
id: d
@ -61,7 +59,7 @@ OnboardingBasePage {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Ok, got it")
onClicked: {
page.btnOkClicked();
root.startupStore.doPrimaryAction()
}
}
}

View File

@ -1,11 +1,11 @@
import QtQuick 2.0
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import QtQuick.Dialogs 1.3
import shared.controls 1.0
import shared 1.0
import shared.panels 1.0
import shared.stores 1.0
import utils 1.0
import StatusQ.Controls 0.1
@ -15,12 +15,17 @@ import StatusQ.Core.Theme 0.1
import "../stores"
import "../controls"
OnboardingBasePage {
Item {
id: root
property StartupStore startupStore
property string password
property string tmpPass
property string displayName
Component.onCompleted: {
root.password = root.startupStore.getPassword()
}
function forcePswInputFocus() { confPswInput.forceActiveFocus(Qt.MouseFocusReason)}
QtObject {
@ -39,22 +44,7 @@ OnboardingBasePage {
return
}
if (OnboardingStore.accountCreated) {
if (root.password !== root.tmpPass) {
OnboardingStore.changePassword(root.tmpPass, root.password)
root.tmpPass = root.password
}
else {
submitBtn.loading = false
root.exit();
}
}
else {
root.tmpPass = root.password
submitBtn.loading = true
OnboardingStore.setCurrentAccountAndDisplayName(root.displayName)
pause.start()
}
root.startupStore.doPrimaryAction()
}
}
@ -158,72 +148,12 @@ OnboardingBasePage {
text: qsTr("Finalise Status Password Creation")
enabled: !submitBtn.loading && (confPswInput.text === root.password)
property Timer sim: Timer {
id: pause
interval: 20
onTriggered: {
// Create account operation blocks the UI so loading = true; will never have any affect until it is done.
// Getting around it with a small pause (timer) in order to get the desired behavior
OnboardingStore.finishCreatingAccount(root.password)
}
}
onClicked: { d.submit() }
Connections {
target: onboardingModule
onAccountSetupError: {
if (error === Constants.existingAccountError) {
importLoginError.title = qsTr("Keys for this account already exist")
importLoginError.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase")
} else {
importLoginError.title = qsTr("Login failed")
importLoginError.text = qsTr("Login failed. Please re-enter your password and try again.")
}
importLoginError.open()
}
}
MessageDialog {
id: importLoginError
title: qsTr("Login failed")
text: qsTr("Login failed. Please re-enter your password and try again.")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
onVisibilityChanged: {
submitBtn.loading = false
}
}
}
}
// Back button:
StatusRoundButton {
enabled: !submitBtn.loading
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
icon.name: "arrow-left"
onClicked: { root.backClicked() }
}
Connections {
target: startupModule
onAppStateChanged: {
if (state === Constants.appState.main) {
if (!!OnboardingStore.profImgUrl) {
OnboardingStore.saveImage();
OnboardingStore.accountCreated = true;
}
submitBtn.loading = false
root.exit()
}
}
}
Connections {
target: OnboardingStore.privacyModule
target: RootStore.privacyModule
onPasswordChanged: {
if (success) {
submitBtn.loading = false

View File

@ -8,12 +8,18 @@ import shared.views 1.0
import "../../Profile/views"
import "../controls"
import "../stores"
OnboardingBasePage {
Item {
id: root
property string newPassword
property string confirmationPassword
property StartupStore startupStore
Component.onCompleted: {
view.newPswText = root.startupStore.getPassword()
view.confirmationPswText = root.startupStore.getPassword()
}
function forceNewPswInputFocus() { view.forceNewPswInputFocus() }
QtObject {
@ -22,9 +28,8 @@ OnboardingBasePage {
readonly property int zFront: 100
function submit() {
root.newPassword = view.newPswText
root.confirmationPassword = view.confirmationPswText
root.exit()
root.startupStore.setPassword(view.newPswText)
root.startupStore.doPrimaryAction()
}
}
@ -34,9 +39,7 @@ OnboardingBasePage {
z: view.zFront
PasswordView {
id: view
onboarding: true
newPswText: root.newPassword
confirmationPswText: root.confirmationPassword
passwordStrengthScoreFunction: root.startupStore.getPasswordStrengthScore
onReturnPressed: { if(view.ready) d.submit() }
}
StatusButton {
@ -48,21 +51,4 @@ OnboardingBasePage {
onClicked: { d.submit() }
}
}
// Back button:
StatusRoundButton {
z: d.zFront // Focusable / clickable component
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
icon.name: "arrow-left"
onClicked: { root.backClicked() }
}
// By clicking anywhere outside password entries fields or focusable element in the view, it is needed to check if passwords entered matches
MouseArea {
anchors.fill: parent
z: d.zBehind // Behind focusable components
onClicked: { view.checkPasswordMatches() }
}
}

View File

@ -1,101 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import shared.panels 1.0
import utils 1.0
import "../controls"
import "../panels"
import "../stores"
OnboardingBasePage {
id: root
anchors.fill: parent
Behavior on opacity { NumberAnimation { duration: 200 }}
state: "username"
signal keysGenerated()
function gotoKeysStack(stackIndex) { createKeysStack.currentIndex = stackIndex }
enum KeysStack {
DETAILS,
CREATE_PWD,
CONFRIM_PWD,
TOUCH_ID
}
QtObject {
id: d
property string newPassword
property string confirmationPassword
}
StackLayout {
id: createKeysStack
anchors.fill: parent
currentIndex: GenKeyView.KeysStack.DETAILS
onCurrentIndexChanged: {
// Set focus:
if(currentIndex === GenKeyView.KeysStack.CREATE_PWD)
createPswView.forceNewPswInputFocus()
else if(currentIndex === GenKeyView.KeysStack.CONFRIM_PWD)
confirmPswView.forcePswInputFocus()
}
InsertDetailsView {
id: userDetailsPanel
onCreatePassword: { gotoKeysStack(GenKeyView.KeysStack.CREATE_PWD) }
}
CreatePasswordView {
id: createPswView
newPassword: d.newPassword
confirmationPassword: d.confirmationPassword
onExit: {
d.newPassword = newPassword
d.confirmationPassword = confirmationPassword
gotoKeysStack(GenKeyView.KeysStack.CONFRIM_PWD)
}
onBackClicked: {
d.newPassword = ""
d.confirmationPassword = ""
gotoKeysStack(GenKeyView.KeysStack.DETAILS)
}
}
ConfirmPasswordView {
id: confirmPswView
password: d.newPassword
displayName: userDetailsPanel.displayName
onExit: {
if (Qt.platform.os == "osx") {
gotoKeysStack(GenKeyView.KeysStack.TOUCH_ID);
} else {
root.keysGenerated();
}
}
onBackClicked: { gotoKeysStack(GenKeyView.KeysStack.CREATE_PWD) }
}
TouchIDAuthView {
userPass: d.newPassword
onGenKeysDone: { root.keysGenerated() }
}
}
onBackClicked: {
if (userDetailsPanel.state === "chatkey") {
userDetailsPanel.state = "username";
} else {
root.exit();
}
}
}

View File

@ -22,25 +22,26 @@ import "../shared"
Item {
id: root
property StartupStore startupStore
property string pubKey
property string address
property string displayName
signal createPassword()
state: "username"
Component.onCompleted: {
if (!!OnboardingStore.onboardingModuleInst.importedAccountPubKey) {
root.address = OnboardingStore.onboardingModuleInst.importedAccountAddress ;
root.pubKey = OnboardingStore.onboardingModuleInst.importedAccountPubKey;
if (!!root.startupStore.startupModuleInst.importedAccountPubKey) {
root.address = root.startupStore.startupModuleInst.importedAccountAddress ;
root.pubKey = root.startupStore.startupModuleInst.importedAccountPubKey;
}
nameInput.text = root.startupStore.getDisplayName();
nameInput.input.edit.forceActiveFocus();
}
Loader {
active: !OnboardingStore.onboardingModuleInst.importedAccountPubKey
active: !root.startupStore.startupModuleInst.importedAccountPubKey
sourceComponent: StatusListView {
model: OnboardingStore.onboardingModuleInst.accountsModel
model: root.startupStore.startupModuleInst.generatedAccountsModel
delegate: Item {
Component.onCompleted: {
if (index === 0) {
@ -66,7 +67,7 @@ Item {
StyledText {
id: txtDesc
Layout.preferredWidth: (root.state === "username") ? 338 : 643
Layout.preferredWidth: root.state === Constants.startupState.userProfileCreate? 338 : 643
Layout.preferredHeight: 44
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
Layout.topMargin: Style.current.padding
@ -211,16 +212,11 @@ Item {
enabled: !!nameInput.text && nameInput.valid
text: qsTr("Next")
onClicked: {
if (root.state === "username") {
if (OnboardingStore.accountCreated) {
OnboardingStore.updatedDisplayName(nameInput.text);
}
OnboardingStore.displayName = nameInput.text;
if (root.state === Constants.startupState.userProfileCreate) {
root.startupStore.setDisplayName(nameInput.text)
root.displayName = nameInput.text;
root.state = "chatkey";
} else {
createPassword();
}
root.startupStore.doPrimaryAction()
}
}
@ -230,7 +226,7 @@ Item {
title: qsTr("Profile picture")
acceptButtonText: qsTr("Make this my profile picture")
onImageCropped: {
const croppedImg = OnboardingStore.generateImage(image,
const croppedImg = root.startupStore.generateImage(image,
cropRect.x.toFixed(),
cropRect.y.toFixed(),
(cropRect.x + cropRect.width).toFixed(),
@ -242,7 +238,8 @@ Item {
states: [
State {
name: "username"
name: Constants.startupState.userProfileCreate
when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate
PropertyChanges {
target: usernameText
text: qsTr("Your profile")
@ -273,7 +270,8 @@ Item {
}
},
State {
name: "chatkey"
name: Constants.startupState.userProfileChatKey
when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey
PropertyChanges {
target: usernameText
text: qsTr("Your emojihash and identicon ring")

View File

@ -11,15 +11,14 @@ import shared 1.0
import shared.panels 1.0
import "../popups"
import "../controls"
import "../stores"
import utils 1.0
OnboardingBasePage {
Item {
id: root
signal buttonClicked()
signal keycardLinkClicked()
signal seedLinkClicked()
property StartupStore startupStore
Item {
id: container
@ -78,7 +77,7 @@ OnboardingBasePage {
enabled: (opacity > 0.1)
Layout.alignment: Qt.AlignHCenter
onClicked: {
root.buttonClicked();
root.startupStore.doPrimaryAction()
}
}
@ -97,7 +96,7 @@ OnboardingBasePage {
parent.font.underline = false
}
onClicked: {
root.keycardLinkClicked();
root.startupStore.doSecondaryAction()
}
}
}
@ -118,7 +117,7 @@ OnboardingBasePage {
parent.font.underline = false
}
onClicked: {
root.seedLinkClicked();
root.startupStore.doTertiaryAction()
}
}
}
@ -127,7 +126,8 @@ OnboardingBasePage {
states: [
State {
name: "connectkeys"
name: Constants.startupState.welcomeOldStatusUser
when: root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeOldStatusUser
PropertyChanges {
target: keysImg
width: 160
@ -157,7 +157,8 @@ OnboardingBasePage {
}
},
State {
name: "getkeys"
name: Constants.startupState.welcomeNewStatusUser
when: root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeNewStatusUser
PropertyChanges {
target: keysImg
width: 160
@ -187,7 +188,8 @@ OnboardingBasePage {
}
},
State {
name: "importseed"
name: Constants.startupState.userProfileImportSeedPhrase
when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileImportSeedPhrase
PropertyChanges {
target: keysImg
width: 257

View File

@ -23,20 +23,20 @@ import "../stores"
import utils 1.0
Item {
property bool loading: false
signal addNewUserClicked()
signal addExistingKeyClicked()
id: root
id: loginView
anchors.fill: parent
property StartupStore startupStore
property bool loading: false
function doLogin(password) {
if (loading || password.length === 0)
return
loading = true
LoginStore.login(password)
txtPassword.textField.clear()
root.startupStore.setPassword(password)
root.startupStore.doPrimaryAction()
}
function resetLogin() {
@ -57,7 +57,7 @@ Item {
Connections{
id: connection
target: LoginStore.loginModuleInst
target: root.startupStore.startupModuleInst
onObtainingPasswordError: {
enabled = false
@ -115,18 +115,19 @@ Item {
ConfirmAddExistingKeyModal {
id: confirmAddExstingKeyModal
onOpenModalClicked: {
addExistingKeyClicked()
root.startupStore.doTertiaryAction()
}
}
SelectAnotherAccountModal {
id: selectAnotherAccountModal
startupStore: root.startupStore
onAccountSelected: {
LoginStore.setCurrentAccount(index)
root.startupStore.setSelectedLoginAccountByIndex(index)
resetLogin()
}
onOpenModalClicked: {
addExistingKeyClicked()
root.startupStore.doTertiaryAction()
}
}
@ -140,16 +141,16 @@ Item {
UserImage {
id: userImage
image: LoginStore.currentAccount.thumbnailImage
name: LoginStore.currentAccount.username
colorId: LoginStore.currentAccount.colorId
colorHash: LoginStore.currentAccount.colorHash
image: root.startupStore.selectedLoginAccount.thumbnailImage
name: root.startupStore.selectedLoginAccount.username
colorId: root.startupStore.selectedLoginAccount.colorId
colorHash: root.startupStore.selectedLoginAccount.colorHash
anchors.left: parent.left
}
StatusBaseText {
id: usernameText
text: LoginStore.currentAccount.username
text: root.startupStore.selectedLoginAccount.username
font.pixelSize: 17
anchors.left: userImage.right
anchors.leftMargin: 16
@ -182,14 +183,14 @@ Item {
dim: false
Repeater {
id: accounts
model: LoginStore.loginModuleInst.accountsModel
model: root.startupStore.startupModuleInst.loginAccountsModel
delegate: AccountMenuItemPanel {
label: model.username
image: model.thumbnailImage
colorId: model.colorId
colorHash: model.colorHash
onClicked: {
LoginStore.setCurrentAccount(index)
root.startupStore.setSelectedLoginAccountByIndex(index)
resetLogin()
accountsPopup.close()
}
@ -200,7 +201,7 @@ Item {
label: qsTr("Add new user")
onClicked: {
accountsPopup.close()
addNewUserClicked();
root.startupStore.doSecondaryAction()
}
}
@ -209,7 +210,7 @@ Item {
iconSettings.name: "wallet"
onClicked: {
accountsPopup.close()
addExistingKeyClicked();
root.startupStore.doTertiaryAction()
}
}
}
@ -268,7 +269,7 @@ Item {
}
Connections {
target: LoginStore.loginModuleInst
target: root.startupStore.startupModuleInst
onAccountLoginError: {
if (error) {
// SQLITE_NOTADB: "file is not a database"

View File

@ -15,12 +15,11 @@ import shared.controls 1.0
import "../controls"
import "../stores"
OnboardingBasePage {
Item {
id: root
state: "existingUser"
property StartupStore startupStore
property bool existingUser: (root.state === "existingUser")
property var mnemonicInput: []
signal seedValidated()
@ -72,29 +71,6 @@ OnboardingBasePage {
return true
}
Connections {
target: OnboardingStore.onboardingModuleInst
onAccountImportError: {
if (error === Constants.existingAccountError) {
importSeedError.title = qsTr("Keys for this account already exist")
importSeedError.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase")
} else {
importSeedError.title = qsTr("Error importing seed")
importSeedError.text = error
}
importSeedError.open()
}
onAccountImportSuccess: {
root.seedValidated()
}
}
MessageDialog {
id: importSeedError
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
Item {
implicitWidth: 565
implicitHeight: parent.height
@ -299,7 +275,9 @@ OnboardingBasePage {
function checkMnemonicLength() {
submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex])
}
text: root.existingUser ? qsTr("Restore Status Profile") : qsTr("Import")
text: root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase?
qsTr("Import") :
qsTr("Restore Status Profile")
onClicked: {
let mnemonicString = "";
var sortTable = mnemonicInput.sort(function (a, b) {
@ -308,9 +286,9 @@ OnboardingBasePage {
for (var i = 0; i < mnemonicInput.length; i++) {
mnemonicString += sortTable[i].seed + ((i === (grid.count-1)) ? "" : " ");
}
if (Utils.isMnemonic(mnemonicString) && !OnboardingStore.validateMnemonic(mnemonicString)) {
OnboardingStore.importMnemonic(mnemonicString)
if (Utils.isMnemonic(mnemonicString) && root.startupStore.validMnemonic(mnemonicString)) {
root.mnemonicInput = [];
root.startupStore.doPrimaryAction()
} else {
invalidSeedTxt.visible = true;
enabled = false;
@ -318,9 +296,4 @@ OnboardingBasePage {
}
}
}
onBackClicked: {
root.mnemonicInput = [];
root.exit();
}
}

View File

@ -14,14 +14,10 @@ import "../controls"
import "../panels"
import "../stores"
OnboardingBasePage {
Item {
id: root
property string userPass
signal genKeysDone()
backButtonVisible: false
property StartupStore startupStore
Item {
id: container
@ -81,9 +77,8 @@ OnboardingBasePage {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Yes, use Touch ID")
onClicked: {
OnboardingStore.accountSettings.storeToKeychainValue = Constants.storeToKeychainValueStore;
dimBackground.active = true;
OnboardingStore.storeToKeyChain(userPass);
dimBackground.active = true
root.startupStore.doPrimaryAction()
}
}
StatusBaseText {
@ -103,8 +98,7 @@ OnboardingBasePage {
parent.font.underline = false
}
onClicked: {
OnboardingStore.accountSettings.storeToKeychainValue = Constants.storeToKeychainValueNever;
root.genKeysDone();
root.startupStore.doSecondaryAction()
}
}
}
@ -119,16 +113,4 @@ OnboardingBasePage {
color: Qt.rgba(0, 0, 0, 0.4)
}
}
Connections {
enabled: !!OnboardingStore.mainModuleInst
target: OnboardingStore.mainModuleInst
onStoringPasswordSuccess: {
dimBackground.active = false;
root.genKeysDone();
}
onStoringPasswordError: {
dimBackground.active = false;
}
}
}

View File

@ -11,18 +11,13 @@ import "../stores"
import utils 1.0
Page {
id: page
Item {
id: root
signal btnNewUserClicked()
signal btnExistingUserClicked()
background: Rectangle {
color: Style.current.background
}
property StartupStore startupStore
Component.onCompleted: {
if (OnboardingStore.showBeforeGetStartedPopup) {
if (root.startupStore.showBeforeGetStartedPopup()) {
beforeGetStartedModal.open();
}
}
@ -30,7 +25,7 @@ Page {
BeforeGetStartedModal {
id: beforeGetStartedModal
onClosed: {
OnboardingStore.showBeforeGetStartedPopup = false;
root.startupStore.beforeGetStartedPopupAccepted()
}
}
@ -81,7 +76,7 @@ Page {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("I am new to Status")
onClicked: {
page.btnNewUserClicked();
root.startupStore.doPrimaryAction()
}
}
@ -92,7 +87,7 @@ Page {
anchors.topMargin: Style.current.bigPadding
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
page.btnExistingUserClicked();
root.startupStore.doSecondaryAction()
}
}
}

View File

@ -8,6 +8,7 @@ import shared 1.0
import shared.views 1.0
import shared.panels 1.0
import shared.controls 1.0
import shared.stores 1.0
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
@ -69,6 +70,7 @@ StatusModal {
id: view
anchors.topMargin: Style.current.padding
anchors.centerIn: parent
passwordStrengthScoreFunction: RootStore.getPasswordStrengthScore
titleVisible: false
introText: qsTr("Change password used to unlock Status on this device & sign transactions.")
createNewPsw: false

View File

@ -12,7 +12,6 @@ QtObject {
property var profileSectionModuleInst: profileSectionModule
property var privacyModule: profileSectionModuleInst.privacyModule
property var onboardingModuleInst: onboardingModule
property var userProfileInst: !!userProfile ? userProfile : null
property var walletSectionInst: !!walletSection ? walletSection : null
property var appSettings: !!localAppSettings ? localAppSettings : null
@ -109,13 +108,8 @@ QtObject {
chatSectionChatContentInputArea.addToRecentsGif(id)
}
function getPasswordStrengthScore(password, onboarding = false) {
if (onboarding) {
let userName = root.onboardingModuleInst.importedAccountAlias;
return root.onboardingModuleInst.getPasswordStrengthScore(password, userName);
} else {
return root.privacyModule.getPasswordStrengthScore(password);
}
function getPasswordStrengthScore(password) {
return root.privacyModule.getPasswordStrengthScore(password);
}
function isFetchingHistory(address) {

View File

@ -22,7 +22,8 @@ Column {
property string introText: qsTr("Create a password to unlock Status on this device & sign transactions.")
property string recoverText: qsTr("You will not be able to recover this password if it is lost.")
property string strengthenText: qsTr("Minimum %1 characters. To strengthen your password consider including:").arg(minPswLen)
property bool onboarding: false
property var passwordStrengthScoreFunction: function () {}
readonly property int zBehind: 1
readonly property int zFront: 100
@ -221,7 +222,7 @@ Column {
d.containsSymbols = d.symbolsValidator(text)
// Update strength indicator:
strengthInditactor.strength = d.convertStrength(RootStore.getPasswordStrengthScore(newPswInput.text, root.onboarding))
strengthInditactor.strength = d.convertStrength(root.passwordStrengthScoreFunction(newPswInput.text))
if (textField.text.length === confirmPswInput.text.length) {
root.checkPasswordMatches(false)
}

View File

@ -5,10 +5,37 @@ import QtQuick 2.13
import StatusQ.Controls.Validators 0.1
QtObject {
readonly property QtObject appState: QtObject {
readonly property int onboarding: 0
readonly property int login: 1
readonly property int main: 2
readonly property int startup: 0
readonly property int main: 1
}
readonly property QtObject startupFlow: QtObject {
readonly property string general: "General"
readonly property string firstRunNewUserNewKeys: "FirstRunNewUserNewKeys"
readonly property string firstRunNewUserNewKeycardKeys: "FirstRunNewUserNewKeycardKeys"
readonly property string firstRunNewUserImportSeedPhrase: "FirstRunNewUserImportSeedPhrase"
readonly property string firstRunOldUserSyncCode: "FirstRunOldUserSyncCode"
readonly property string firstRunOldUserKeycardImport: "FirstRunOldUserKeycardImport"
readonly property string firstRunOldUserImportSeedPhrase: "FirstRunOldUserImportSeedPhrase"
readonly property string appLogin: "AppLogin"
}
readonly property QtObject startupState: QtObject {
readonly property string noState: "NoState"
readonly property string allowNotifications: "AllowNotifications"
readonly property string welcome: "Welcome"
readonly property string welcomeNewStatusUser: "WelcomeNewStatusUser"
readonly property string welcomeOldStatusUser: "WelcomeOldStatusUser"
readonly property string userProfileCreate: "UserProfileCreate"
readonly property string userProfileChatKey: "UserProfileChatKey"
readonly property string userProfileCreatePassword: "UserProfileCreatePassword"
readonly property string userProfileConfirmPassword: "UserProfileConfirmPassword"
readonly property string userProfileImportSeedPhrase: "UserProfileImportSeedPhrase"
readonly property string userProfileEnterSeedPhrase: "UserProfileEnterSeedPhrase"
readonly property string biometrics: "Biometrics"
readonly property string login: "Login"
}
readonly property QtObject appSection: QtObject {

View File

@ -18,7 +18,6 @@ import mainui 1.0
import AppLayouts.Onboarding 1.0
StatusWindow {
property bool hasAccounts: startupModule.appState !== Constants.appState.onboarding
property bool appIsReady: false
Universal.theme: Universal.System
@ -134,6 +133,9 @@ StatusWindow {
if(state === Constants.appState.main) {
// We set main module to the Global singleton once user is logged in and we move to the main app.
Global.mainModuleInst = mainModule
loader.sourceComponent = app
startupOnboarding.unload()
startupOnboarding.visible = false
if(localAccountSensitiveSettings.recentEmojis === "") {
localAccountSensitiveSettings.recentEmojis = [];
@ -282,17 +284,9 @@ StatusWindow {
}
OnboardingLayout {
hasAccounts: applicationWindow.hasAccounts
onLoadApp: {
loader.sourceComponent = app;
}
onOnBoardingStepChanged: {
loader.sourceComponent = view;
if (!!state) {
loader.item.state = state;
}
}
id: startupOnboarding
anchors.fill: parent
visible: !splashScreen.visible
}
NotificationWindow {