From f9244e2c9f144a778e8c00361dbbb367f7231b43 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 20 Jul 2022 14:34:44 +0200 Subject: [PATCH] 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 --- src/app/boot/app_controller.nim | 3 +- src/app/global/utils.nim | 8 +- src/app/modules/main/controller.nim | 8 +- src/app/modules/startup/controller.nim | 164 ++++++- .../startup/internal/biometrics_state.nim | 38 ++ .../modules/startup/internal/login_state.nim | 25 ++ .../startup/internal/notification_state.nim | 14 + src/app/modules/startup/internal/state.nim | 111 +++++ .../startup/internal/state_wrapper.nim | 62 +++ .../internal/user_profile_chat_key_state.nim | 15 + .../user_profile_confirm_password_state.nim | 34 ++ .../user_profile_create_password_state.nim | 19 + .../internal/user_profile_create_state.nim | 19 + .../user_profile_enter_seed_phrase_state.nim | 26 ++ .../user_profile_import_seed_phrase_state.nim | 15 + .../startup/internal/welcome_state.nim | 18 + .../internal/welcome_state_new_user.nim | 24 ++ .../internal/welcome_state_old_user.nim | 29 ++ src/app/modules/startup/io_interface.nim | 59 ++- src/app/modules/startup/login/controller.nim | 81 ---- .../modules/startup/login/io_interface.nim | 33 -- src/app/modules/startup/login/module.nim | 88 ---- src/app/modules/startup/login/view.nim | 79 ---- .../generated_account_item.nim} | 0 .../generated_account_model.nim} | 2 +- .../login_account_item.nim} | 0 .../login_account_model.nim} | 4 +- src/app/modules/startup/module.nim | 182 ++++++-- .../modules/startup/onboarding/controller.nim | 75 ---- .../startup/onboarding/io_interface.nim | 51 --- src/app/modules/startup/onboarding/module.nim | 93 ---- src/app/modules/startup/onboarding/view.nim | 100 ----- ...account.nim => selected_login_account.nim} | 24 +- src/app/modules/startup/view.nim | 176 +++++++- .../service/accounts/dto/accounts.nim | 3 +- src/app_service/service/accounts/service.nim | 26 +- src/app_service/service/accounts/utils.nim | 18 + src/app_service/service/keychain/service.nim | 7 +- src/backend/accounts.nim | 2 +- .../Onboarding/OnboardingLayout.qml | 407 ++++++------------ .../controls/OnboardingBasePage.qml | 1 - .../popups/SelectAnotherAccountModal.qml | 16 +- .../popups/UploadProfilePicModal.qml | 86 ---- .../Onboarding/stores/KeycardStore.qml | 20 - .../Onboarding/stores/LoginStore.qml | 20 - .../Onboarding/stores/OnboardingStore.qml | 102 ----- .../Onboarding/stores/StartupStore.qml | 66 +++ ui/app/AppLayouts/Onboarding/stores/qmldir | 3 - .../views/AllowNotificationsView.qml | 12 +- .../Onboarding/views/ConfirmPasswordView.qml | 92 +--- .../Onboarding/views/CreatePasswordView.qml | 38 +- .../Onboarding/views/GenKeyView.qml | 101 ----- .../Onboarding/views/InsertDetailsView.qml | 36 +- .../Onboarding/views/KeysMainView.qml | 22 +- .../AppLayouts/Onboarding/views/LoginView.qml | 41 +- .../Onboarding/views/SeedPhraseInputView.qml | 41 +- .../Onboarding/views/TouchIDAuthView.qml | 28 +- .../Onboarding/views/WelcomeView.qml | 19 +- .../Profile/popups/ChangePasswordModal.qml | 2 + ui/imports/shared/stores/RootStore.qml | 10 +- ui/imports/shared/views/PasswordView.qml | 5 +- ui/imports/utils/Constants.qml | 33 +- ui/main.qml | 18 +- 63 files changed, 1377 insertions(+), 1577 deletions(-) create mode 100644 src/app/modules/startup/internal/biometrics_state.nim create mode 100644 src/app/modules/startup/internal/login_state.nim create mode 100644 src/app/modules/startup/internal/notification_state.nim create mode 100644 src/app/modules/startup/internal/state.nim create mode 100644 src/app/modules/startup/internal/state_wrapper.nim create mode 100644 src/app/modules/startup/internal/user_profile_chat_key_state.nim create mode 100644 src/app/modules/startup/internal/user_profile_confirm_password_state.nim create mode 100644 src/app/modules/startup/internal/user_profile_create_password_state.nim create mode 100644 src/app/modules/startup/internal/user_profile_create_state.nim create mode 100644 src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim create mode 100644 src/app/modules/startup/internal/user_profile_import_seed_phrase_state.nim create mode 100644 src/app/modules/startup/internal/welcome_state.nim create mode 100644 src/app/modules/startup/internal/welcome_state_new_user.nim create mode 100644 src/app/modules/startup/internal/welcome_state_old_user.nim delete mode 100644 src/app/modules/startup/login/controller.nim delete mode 100644 src/app/modules/startup/login/io_interface.nim delete mode 100644 src/app/modules/startup/login/module.nim delete mode 100644 src/app/modules/startup/login/view.nim rename src/app/modules/startup/{onboarding/item.nim => models/generated_account_item.nim} (100%) rename src/app/modules/startup/{onboarding/model.nim => models/generated_account_model.nim} (98%) rename src/app/modules/startup/{login/item.nim => models/login_account_item.nim} (100%) rename src/app/modules/startup/{login/model.nim => models/login_account_model.nim} (96%) delete mode 100644 src/app/modules/startup/onboarding/controller.nim delete mode 100644 src/app/modules/startup/onboarding/io_interface.nim delete mode 100644 src/app/modules/startup/onboarding/module.nim delete mode 100644 src/app/modules/startup/onboarding/view.nim rename src/app/modules/startup/{login/selected_account.nim => selected_login_account.nim} (50%) create mode 100644 src/app_service/service/accounts/utils.nim delete mode 100644 ui/app/AppLayouts/Onboarding/popups/UploadProfilePicModal.qml delete mode 100644 ui/app/AppLayouts/Onboarding/stores/KeycardStore.qml delete mode 100644 ui/app/AppLayouts/Onboarding/stores/LoginStore.qml delete mode 100644 ui/app/AppLayouts/Onboarding/stores/OnboardingStore.qml create mode 100644 ui/app/AppLayouts/Onboarding/stores/StartupStore.qml delete mode 100644 ui/app/AppLayouts/Onboarding/stores/qmldir delete mode 100644 ui/app/AppLayouts/Onboarding/views/GenKeyView.qml diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index b9be6c9452..89fb1e4a63 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -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, diff --git a/src/app/global/utils.nim b/src/app/global/utils.nim index 3945f07e79..5d97129883 100644 --- a/src/app/global/utils.nim +++ b/src/app/global/utils.nim @@ -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) diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 8c1f7ca874..fb10524c49 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -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) diff --git a/src/app/modules/startup/controller.nim b/src/app/modules/startup/controller.nim index a83f44f658..4c3b9887a4 100644 --- a/src/app/modules/startup/controller.nim +++ b/src/app/modules/startup/controller.nim @@ -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) \ No newline at end of file diff --git a/src/app/modules/startup/internal/biometrics_state.nim b/src/app/modules/startup/internal/biometrics_state.nim new file mode 100644 index 0000000000..bab4fe78e3 --- /dev/null +++ b/src/app/modules/startup/internal/biometrics_state.nim @@ -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 \ No newline at end of file diff --git a/src/app/modules/startup/internal/login_state.nim b/src/app/modules/startup/internal/login_state.nim new file mode 100644 index 0000000000..7a5760627b --- /dev/null +++ b/src/app/modules/startup/internal/login_state.nim @@ -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) \ No newline at end of file diff --git a/src/app/modules/startup/internal/notification_state.nim b/src/app/modules/startup/internal/notification_state.nim new file mode 100644 index 0000000000..20278b8fbb --- /dev/null +++ b/src/app/modules/startup/internal/notification_state.nim @@ -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) diff --git a/src/app/modules/startup/internal/state.nim b/src/app/modules/startup/internal/state.nim new file mode 100644 index 0000000000..cad4a11337 --- /dev/null +++ b/src/app/modules/startup/internal/state.nim @@ -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 + diff --git a/src/app/modules/startup/internal/state_wrapper.nim b/src/app/modules/startup/internal/state_wrapper.nim new file mode 100644 index 0000000000..84e21005ea --- /dev/null +++ b/src/app/modules/startup/internal/state_wrapper.nim @@ -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() \ No newline at end of file diff --git a/src/app/modules/startup/internal/user_profile_chat_key_state.nim b/src/app/modules/startup/internal/user_profile_chat_key_state.nim new file mode 100644 index 0000000000..dd3649d832 --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_chat_key_state.nim @@ -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) diff --git a/src/app/modules/startup/internal/user_profile_confirm_password_state.nim b/src/app/modules/startup/internal/user_profile_confirm_password_state.nim new file mode 100644 index 0000000000..84950f722f --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_confirm_password_state.nim @@ -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 + diff --git a/src/app/modules/startup/internal/user_profile_create_password_state.nim b/src/app/modules/startup/internal/user_profile_create_password_state.nim new file mode 100644 index 0000000000..ff6febf90e --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_create_password_state.nim @@ -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("") \ No newline at end of file diff --git a/src/app/modules/startup/internal/user_profile_create_state.nim b/src/app/modules/startup/internal/user_profile_create_state.nim new file mode 100644 index 0000000000..6eca532399 --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_create_state.nim @@ -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("") \ No newline at end of file diff --git a/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim b/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim new file mode 100644 index 0000000000..33b41151fa --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim @@ -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() \ No newline at end of file diff --git a/src/app/modules/startup/internal/user_profile_import_seed_phrase_state.nim b/src/app/modules/startup/internal/user_profile_import_seed_phrase_state.nim new file mode 100644 index 0000000000..ac56a7a365 --- /dev/null +++ b/src/app/modules/startup/internal/user_profile_import_seed_phrase_state.nim @@ -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) \ No newline at end of file diff --git a/src/app/modules/startup/internal/welcome_state.nim b/src/app/modules/startup/internal/welcome_state.nim new file mode 100644 index 0000000000..d229ec7d5e --- /dev/null +++ b/src/app/modules/startup/internal/welcome_state.nim @@ -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) diff --git a/src/app/modules/startup/internal/welcome_state_new_user.nim b/src/app/modules/startup/internal/welcome_state_new_user.nim new file mode 100644 index 0000000000..5b80da0329 --- /dev/null +++ b/src/app/modules/startup/internal/welcome_state_new_user.nim @@ -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) + + diff --git a/src/app/modules/startup/internal/welcome_state_old_user.nim b/src/app/modules/startup/internal/welcome_state_old_user.nim new file mode 100644 index 0000000000..070549a09a --- /dev/null +++ b/src/app/modules/startup/internal/welcome_state_old_user.nim @@ -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) + diff --git a/src/app/modules/startup/io_interface.nim b/src/app/modules/startup/io_interface.nim index 646c87e908..47ccb6849e 100644 --- a/src/app/modules/startup/io_interface.nim +++ b/src/app/modules/startup/io_interface.nim @@ -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 diff --git a/src/app/modules/startup/login/controller.nim b/src/app/modules/startup/login/controller.nim deleted file mode 100644 index ca35e7dfa4..0000000000 --- a/src/app/modules/startup/login/controller.nim +++ /dev/null @@ -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) diff --git a/src/app/modules/startup/login/io_interface.nim b/src/app/modules/startup/login/io_interface.nim deleted file mode 100644 index bb83b09e75..0000000000 --- a/src/app/modules/startup/login/io_interface.nim +++ /dev/null @@ -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") diff --git a/src/app/modules/startup/login/module.nim b/src/app/modules/startup/login/module.nim deleted file mode 100644 index c6ec3070e3..0000000000 --- a/src/app/modules/startup/login/module.nim +++ /dev/null @@ -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) diff --git a/src/app/modules/startup/login/view.nim b/src/app/modules/startup/login/view.nim deleted file mode 100644 index 77087778b2..0000000000 --- a/src/app/modules/startup/login/view.nim +++ /dev/null @@ -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) diff --git a/src/app/modules/startup/onboarding/item.nim b/src/app/modules/startup/models/generated_account_item.nim similarity index 100% rename from src/app/modules/startup/onboarding/item.nim rename to src/app/modules/startup/models/generated_account_item.nim diff --git a/src/app/modules/startup/onboarding/model.nim b/src/app/modules/startup/models/generated_account_model.nim similarity index 98% rename from src/app/modules/startup/onboarding/model.nim rename to src/app/modules/startup/models/generated_account_model.nim index 4f986fae27..a93b122188 100644 --- a/src/app/modules/startup/onboarding/model.nim +++ b/src/app/modules/startup/models/generated_account_model.nim @@ -1,6 +1,6 @@ import NimQml, Tables, strutils -import item +import generated_account_item type ModelRole {.pure.} = enum diff --git a/src/app/modules/startup/login/item.nim b/src/app/modules/startup/models/login_account_item.nim similarity index 100% rename from src/app/modules/startup/login/item.nim rename to src/app/modules/startup/models/login_account_item.nim diff --git a/src/app/modules/startup/login/model.nim b/src/app/modules/startup/models/login_account_model.nim similarity index 96% rename from src/app/modules/startup/login/model.nim rename to src/app/modules/startup/models/login_account_model.nim index 7000272da4..97939650a7 100644 --- a/src/app/modules/startup/login/model.nim +++ b/src/app/modules/startup/models/login_account_model.nim @@ -1,6 +1,6 @@ -import NimQml, Tables, strutils, strformat +import NimQml, Tables, strutils -import item +import login_account_item type ModelRole {.pure.} = enum diff --git a/src/app/modules/startup/module.nim b/src/app/modules/startup/module.nim index ef8e79c643..2518ab14ea 100644 --- a/src/app/modules/startup/module.nim +++ b/src/app/modules/startup/module.nim @@ -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 \ No newline at end of file diff --git a/src/app/modules/startup/onboarding/controller.nim b/src/app/modules/startup/onboarding/controller.nim deleted file mode 100644 index 90c682dfe8..0000000000 --- a/src/app/modules/startup/onboarding/controller.nim +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/app/modules/startup/onboarding/io_interface.nim b/src/app/modules/startup/onboarding/io_interface.nim deleted file mode 100644 index 5a731a1cca..0000000000 --- a/src/app/modules/startup/onboarding/io_interface.nim +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/app/modules/startup/onboarding/module.nim b/src/app/modules/startup/onboarding/module.nim deleted file mode 100644 index dc67577440..0000000000 --- a/src/app/modules/startup/onboarding/module.nim +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/app/modules/startup/onboarding/view.nim b/src/app/modules/startup/onboarding/view.nim deleted file mode 100644 index 0f1272e4ae..0000000000 --- a/src/app/modules/startup/onboarding/view.nim +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/app/modules/startup/login/selected_account.nim b/src/app/modules/startup/selected_login_account.nim similarity index 50% rename from src/app/modules/startup/login/selected_account.nim rename to src/app/modules/startup/selected_login_account.nim index 68c8ce1775..9f81f82bd4 100644 --- a/src/app/modules/startup/login/selected_account.nim +++ b/src/app/modules/startup/selected_login_account.nim @@ -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: diff --git a/src/app/modules/startup/view.nim b/src/app/modules/startup/view.nim index f12a438d7a..fba19023e9 100644 --- a/src/app/modules/startup/view.nim +++ b/src/app/modules/startup/view.nim @@ -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) \ No newline at end of file diff --git a/src/app_service/service/accounts/dto/accounts.nim b/src/app_service/service/accounts/dto/accounts.nim index efbb2d9c16..8347cecd30 100644 --- a/src/app_service/service/accounts/dto/accounts.nim +++ b/src/app_service/service/accounts/dto/accounts.nim @@ -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): diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index f82acbb1f6..10cb29ef6d 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -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 diff --git a/src/app_service/service/accounts/utils.nim b/src/app_service/service/accounts/utils.nim new file mode 100644 index 0000000000..78910db37e --- /dev/null +++ b/src/app_service/service/accounts/utils.nim @@ -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) \ No newline at end of file diff --git a/src/app_service/service/keychain/service.nim b/src/app_service/service/keychain/service.nim index 24164ea539..c7cb9eb413 100644 --- a/src/app_service/service/keychain/service.nim +++ b/src/app_service/service/keychain/service.nim @@ -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)) diff --git a/src/backend/accounts.nim b/src/backend/accounts.nim index 89a391de9c..478d209940 100644 --- a/src/backend/accounts.nim +++ b/src/backend/accounts.nim @@ -1,4 +1,4 @@ -import json, json_serialization, chronicles, nimcrypto, strutils +import json, json_serialization, chronicles, strutils import ./core, ./utils import ./response_type diff --git a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml index 7867557fbe..c387921f70 100644 --- a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml @@ -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 } } } diff --git a/ui/app/AppLayouts/Onboarding/controls/OnboardingBasePage.qml b/ui/app/AppLayouts/Onboarding/controls/OnboardingBasePage.qml index cb0bb54096..180fd852d5 100644 --- a/ui/app/AppLayouts/Onboarding/controls/OnboardingBasePage.qml +++ b/ui/app/AppLayouts/Onboarding/controls/OnboardingBasePage.qml @@ -10,7 +10,6 @@ Page { property alias backButtonVisible: backButton.visible - signal exit() signal backClicked() background: Rectangle { diff --git a/ui/app/AppLayouts/Onboarding/popups/SelectAnotherAccountModal.qml b/ui/app/AppLayouts/Onboarding/popups/SelectAnotherAccountModal.qml index 28d1fe5eef..d7ad8ddbb3 100644 --- a/ui/app/AppLayouts/Onboarding/popups/SelectAnotherAccountModal.qml +++ b/ui/app/AppLayouts/Onboarding/popups/SelectAnotherAccountModal.qml @@ -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() } } } diff --git a/ui/app/AppLayouts/Onboarding/popups/UploadProfilePicModal.qml b/ui/app/AppLayouts/Onboarding/popups/UploadProfilePicModal.qml deleted file mode 100644 index 8ec5031f68..0000000000 --- a/ui/app/AppLayouts/Onboarding/popups/UploadProfilePicModal.qml +++ /dev/null @@ -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(); - } - } - ] -} - diff --git a/ui/app/AppLayouts/Onboarding/stores/KeycardStore.qml b/ui/app/AppLayouts/Onboarding/stores/KeycardStore.qml deleted file mode 100644 index ea7a1270cf..0000000000 --- a/ui/app/AppLayouts/Onboarding/stores/KeycardStore.qml +++ /dev/null @@ -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() - } -} diff --git a/ui/app/AppLayouts/Onboarding/stores/LoginStore.qml b/ui/app/AppLayouts/Onboarding/stores/LoginStore.qml deleted file mode 100644 index f5fa92e380..0000000000 --- a/ui/app/AppLayouts/Onboarding/stores/LoginStore.qml +++ /dev/null @@ -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() - } -} diff --git a/ui/app/AppLayouts/Onboarding/stores/OnboardingStore.qml b/ui/app/AppLayouts/Onboarding/stores/OnboardingStore.qml deleted file mode 100644 index bccde92043..0000000000 --- a/ui/app/AppLayouts/Onboarding/stores/OnboardingStore.qml +++ /dev/null @@ -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" - } - } -} diff --git a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml new file mode 100644 index 0000000000..3b1a994130 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml @@ -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) + } +} diff --git a/ui/app/AppLayouts/Onboarding/stores/qmldir b/ui/app/AppLayouts/Onboarding/stores/qmldir deleted file mode 100644 index 73e94ffd37..0000000000 --- a/ui/app/AppLayouts/Onboarding/stores/qmldir +++ /dev/null @@ -1,3 +0,0 @@ -singleton OnboardingStore 1.0 OnboardingStore.qml -singleton LoginStore 1.0 LoginStore.qml -singleton KeycardStore 1.0 KeycardStore.qml diff --git a/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml b/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml index 4e600979f1..a184585dfd 100644 --- a/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml +++ b/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml @@ -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() } } } diff --git a/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml b/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml index ca33b49544..a46d8ba805 100644 --- a/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml +++ b/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml @@ -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 diff --git a/ui/app/AppLayouts/Onboarding/views/CreatePasswordView.qml b/ui/app/AppLayouts/Onboarding/views/CreatePasswordView.qml index 6cd55824a9..871332cfce 100644 --- a/ui/app/AppLayouts/Onboarding/views/CreatePasswordView.qml +++ b/ui/app/AppLayouts/Onboarding/views/CreatePasswordView.qml @@ -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() } - } } diff --git a/ui/app/AppLayouts/Onboarding/views/GenKeyView.qml b/ui/app/AppLayouts/Onboarding/views/GenKeyView.qml deleted file mode 100644 index aebb382a61..0000000000 --- a/ui/app/AppLayouts/Onboarding/views/GenKeyView.qml +++ /dev/null @@ -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(); - } - } -} diff --git a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml index 053b211289..5b3a7b2d4d 100644 --- a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml +++ b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml @@ -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") diff --git a/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml b/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml index 73b0a8d167..20ecce5ab6 100644 --- a/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml +++ b/ui/app/AppLayouts/Onboarding/views/KeysMainView.qml @@ -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 diff --git a/ui/app/AppLayouts/Onboarding/views/LoginView.qml b/ui/app/AppLayouts/Onboarding/views/LoginView.qml index ff4ef4166b..5e1c48cdef 100644 --- a/ui/app/AppLayouts/Onboarding/views/LoginView.qml +++ b/ui/app/AppLayouts/Onboarding/views/LoginView.qml @@ -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" diff --git a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml index d18a8600ad..e73867e027 100644 --- a/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml +++ b/ui/app/AppLayouts/Onboarding/views/SeedPhraseInputView.qml @@ -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(); - } } diff --git a/ui/app/AppLayouts/Onboarding/views/TouchIDAuthView.qml b/ui/app/AppLayouts/Onboarding/views/TouchIDAuthView.qml index bc78a9fe15..ecf2d31c05 100644 --- a/ui/app/AppLayouts/Onboarding/views/TouchIDAuthView.qml +++ b/ui/app/AppLayouts/Onboarding/views/TouchIDAuthView.qml @@ -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; - } - } } diff --git a/ui/app/AppLayouts/Onboarding/views/WelcomeView.qml b/ui/app/AppLayouts/Onboarding/views/WelcomeView.qml index 69a1b76452..4ba3362390 100644 --- a/ui/app/AppLayouts/Onboarding/views/WelcomeView.qml +++ b/ui/app/AppLayouts/Onboarding/views/WelcomeView.qml @@ -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() } } } diff --git a/ui/app/AppLayouts/Profile/popups/ChangePasswordModal.qml b/ui/app/AppLayouts/Profile/popups/ChangePasswordModal.qml index c3122e41fa..0149b61b34 100644 --- a/ui/app/AppLayouts/Profile/popups/ChangePasswordModal.qml +++ b/ui/app/AppLayouts/Profile/popups/ChangePasswordModal.qml @@ -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 diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index 8d54a7030f..11d088965d 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -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) { diff --git a/ui/imports/shared/views/PasswordView.qml b/ui/imports/shared/views/PasswordView.qml index 2efeca25e6..6390639064 100644 --- a/ui/imports/shared/views/PasswordView.qml +++ b/ui/imports/shared/views/PasswordView.qml @@ -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) } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index d63ec9314a..ea3bffb658 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -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 { diff --git a/ui/main.qml b/ui/main.qml index d86fa6fb74..ebf40f271a 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -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 {